Restructure / cleanup / improve icon pack handling

This commit is contained in:
MM20
2023-02-27 22:36:46 +01:00
parent 96f69356c4
commit 7168169e35
25 changed files with 1441 additions and 725 deletions
@@ -1,6 +1,7 @@
package de.mm20.launcher2.icons
import android.content.Context
import android.content.pm.PackageInfo
import android.content.pm.ResolveInfo
import de.mm20.launcher2.database.entities.IconPackEntity
@@ -8,13 +9,13 @@ data class IconPack(
val name: String,
val packageName: String,
val version: String,
var scale: Float = 1f,
val scale: Float = 1f,
val themed: Boolean = false,
) {
constructor(entity: IconPackEntity) : this(
name = entity.name,
packageName = entity.packageName,
version = entity.packageName,
version = entity.version,
scale = entity.scale,
themed = entity.themed,
)
@@ -30,6 +31,17 @@ data class IconPack(
themed = themed,
)
internal constructor(
context: Context,
packageInfo: PackageInfo,
themed: Boolean = false
): this(
name = packageInfo.applicationInfo.loadLabel(context.packageManager).toString(),
packageName = packageInfo.packageName,
version = context.packageManager.getPackageInfo(packageInfo.packageName, 0).versionName,
themed = themed,
)
fun toDatabaseEntity(): IconPackEntity {
return IconPackEntity(
name = name,
@@ -1,33 +1,188 @@
package de.mm20.launcher2.icons
import android.content.ComponentName
import de.mm20.launcher2.database.entities.IconEntity
import de.mm20.launcher2.icons.compat.ClockIconConfig
import de.mm20.launcher2.ktx.jsonObjectOf
import org.json.JSONObject
data class IconPackIcon(
val type: String,
val componentName: ComponentName?,
val drawable: String?,
val iconPack: String,
val themed: Boolean = false,
val name: String? = null,
) {
constructor(entity: IconEntity) : this(
type = entity.type,
componentName = entity.componentName,
drawable = entity.drawable,
iconPack = entity.iconPack,
name = entity.name,
themed = entity.themed,
)
sealed interface IconPackComponent {
val iconPack: String
fun toDatabaseEntity(): IconEntity
}
fun toDatabaseEntity(): IconEntity {
sealed interface IconPackAppIcon: IconPackComponent {
val packageName: String
val activityName: String?
val name: String?
val themed: Boolean
}
data class IconBack(
val drawable: String,
override val iconPack: String,
): IconPackComponent {
override fun toDatabaseEntity(): IconEntity {
return IconEntity(
type = type,
componentName = componentName,
type = "iconback",
drawable = drawable,
iconPack = iconPack,
)
}
}
data class IconUpon(
val drawable: String,
override val iconPack: String,
): IconPackComponent {
override fun toDatabaseEntity(): IconEntity {
return IconEntity(
type = "iconupon",
drawable = drawable,
iconPack = iconPack,
)
}
}
data class IconMask(
val drawable: String,
override val iconPack: String,
): IconPackComponent {
override fun toDatabaseEntity(): IconEntity {
return IconEntity(
type = "iconmask",
drawable = drawable,
iconPack = iconPack,
)
}
}
data class AppIcon(
val drawable: String,
override val iconPack: String,
override val packageName: String,
override val activityName: String?,
override val name: String?,
override val themed: Boolean = false,
): IconPackComponent, IconPackAppIcon {
override fun toDatabaseEntity(): IconEntity {
return IconEntity(
type = "app",
packageName = packageName,
activityName = activityName,
drawable = drawable,
name = name,
iconPack = iconPack,
themed = themed,
)
}
}
data class CalendarIcon(
val drawables: List<String>,
override val iconPack: String,
override val packageName: String,
override val activityName: String? = null,
override val name: String? = null,
override val themed: Boolean = false,
): IconPackComponent, IconPackAppIcon {
override fun toDatabaseEntity(): IconEntity {
return IconEntity(
type = "calendar",
drawable = drawables.joinToString(","),
iconPack = iconPack,
packageName = packageName,
activityName = activityName,
name = name,
themed = themed,
)
}
}
}
data class ClockIcon(
val drawable: String,
override val iconPack: String,
override val packageName: String,
override val activityName: String? = null,
override val name: String? = null,
override val themed: Boolean,
val config: ClockIconConfig,
): IconPackComponent, IconPackAppIcon {
override fun toDatabaseEntity(): IconEntity {
return IconEntity(
type = "clock",
packageName = packageName,
activityName = activityName,
drawable = drawable,
name = name,
iconPack = iconPack,
themed = themed,
extras = jsonObjectOf(
"defaultSecond" to config.defaultSecond,
"defaultMinute" to config.defaultMinute,
"defaultHour" to config.defaultHour,
"hourLayer" to config.hourLayer,
"minuteLayer" to config.minuteLayer,
"secondLayer" to config.secondLayer,
).toString(),
)
}
}
fun Icon(entity: IconEntity): IconPackComponent? {
return when(entity.type) {
"iconback" -> IconBack(
drawable = entity.drawable ?: return null,
iconPack = entity.iconPack,
)
"iconupon" -> IconUpon(
drawable = entity.drawable ?: return null,
iconPack = entity.iconPack,
)
"iconmask" -> IconMask(
drawable = entity.drawable ?: return null,
iconPack = entity.iconPack,
)
"app" -> AppIcon(
drawable = entity.drawable ?: return null,
iconPack = entity.iconPack,
packageName = entity.packageName ?: return null,
activityName = entity.activityName,
themed = entity.themed,
name = entity.name,
)
"calendar" -> CalendarIcon(
drawables = entity.drawable?.split(",") ?: return null,
iconPack = entity.iconPack,
themed = entity.themed,
packageName = entity.packageName ?: return null,
activityName = entity.activityName,
name = entity.name,
)
"clock" -> {
val config = JSONObject(entity.extras ?: return null)
ClockIcon(
drawable = entity.drawable!!,
iconPack = entity.iconPack,
packageName = entity.packageName ?: return null,
name = entity.name,
activityName = entity.activityName,
themed = entity.themed,
config = ClockIconConfig(
defaultSecond = config.optInt("defaultSecond", 0),
defaultMinute = config.optInt("defaultMinute", 0),
defaultHour = config.optInt("defaultHour", 0),
hourLayer = config.optInt("hourLayer", 0),
minuteLayer = config.optInt("minuteLayer", 0),
secondLayer = config.optInt("secondLayer", 0),
)
)
}
else -> null
}
}
fun IconPackAppIcon(entity: IconEntity): IconPackAppIcon? {
if (entity.type != "app" && entity.type != "calendar" && entity.type != "clock") return null
return Icon(entity) as? IconPackAppIcon
}
@@ -18,14 +18,14 @@ import android.graphics.drawable.RotateDrawable
import android.util.Log
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.GrayscaleMapInstaller
import de.mm20.launcher2.icons.loaders.IconPackInstaller
import de.mm20.launcher2.icons.loaders.AppFilterIconPackInstaller
import de.mm20.launcher2.icons.loaders.GrayscaleMapIconPackInstaller
import de.mm20.launcher2.ktx.isAtLeastApiLevel
import de.mm20.launcher2.ktx.obtainTypedArrayOrNull
import de.mm20.launcher2.ktx.randomElementOrNull
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.withContext
import kotlin.math.roundToInt
@@ -50,16 +50,33 @@ class IconPackManager(
}
}
suspend fun updateIconPacks() {
withContext(Dispatchers.IO) {
IconPackInstaller(context, appDatabase).installIcons()
GrayscaleMapInstaller(context, appDatabase).installIcons()
private var updateIconPacksMutex = Mutex()
suspend fun updateIconPacks(): Boolean {
var iconsHaveBeenUpdated = false
updateIconPacksMutex.lock()
val installers = listOf(
AppFilterIconPackInstaller(context, appDatabase),
GrayscaleMapIconPackInstaller(context, appDatabase),
)
for (installer in installers) {
val iconPacks = installer.getInstalledIconPacks()
for (pack in iconPacks) {
if (!installer.isInstalledAndUpToDate(pack)) {
installer.install(pack)
iconsHaveBeenUpdated = true
} else {
Log.d("MM20", "Icon pack ${pack.packageName} is up to date")
}
}
}
updateIconPacksMutex.unlock()
return iconsHaveBeenUpdated
}
suspend fun getIcon(
iconPack: String,
componentName: ComponentName,
packageName: String,
activityName: String?,
): LauncherIcon? {
val res = try {
context.packageManager.getResourcesForApplication(iconPack)
@@ -68,69 +85,17 @@ class IconPackManager(
return null
}
val iconDao = appDatabase.iconDao()
val icon = iconDao.getIcon(componentName.flattenToString(), iconPack)
val icon = iconDao.getIcon(packageName, activityName, iconPack)?.let { IconPackAppIcon(it) }
?: return null
val drawableName = icon.drawable ?: return null
if (icon.type == "calendar") {
return getIconPackCalendarIcon(context, iconPack, drawableName, icon.themed)
}
val resId = res.getIdentifier(drawableName, "drawable", iconPack).takeIf { it != 0 }
?: return null
val drawable = try {
ResourcesCompat.getDrawable(res, resId, context.theme) ?: return null
} catch (e: Resources.NotFoundException) {
return null
}
return when {
icon.themed && drawable is AdaptiveIconDrawable -> {
if (isAtLeastApiLevel(33) && drawable.monochrome != null) {
return StaticLauncherIcon(
foregroundLayer = StaticIconLayer(
icon = drawable.monochrome!!,
scale = 1f,
),
backgroundLayer = ColorLayer(),
)
} else {
return StaticLauncherIcon(
foregroundLayer = TintedIconLayer(
icon = drawable.foreground,
scale = 1.5f,
),
backgroundLayer = ColorLayer(),
)
}
}
drawable is AdaptiveIconDrawable -> {
return 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
)
}
if (icon is CalendarIcon) {
return getIconPackCalendarIcon(icon, res)
} else if (icon is AppIcon) {
return getIconPackStaticIcon(icon, res)
} else if (icon is ClockIcon) {
return getIconPackClockIcon(icon, res)
}
return null
}
suspend fun generateIcon(
@@ -233,10 +198,10 @@ class IconPackManager(
)
}
suspend fun getAllIconPackIcons(componentName: ComponentName): List<IconPackIcon> {
suspend fun getAllIconPackIcons(componentName: ComponentName): List<IconPackAppIcon> {
val iconDao = appDatabase.iconDao()
return iconDao.getIconsFromAllPacks(componentName.flattenToString())
.map { IconPackIcon(it) }
return iconDao.getIconsFromAllPacks(componentName.packageName, componentName.shortClassName)
.mapNotNull { IconPackAppIcon(it) }
}
private suspend fun getIconBack(iconPack: String): String? {
@@ -262,25 +227,90 @@ class IconPackManager(
return iconDao.getScale(iconPack) ?: 1f
}
private fun getIconPackCalendarIcon(
context: Context,
iconPack: String,
baseIconName: String,
themed: Boolean,
private fun getIconPackStaticIcon(
icon: AppIcon,
resources: Resources,
): LauncherIcon? {
val resources = try {
context.packageManager.getResourcesForApplication(iconPack)
} catch (e: PackageManager.NameNotFoundException) {
val resId =
resources.getIdentifier(icon.drawable, "drawable", icon.iconPack).takeIf { it != 0 }
?: return null
val drawable = try {
ResourcesCompat.getDrawable(resources, resId, context.theme) ?: return null
} catch (e: Resources.NotFoundException) {
return null
}
val drawableIds = (1..31).map {
val drawableName = baseIconName + it
val id = resources.getIdentifier(drawableName, "drawable", iconPack)
return when {
icon.themed && drawable is AdaptiveIconDrawable -> {
if (isAtLeastApiLevel(33) && drawable.monochrome != null) {
return StaticLauncherIcon(
foregroundLayer = TintedIconLayer(
icon = drawable.monochrome!!,
scale = 1f,
),
backgroundLayer = ColorLayer(),
)
} else {
return StaticLauncherIcon(
foregroundLayer = TintedIconLayer(
icon = drawable.foreground,
scale = 1.5f,
),
backgroundLayer = ColorLayer(),
)
}
}
icon.themed -> {
return StaticLauncherIcon(
foregroundLayer = TintedIconLayer(
icon = drawable,
scale = 0.5f,
),
backgroundLayer = ColorLayer(),
)
}
drawable is AdaptiveIconDrawable -> {
return 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
)
}
}
}
private fun getIconPackCalendarIcon(
icon: CalendarIcon,
resources: Resources,
): LauncherIcon? {
val drawableIds = icon.drawables.map {
val id = resources.getIdentifier(it, "drawable", icon.iconPack)
if (id == 0) return null
id
}.toIntArray()
if (themed) {
if (icon.themed) {
return ThemedDynamicCalendarIcon(
resources = resources,
resourceIds = drawableIds,
@@ -292,142 +322,91 @@ class IconPackManager(
)
}
suspend fun getThemedIcon(packageName: String): LauncherIcon? {
val icon = getGreyscaleIcon(packageName) ?: return null
val resId = icon.drawable?.toIntOrNull() ?: return null
try {
val resources = context.packageManager.getResourcesForApplication(icon.iconPack)
return getThemedClockIcon(resources, resId) ?: getThemedCalendarIcon(
resources,
resId,
iconProviderPackage = icon.iconPack
) ?: getThemedStaticIcon(resources, resId)
} catch (e: PackageManager.NameNotFoundException) {
CrashReporter.logException(e)
}
return null
}
suspend fun getGreyscaleIcon(packageName: String): IconPackIcon? {
val iconDao = AppDatabase.getInstance(context).iconDao()
return iconDao.getGreyscaleIcon(ComponentName(packageName, packageName).flattenToString())
?.let { IconPackIcon(it) }
}
private fun getThemedStaticIcon(resources: Resources, resId: Int): LauncherIcon? {
try {
val fg = ResourcesCompat.getDrawable(resources, resId, null) ?: return null
return StaticLauncherIcon(
foregroundLayer = TintedIconLayer(
icon = fg,
scale = 0.5f,
),
backgroundLayer = ColorLayer()
)
private fun getIconPackClockIcon(
icon: ClockIcon,
resources: Resources,
): LauncherIcon? {
var drawable = try {
resources.getIdentifier(icon.drawable, "drawable", icon.iconPack).takeIf { it != 0 }
?.let { ResourcesCompat.getDrawable(resources, it, null) }
} catch (e: Resources.NotFoundException) {
return null
}
}
null
} ?: return null
private fun getThemedClockIcon(resources: Resources, resId: Int): LauncherIcon? {
try {
val array = resources.obtainTypedArrayOrNull(resId) ?: return null
var i = 0
var drawable: LayerDrawable? = null
var minuteIndex: Int? = null
var defaultMinute = 0
var hourIndex: Int? = null
var defaultHour = 0
while (i < array.length()) {
when (array.getString(i)) {
"com.android.launcher3.LEVEL_PER_TICK_ICON_ROUND" -> {
i++
drawable = array.getDrawable(i) as? LayerDrawable
}
val background = (drawable as? AdaptiveIconDrawable)?.background
val foreground = (drawable as? AdaptiveIconDrawable)?.foreground ?: drawable
"com.android.launcher3.HOUR_LAYER_INDEX" -> {
i++
hourIndex = array.getInt(i, -1).takeIf { it != -1 }
}
if (foreground !is LayerDrawable) return null
"com.android.launcher3.MINUTE_LAYER_INDEX" -> {
i++
minuteIndex = array.getInt(i, -1).takeIf { it != -1 }
}
"com.android.launcher3.DEFAULT_HOUR" -> {
i++
defaultHour = array.getInt(i, 0)
}
"com.android.launcher3.DEFAULT_MINUTE" -> {
i++
defaultMinute = array.getInt(i, 0)
}
val layers = (0 until foreground.numberOfLayers).map {
val drw = foreground.getDrawable(it)
ClockSublayer(
drawable = drw,
role = when (it) {
icon.config.hourLayer -> ClockSublayerRole.Hour
icon.config.minuteLayer -> ClockSublayerRole.Minute
icon.config.secondLayer -> ClockSublayerRole.Second
else -> ClockSublayerRole.Static
}
i++
}
if (drawable != null && minuteIndex != null && hourIndex != null) {
)
}
return StaticLauncherIcon(
return when {
icon.themed && drawable is AdaptiveIconDrawable -> {
StaticLauncherIcon(
foregroundLayer = TintedClockLayer(
sublayers = (0 until drawable.numberOfLayers).map {
val drw = drawable.getDrawable(it)
if (drw is RotateDrawable) {
drw.level = when (it) {
hourIndex -> {
(12 - defaultHour) * 60
}
minuteIndex -> {
(60 - defaultMinute)
}
else -> 0
}
}
ClockSublayer(
drawable = drw,
role = when (it) {
hourIndex -> ClockSublayerRole.Hour
minuteIndex -> ClockSublayerRole.Minute
else -> ClockSublayerRole.Static
}
)
},
defaultHour = icon.config.defaultHour,
defaultMinute = icon.config.defaultMinute,
defaultSecond = icon.config.defaultSecond,
sublayers = layers,
scale = 1.5f,
),
backgroundLayer = ColorLayer()
backgroundLayer = ColorLayer(),
)
}
icon.themed -> {
StaticLauncherIcon(
foregroundLayer = TintedClockLayer(
defaultHour = icon.config.defaultHour,
defaultMinute = icon.config.defaultMinute,
defaultSecond = icon.config.defaultSecond,
sublayers = layers,
scale = 1f,
),
backgroundLayer = ColorLayer(),
)
}
drawable is AdaptiveIconDrawable -> {
StaticLauncherIcon(
foregroundLayer = ClockLayer(
defaultHour = icon.config.defaultHour,
defaultMinute = icon.config.defaultMinute,
defaultSecond = icon.config.defaultSecond,
sublayers = layers,
scale = 1.5f,
),
backgroundLayer = StaticIconLayer(
icon = background!!,
scale = 1.5f
),
)
}
else -> {
StaticLauncherIcon(
foregroundLayer = ClockLayer(
defaultHour = icon.config.defaultHour,
defaultMinute = icon.config.defaultMinute,
defaultSecond = icon.config.defaultSecond,
sublayers = layers,
scale = 1f,
),
backgroundLayer = TransparentLayer,
)
}
} catch (e: Resources.NotFoundException) {
}
return null
}
private fun getThemedCalendarIcon(
resources: Resources,
resId: Int,
iconProviderPackage: String
): LauncherIcon? {
try {
val array = resources.obtainTypedArrayOrNull(resId) ?: return null
if (array.length() != 31) return null
return ThemedDynamicCalendarIcon(
resources = resources,
resourceIds = IntArray(31) {
array.getResourceId(it, 0).takeIf { it != 0 } ?: return null
},
)
} catch (e: Resources.NotFoundException) {
}
return null
}
suspend fun searchIconPackIcon(query: String, iconPack: IconPack?): List<IconPackIcon> {
suspend fun searchIconPackIcon(query: String, iconPack: IconPack?): List<IconPackAppIcon> {
val iconDao = appDatabase.iconDao()
val drawableQuery = query.replace(" ", "_").lowercase()
return iconDao.searchIconPackIcons(
@@ -435,17 +414,11 @@ class IconPackManager(
componentQuery = "%$query%",
nameQuery = "%$query%",
iconPack = iconPack?.packageName,
).map {
IconPackIcon(it)
).mapNotNull {
IconPackAppIcon(it)
}
}
suspend fun searchThemedIcons(query: String): List<IconPackIcon> {
val iconDao = appDatabase.iconDao()
return iconDao.searchGreyscaleIcons("%$query%").map {
IconPackIcon(it)
}
}
}
@@ -1,6 +1,7 @@
package de.mm20.launcher2.icons
import android.content.BroadcastReceiver
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
@@ -24,7 +25,6 @@ import de.mm20.launcher2.icons.providers.IconPackIconProvider
import de.mm20.launcher2.icons.providers.IconProvider
import de.mm20.launcher2.icons.providers.PlaceholderIconProvider
import de.mm20.launcher2.icons.providers.SystemIconProvider
import de.mm20.launcher2.icons.providers.ThemedIconProvider
import de.mm20.launcher2.icons.providers.ThemedPlaceholderIconProvider
import de.mm20.launcher2.icons.providers.getFirstIcon
import de.mm20.launcher2.icons.transformations.ForceThemedIconTransformation
@@ -39,11 +39,13 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.internal.ChannelFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
@@ -67,6 +69,11 @@ class IconRepository(
private var iconProviders: MutableStateFlow<List<IconProvider>> = MutableStateFlow(listOf())
private var placeholderProvider: IconProvider? = null
/**
* Signal that installed icon packs have been updated. Force a reload of all icons.
*/
private val iconPacksUpdated = MutableSharedFlow<Unit>(1)
private var transformations: MutableStateFlow<List<LauncherIconTransformation>> =
MutableStateFlow(
listOf()
@@ -83,51 +90,52 @@ class IconRepository(
addDataScheme("package")
})
iconPacksUpdated.tryEmit(Unit)
scope.launch {
dataStore.data.map { it.icons }.distinctUntilChanged().collectLatest { settings ->
val placeholderProvider = if (settings.themedIcons) {
ThemedPlaceholderIconProvider(context)
} else {
PlaceholderIconProvider(context)
}
val providers = mutableListOf<IconProvider>()
if (settings.iconPack.isNotBlank()) {
val pack = iconPackManager.getIconPack(settings.iconPack)
if (pack != null) {
providers.add(
IconPackIconProvider(
context,
pack,
iconPackManager
)
)
iconPacksUpdated.collectLatest {
val placeholderProvider = if (settings.themedIcons) {
ThemedPlaceholderIconProvider(context)
} else {
Log.w("MM20", "Icon pack ${settings.iconPack} not found")
PlaceholderIconProvider(context)
}
}
if (settings.themedIcons) {
providers.add(ThemedIconProvider(iconPackManager))
}
providers.add(DynamicClockIconProvider(context, settings.themedIcons))
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()
val providers = mutableListOf<IconProvider>()
val transformations = mutableListOf<LauncherIconTransformation>()
if (settings.iconPack.isNotBlank()) {
val pack = iconPackManager.getIconPack(settings.iconPack)
if (pack != null) {
providers.add(
IconPackIconProvider(
context,
pack,
iconPackManager
)
)
} else {
Log.w("MM20", "Icon pack ${settings.iconPack} not found")
}
}
providers.add(DynamicClockIconProvider(context, settings.themedIcons))
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()
if (settings.adaptify) transformations.add(LegacyToAdaptiveTransformation())
if (settings.themedIcons && settings.forceThemed) transformations.add(
ForceThemedIconTransformation()
)
val transformations = mutableListOf<LauncherIconTransformation>()
this@IconRepository.placeholderProvider = placeholderProvider
iconProviders.value = providers
this@IconRepository.transformations.value = transformations
if (settings.adaptify) transformations.add(LegacyToAdaptiveTransformation())
if (settings.themedIcons && settings.forceThemed) transformations.add(
ForceThemedIconTransformation()
)
this@IconRepository.placeholderProvider = placeholderProvider
iconProviders.value = providers
this@IconRepository.transformations.value = transformations
}
}
}
}
@@ -215,7 +223,9 @@ class IconRepository(
fun requestIconPackListUpdate() {
scope.launch {
iconPackManager.updateIconPacks()
iconPackManager.updateIconPacks().also {
if (it)iconPacksUpdated.tryEmit(Unit)
}
}
}
@@ -285,24 +295,14 @@ class IconRepository(
iconPackIcons.mapNotNull {
CustomIconPackIcon(
iconPackPackage = it.iconPack,
iconComponentName = it.componentName?.flattenToString()
?: return@mapNotNull null
iconActivityName = it.activityName,
iconPackageName = it.packageName,
)
}
)
val themedIcon = iconPackManager.getGreyscaleIcon(searchable.`package`)
if (themedIcon != null && themedIcon.componentName?.packageName != null) {
providerOptions.add(
CustomThemedIcon(
iconPackageName = themedIcon.componentName.packageName,
)
)
} else {
transformationOptions.add(
ForceThemedIcon
)
}
transformationOptions.add(
ForceThemedIcon
)
} else {
transformationOptions.add(
ForceThemedIcon
@@ -359,31 +359,18 @@ class IconRepository(
suspend fun searchCustomIcons(query: String, iconPack: IconPack?): List<CustomIconWithPreview> {
val transformations = this.transformations.first()
val iconPackIcons = iconPackManager.searchIconPackIcon(query, iconPack).mapNotNull {
val componentName = it.componentName ?: return@mapNotNull null
CustomIconWithPreview(
customIcon = CustomIconPackIcon(
iconPackPackage = it.iconPack,
iconComponentName = componentName.flattenToString(),
iconActivityName = it.activityName,
iconPackageName = it.packageName,
),
preview = iconPackManager.getIcon(it.iconPack, componentName)
preview = iconPackManager.getIcon(it.iconPack, it.packageName, it.activityName)
?.transform(transformations) ?: return@mapNotNull null
)
}
val themedIcons = iconPackManager.searchThemedIcons(query).mapNotNull {
val componentName = it.componentName ?: return@mapNotNull null
CustomIconWithPreview(
customIcon = CustomThemedIcon(
iconPackageName = componentName.packageName,
),
preview = iconPackManager.getThemedIcon(componentName.packageName)
?.transform(transformations) ?: return@mapNotNull null
)
}
return iconPackIcons + themedIcons
return iconPackIcons
}
fun setCustomIcon(searchable: SavableSearchable, icon: CustomIcon?) {
@@ -145,23 +145,6 @@ fun AdaptiveIconDrawableCompat.toLauncherIcon(
if (clock != null && clockForeground != null) {
val clockLayers = (0 until clockForeground.numberOfLayers).map {
val drw = clockForeground.getDrawable(it)
if (drw is RotateDrawable) {
drw.level = when (it) {
clock.hourLayer -> {
(12 - clock.defaultHour) * 60
}
clock.minuteLayer -> {
(60 - clock.defaultMinute)
}
clock.secondLayer -> {
(60 - clock.defaultSecond) * 10
}
else -> 0
}
}
ClockSublayer(
drawable = drw,
role = when (it) {
@@ -175,6 +158,9 @@ fun AdaptiveIconDrawableCompat.toLauncherIcon(
if (themed) {
return StaticLauncherIcon(
foregroundLayer = TintedClockLayer(
defaultHour = clock.defaultHour,
defaultMinute = clock.defaultMinute,
defaultSecond = clock.defaultSecond,
sublayers = clockLayers,
scale = 1.5f,
),
@@ -183,6 +169,9 @@ fun AdaptiveIconDrawableCompat.toLauncherIcon(
}
return StaticLauncherIcon(
foregroundLayer = ClockLayer(
defaultHour = clock.defaultHour,
defaultMinute = clock.defaultMinute,
defaultSecond = clock.defaultSecond,
sublayers = clockLayers,
scale = 1.5f,
),
@@ -0,0 +1,197 @@
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.AppIcon
import de.mm20.launcher2.icons.CalendarIcon
import de.mm20.launcher2.icons.IconBack
import de.mm20.launcher2.icons.IconMask
import de.mm20.launcher2.icons.IconPack
import de.mm20.launcher2.icons.IconUpon
import kotlinx.coroutines.Dispatchers
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
class AppFilterIconPackInstaller(
private val context: Context,
database: AppDatabase,
) : IconPackInstaller(database) {
override suspend fun IconPackInstallerScope.buildIconPack(iconPack: IconPack) {
withContext(Dispatchers.IO) {
val pkgName = iconPack.packageName
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@withContext
}
XmlPullParserFactory.newInstance().newPullParser().apply {
setInput(inStream)
}
}
}
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 = AppIcon(
packageName = componentName.packageName,
activityName = componentName.shortClassName,
drawable = drawable,
iconPack = pkgName,
name = name,
themed = iconPack.themed,
)
addIcon(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 = CalendarIcon(
packageName = componentName.packageName,
activityName = componentName.shortClassName,
drawables = (0..31).map { "$drawable$it" },
iconPack = pkgName,
themed = iconPack.themed,
name = name,
)
addIcon(icon)
}
"iconback" -> {
for (i in 0 until parser.attributeCount) {
if (parser.getAttributeName(i).startsWith("img")) {
val drawable = parser.getAttributeValue(i)
val icon = IconBack(
drawable = drawable,
iconPack = pkgName,
)
addIcon(icon)
}
}
}
"iconupon" -> {
for (i in 0 until parser.attributeCount) {
if (parser.getAttributeName(i).startsWith("img")) {
val drawable = parser.getAttributeValue(i)
val icon = IconUpon(
drawable = drawable,
iconPack = pkgName,
)
addIcon(icon)
}
}
}
"iconmask" -> {
for (i in 0 until parser.attributeCount) {
if (parser.getAttributeName(i).startsWith("img")) {
val drawable = parser.getAttributeValue(i)
val icon = IconMask(
drawable = drawable,
iconPack = pkgName,
)
addIcon(icon)
}
}
}
"scale" -> {
val scale = parser.getAttributeValue(null, "factor")?.toFloatOrNull()
?: continue@loop
updatePackInfo { it.copy(scale = scale) }
}
}
}
(parser as? XmlResourceParser)?.close()
inStream?.close()
Log.d("MM20", "Icon pack $pkgName 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)
}
}
}
override fun getInstalledIconPacks(): 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 }
}
}
@@ -0,0 +1,178 @@
package de.mm20.launcher2.icons.loaders
import android.content.Context
import android.content.pm.PackageManager
import android.content.res.Resources
import android.graphics.drawable.LayerDrawable
import android.util.Log
import de.mm20.launcher2.crashreporter.CrashReporter
import de.mm20.launcher2.database.AppDatabase
import de.mm20.launcher2.icons.AppIcon
import de.mm20.launcher2.icons.CalendarIcon
import de.mm20.launcher2.icons.ClockIcon
import de.mm20.launcher2.icons.IconPack
import de.mm20.launcher2.icons.compat.ClockIconConfig
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.xmlpull.v1.XmlPullParser
class GrayscaleMapIconPackInstaller(
private val context: Context,
database: AppDatabase,
) : IconPackInstaller(database) {
private val SUPPORTED_GRAYSCALE_MAP_PROVIDERS = arrayOf(
"app.lawnchair.lawnicons", // Lawnicons
"de.mm20.launcher2.themedicons",
"de.kvaesitso.icons",
)
override suspend fun IconPackInstallerScope.buildIconPack(iconPack: IconPack) {
withContext(Dispatchers.IO) {
try {
val packageName = iconPack.packageName
val resources = context.packageManager.getResourcesForApplication(packageName)
val resId = resources.getIdentifier("grayscale_icon_map", "xml", packageName)
if (resId == 0) {
Log.d("MM20", "Could not find grayscale_icon_map.xml in $packageName")
return@withContext
}
val parser = resources.getXml(resId)
loop@ while (parser.next() != XmlPullParser.END_DOCUMENT) {
if (parser.eventType != XmlPullParser.START_TAG) continue
if (parser.name == "icon") {
val pkg = parser.getAttributeValue(null, "package") ?: continue@loop
val drawableRes =
parser.getAttributeResourceValue(null, "drawable", 0)
val type = try {
resources.getResourceTypeName(drawableRes)
} catch (e: Resources.NotFoundException) {
continue@loop
}
if (type == "drawable") {
val drawableName =
resources.getResourceEntryNameOrNull(drawableRes) ?: continue@loop
val icon = AppIcon(
drawable = drawableName,
packageName = pkg,
activityName = null,
iconPack = packageName,
name = null,
themed = true
)
addIcon(icon)
} else if (type == "array") {
val array = resources.obtainTypedArray(drawableRes)
if (array.length() == 31) {
val drawables = mutableListOf<String>()
for (i in 0 until 31) {
val res = array.getResourceId(i, 0)
if (res == 0) break
val drawableName =
resources.getResourceEntryNameOrNull(res) ?: break
drawables.add(drawableName)
}
if (drawables.size == 31) {
addIcon(
CalendarIcon(
drawables = drawables,
packageName = pkg,
iconPack = packageName,
themed = true
)
)
}
} else {
var i = 0
var drawable: LayerDrawable? = null
var drawableName: String? = null
var minuteIndex: Int? = null
var defaultMinute = 0
var hourIndex: Int? = null
var defaultHour = 0
while (i < array.length()) {
when (array.getString(i)) {
"com.android.launcher3.LEVEL_PER_TICK_ICON_ROUND" -> {
i++
drawable = array.getDrawable(i) as? LayerDrawable
drawableName =
array.getResourceId(i, 0).takeIf { it != 0 }
?.let { resources.getResourceEntryNameOrNull(it) }
}
"com.android.launcher3.HOUR_LAYER_INDEX" -> {
i++
hourIndex = array.getInt(i, -1).takeIf { it != -1 }
}
"com.android.launcher3.MINUTE_LAYER_INDEX" -> {
i++
minuteIndex = array.getInt(i, -1).takeIf { it != -1 }
}
"com.android.launcher3.DEFAULT_HOUR" -> {
i++
defaultHour = array.getInt(i, 0)
}
"com.android.launcher3.DEFAULT_MINUTE" -> {
i++
defaultMinute = array.getInt(i, 0)
}
}
i++
}
if (drawable != null && drawableName != null && minuteIndex != null && hourIndex != null) {
addIcon(
ClockIcon(
drawable = drawableName,
packageName = pkg,
config = ClockIconConfig(
hourLayer = hourIndex,
minuteLayer = minuteIndex,
defaultHour = defaultHour,
defaultMinute = defaultMinute,
defaultSecond = 0,
secondLayer = -1,
),
iconPack = packageName,
themed = true,
)
)
}
}
array.recycle()
}
}
}
} catch (e: PackageManager.NameNotFoundException) {
CrashReporter.logException(e)
}
}
}
override fun getInstalledIconPacks(): List<IconPack> {
val pm = context.packageManager
return SUPPORTED_GRAYSCALE_MAP_PROVIDERS.mapNotNull {
try {
val packageInfo = pm.getPackageInfo(it, 0)
IconPack(
context = context,
packageInfo = packageInfo,
themed = true,
)
} catch (e: PackageManager.NameNotFoundException) {
null
}
}
}
}
internal fun Resources.getResourceEntryNameOrNull(res: Int): String? {
return try {
getResourceEntryName(res)
} catch (e: Resources.NotFoundException) {
null
}
}
@@ -1,88 +0,0 @@
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
}
}
}
}
@@ -1,233 +1,53 @@
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 androidx.room.withTransaction
import de.mm20.launcher2.database.AppDatabase
import de.mm20.launcher2.icons.BuildConfig
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
import de.mm20.launcher2.icons.IconPackComponent
class IconPackInstaller(
private val context: Context,
abstract class IconPackInstaller(
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,
themed = iconPack.themed,
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",
themed = iconPack.themed,
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
}
}
suspend fun install(iconPack: IconPack) {
var pack = iconPack
val dao = database.iconDao()
database.withTransaction {
dao.deleteIconPack(iconPack.toDatabaseEntity())
dao.deleteIcons(iconPack.packageName)
val icons = mutableListOf<IconPackComponent>()
val installerScope = object: IconPackInstallerScope {
override suspend fun addIcon(icon: IconPackComponent) {
icons.add(icon)
if (icons.size >= 100) {
iconDao.insertAll(icons.map { it.toDatabaseEntity() })
dao.insertAll(icons.map { it.toDatabaseEntity() })
icons.clear()
}
}
if (icons.isNotEmpty()) {
iconDao.insertAll(icons.map { it.toDatabaseEntity() })
override suspend fun updatePackInfo(update: (IconPack) -> IconPack) {
pack = update(iconPack)
}
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)
}
installerScope.buildIconPack(iconPack)
if (icons.isNotEmpty()) dao.insertAll(icons.map { it.toDatabaseEntity() })
dao.installIconPack(pack.toDatabaseEntity())
}
}
abstract suspend fun IconPackInstallerScope.buildIconPack(iconPack: IconPack)
abstract fun getInstalledIconPacks(): List<IconPack>
suspend fun isInstalledAndUpToDate(iconPack: IconPack): Boolean {
if (BuildConfig.DEBUG) return false
val dao = database.iconDao()
val installed = dao.getIconPack(iconPack.packageName)?.let { IconPack(it) } ?: return false
return installed.version == iconPack.version
}
}
interface IconPackInstallerScope {
suspend fun addIcon(icon: IconPackComponent)
suspend fun updatePackInfo(update: (IconPack) -> IconPack)
}
@@ -13,7 +13,8 @@ class CustomIconPackIconProvider(
override suspend fun getIcon(searchable: SavableSearchable, size: Int): LauncherIcon? {
return iconPackManager.getIcon(
customIcon.iconPackPackage,
ComponentName.unflattenFromString(customIcon.iconComponentName) ?: return null
customIcon.iconPackageName,
customIcon.iconActivityName,
)
}
}
@@ -10,6 +10,6 @@ class CustomThemedIconProvider(
private val iconPackManager: IconPackManager,
): IconProvider {
override suspend fun getIcon(searchable: SavableSearchable, size: Int): LauncherIcon? {
return iconPackManager.getThemedIcon(customIcon.iconPackageName)
return null //iconPackManager.getThemedIcon(customIcon.iconPackageName)
}
}
@@ -16,8 +16,7 @@ class IconPackIconProvider(
override suspend fun getIcon(searchable: SavableSearchable, size: Int): LauncherIcon? {
if (searchable !is LauncherApp) return null
val component = ComponentName(searchable.`package`, searchable.activity)
return iconPackManager.getIcon(iconPack.packageName, component)
return iconPackManager.getIcon(iconPack.packageName, searchable.`package`, searchable.activity)
?: iconPackManager.generateIcon(
context,
iconPack.packageName,
@@ -1,15 +0,0 @@
package de.mm20.launcher2.icons.providers
import de.mm20.launcher2.icons.*
import de.mm20.launcher2.search.SavableSearchable
import de.mm20.launcher2.search.data.LauncherApp
internal class ThemedIconProvider(
private val iconPackManager: IconPackManager,
) : IconProvider {
override suspend fun getIcon(searchable: SavableSearchable, size: Int): LauncherIcon? {
if (searchable !is LauncherApp) return null
return iconPackManager.getThemedIcon(searchable.`package`)
}
}
@@ -22,6 +22,9 @@ internal class ThemedPlaceholderIconProvider(
is ClockLayer -> TintedClockLayer(
scale = layer.scale,
color = 0,
defaultHour = layer.defaultHour,
defaultMinute = layer.defaultMinute,
defaultSecond = layer.defaultSecond,
sublayers = layer.sublayers,
)
is ColorLayer -> layer.copy(color = 0)
@@ -14,6 +14,9 @@ internal class ForceThemedIconTransformation : LauncherIconTransformation {
return when(layer) {
is ClockLayer -> TintedClockLayer(
scale = layer.scale,
defaultHour = layer.defaultHour,
defaultMinute = layer.defaultMinute,
defaultSecond = layer.defaultSecond,
sublayers = layer.sublayers,
)
is ColorLayer -> layer.copy(color = 0)