From dbd7fda7fbd4ace93c4fd9cdea7318a2ff278313 Mon Sep 17 00:00:00 2001 From: MM20 <15646950+MM2-0@users.noreply.github.com> Date: Thu, 2 Dec 2021 21:49:18 +0100 Subject: [PATCH] Rework icon repository --- applications/build.gradle.kts | 1 - icons/build.gradle.kts | 2 + .../mm20/launcher2/icons/IconPackManager.kt | 255 ------------------ .../de/mm20/launcher2/icons/IconRepository.kt | 47 +++- .../java/de/mm20/launcher2/icons/Module.kt | 2 +- .../icons/providers/CalendarIconProvider.kt | 50 ++++ .../providers/GoogleClockIconProvider.kt | 58 ++++ .../icons/providers/IconPackIconProvider.kt | 207 ++++++++++++++ .../launcher2/icons/providers/IconProvider.kt | 8 + .../providers/PlaceholderIconProvider.kt | 11 + .../icons/providers/SystemIconProvider.kt | 11 + 11 files changed, 383 insertions(+), 269 deletions(-) create mode 100644 icons/src/main/java/de/mm20/launcher2/icons/providers/CalendarIconProvider.kt create mode 100644 icons/src/main/java/de/mm20/launcher2/icons/providers/GoogleClockIconProvider.kt create mode 100644 icons/src/main/java/de/mm20/launcher2/icons/providers/IconPackIconProvider.kt create mode 100644 icons/src/main/java/de/mm20/launcher2/icons/providers/IconProvider.kt create mode 100644 icons/src/main/java/de/mm20/launcher2/icons/providers/PlaceholderIconProvider.kt create mode 100644 icons/src/main/java/de/mm20/launcher2/icons/providers/SystemIconProvider.kt diff --git a/applications/build.gradle.kts b/applications/build.gradle.kts index 0b774fc6..9957b582 100644 --- a/applications/build.gradle.kts +++ b/applications/build.gradle.kts @@ -46,7 +46,6 @@ dependencies { implementation(project(":search")) implementation(project(":base")) - implementation(project(":icons")) implementation(project(":preferences")) implementation(project(":ktx")) implementation(project(":badges")) diff --git a/icons/build.gradle.kts b/icons/build.gradle.kts index 92c00620..ee650cd3 100644 --- a/icons/build.gradle.kts +++ b/icons/build.gradle.kts @@ -40,6 +40,7 @@ dependencies { implementation(libs.androidx.core) implementation(libs.androidx.appcompat) implementation(libs.androidx.palette) + implementation(libs.materialcomponents) implementation(libs.bundles.androidx.lifecycle) @@ -50,6 +51,7 @@ dependencies { implementation(project(":ktx")) implementation(project(":base")) implementation(project(":search")) + implementation(project(":applications")) implementation(project(":crashreporter")) } \ No newline at end of file diff --git a/icons/src/main/java/de/mm20/launcher2/icons/IconPackManager.kt b/icons/src/main/java/de/mm20/launcher2/icons/IconPackManager.kt index 3adbb15b..9e26a1b9 100644 --- a/icons/src/main/java/de/mm20/launcher2/icons/IconPackManager.kt +++ b/icons/src/main/java/de/mm20/launcher2/icons/IconPackManager.kt @@ -54,203 +54,6 @@ class IconPackManager( selectedIconPack = iconPack } - fun getIcon(context: Context, activity: LauncherActivityInfo, size: Int): LauncherIcon? { - if (selectedIconPack.isEmpty()) return getDefaultIcon(context, activity) - return getFromPack(context, activity, size) ?: generateIcon(context, activity, size) - } - - private fun getFromPack( - context: Context, - activity: LauncherActivityInfo, - size: Int - ): LauncherIcon? { - val res = try { - context.packageManager.getResourcesForApplication(selectedIconPack) - } catch (e: PackageManager.NameNotFoundException) { - Log.e("MM20", "Icon pack package $selectedIconPack not found!") - return getDefaultIcon(context, activity) - } - val iconDao = AppDatabase.getInstance(context).iconDao() - val component = ComponentName(activity.applicationInfo.packageName, activity.name) - val icon = iconDao.getIcon(component.flattenToString(), selectedIconPack) - ?: return generateIcon(context, activity, size) - - if (icon.type == "calendar") { - return getIconPackCalendarIcon(context, icon.iconPack, icon.drawable ?: return null)?.also { - dynamicIconController.registerIcon(it) - } - } - val drawableName = icon.drawable - val resId = res.getIdentifier(drawableName, "drawable", selectedIconPack).takeIf { it != 0 } - ?: return generateIcon(context, activity, size) - val drawable = ResourcesCompat.getDrawable(res, resId, context.theme) ?: return null - return when { - Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && drawable is AdaptiveIconDrawable -> { - LauncherIcon( - foreground = drawable.foreground, - background = drawable.background, - foregroundScale = 1.5f, - backgroundScale = 1.5f - ) - } - else -> { - LauncherIcon( - foreground = drawable, - foregroundScale = getScale(), - autoGenerateBackgroundMode = LauncherPreferences.instance.legacyIconBg.toInt() - ) - } - } - } - - - private fun generateIcon( - context: Context, - activity: LauncherActivityInfo, - size: Int - ): LauncherIcon? { - val back = getIconBack() - val upon = getIconUpon() - val mask = getIconMask() - val scale = getPackScale() - - if (back == null && upon == null && mask == null) { - return getDefaultIcon(context, activity) - } - - val drawable = activity.getIcon(context.resources.displayMetrics.densityDpi) - val bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888) - - val canvas = Canvas(bitmap) - val paint = Paint() - paint.isAntiAlias = true - paint.isFilterBitmap = true - paint.isDither = true - - - var inBounds: Rect - var outBounds: Rect - - val icon = drawable.toBitmap(width = size, height = size) - - inBounds = Rect(0, 0, icon.width, icon.height) - outBounds = Rect( - (bitmap.width * (1 - scale) * 0.5).roundToInt(), - (bitmap.height * (1 - scale) * 0.5).roundToInt(), - (bitmap.width - bitmap.width * (1 - scale) * 0.5).roundToInt(), - (bitmap.height - bitmap.height * (1 - scale) * 0.5).roundToInt() - ) - canvas.drawBitmap(icon, inBounds, outBounds, paint) - - val pack = selectedIconPack - val pm = context.packageManager - val res = try { - pm.getResourcesForApplication(pack) - } catch (e: Resources.NotFoundException) { - return getDefaultIcon(context, activity) - } - - if (mask != null) { - res.getIdentifier(mask, "drawable", pack).takeIf { it != 0 }?.let { - paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OUT) - val maskDrawable = ResourcesCompat.getDrawable(res, it, null) ?: return null - val maskBmp = maskDrawable.toBitmap(size, size) - inBounds = Rect(0, 0, maskBmp.width, maskBmp.height) - outBounds = Rect(0, 0, bitmap.width, bitmap.height) - canvas.drawBitmap(maskBmp, inBounds, outBounds, paint) - } - } - if (upon != null) { - res.getIdentifier(upon, "drawable", pack).takeIf { it != 0 }?.let { - paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_OVER); - val maskDrawable = ResourcesCompat.getDrawable(res, it, null) ?: return null - val maskBmp = maskDrawable.toBitmap(size, size) - inBounds = Rect(0, 0, maskBmp.width, maskBmp.height) - outBounds = Rect(0, 0, bitmap.width, bitmap.height) - canvas.drawBitmap(maskBmp, inBounds, outBounds, paint) - } - } - if (back != null) { - res.getIdentifier(back, "drawable", pack).takeIf { it != 0 }?.let { - paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OVER); - val maskDrawable = ResourcesCompat.getDrawable(res, it, null) ?: return null - val maskBmp = maskDrawable.toBitmap(size, size) - inBounds = Rect(0, 0, maskBmp.width, maskBmp.height) - outBounds = Rect(0, 0, bitmap.width, bitmap.height) - canvas.drawBitmap(maskBmp, inBounds, outBounds, paint) - } - } - - return LauncherIcon( - foreground = BitmapDrawable(context.resources, bitmap), - foregroundScale = getScale(), - autoGenerateBackgroundMode = LauncherPreferences.instance.legacyIconBg.toInt() - ) - } - - private fun getDefaultIcon(context: Context, activity: LauncherActivityInfo): LauncherIcon? { - if (activity.applicationInfo.packageName == GOOGLE_DESK_CLOCK_PACKAGE_NAME) { - getGoogleDeskClockIcon(context)?.let { - dynamicIconController.registerIcon(it) - return it - } - } - getCalendarIcon(context, activity)?.let { - dynamicIconController.registerIcon(it) - return it - } - try { - val icon = activity.getIcon(context.resources.displayMetrics.densityDpi) ?: return null - when { - Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && icon is AdaptiveIconDrawable -> { - return LauncherIcon( - foreground = icon.foreground ?: return null, - background = icon.background, - foregroundScale = 1.5f, - backgroundScale = 1.5f - ) - } - else -> { - return LauncherIcon( - foreground = icon, - foregroundScale = getScale(), - autoGenerateBackgroundMode = LauncherPreferences.instance.legacyIconBg.toInt() - ) - } - } - } catch (e: PackageManager.NameNotFoundException) { - return null - } - } - - private fun getIconPackCalendarIcon( - context: Context, - iconPack: String, - baseIconName: String - ): CalendarDynamicLauncherIcon? { - val resources = try { - context.packageManager.getResourcesForApplication(iconPack) - } catch (e: PackageManager.NameNotFoundException) { - return null - } - val drawableIds = (1..31).map { - val drawableName = baseIconName + it - val id = resources.getIdentifier(drawableName, "drawable", iconPack) - if (id == 0) return null - id - }.toIntArray() - return CalendarDynamicLauncherIcon( - context = context, - background = ColorDrawable(0), - foreground = ColorDrawable(0), - foregroundScale = 1.5f, - backgroundScale = 1.5f, - packageName = iconPack, - drawableIds = drawableIds, - autoGenerateBackgroundMode = LauncherPreferences.instance.legacyIconBg.toInt() - ) - } - private fun getCalendarIcon( context: Context, activity: LauncherActivityInfo @@ -289,41 +92,6 @@ class IconPackManager( ) } - private fun getGoogleDeskClockIcon(context: Context): ClockDynamicLauncherIcon? { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return null - val pm = context.packageManager - val appInfo = - pm.getApplicationInfo(GOOGLE_DESK_CLOCK_PACKAGE_NAME, PackageManager.GET_META_DATA) - ?: return null - val drawable = - appInfo.metaData.getInt("com.google.android.apps.nexuslauncher.LEVEL_PER_TICK_ICON_ROUND") - val resources = pm.getResourcesForApplication(appInfo) - val baseIcon = try { - ResourcesCompat.getDrawable(resources, drawable, null) as? AdaptiveIconDrawable - ?: return null - } catch (e: Resources.NotFoundException) { - return null - } - val foreground = baseIcon.foreground as? LayerDrawable ?: return null - val hourLayer = - appInfo.metaData.getInt("com.google.android.apps.nexuslauncher.HOUR_LAYER_INDEX") - val minuteLayer = - appInfo.metaData.getInt("com.google.android.apps.nexuslauncher.MINUTE_LAYER_INDEX") - val secondLayer = - appInfo.metaData.getInt("com.google.android.apps.nexuslauncher.SECOND_LAYER_INDEX") - return ClockDynamicLauncherIcon( - context = context, - background = baseIcon.background, - backgroundScale = 1.5f, - foreground = foreground, - foregroundScale = 1.5f, - badgeNumber = 0f, - hourLayer = hourLayer, - minuteLayer = minuteLayer, - secondLayer = secondLayer - ) - } - private fun getScale(): Float { return when (LauncherPreferences.instance.iconShape) { IconShape.CIRCLE, IconShape.PLATFORM_DEFAULT -> 0.7f @@ -332,29 +100,6 @@ class IconPackManager( } } - private fun getIconBack(): String? { - val iconDao = AppDatabase.getInstance(context).iconDao() - val iconbacks = iconDao.getIconBacks(selectedIconPack) - return iconbacks.randomElementOrNull() - } - - private fun getIconUpon(): String? { - val iconDao = AppDatabase.getInstance(context).iconDao() - val iconupons = iconDao.getIconUpons(selectedIconPack) - return iconupons.randomElementOrNull() - } - - private fun getIconMask(): String? { - val iconDao = AppDatabase.getInstance(context).iconDao() - val iconmasks = iconDao.getIconMasks(selectedIconPack) - return iconmasks.randomElementOrNull() - } - - private fun getPackScale(): Float { - val iconDao = AppDatabase.getInstance(context).iconDao() - return iconDao.getScale(selectedIconPack) ?: 1f - } - suspend fun getInstalledIconPacks(): List { return withContext(Dispatchers.IO) { AppDatabase.getInstance(context).iconDao().getInstalledIconPacks().map { diff --git a/icons/src/main/java/de/mm20/launcher2/icons/IconRepository.kt b/icons/src/main/java/de/mm20/launcher2/icons/IconRepository.kt index cf6c8a7e..cc9acf86 100644 --- a/icons/src/main/java/de/mm20/launcher2/icons/IconRepository.kt +++ b/icons/src/main/java/de/mm20/launcher2/icons/IconRepository.kt @@ -5,6 +5,8 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import android.util.LruCache +import de.mm20.launcher2.icons.providers.* +import de.mm20.launcher2.preferences.LauncherPreferences import de.mm20.launcher2.search.data.Searchable import kotlinx.coroutines.* import kotlinx.coroutines.flow.Flow @@ -12,7 +14,8 @@ import kotlinx.coroutines.flow.flow class IconRepository( val context: Context, - val iconPackManager: IconPackManager + private val iconPackManager: IconPackManager, + private val dynamicIconController: DynamicIconController, ) { private val appReceiver = object : BroadcastReceiver() { @@ -23,6 +26,10 @@ class IconRepository( private val scope = CoroutineScope(Job() + Dispatchers.Main) + private val cache = LruCache(200) + + private var iconProviders: List = listOf() + private lateinit var placeholderProvider: IconProvider init { requestIconPackListUpdate() @@ -34,9 +41,9 @@ class IconRepository( addAction(Intent.ACTION_PACKAGE_CHANGED) addDataScheme("package") }) + recreate() } - private val cache = LruCache(200) fun getIcon(searchable: Searchable, size: Int): Flow = flow { var icon = cache.get(searchable.key) @@ -44,12 +51,19 @@ class IconRepository( emit(icon) return@flow } - val placeholderIcon = withContext(Dispatchers.IO) { - searchable.getPlaceholderIcon(context) - } - emit(placeholderIcon) - icon = withContext(Dispatchers.IO) { - searchable.loadIconAsync(context, size) + + icon = placeholderProvider.getIcon(searchable, size) + emit(icon) + + for (provider in iconProviders) { + val ic = provider.getIcon(searchable, size) + if (ic != null) { + icon = ic + if (icon is DynamicLauncherIcon) { + dynamicIconController.registerIcon(icon) + } + break + } } if (icon != null) { cache.put(searchable.key, icon) @@ -71,11 +85,20 @@ class IconRepository( } } - fun removeIconFromCache(searchable: Searchable) { - cache.remove(searchable.key) - } + fun recreate() { + placeholderProvider = PlaceholderIconProvider(context) + val providers = mutableListOf() - fun clearCache() { + if (iconPackManager.selectedIconPack.isNotBlank()) { + providers.add(IconPackIconProvider(context, iconPackManager.selectedIconPack)) + } + + providers.add(GoogleClockIconProvider(context)) + providers.add(CalendarIconProvider(context)) + providers.add(SystemIconProvider(context)) + providers.add(placeholderProvider) cache.evictAll() + + iconProviders = providers } } \ No newline at end of file diff --git a/icons/src/main/java/de/mm20/launcher2/icons/Module.kt b/icons/src/main/java/de/mm20/launcher2/icons/Module.kt index d3d487b3..dca1c618 100644 --- a/icons/src/main/java/de/mm20/launcher2/icons/Module.kt +++ b/icons/src/main/java/de/mm20/launcher2/icons/Module.kt @@ -6,5 +6,5 @@ import org.koin.dsl.module val iconsModule = module { single { DynamicIconController(androidContext()) } single { IconPackManager(androidContext(), get()) } - single { IconRepository(androidContext(), get()) } + single { IconRepository(androidContext(), get(), get()) } } \ No newline at end of file diff --git a/icons/src/main/java/de/mm20/launcher2/icons/providers/CalendarIconProvider.kt b/icons/src/main/java/de/mm20/launcher2/icons/providers/CalendarIconProvider.kt new file mode 100644 index 00000000..7d753300 --- /dev/null +++ b/icons/src/main/java/de/mm20/launcher2/icons/providers/CalendarIconProvider.kt @@ -0,0 +1,50 @@ +package de.mm20.launcher2.icons.providers + +import android.content.ComponentName +import android.content.Context +import android.content.pm.PackageManager +import android.graphics.drawable.ColorDrawable +import de.mm20.launcher2.icons.CalendarDynamicLauncherIcon +import de.mm20.launcher2.icons.LauncherIcon +import de.mm20.launcher2.ktx.obtainTypedArrayOrNull +import de.mm20.launcher2.preferences.LauncherPreferences +import de.mm20.launcher2.search.data.Application +import de.mm20.launcher2.search.data.Searchable + +class CalendarIconProvider(val context: Context): IconProvider { + override suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon? { + if(searchable !is Application) return null + val component = ComponentName(searchable.`package`, searchable.activity) + val pm = context.packageManager + val ai = try { + pm.getActivityInfo(component, PackageManager.GET_META_DATA) + } catch (e: PackageManager.NameNotFoundException) { + return null + } + val resources = pm.getResourcesForActivity(component) + var arrayId = ai.metaData?.getInt("com.teslacoilsw.launcher.calendarIconArray") ?: 0 + if (arrayId == 0) arrayId = ai.metaData?.getInt("com.google.android.calendar.dynamic_icons") + ?: return null + if (arrayId == 0) return null + val typedArray = resources.obtainTypedArrayOrNull(arrayId) ?: return null + if (typedArray.length() != 31) { + typedArray.recycle() + return null + } + val drawableIds = IntArray(31) + for (i in 0 until 31) { + drawableIds[i] = typedArray.getResourceId(i, 0) + } + typedArray.recycle() + return CalendarDynamicLauncherIcon( + context = context, + background = ColorDrawable(0), + foreground = ColorDrawable(0), + foregroundScale = 1.5f, + backgroundScale = 1.5f, + packageName = component.packageName, + drawableIds = drawableIds, + autoGenerateBackgroundMode = LauncherPreferences.instance.legacyIconBg.toInt() + ) + } +} \ No newline at end of file diff --git a/icons/src/main/java/de/mm20/launcher2/icons/providers/GoogleClockIconProvider.kt b/icons/src/main/java/de/mm20/launcher2/icons/providers/GoogleClockIconProvider.kt new file mode 100644 index 00000000..9ec5a657 --- /dev/null +++ b/icons/src/main/java/de/mm20/launcher2/icons/providers/GoogleClockIconProvider.kt @@ -0,0 +1,58 @@ +package de.mm20.launcher2.icons.providers + +import android.content.Context +import android.content.pm.PackageManager +import android.content.res.Resources +import android.graphics.drawable.AdaptiveIconDrawable +import android.graphics.drawable.LayerDrawable +import android.os.Build +import androidx.core.content.res.ResourcesCompat +import de.mm20.launcher2.icons.ClockDynamicLauncherIcon +import de.mm20.launcher2.icons.IconPackManager +import de.mm20.launcher2.icons.LauncherIcon +import de.mm20.launcher2.search.data.Application +import de.mm20.launcher2.search.data.Searchable + +class GoogleClockIconProvider(val context: Context) : IconProvider { + override suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon? { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return null + if (searchable !is Application) return null + if (searchable.`package` != "com.google.android.deskclock") return null + val pm = context.packageManager + val appInfo = try { + pm.getApplicationInfo( + IconPackManager.GOOGLE_DESK_CLOCK_PACKAGE_NAME, + PackageManager.GET_META_DATA + ) + } catch (e: PackageManager.NameNotFoundException) { + return null + } + val drawable = + appInfo.metaData.getInt("com.google.android.apps.nexuslauncher.LEVEL_PER_TICK_ICON_ROUND") + val resources = pm.getResourcesForApplication(appInfo) + val baseIcon = try { + ResourcesCompat.getDrawable(resources, drawable, null) as? AdaptiveIconDrawable + ?: return null + } catch (e: Resources.NotFoundException) { + return null + } + val foreground = baseIcon.foreground as? LayerDrawable ?: return null + val hourLayer = + appInfo.metaData.getInt("com.google.android.apps.nexuslauncher.HOUR_LAYER_INDEX") + val minuteLayer = + appInfo.metaData.getInt("com.google.android.apps.nexuslauncher.MINUTE_LAYER_INDEX") + val secondLayer = + appInfo.metaData.getInt("com.google.android.apps.nexuslauncher.SECOND_LAYER_INDEX") + return ClockDynamicLauncherIcon( + context = context, + background = baseIcon.background, + backgroundScale = 1.5f, + foreground = foreground, + foregroundScale = 1.5f, + badgeNumber = 0f, + hourLayer = hourLayer, + minuteLayer = minuteLayer, + secondLayer = secondLayer + ) + } +} \ No newline at end of file diff --git a/icons/src/main/java/de/mm20/launcher2/icons/providers/IconPackIconProvider.kt b/icons/src/main/java/de/mm20/launcher2/icons/providers/IconPackIconProvider.kt new file mode 100644 index 00000000..954f2d5c --- /dev/null +++ b/icons/src/main/java/de/mm20/launcher2/icons/providers/IconPackIconProvider.kt @@ -0,0 +1,207 @@ +package de.mm20.launcher2.icons.providers + +import android.content.ComponentName +import android.content.Context +import android.content.pm.LauncherActivityInfo +import android.content.pm.PackageManager +import android.content.res.Resources +import android.graphics.* +import android.graphics.drawable.AdaptiveIconDrawable +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.ColorDrawable +import android.os.Build +import android.util.Log +import androidx.core.content.res.ResourcesCompat +import androidx.core.graphics.drawable.toBitmap +import de.mm20.launcher2.database.AppDatabase +import de.mm20.launcher2.icons.CalendarDynamicLauncherIcon +import de.mm20.launcher2.icons.LauncherIcon +import de.mm20.launcher2.ktx.randomElementOrNull +import de.mm20.launcher2.preferences.IconShape +import de.mm20.launcher2.preferences.LauncherPreferences +import de.mm20.launcher2.search.data.LauncherApp +import de.mm20.launcher2.search.data.Searchable +import kotlin.math.roundToInt + +class IconPackIconProvider(val context: Context, val iconPack: String): IconProvider { + override suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon? { + if (searchable !is LauncherApp) return null + val res = try { + context.packageManager.getResourcesForApplication(iconPack) + } catch (e: PackageManager.NameNotFoundException) { + Log.e("MM20", "Icon pack package $iconPack not found!") + return null + } + val iconDao = AppDatabase.getInstance(context).iconDao() + val component = ComponentName(searchable.`package`, searchable.activity) + val icon = iconDao.getIcon(component.flattenToString(), iconPack) + ?: return generateIcon(context, searchable.launcherActivityInfo, size) + + if (icon.type == "calendar") { + return getIconPackCalendarIcon(context, icon.drawable ?: return null) + } + val drawableName = icon.drawable + val resId = res.getIdentifier(drawableName, "drawable", iconPack).takeIf { it != 0 } + ?: return generateIcon(context, searchable.launcherActivityInfo, size) + val drawable = ResourcesCompat.getDrawable(res, resId, context.theme) ?: return null + return when { + Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && drawable is AdaptiveIconDrawable -> { + LauncherIcon( + foreground = drawable.foreground, + background = drawable.background, + foregroundScale = 1.5f, + backgroundScale = 1.5f + ) + } + else -> { + LauncherIcon( + foreground = drawable, + foregroundScale = getScale(), + autoGenerateBackgroundMode = LauncherPreferences.instance.legacyIconBg.toInt() + ) + } + } + } + + private fun getScale(): Float { + return when (LauncherPreferences.instance.iconShape) { + IconShape.CIRCLE, IconShape.PLATFORM_DEFAULT -> 0.7f + else -> 0.8f + + } + } + + private suspend fun generateIcon( + context: Context, + activity: LauncherActivityInfo, + size: Int + ): LauncherIcon? { + val back = getIconBack() + val upon = getIconUpon() + val mask = getIconMask() + val scale = getPackScale() + + if (back == null && upon == null && mask == null) { + return null + } + + val drawable = activity.getIcon(context.resources.displayMetrics.densityDpi) + val bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888) + + val canvas = Canvas(bitmap) + val paint = Paint() + paint.isAntiAlias = true + paint.isFilterBitmap = true + paint.isDither = true + + + var inBounds: Rect + var outBounds: Rect + + val icon = drawable.toBitmap(width = size, height = size) + + inBounds = Rect(0, 0, icon.width, icon.height) + outBounds = Rect( + (bitmap.width * (1 - scale) * 0.5).roundToInt(), + (bitmap.height * (1 - scale) * 0.5).roundToInt(), + (bitmap.width - bitmap.width * (1 - scale) * 0.5).roundToInt(), + (bitmap.height - bitmap.height * (1 - scale) * 0.5).roundToInt() + ) + canvas.drawBitmap(icon, inBounds, outBounds, paint) + + val pack = iconPack + val pm = context.packageManager + val res = try { + pm.getResourcesForApplication(pack) + } catch (e: Resources.NotFoundException) { + return null + } + + if (mask != null) { + res.getIdentifier(mask, "drawable", pack).takeIf { it != 0 }?.let { + paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OUT) + val maskDrawable = ResourcesCompat.getDrawable(res, it, null) ?: return null + val maskBmp = maskDrawable.toBitmap(size, size) + inBounds = Rect(0, 0, maskBmp.width, maskBmp.height) + outBounds = Rect(0, 0, bitmap.width, bitmap.height) + canvas.drawBitmap(maskBmp, inBounds, outBounds, paint) + } + } + if (upon != null) { + res.getIdentifier(upon, "drawable", pack).takeIf { it != 0 }?.let { + paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_OVER); + val maskDrawable = ResourcesCompat.getDrawable(res, it, null) ?: return null + val maskBmp = maskDrawable.toBitmap(size, size) + inBounds = Rect(0, 0, maskBmp.width, maskBmp.height) + outBounds = Rect(0, 0, bitmap.width, bitmap.height) + canvas.drawBitmap(maskBmp, inBounds, outBounds, paint) + } + } + if (back != null) { + res.getIdentifier(back, "drawable", pack).takeIf { it != 0 }?.let { + paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OVER); + val maskDrawable = ResourcesCompat.getDrawable(res, it, null) ?: return null + val maskBmp = maskDrawable.toBitmap(size, size) + inBounds = Rect(0, 0, maskBmp.width, maskBmp.height) + outBounds = Rect(0, 0, bitmap.width, bitmap.height) + canvas.drawBitmap(maskBmp, inBounds, outBounds, paint) + } + } + + return LauncherIcon( + foreground = BitmapDrawable(context.resources, bitmap), + foregroundScale = getScale(), + autoGenerateBackgroundMode = LauncherPreferences.instance.legacyIconBg.toInt() + ) + } + + private suspend fun getIconBack(): String? { + val iconDao = AppDatabase.getInstance(context).iconDao() + val iconbacks = iconDao.getIconBacks(iconPack) + return iconbacks.randomElementOrNull() + } + + private suspend fun getIconUpon(): String? { + val iconDao = AppDatabase.getInstance(context).iconDao() + val iconupons = iconDao.getIconUpons(iconPack) + return iconupons.randomElementOrNull() + } + + private suspend fun getIconMask(): String? { + val iconDao = AppDatabase.getInstance(context).iconDao() + val iconmasks = iconDao.getIconMasks(iconPack) + return iconmasks.randomElementOrNull() + } + + private suspend fun getPackScale(): Float { + val iconDao = AppDatabase.getInstance(context).iconDao() + return iconDao.getScale(iconPack) ?: 1f + } + + private fun getIconPackCalendarIcon( + context: Context, + baseIconName: String + ): CalendarDynamicLauncherIcon? { + val resources = try { + context.packageManager.getResourcesForApplication(iconPack) + } catch (e: PackageManager.NameNotFoundException) { + return null + } + val drawableIds = (1..31).map { + val drawableName = baseIconName + it + val id = resources.getIdentifier(drawableName, "drawable", iconPack) + if (id == 0) return null + id + }.toIntArray() + return CalendarDynamicLauncherIcon( + context = context, + background = ColorDrawable(0), + foreground = ColorDrawable(0), + foregroundScale = 1.5f, + backgroundScale = 1.5f, + packageName = iconPack, + drawableIds = drawableIds, + autoGenerateBackgroundMode = LauncherPreferences.instance.legacyIconBg.toInt() + ) + } +} \ No newline at end of file diff --git a/icons/src/main/java/de/mm20/launcher2/icons/providers/IconProvider.kt b/icons/src/main/java/de/mm20/launcher2/icons/providers/IconProvider.kt new file mode 100644 index 00000000..5a6cde1a --- /dev/null +++ b/icons/src/main/java/de/mm20/launcher2/icons/providers/IconProvider.kt @@ -0,0 +1,8 @@ +package de.mm20.launcher2.icons.providers + +import de.mm20.launcher2.icons.LauncherIcon +import de.mm20.launcher2.search.data.Searchable + +interface IconProvider { + suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon? +} \ No newline at end of file diff --git a/icons/src/main/java/de/mm20/launcher2/icons/providers/PlaceholderIconProvider.kt b/icons/src/main/java/de/mm20/launcher2/icons/providers/PlaceholderIconProvider.kt new file mode 100644 index 00000000..539f5dbf --- /dev/null +++ b/icons/src/main/java/de/mm20/launcher2/icons/providers/PlaceholderIconProvider.kt @@ -0,0 +1,11 @@ +package de.mm20.launcher2.icons.providers + +import android.content.Context +import de.mm20.launcher2.icons.LauncherIcon +import de.mm20.launcher2.search.data.Searchable + +class PlaceholderIconProvider(val context: Context) : IconProvider { + override suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon? { + return searchable.getPlaceholderIcon(context) + } +} \ No newline at end of file diff --git a/icons/src/main/java/de/mm20/launcher2/icons/providers/SystemIconProvider.kt b/icons/src/main/java/de/mm20/launcher2/icons/providers/SystemIconProvider.kt new file mode 100644 index 00000000..9cb49cb8 --- /dev/null +++ b/icons/src/main/java/de/mm20/launcher2/icons/providers/SystemIconProvider.kt @@ -0,0 +1,11 @@ +package de.mm20.launcher2.icons.providers + +import android.content.Context +import de.mm20.launcher2.icons.LauncherIcon +import de.mm20.launcher2.search.data.Searchable + +class SystemIconProvider(val context: Context) : IconProvider { + override suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon? { + return searchable.loadIconAsync(context, size) + } +} \ No newline at end of file