Compat themed icons
- Use native themed icons on Android 8.0 to 12
This commit is contained in:
parent
9e56f960e6
commit
59b8f17fc4
@ -16,6 +16,9 @@ interface IconDao {
|
|||||||
@Query("SELECT * FROM Icons WHERE componentName = :componentName AND iconPack = :iconPack AND (type = 'app' OR type = 'calendar') LIMIT 1")
|
@Query("SELECT * FROM Icons WHERE componentName = :componentName AND iconPack = :iconPack AND (type = 'app' OR type = 'calendar') LIMIT 1")
|
||||||
suspend fun getIcon(componentName: String, iconPack: String): IconEntity?
|
suspend fun getIcon(componentName: String, iconPack: String): IconEntity?
|
||||||
|
|
||||||
|
@Query("SELECT * FROM Icons WHERE componentName = :componentName AND (type = 'themed-compat') LIMIT 1")
|
||||||
|
suspend fun getCompatThemedIcon(componentName: String): IconEntity?
|
||||||
|
|
||||||
@Query("SELECT * FROM Icons WHERE componentName = :componentName AND (type = 'app' OR type = 'calendar')")
|
@Query("SELECT * FROM Icons WHERE componentName = :componentName AND (type = 'app' OR type = 'calendar')")
|
||||||
suspend fun getIconsFromAllPacks(componentName: String): List<IconEntity>
|
suspend fun getIconsFromAllPacks(componentName: String): List<IconEntity>
|
||||||
|
|
||||||
@ -41,7 +44,7 @@ interface IconDao {
|
|||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
suspend fun installGrayscaleIconMap(packageName: String, icons: List<IconEntity>) {
|
suspend fun installGrayscaleIconMap(packageName: String, icons: List<IconEntity>) {
|
||||||
deleteIcons(packageName)
|
deleteGrayscaleIcons(packageName)
|
||||||
insertAll(icons)
|
insertAll(icons)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,15 +71,21 @@ interface IconDao {
|
|||||||
return getPacks(iconPack.packageName, iconPack.version).isNotEmpty()
|
return getPacks(iconPack.packageName, iconPack.version).isNotEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Query("DELETE FROM Icons WHERE iconPack NOT IN (:packs)")
|
@Query("DELETE FROM Icons WHERE iconPack NOT IN (:packs) AND (type = 'calendar' OR type = 'app')")
|
||||||
fun deleteAllIconsExcept(packs: List<String>)
|
fun deleteAllIconsPackIconsExcept(packs: List<String>)
|
||||||
|
|
||||||
|
@Query("DELETE FROM Icons WHERE iconPack NOT IN (:packs) AND type = 'greyscale_icon'")
|
||||||
|
fun deleteAllGrayscaleIconsExcept(packs: List<String>)
|
||||||
|
|
||||||
@Query("DELETE FROM IconPack WHERE packageName NOT IN (:packs)")
|
@Query("DELETE FROM IconPack WHERE packageName NOT IN (:packs)")
|
||||||
fun deleteAllPacksExcept(packs: List<String>)
|
fun deleteAllPacksExcept(packs: List<String>)
|
||||||
|
|
||||||
|
@Query("DELETE FROM Icons WHERE type = 'themed-compat'")
|
||||||
|
fun deleteAllCompatThemedIcons()
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
fun uninstallIconPacksExcept(packs: List<String>) {
|
fun uninstallIconPacksExcept(packs: List<String>) {
|
||||||
deleteAllIconsExcept(packs)
|
deleteAllIconsPackIconsExcept(packs)
|
||||||
deleteAllPacksExcept(packs)
|
deleteAllPacksExcept(packs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,10 @@
|
|||||||
|
package de.mm20.launcher2.ktx
|
||||||
|
|
||||||
|
import org.xmlpull.v1.XmlPullParser
|
||||||
|
|
||||||
|
fun XmlPullParser.skipToNextTag(): Boolean {
|
||||||
|
while (next() != XmlPullParser.END_DOCUMENT) {
|
||||||
|
if (eventType == XmlPullParser.START_TAG) return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
@ -2,38 +2,34 @@ package de.mm20.launcher2.icons
|
|||||||
|
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.content.pm.ResolveInfo
|
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.content.res.XmlResourceParser
|
import android.graphics.Bitmap
|
||||||
import android.graphics.*
|
import android.graphics.Canvas
|
||||||
import android.graphics.drawable.*
|
import android.graphics.Paint
|
||||||
|
import android.graphics.PorterDuff
|
||||||
|
import android.graphics.PorterDuffXfermode
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.graphics.drawable.AdaptiveIconDrawable
|
||||||
|
import android.graphics.drawable.BitmapDrawable
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.graphics.drawable.LayerDrawable
|
||||||
|
import android.graphics.drawable.RotateDrawable
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.core.content.res.ResourcesCompat
|
import androidx.core.content.res.ResourcesCompat
|
||||||
import androidx.core.graphics.drawable.toBitmap
|
import androidx.core.graphics.drawable.toBitmap
|
||||||
import de.mm20.launcher2.crashreporter.CrashReporter
|
import de.mm20.launcher2.crashreporter.CrashReporter
|
||||||
import de.mm20.launcher2.database.AppDatabase
|
import de.mm20.launcher2.database.AppDatabase
|
||||||
|
import de.mm20.launcher2.icons.loaders.CompatThemedIconInstaller
|
||||||
|
import de.mm20.launcher2.icons.loaders.GrayscaleMapInstaller
|
||||||
|
import de.mm20.launcher2.icons.loaders.IconPackInstaller
|
||||||
import de.mm20.launcher2.ktx.isAtLeastApiLevel
|
import de.mm20.launcher2.ktx.isAtLeastApiLevel
|
||||||
import de.mm20.launcher2.ktx.obtainTypedArrayOrNull
|
import de.mm20.launcher2.ktx.obtainTypedArrayOrNull
|
||||||
import de.mm20.launcher2.ktx.randomElementOrNull
|
import de.mm20.launcher2.ktx.randomElementOrNull
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.xmlpull.v1.XmlPullParser
|
|
||||||
import org.xmlpull.v1.XmlPullParserException
|
|
||||||
import org.xmlpull.v1.XmlPullParserFactory
|
|
||||||
import java.io.IOException
|
|
||||||
import java.io.Reader
|
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
private val SUPPORTED_GRAYSCALE_MAP_PROVIDERS = arrayOf(
|
|
||||||
"com.google.android.apps.nexuslauncher", // Pixel Launcher
|
|
||||||
"app.lawnchair.lawnicons", // Lawnicons
|
|
||||||
"app.lawnchair", // Lawnchair
|
|
||||||
"de.mm20.launcher2.themedicons",
|
|
||||||
"de.kvaesitso.icons",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class IconPackManager(
|
class IconPackManager(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
@ -57,11 +53,17 @@ class IconPackManager(
|
|||||||
|
|
||||||
suspend fun updateIconPacks() {
|
suspend fun updateIconPacks() {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
UpdateIconPacksWorker(context).doWork()
|
IconPackInstaller(context, appDatabase).installIcons()
|
||||||
|
GrayscaleMapInstaller(context, appDatabase).installIcons()
|
||||||
|
CompatThemedIconInstaller(context, appDatabase).installIcons()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getIcon(iconPack: String, componentName: ComponentName, themed: Boolean = false): LauncherIcon? {
|
suspend fun getIcon(
|
||||||
|
iconPack: String,
|
||||||
|
componentName: ComponentName,
|
||||||
|
themed: Boolean = false
|
||||||
|
): LauncherIcon? {
|
||||||
val res = try {
|
val res = try {
|
||||||
context.packageManager.getResourcesForApplication(iconPack)
|
context.packageManager.getResourcesForApplication(iconPack)
|
||||||
} catch (e: PackageManager.NameNotFoundException) {
|
} catch (e: PackageManager.NameNotFoundException) {
|
||||||
@ -104,6 +106,7 @@ class IconPackManager(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
drawable is AdaptiveIconDrawable -> {
|
drawable is AdaptiveIconDrawable -> {
|
||||||
return StaticLauncherIcon(
|
return StaticLauncherIcon(
|
||||||
foregroundLayer = drawable.foreground?.let {
|
foregroundLayer = drawable.foreground?.let {
|
||||||
@ -233,6 +236,37 @@ class IconPackManager(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getCompatThemedIcon(componentName: ComponentName): LauncherIcon? {
|
||||||
|
val iconDao = appDatabase.iconDao()
|
||||||
|
val icon = iconDao.getCompatThemedIcon(componentName.flattenToString())
|
||||||
|
?: return null
|
||||||
|
|
||||||
|
val drawableName = icon.drawable ?: return null
|
||||||
|
|
||||||
|
val res = try {
|
||||||
|
context.packageManager.getResourcesForApplication(componentName.packageName)
|
||||||
|
} catch (e: Resources.NotFoundException) {
|
||||||
|
return null
|
||||||
|
} catch (e: PackageManager.NameNotFoundException) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val resourceId = res.getIdentifier(drawableName, null, null)
|
||||||
|
val drawable = try {
|
||||||
|
ResourcesCompat.getDrawable(res, resourceId, null)
|
||||||
|
} catch (e: Resources.NotFoundException) {
|
||||||
|
return null
|
||||||
|
} ?: return null
|
||||||
|
|
||||||
|
return StaticLauncherIcon(
|
||||||
|
foregroundLayer = TintedIconLayer(
|
||||||
|
icon = drawable,
|
||||||
|
scale = 1f,
|
||||||
|
),
|
||||||
|
backgroundLayer = ColorLayer()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun getAllIconPackIcons(componentName: ComponentName): List<IconPackIcon> {
|
suspend fun getAllIconPackIcons(componentName: ComponentName): List<IconPackIcon> {
|
||||||
val iconDao = appDatabase.iconDao()
|
val iconDao = appDatabase.iconDao()
|
||||||
return iconDao.getIconsFromAllPacks(componentName.flattenToString())
|
return iconDao.getIconsFromAllPacks(componentName.flattenToString())
|
||||||
@ -443,279 +477,3 @@ class IconPackManager(
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class UpdateIconPacksWorker(val context: Context) {
|
|
||||||
|
|
||||||
fun doWork() {
|
|
||||||
val packs = loadInstalledPacks(context)
|
|
||||||
val grayscaleProviders = loadInstalledGreyscaleProviders(context)
|
|
||||||
val iconDao = AppDatabase.getInstance(context).iconDao()
|
|
||||||
iconDao.uninstallIconPacksExcept(
|
|
||||||
packs.map { it.packageName }.union(grayscaleProviders).toList()
|
|
||||||
)
|
|
||||||
|
|
||||||
for (pack in packs) {
|
|
||||||
try {
|
|
||||||
installIconPack(pack)
|
|
||||||
} catch (e: PackageManager.NameNotFoundException) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val supportedGrayscaleMapPackages = SUPPORTED_GRAYSCALE_MAP_PROVIDERS
|
|
||||||
supportedGrayscaleMapPackages.forEach { installGrayscaleIconMap(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadInstalledGreyscaleProviders(context: Context): List<String> {
|
|
||||||
val pm = context.packageManager
|
|
||||||
return SUPPORTED_GRAYSCALE_MAP_PROVIDERS.filter {
|
|
||||||
try {
|
|
||||||
pm.getPackageInfo(it, 0)
|
|
||||||
true
|
|
||||||
} catch (e: PackageManager.NameNotFoundException) {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadInstalledPacks(context: Context): List<IconPack> {
|
|
||||||
val packs = mutableListOf<IconPack>()
|
|
||||||
val pm = context.packageManager
|
|
||||||
var intent = Intent("app.lawnchair.icons.THEMED_ICON")
|
|
||||||
val themedPacks = pm.queryIntentActivities(intent, 0)
|
|
||||||
packs.addAll(themedPacks.map { IconPack(context, it, true) })
|
|
||||||
intent = Intent("org.adw.ActivityStarter.THEMES")
|
|
||||||
val adwPacks = pm.queryIntentActivities(intent, 0)
|
|
||||||
packs.addAll(adwPacks.map { IconPack(context, it, false) })
|
|
||||||
intent = Intent("com.novalauncher.THEME")
|
|
||||||
val novaPacks = pm.queryIntentActivities(intent, 0)
|
|
||||||
packs.addAll(novaPacks.map { IconPack(context, it, false) })
|
|
||||||
return packs.distinctBy { it.packageName }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun installIconPack(iconPack: IconPack) {
|
|
||||||
val pkgName = iconPack.packageName
|
|
||||||
|
|
||||||
val icons = mutableListOf<IconPackIcon>()
|
|
||||||
val database = AppDatabase.getInstance(context)
|
|
||||||
database.runInTransaction {
|
|
||||||
try {
|
|
||||||
val res = context.packageManager.getResourcesForApplication(pkgName)
|
|
||||||
val parser: XmlPullParser
|
|
||||||
var inStream: Reader? = null
|
|
||||||
val xmlId = res.getIdentifier("appfilter", "xml", pkgName)
|
|
||||||
val rawId = res.getIdentifier("appfilter", "raw", pkgName)
|
|
||||||
parser = when {
|
|
||||||
xmlId != 0 -> res.getXml(xmlId)
|
|
||||||
rawId != 0 -> {
|
|
||||||
inStream = res.openRawResource(rawId).reader()
|
|
||||||
XmlPullParserFactory.newInstance().newPullParser().apply {
|
|
||||||
setInput(inStream)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
val iconPackContext = context.createPackageContext(
|
|
||||||
pkgName,
|
|
||||||
Context.CONTEXT_IGNORE_SECURITY
|
|
||||||
)
|
|
||||||
inStream = try {
|
|
||||||
iconPackContext.assets.open("appfilter.xml").reader()
|
|
||||||
} catch (e: IOException) {
|
|
||||||
CrashReporter.logException(e)
|
|
||||||
Log.e(
|
|
||||||
"MM20",
|
|
||||||
"appfilter.xml not found in $pkgName. Searched locations: res/xml/appfilter.xml, res/raw/appfilter.xml, assets/appfilter.xml"
|
|
||||||
)
|
|
||||||
return@runInTransaction
|
|
||||||
}
|
|
||||||
XmlPullParserFactory.newInstance().newPullParser().apply {
|
|
||||||
setInput(inStream)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val iconDao = database.iconDao()
|
|
||||||
|
|
||||||
iconDao.deleteIconPack(iconPack.toDatabaseEntity())
|
|
||||||
iconDao.deleteIcons(iconPack.packageName)
|
|
||||||
|
|
||||||
loop@ while (parser.next() != XmlPullParser.END_DOCUMENT) {
|
|
||||||
if (parser.eventType != XmlPullParser.START_TAG) continue
|
|
||||||
when (parser.name) {
|
|
||||||
"item" -> {
|
|
||||||
val component = parser.getAttributeValue(null, "component")
|
|
||||||
?: continue@loop
|
|
||||||
val drawable = parser.getAttributeValue(null, "drawable")
|
|
||||||
?: continue@loop
|
|
||||||
if (component.length <= 14) continue@loop
|
|
||||||
val componentName = ComponentName.unflattenFromString(
|
|
||||||
component.substring(
|
|
||||||
14,
|
|
||||||
component.lastIndex
|
|
||||||
)
|
|
||||||
)
|
|
||||||
?: continue@loop
|
|
||||||
|
|
||||||
val name = parser.getAttributeValue(null, "name")
|
|
||||||
|
|
||||||
val icon = IconPackIcon(
|
|
||||||
componentName = componentName,
|
|
||||||
drawable = drawable,
|
|
||||||
iconPack = pkgName,
|
|
||||||
name = name,
|
|
||||||
type = "app"
|
|
||||||
)
|
|
||||||
icons.add(icon)
|
|
||||||
}
|
|
||||||
|
|
||||||
"calendar" -> {
|
|
||||||
val component = parser.getAttributeValue(null, "component")
|
|
||||||
?: continue@loop
|
|
||||||
val drawable = parser.getAttributeValue(null, "prefix") ?: continue@loop
|
|
||||||
if (component.length < 14) continue@loop
|
|
||||||
val componentName = ComponentName.unflattenFromString(
|
|
||||||
component.substring(
|
|
||||||
14,
|
|
||||||
component.lastIndex
|
|
||||||
)
|
|
||||||
)
|
|
||||||
?: continue@loop
|
|
||||||
|
|
||||||
val name = parser.getAttributeValue(null, "name")
|
|
||||||
|
|
||||||
val icon = IconPackIcon(
|
|
||||||
componentName = componentName,
|
|
||||||
drawable = drawable,
|
|
||||||
iconPack = pkgName,
|
|
||||||
type = "calendar",
|
|
||||||
name = name,
|
|
||||||
)
|
|
||||||
icons.add(icon)
|
|
||||||
}
|
|
||||||
|
|
||||||
"iconback" -> {
|
|
||||||
for (i in 0 until parser.attributeCount) {
|
|
||||||
if (parser.getAttributeName(i).startsWith("img")) {
|
|
||||||
val drawable = parser.getAttributeValue(i)
|
|
||||||
val icon = IconPackIcon(
|
|
||||||
componentName = null,
|
|
||||||
drawable = drawable,
|
|
||||||
iconPack = pkgName,
|
|
||||||
type = "iconback"
|
|
||||||
)
|
|
||||||
icons.add(icon)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"iconupon" -> {
|
|
||||||
for (i in 0 until parser.attributeCount) {
|
|
||||||
if (parser.getAttributeName(i).startsWith("img")) {
|
|
||||||
val drawable = parser.getAttributeValue(i)
|
|
||||||
val icon = IconPackIcon(
|
|
||||||
componentName = null,
|
|
||||||
drawable = drawable,
|
|
||||||
iconPack = pkgName,
|
|
||||||
type = "iconupon"
|
|
||||||
)
|
|
||||||
icons.add(icon)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"iconmask" -> {
|
|
||||||
for (i in 0 until parser.attributeCount) {
|
|
||||||
if (parser.getAttributeName(i).startsWith("img")) {
|
|
||||||
val drawable = parser.getAttributeValue(i)
|
|
||||||
val icon = IconPackIcon(
|
|
||||||
componentName = null,
|
|
||||||
drawable = drawable,
|
|
||||||
iconPack = pkgName,
|
|
||||||
type = "iconmask"
|
|
||||||
)
|
|
||||||
icons.add(icon)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"scale" -> {
|
|
||||||
val scale = parser.getAttributeValue(null, "factor")?.toFloatOrNull()
|
|
||||||
?: continue@loop
|
|
||||||
iconPack.scale = scale
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (icons.size >= 100) {
|
|
||||||
iconDao.insertAll(icons.map { it.toDatabaseEntity() })
|
|
||||||
icons.clear()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (icons.isNotEmpty()) {
|
|
||||||
iconDao.insertAll(icons.map { it.toDatabaseEntity() })
|
|
||||||
}
|
|
||||||
iconDao.installIconPack(iconPack.toDatabaseEntity())
|
|
||||||
|
|
||||||
(parser as? XmlResourceParser)?.close()
|
|
||||||
inStream?.close()
|
|
||||||
|
|
||||||
Log.d("MM20", "Icon pack has been installed successfully")
|
|
||||||
} catch (e: PackageManager.NameNotFoundException) {
|
|
||||||
Log.e("MM20", "Could not install icon pack $pkgName: package not found.")
|
|
||||||
} catch (e: XmlPullParserException) {
|
|
||||||
CrashReporter.logException(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun installGrayscaleIconMap(packageName: String) {
|
|
||||||
val database = AppDatabase.getInstance(context)
|
|
||||||
database.runInTransaction {
|
|
||||||
val iconDao = database.iconDao()
|
|
||||||
try {
|
|
||||||
val resources = context.packageManager.getResourcesForApplication(packageName)
|
|
||||||
val resId = resources.getIdentifier("grayscale_icon_map", "xml", packageName)
|
|
||||||
iconDao.deleteGrayscaleIcons(packageName)
|
|
||||||
if (resId == 0) {
|
|
||||||
return@runInTransaction
|
|
||||||
}
|
|
||||||
val icons = mutableListOf<IconPackIcon>()
|
|
||||||
val parser = resources.getXml(resId)
|
|
||||||
loop@ while (parser.next() != XmlPullParser.END_DOCUMENT) {
|
|
||||||
if (parser.eventType != XmlPullParser.START_TAG) continue
|
|
||||||
when (parser.name) {
|
|
||||||
"icon" -> {
|
|
||||||
val drawable =
|
|
||||||
parser.getAttributeResourceValue(null, "drawable", 0).toString()
|
|
||||||
val pkg = parser.getAttributeValue(null, "package")
|
|
||||||
val componentName = ComponentName(pkg, pkg)
|
|
||||||
val icon = IconPackIcon(
|
|
||||||
drawable = drawable,
|
|
||||||
componentName = componentName,
|
|
||||||
iconPack = packageName,
|
|
||||||
type = "greyscale_icon"
|
|
||||||
)
|
|
||||||
icons.add(icon)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (icons.size >= 100) {
|
|
||||||
iconDao.insertAll(icons.map { it.toDatabaseEntity() })
|
|
||||||
icons.clear()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (icons.isNotEmpty()) {
|
|
||||||
iconDao.insertAll(icons.map { it.toDatabaseEntity() })
|
|
||||||
}
|
|
||||||
} catch (e: PackageManager.NameNotFoundException) {
|
|
||||||
iconDao.deleteGrayscaleIcons(packageName)
|
|
||||||
return@runInTransaction
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private const val PREFERENCE_NAME = "icon_pack"
|
|
||||||
private const val KEY_ICON_PACK = "icon_pack"
|
|
||||||
private const val KEY_VERSION = "version"
|
|
||||||
private const val KEY_ICONSCALE = "iconscale"
|
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import de.mm20.launcher2.data.customattrs.DefaultPlaceholderIcon
|
|||||||
import de.mm20.launcher2.data.customattrs.ForceThemedIcon
|
import de.mm20.launcher2.data.customattrs.ForceThemedIcon
|
||||||
import de.mm20.launcher2.data.customattrs.UnmodifiedSystemDefaultIcon
|
import de.mm20.launcher2.data.customattrs.UnmodifiedSystemDefaultIcon
|
||||||
import de.mm20.launcher2.icons.providers.CalendarIconProvider
|
import de.mm20.launcher2.icons.providers.CalendarIconProvider
|
||||||
|
import de.mm20.launcher2.icons.providers.CompatThemedIconProvider
|
||||||
import de.mm20.launcher2.icons.providers.CustomIconPackIconProvider
|
import de.mm20.launcher2.icons.providers.CustomIconPackIconProvider
|
||||||
import de.mm20.launcher2.icons.providers.CustomThemedIconProvider
|
import de.mm20.launcher2.icons.providers.CustomThemedIconProvider
|
||||||
import de.mm20.launcher2.icons.providers.GoogleClockIconProvider
|
import de.mm20.launcher2.icons.providers.GoogleClockIconProvider
|
||||||
@ -30,6 +31,7 @@ import de.mm20.launcher2.icons.transformations.ForceThemedIconTransformation
|
|||||||
import de.mm20.launcher2.icons.transformations.LauncherIconTransformation
|
import de.mm20.launcher2.icons.transformations.LauncherIconTransformation
|
||||||
import de.mm20.launcher2.icons.transformations.LegacyToAdaptiveTransformation
|
import de.mm20.launcher2.icons.transformations.LegacyToAdaptiveTransformation
|
||||||
import de.mm20.launcher2.icons.transformations.transform
|
import de.mm20.launcher2.icons.transformations.transform
|
||||||
|
import de.mm20.launcher2.ktx.isAtLeastApiLevel
|
||||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||||
import de.mm20.launcher2.search.SavableSearchable
|
import de.mm20.launcher2.search.SavableSearchable
|
||||||
import de.mm20.launcher2.search.data.LauncherApp
|
import de.mm20.launcher2.search.data.LauncherApp
|
||||||
@ -90,10 +92,6 @@ class IconRepository(
|
|||||||
}
|
}
|
||||||
val providers = mutableListOf<IconProvider>()
|
val providers = mutableListOf<IconProvider>()
|
||||||
|
|
||||||
if (settings.themedIcons) {
|
|
||||||
providers.add(ThemedIconProvider(iconPackManager))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (settings.iconPack.isNotBlank()) {
|
if (settings.iconPack.isNotBlank()) {
|
||||||
val pack = iconPackManager.getIconPack(settings.iconPack)
|
val pack = iconPackManager.getIconPack(settings.iconPack)
|
||||||
if (pack != null) {
|
if (pack != null) {
|
||||||
@ -110,6 +108,12 @@ class IconRepository(
|
|||||||
}
|
}
|
||||||
providers.add(GoogleClockIconProvider(context))
|
providers.add(GoogleClockIconProvider(context))
|
||||||
providers.add(CalendarIconProvider(context))
|
providers.add(CalendarIconProvider(context))
|
||||||
|
if (settings.themedIcons) {
|
||||||
|
if (!isAtLeastApiLevel(33)) {
|
||||||
|
providers.add(CompatThemedIconProvider(iconPackManager))
|
||||||
|
}
|
||||||
|
providers.add(ThemedIconProvider(iconPackManager))
|
||||||
|
}
|
||||||
providers.add(SystemIconProvider(context, settings.themedIcons))
|
providers.add(SystemIconProvider(context, settings.themedIcons))
|
||||||
providers.add(placeholderProvider)
|
providers.add(placeholderProvider)
|
||||||
cache.evictAll()
|
cache.evictAll()
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package de.mm20.launcher2.icons
|
package de.mm20.launcher2.icons
|
||||||
|
|
||||||
|
import de.mm20.launcher2.icons.compat.ThemedIconsCompatManager
|
||||||
import org.koin.android.ext.koin.androidContext
|
import org.koin.android.ext.koin.androidContext
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,26 @@
|
|||||||
|
package de.mm20.launcher2.icons.compat
|
||||||
|
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.ActivityInfo
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.content.res.Resources
|
||||||
|
import android.content.res.XmlResourceParser
|
||||||
|
import android.util.Log
|
||||||
|
import de.mm20.launcher2.crashreporter.CrashReporter
|
||||||
|
import de.mm20.launcher2.icons.IconPackIcon
|
||||||
|
import de.mm20.launcher2.ktx.isAtLeastApiLevel
|
||||||
|
import de.mm20.launcher2.ktx.skipToNextTag
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.xmlpull.v1.XmlPullParserException
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
|
||||||
|
internal class ThemedIconsCompatManager(
|
||||||
|
private val context: Context,
|
||||||
|
) {
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,109 @@
|
|||||||
|
package de.mm20.launcher2.icons.loaders
|
||||||
|
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.ActivityInfo
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.content.res.Resources
|
||||||
|
import android.content.res.XmlResourceParser
|
||||||
|
import de.mm20.launcher2.crashreporter.CrashReporter
|
||||||
|
import de.mm20.launcher2.database.AppDatabase
|
||||||
|
import de.mm20.launcher2.icons.IconPackIcon
|
||||||
|
import de.mm20.launcher2.ktx.isAtLeastApiLevel
|
||||||
|
import de.mm20.launcher2.ktx.skipToNextTag
|
||||||
|
import org.xmlpull.v1.XmlPullParserException
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
class CompatThemedIconInstaller(
|
||||||
|
private val context: Context,
|
||||||
|
private val database: AppDatabase,
|
||||||
|
) {
|
||||||
|
fun installIcons() {
|
||||||
|
if (isAtLeastApiLevel(33)) return
|
||||||
|
val launcherActivities = getLauncherActivities()
|
||||||
|
|
||||||
|
val dao = database.iconDao()
|
||||||
|
|
||||||
|
val icons = mutableListOf<IconPackIcon>()
|
||||||
|
database.runInTransaction {
|
||||||
|
dao.deleteAllCompatThemedIcons()
|
||||||
|
for (activity in launcherActivities) {
|
||||||
|
val componentName = ComponentName(activity.applicationInfo.packageName, activity.name)
|
||||||
|
val monochromeIcon = getMonochromeIconResource(activity)
|
||||||
|
|
||||||
|
if (monochromeIcon != null) {
|
||||||
|
val icon = IconPackIcon(
|
||||||
|
type = "themed-compat",
|
||||||
|
componentName = componentName,
|
||||||
|
name = null,
|
||||||
|
drawable = monochromeIcon,
|
||||||
|
iconPack = componentName.packageName
|
||||||
|
)
|
||||||
|
icons.add(icon)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (icons.size > 100) {
|
||||||
|
dao.insertAll(icons.map { it.toDatabaseEntity() })
|
||||||
|
icons.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (icons.isNotEmpty()) {
|
||||||
|
dao.insertAll(icons.map { it.toDatabaseEntity() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getMonochromeIconResource(activityInfo: ActivityInfo): String? {
|
||||||
|
val iconResource = activityInfo.iconResource
|
||||||
|
val resources = try {
|
||||||
|
context.packageManager.getResourcesForApplication(activityInfo.packageName)
|
||||||
|
} catch (e: PackageManager.NameNotFoundException) {
|
||||||
|
CrashReporter.logException(e)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
var xmlParser: XmlResourceParser? = null
|
||||||
|
try {
|
||||||
|
xmlParser = resources.getXml(iconResource)
|
||||||
|
if (!xmlParser.skipToNextTag()) return null
|
||||||
|
|
||||||
|
if (xmlParser.name != "adaptive-icon") {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
while (xmlParser.skipToNextTag()) {
|
||||||
|
if (xmlParser.name == "monochrome") {
|
||||||
|
val drawable = xmlParser.getAttributeResourceValue(
|
||||||
|
"http://schemas.android.com/apk/res/android",
|
||||||
|
"drawable",
|
||||||
|
0
|
||||||
|
)
|
||||||
|
if (drawable == 0) return null
|
||||||
|
return resources.getResourceName(drawable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Resources.NotFoundException) {
|
||||||
|
CrashReporter.logException(e)
|
||||||
|
return null
|
||||||
|
} catch (e: IOException) {
|
||||||
|
CrashReporter.logException(e)
|
||||||
|
return null
|
||||||
|
} catch (e: XmlPullParserException) {
|
||||||
|
CrashReporter.logException(e)
|
||||||
|
return null
|
||||||
|
} finally {
|
||||||
|
xmlParser?.close()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getLauncherActivities(): List<ActivityInfo> {
|
||||||
|
val resolveInfos = context.packageManager.queryIntentActivities(
|
||||||
|
Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER), 0
|
||||||
|
)
|
||||||
|
|
||||||
|
return resolveInfos.mapNotNull { it.activityInfo }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,88 @@
|
|||||||
|
package de.mm20.launcher2.icons.loaders
|
||||||
|
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import de.mm20.launcher2.database.AppDatabase
|
||||||
|
import de.mm20.launcher2.icons.IconPackIcon
|
||||||
|
import org.xmlpull.v1.XmlPullParser
|
||||||
|
|
||||||
|
class GrayscaleMapInstaller(
|
||||||
|
private val context: Context,
|
||||||
|
private val database: AppDatabase,
|
||||||
|
) {
|
||||||
|
private val SUPPORTED_GRAYSCALE_MAP_PROVIDERS = arrayOf(
|
||||||
|
"com.google.android.apps.nexuslauncher", // Pixel Launcher
|
||||||
|
"app.lawnchair.lawnicons", // Lawnicons
|
||||||
|
"app.lawnchair", // Lawnchair
|
||||||
|
"de.mm20.launcher2.themedicons",
|
||||||
|
"de.kvaesitso.icons",
|
||||||
|
)
|
||||||
|
|
||||||
|
fun installIcons() {
|
||||||
|
val grayscaleProviders = loadInstalledGreyscaleProviders(context)
|
||||||
|
|
||||||
|
val dao = database.iconDao()
|
||||||
|
|
||||||
|
grayscaleProviders.forEach { installGrayscaleIconMap(it) }
|
||||||
|
|
||||||
|
dao.deleteAllGrayscaleIconsExcept(grayscaleProviders)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadInstalledGreyscaleProviders(context: Context): List<String> {
|
||||||
|
val pm = context.packageManager
|
||||||
|
return SUPPORTED_GRAYSCALE_MAP_PROVIDERS.filter {
|
||||||
|
try {
|
||||||
|
pm.getPackageInfo(it, 0)
|
||||||
|
true
|
||||||
|
} catch (e: PackageManager.NameNotFoundException) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun installGrayscaleIconMap(packageName: String) {
|
||||||
|
database.runInTransaction {
|
||||||
|
val iconDao = database.iconDao()
|
||||||
|
try {
|
||||||
|
val resources = context.packageManager.getResourcesForApplication(packageName)
|
||||||
|
val resId = resources.getIdentifier("grayscale_icon_map", "xml", packageName)
|
||||||
|
iconDao.deleteGrayscaleIcons(packageName)
|
||||||
|
if (resId == 0) {
|
||||||
|
return@runInTransaction
|
||||||
|
}
|
||||||
|
val icons = mutableListOf<IconPackIcon>()
|
||||||
|
val parser = resources.getXml(resId)
|
||||||
|
loop@ while (parser.next() != XmlPullParser.END_DOCUMENT) {
|
||||||
|
if (parser.eventType != XmlPullParser.START_TAG) continue
|
||||||
|
when (parser.name) {
|
||||||
|
"icon" -> {
|
||||||
|
val drawable =
|
||||||
|
parser.getAttributeResourceValue(null, "drawable", 0).toString()
|
||||||
|
val pkg = parser.getAttributeValue(null, "package")
|
||||||
|
val componentName = ComponentName(pkg, pkg)
|
||||||
|
val icon = IconPackIcon(
|
||||||
|
drawable = drawable,
|
||||||
|
componentName = componentName,
|
||||||
|
iconPack = packageName,
|
||||||
|
type = "greyscale_icon"
|
||||||
|
)
|
||||||
|
icons.add(icon)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (icons.size >= 100) {
|
||||||
|
iconDao.insertAll(icons.map { it.toDatabaseEntity() })
|
||||||
|
icons.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (icons.isNotEmpty()) {
|
||||||
|
iconDao.insertAll(icons.map { it.toDatabaseEntity() })
|
||||||
|
}
|
||||||
|
} catch (e: PackageManager.NameNotFoundException) {
|
||||||
|
iconDao.deleteGrayscaleIcons(packageName)
|
||||||
|
return@runInTransaction
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,231 @@
|
|||||||
|
package de.mm20.launcher2.icons.loaders
|
||||||
|
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.content.res.XmlResourceParser
|
||||||
|
import android.util.Log
|
||||||
|
import de.mm20.launcher2.crashreporter.CrashReporter
|
||||||
|
import de.mm20.launcher2.database.AppDatabase
|
||||||
|
import de.mm20.launcher2.icons.IconPack
|
||||||
|
import de.mm20.launcher2.icons.IconPackIcon
|
||||||
|
import org.xmlpull.v1.XmlPullParser
|
||||||
|
import org.xmlpull.v1.XmlPullParserException
|
||||||
|
import org.xmlpull.v1.XmlPullParserFactory
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.Reader
|
||||||
|
|
||||||
|
class IconPackInstaller(
|
||||||
|
private val context: Context,
|
||||||
|
private val database: AppDatabase,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun installIcons() {
|
||||||
|
val packs = loadInstalledPacks(context)
|
||||||
|
val iconDao = database.iconDao()
|
||||||
|
|
||||||
|
for (pack in packs) {
|
||||||
|
try {
|
||||||
|
installIconPack(pack)
|
||||||
|
} catch (e: PackageManager.NameNotFoundException) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
iconDao.uninstallIconPacksExcept(
|
||||||
|
packs.map { it.packageName }.toList()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadInstalledPacks(context: Context): List<IconPack> {
|
||||||
|
val packs = mutableListOf<IconPack>()
|
||||||
|
val pm = context.packageManager
|
||||||
|
var intent = Intent("app.lawnchair.icons.THEMED_ICON")
|
||||||
|
val themedPacks = pm.queryIntentActivities(intent, 0)
|
||||||
|
packs.addAll(themedPacks.map { IconPack(context, it, true) })
|
||||||
|
intent = Intent("org.adw.ActivityStarter.THEMES")
|
||||||
|
val adwPacks = pm.queryIntentActivities(intent, 0)
|
||||||
|
packs.addAll(adwPacks.map { IconPack(context, it, false) })
|
||||||
|
intent = Intent("com.novalauncher.THEME")
|
||||||
|
val novaPacks = pm.queryIntentActivities(intent, 0)
|
||||||
|
packs.addAll(novaPacks.map { IconPack(context, it, false) })
|
||||||
|
return packs.distinctBy { it.packageName }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun installIconPack(iconPack: IconPack) {
|
||||||
|
val pkgName = iconPack.packageName
|
||||||
|
|
||||||
|
val icons = mutableListOf<IconPackIcon>()
|
||||||
|
database.runInTransaction {
|
||||||
|
try {
|
||||||
|
val res = context.packageManager.getResourcesForApplication(pkgName)
|
||||||
|
val parser: XmlPullParser
|
||||||
|
var inStream: Reader? = null
|
||||||
|
val xmlId = res.getIdentifier("appfilter", "xml", pkgName)
|
||||||
|
val rawId = res.getIdentifier("appfilter", "raw", pkgName)
|
||||||
|
parser = when {
|
||||||
|
xmlId != 0 -> res.getXml(xmlId)
|
||||||
|
rawId != 0 -> {
|
||||||
|
inStream = res.openRawResource(rawId).reader()
|
||||||
|
XmlPullParserFactory.newInstance().newPullParser().apply {
|
||||||
|
setInput(inStream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
val iconPackContext = context.createPackageContext(
|
||||||
|
pkgName,
|
||||||
|
Context.CONTEXT_IGNORE_SECURITY
|
||||||
|
)
|
||||||
|
inStream = try {
|
||||||
|
iconPackContext.assets.open("appfilter.xml").reader()
|
||||||
|
} catch (e: IOException) {
|
||||||
|
CrashReporter.logException(e)
|
||||||
|
Log.e(
|
||||||
|
"MM20",
|
||||||
|
"appfilter.xml not found in $pkgName. Searched locations: res/xml/appfilter.xml, res/raw/appfilter.xml, assets/appfilter.xml"
|
||||||
|
)
|
||||||
|
return@runInTransaction
|
||||||
|
}
|
||||||
|
XmlPullParserFactory.newInstance().newPullParser().apply {
|
||||||
|
setInput(inStream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val iconDao = database.iconDao()
|
||||||
|
|
||||||
|
iconDao.deleteIconPack(iconPack.toDatabaseEntity())
|
||||||
|
iconDao.deleteIcons(iconPack.packageName)
|
||||||
|
|
||||||
|
loop@ while (parser.next() != XmlPullParser.END_DOCUMENT) {
|
||||||
|
if (parser.eventType != XmlPullParser.START_TAG) continue
|
||||||
|
when (parser.name) {
|
||||||
|
"item" -> {
|
||||||
|
val component = parser.getAttributeValue(null, "component")
|
||||||
|
?: continue@loop
|
||||||
|
val drawable = parser.getAttributeValue(null, "drawable")
|
||||||
|
?: continue@loop
|
||||||
|
if (component.length <= 14) continue@loop
|
||||||
|
val componentName = ComponentName.unflattenFromString(
|
||||||
|
component.substring(
|
||||||
|
14,
|
||||||
|
component.lastIndex
|
||||||
|
)
|
||||||
|
)
|
||||||
|
?: continue@loop
|
||||||
|
|
||||||
|
val name = parser.getAttributeValue(null, "name")
|
||||||
|
|
||||||
|
val icon = IconPackIcon(
|
||||||
|
componentName = componentName,
|
||||||
|
drawable = drawable,
|
||||||
|
iconPack = pkgName,
|
||||||
|
name = name,
|
||||||
|
type = "app"
|
||||||
|
)
|
||||||
|
icons.add(icon)
|
||||||
|
}
|
||||||
|
|
||||||
|
"calendar" -> {
|
||||||
|
val component = parser.getAttributeValue(null, "component")
|
||||||
|
?: continue@loop
|
||||||
|
val drawable = parser.getAttributeValue(null, "prefix") ?: continue@loop
|
||||||
|
if (component.length < 14) continue@loop
|
||||||
|
val componentName = ComponentName.unflattenFromString(
|
||||||
|
component.substring(
|
||||||
|
14,
|
||||||
|
component.lastIndex
|
||||||
|
)
|
||||||
|
)
|
||||||
|
?: continue@loop
|
||||||
|
|
||||||
|
val name = parser.getAttributeValue(null, "name")
|
||||||
|
|
||||||
|
val icon = IconPackIcon(
|
||||||
|
componentName = componentName,
|
||||||
|
drawable = drawable,
|
||||||
|
iconPack = pkgName,
|
||||||
|
type = "calendar",
|
||||||
|
name = name,
|
||||||
|
)
|
||||||
|
icons.add(icon)
|
||||||
|
}
|
||||||
|
|
||||||
|
"iconback" -> {
|
||||||
|
for (i in 0 until parser.attributeCount) {
|
||||||
|
if (parser.getAttributeName(i).startsWith("img")) {
|
||||||
|
val drawable = parser.getAttributeValue(i)
|
||||||
|
val icon = IconPackIcon(
|
||||||
|
componentName = null,
|
||||||
|
drawable = drawable,
|
||||||
|
iconPack = pkgName,
|
||||||
|
type = "iconback"
|
||||||
|
)
|
||||||
|
icons.add(icon)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"iconupon" -> {
|
||||||
|
for (i in 0 until parser.attributeCount) {
|
||||||
|
if (parser.getAttributeName(i).startsWith("img")) {
|
||||||
|
val drawable = parser.getAttributeValue(i)
|
||||||
|
val icon = IconPackIcon(
|
||||||
|
componentName = null,
|
||||||
|
drawable = drawable,
|
||||||
|
iconPack = pkgName,
|
||||||
|
type = "iconupon"
|
||||||
|
)
|
||||||
|
icons.add(icon)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"iconmask" -> {
|
||||||
|
for (i in 0 until parser.attributeCount) {
|
||||||
|
if (parser.getAttributeName(i).startsWith("img")) {
|
||||||
|
val drawable = parser.getAttributeValue(i)
|
||||||
|
val icon = IconPackIcon(
|
||||||
|
componentName = null,
|
||||||
|
drawable = drawable,
|
||||||
|
iconPack = pkgName,
|
||||||
|
type = "iconmask"
|
||||||
|
)
|
||||||
|
icons.add(icon)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"scale" -> {
|
||||||
|
val scale = parser.getAttributeValue(null, "factor")?.toFloatOrNull()
|
||||||
|
?: continue@loop
|
||||||
|
iconPack.scale = scale
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (icons.size >= 100) {
|
||||||
|
iconDao.insertAll(icons.map { it.toDatabaseEntity() })
|
||||||
|
icons.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (icons.isNotEmpty()) {
|
||||||
|
iconDao.insertAll(icons.map { it.toDatabaseEntity() })
|
||||||
|
}
|
||||||
|
iconDao.installIconPack(iconPack.toDatabaseEntity())
|
||||||
|
|
||||||
|
(parser as? XmlResourceParser)?.close()
|
||||||
|
inStream?.close()
|
||||||
|
|
||||||
|
Log.d("MM20", "Icon pack has been installed successfully")
|
||||||
|
} catch (e: PackageManager.NameNotFoundException) {
|
||||||
|
Log.e("MM20", "Could not install icon pack $pkgName: package not found.")
|
||||||
|
} catch (e: XmlPullParserException) {
|
||||||
|
CrashReporter.logException(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
package de.mm20.launcher2.icons.providers
|
||||||
|
|
||||||
|
import android.content.ComponentName
|
||||||
|
import de.mm20.launcher2.icons.IconPackManager
|
||||||
|
import de.mm20.launcher2.icons.LauncherIcon
|
||||||
|
import de.mm20.launcher2.search.SavableSearchable
|
||||||
|
import de.mm20.launcher2.search.data.LauncherApp
|
||||||
|
|
||||||
|
class CompatThemedIconProvider(
|
||||||
|
private val iconPackManager: IconPackManager,
|
||||||
|
): IconProvider {
|
||||||
|
override suspend fun getIcon(searchable: SavableSearchable, size: Int): LauncherIcon? {
|
||||||
|
if (searchable !is LauncherApp) return null
|
||||||
|
val component = ComponentName(searchable.`package`, searchable.activity)
|
||||||
|
return iconPackManager.getCompatThemedIcon(component)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user