Don't store compat themed icons in database

This commit is contained in:
MM20 2023-02-15 18:41:30 +01:00
parent 59b8f17fc4
commit 23252726b0
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
13 changed files with 289 additions and 259 deletions

View File

@ -16,9 +16,6 @@ interface IconDao {
@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?
@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')")
suspend fun getIconsFromAllPacks(componentName: String): List<IconEntity>
@ -80,9 +77,6 @@ interface IconDao {
@Query("DELETE FROM IconPack WHERE packageName NOT IN (:packs)")
fun deleteAllPacksExcept(packs: List<String>)
@Query("DELETE FROM Icons WHERE type = 'themed-compat'")
fun deleteAllCompatThemedIcons()
@Transaction
fun uninstallIconPacksExcept(packs: List<String>) {
deleteAllIconsPackIconsExcept(packs)

View File

@ -1,10 +1,13 @@
package de.mm20.launcher2.ktx
import android.util.Log
import org.xmlpull.v1.XmlPullParser
fun XmlPullParser.skipToNextTag(): Boolean {
while (next() != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG) return true
if (eventType == XmlPullParser.START_TAG) {
return true
}
}
return false
}

View File

@ -3,6 +3,8 @@ package de.mm20.launcher2.icons
import android.content.res.Resources
import android.graphics.drawable.AdaptiveIconDrawable
import androidx.core.content.res.ResourcesCompat
import de.mm20.launcher2.icons.compat.AdaptiveIconDrawableCompat
import de.mm20.launcher2.icons.compat.toLauncherIcon
import de.mm20.launcher2.icons.transformations.LauncherIconTransformation
import de.mm20.launcher2.ktx.isAtLeastApiLevel
import kotlinx.coroutines.Dispatchers
@ -25,66 +27,30 @@ internal class DynamicCalendarIcon(
val day = Instant.ofEpochMilli(time).atZone(ZoneId.systemDefault()).dayOfMonth
val resId = resourceIds[day - 1]
val drawable = try {
ResourcesCompat.getDrawable(resources, resId, null)
} catch (e: Resources.NotFoundException) {
null
} ?: return@withContext StaticLauncherIcon(
foregroundLayer = TextLayer(day.toString()),
backgroundLayer = ColorLayer()
)
val adaptiveIcon = AdaptiveIconDrawableCompat.from(resources, resId)
var icon = when {
isThemed && drawable is AdaptiveIconDrawable -> {
if (isAtLeastApiLevel(33) && drawable.monochrome != null) {
return@withContext StaticLauncherIcon(
var icon = if (adaptiveIcon != null) {
adaptiveIcon.toLauncherIcon(themed = isThemed)
} else {
try {
val drawable = ResourcesCompat.getDrawable(resources, resId, null)
when {
drawable is AdaptiveIconDrawable -> AdaptiveIconDrawableCompat.from(drawable).toLauncherIcon(themed = isThemed)
drawable != null -> StaticLauncherIcon(
foregroundLayer = TintedIconLayer(
icon = drawable.monochrome!!,
icon = drawable,
scale = 1f,
),
backgroundLayer = ColorLayer()
)
} else {
return@withContext StaticLauncherIcon(
foregroundLayer = TintedIconLayer(
icon = drawable.foreground!!,
scale = 1.5f,
),
backgroundLayer = ColorLayer()
backgroundLayer = TransparentLayer,
)
else -> null
}
}
isThemed -> {
StaticLauncherIcon(
foregroundLayer = TintedIconLayer(
icon = drawable,
scale = 0.5f,
),
backgroundLayer = ColorLayer()
)
}
drawable is AdaptiveIconDrawable -> {
return@withContext StaticLauncherIcon(
foregroundLayer = drawable.foreground?.let {
StaticIconLayer(
icon = it,
scale = 1.5f,
)
} ?: TransparentLayer,
backgroundLayer = drawable.background?.let {
StaticIconLayer(
icon = it,
scale = 1.5f,
)
} ?: TransparentLayer,
)
}
else -> StaticLauncherIcon(
foregroundLayer = StaticIconLayer(
icon = drawable,
scale = 1f,
),
backgroundLayer = TransparentLayer
} catch (e: Resources.NotFoundException) {
null
} ?: return@withContext StaticLauncherIcon(
foregroundLayer = TextLayer(day.toString()),
backgroundLayer = ColorLayer()
)
}

View File

@ -20,7 +20,6 @@ import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.drawable.toBitmap
import de.mm20.launcher2.crashreporter.CrashReporter
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
@ -55,7 +54,6 @@ class IconPackManager(
withContext(Dispatchers.IO) {
IconPackInstaller(context, appDatabase).installIcons()
GrayscaleMapInstaller(context, appDatabase).installIcons()
CompatThemedIconInstaller(context, appDatabase).installIcons()
}
}
@ -236,37 +234,6 @@ 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> {
val iconDao = appDatabase.iconDao()
return iconDao.getIconsFromAllPacks(componentName.flattenToString())
@ -301,7 +268,7 @@ class IconPackManager(
iconPack: String,
baseIconName: String,
themed: Boolean,
): DynamicCalendarIcon? {
): LauncherIcon? {
val resources = try {
context.packageManager.getResourcesForApplication(iconPack)
} catch (e: PackageManager.NameNotFoundException) {
@ -313,10 +280,16 @@ class IconPackManager(
if (id == 0) return null
id
}.toIntArray()
if (themed) {
return ThemedDynamicCalendarIcon(
resources = resources,
resourceIds = drawableIds,
)
}
return DynamicCalendarIcon(
resources = resources,
resourceIds = drawableIds,
isThemed = themed,
)
}
@ -444,12 +417,11 @@ class IconPackManager(
val array = resources.obtainTypedArrayOrNull(resId) ?: return null
if (array.length() != 31) return null
return DynamicCalendarIcon(
return ThemedDynamicCalendarIcon(
resources = resources,
resourceIds = IntArray(31) {
array.getResourceId(it, 0).takeIf { it != 0 } ?: return null
},
isThemed = true
)
} catch (e: Resources.NotFoundException) {
}

View File

@ -16,7 +16,7 @@ import de.mm20.launcher2.data.customattrs.DefaultPlaceholderIcon
import de.mm20.launcher2.data.customattrs.ForceThemedIcon
import de.mm20.launcher2.data.customattrs.UnmodifiedSystemDefaultIcon
import de.mm20.launcher2.icons.providers.CalendarIconProvider
import de.mm20.launcher2.icons.providers.CompatThemedIconProvider
import de.mm20.launcher2.icons.providers.CompatIconProvider
import de.mm20.launcher2.icons.providers.CustomIconPackIconProvider
import de.mm20.launcher2.icons.providers.CustomThemedIconProvider
import de.mm20.launcher2.icons.providers.GoogleClockIconProvider
@ -106,14 +106,14 @@ class IconRepository(
Log.w("MM20", "Icon pack ${settings.iconPack} not found")
}
}
providers.add(GoogleClockIconProvider(context))
providers.add(CalendarIconProvider(context))
if (settings.themedIcons) {
if (!isAtLeastApiLevel(33)) {
providers.add(CompatThemedIconProvider(iconPackManager))
}
providers.add(ThemedIconProvider(iconPackManager))
}
providers.add(GoogleClockIconProvider(context))
providers.add(CalendarIconProvider(context, settings.themedIcons))
if (!isAtLeastApiLevel(33)) {
providers.add(CompatIconProvider(context, settings.themedIcons))
}
providers.add(SystemIconProvider(context, settings.themedIcons))
providers.add(placeholderProvider)
cache.evictAll()

View File

@ -1,6 +1,5 @@
package de.mm20.launcher2.icons
import de.mm20.launcher2.icons.compat.ThemedIconsCompatManager
import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module

View File

@ -0,0 +1,58 @@
package de.mm20.launcher2.icons
import android.content.res.Resources
import android.graphics.drawable.AdaptiveIconDrawable
import androidx.core.content.res.ResourcesCompat
import de.mm20.launcher2.icons.transformations.LauncherIconTransformation
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.time.Instant
import java.time.ZoneId
internal class ThemedDynamicCalendarIcon(
val resources: Resources,
val resourceIds: IntArray,
private var transformations: List<LauncherIconTransformation> = emptyList(),
) : DynamicLauncherIcon, TransformableDynamicLauncherIcon {
override suspend fun getIcon(time: Long): StaticLauncherIcon = withContext(Dispatchers.IO) {
val day = Instant.ofEpochMilli(time).atZone(ZoneId.systemDefault()).dayOfMonth
val resId = resourceIds[day - 1]
val drawable = try {
ResourcesCompat.getDrawable(resources, resId, null)
} catch (e: Resources.NotFoundException) {
null
} ?: return@withContext StaticLauncherIcon(
foregroundLayer = TextLayer(day.toString()),
backgroundLayer = ColorLayer()
)
var icon = when (drawable) {
is AdaptiveIconDrawable -> StaticLauncherIcon(
foregroundLayer = TintedIconLayer(
icon = drawable.foreground,
scale = 1.5f,
),
backgroundLayer = ColorLayer()
)
else -> StaticLauncherIcon(
foregroundLayer = TintedIconLayer(
icon = drawable,
scale = 0.5f,
),
backgroundLayer = ColorLayer()
)
}
for (transformation in transformations) {
icon = transformation.transform(icon)
}
return@withContext icon
}
override fun setTransformations(transformations: List<LauncherIconTransformation>) {
this.transformations = transformations
}
}

View File

@ -0,0 +1,150 @@
package de.mm20.launcher2.icons.compat
import android.content.res.Resources
import android.content.res.XmlResourceParser
import android.graphics.drawable.AdaptiveIconDrawable
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.util.Log
import android.util.Xml
import android.view.InflateException
import androidx.core.content.res.ResourcesCompat
import de.mm20.launcher2.crashreporter.CrashReporter
import de.mm20.launcher2.icons.ColorLayer
import de.mm20.launcher2.icons.StaticIconLayer
import de.mm20.launcher2.icons.StaticLauncherIcon
import de.mm20.launcher2.icons.TintedIconLayer
import de.mm20.launcher2.ktx.isAtLeastApiLevel
import de.mm20.launcher2.ktx.skipToNextTag
import org.xmlpull.v1.XmlPullParserException
import java.io.IOException
data class AdaptiveIconDrawableCompat(
val background: Drawable,
val foreground: Drawable,
val monochrome: Drawable?,
) {
companion object {
fun from(adaptiveIconDrawable: AdaptiveIconDrawable): AdaptiveIconDrawableCompat {
return AdaptiveIconDrawableCompat(
background = adaptiveIconDrawable.background,
foreground = adaptiveIconDrawable.foreground,
monochrome = if (isAtLeastApiLevel(33)) adaptiveIconDrawable.monochrome else null,
)
}
fun from(resources: Resources, resId: Int): AdaptiveIconDrawableCompat? {
var xmlParser: XmlResourceParser? = null
try {
xmlParser = resources.getXml(resId)
val attrs = Xml.asAttributeSet(xmlParser)
if (!xmlParser.skipToNextTag()) return null
Log.d("MM20", "xmlParser.name: ${xmlParser.name}")
if (xmlParser.name != "adaptive-icon") {
return null
}
var background: Drawable? = null
var foreground: Drawable? = null
var monochrome: Drawable? = null
while (xmlParser.skipToNextTag()) {
when (xmlParser.name) {
"monochrome" -> {
monochrome = parseLayer(resources, xmlParser, attrs)
}
"background" -> {
background = parseLayer(resources, xmlParser, attrs)
}
"foreground" -> {
foreground = parseLayer(resources, xmlParser, attrs)
}
}
}
Log.d(
"MM20",
"background: $background, foreground: $foreground, monochrome: $monochrome"
)
if (foreground != null && background != null) {
return AdaptiveIconDrawableCompat(
background = background,
foreground = foreground,
monochrome = monochrome,
)
}
} 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
}
@Throws(
XmlPullParserException::class,
IOException::class,
Resources.NotFoundException::class
)
private fun parseLayer(
resources: Resources,
parser: XmlResourceParser,
attrs: AttributeSet
): Drawable? {
val drawableId = parser.getAttributeResourceValue(
"http://schemas.android.com/apk/res/android",
"drawable",
0
)
if (drawableId != 0) {
return ResourcesCompat.getDrawable(resources, drawableId, null)
}
if (!parser.skipToNextTag()) return null
return try {
Drawable.createFromXmlInner(resources, parser, attrs)
} catch (e: InflateException) {
CrashReporter.logException(e)
null
}
}
}
}
fun AdaptiveIconDrawableCompat.toLauncherIcon(
themed: Boolean = false,
): StaticLauncherIcon {
if (themed && this.monochrome != null) {
return StaticLauncherIcon(
foregroundLayer = TintedIconLayer(
scale = 1f,
icon = this.monochrome,
),
backgroundLayer = ColorLayer()
)
} else {
return StaticLauncherIcon(
foregroundLayer = StaticIconLayer(
scale = 1.5f,
icon = this.foreground,
),
backgroundLayer = StaticIconLayer(
scale = 1.5f,
icon = this.background,
)
)
}
}

View File

@ -1,26 +0,0 @@
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,
) {
}

View File

@ -1,109 +0,0 @@
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 }
}
}

View File

@ -9,7 +9,7 @@ import de.mm20.launcher2.ktx.obtainTypedArrayOrNull
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.data.LauncherApp
class CalendarIconProvider(val context: Context): IconProvider {
class CalendarIconProvider(val context: Context, val themed: Boolean): IconProvider {
override suspend fun getIcon(searchable: SavableSearchable, size: Int): LauncherIcon? {
if(searchable !is LauncherApp) return null
val component = ComponentName(searchable.`package`, searchable.activity)
@ -40,7 +40,8 @@ class CalendarIconProvider(val context: Context): IconProvider {
typedArray.recycle()
return DynamicCalendarIcon(
resources = resources,
resourceIds = drawableIds
resourceIds = drawableIds,
isThemed = themed
)
}
}

View File

@ -0,0 +1,39 @@
package de.mm20.launcher2.icons.providers
import android.content.ComponentName
import android.content.Context
import android.content.pm.PackageManager
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.icons.compat.AdaptiveIconDrawableCompat
import de.mm20.launcher2.icons.compat.toLauncherIcon
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.data.LauncherApp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class CompatIconProvider(
private val context: Context,
private val themed: Boolean = false,
) : IconProvider {
override suspend fun getIcon(searchable: SavableSearchable, size: Int): LauncherIcon? {
if (searchable !is LauncherApp) return null
val component = ComponentName(searchable.`package`, searchable.activity)
val activityInfo = try {
context.packageManager.getActivityInfo(component, 0)
} catch (e: PackageManager.NameNotFoundException) {
return null
}
val iconRes = activityInfo.iconResource
val resources = try {
context.packageManager.getResourcesForApplication(activityInfo.packageName)
} catch (e: PackageManager.NameNotFoundException) {
return null
}
val icon = withContext(Dispatchers.IO) {
AdaptiveIconDrawableCompat.from(resources, iconRes)
} ?: return null
return icon.toLauncherIcon(themed = themed)
}
}

View File

@ -1,17 +0,0 @@
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)
}
}