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 814f4c08..7cfbfd3a 100644 --- a/icons/src/main/java/de/mm20/launcher2/icons/IconPackManager.kt +++ b/icons/src/main/java/de/mm20/launcher2/icons/IconPackManager.kt @@ -5,16 +5,25 @@ import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.content.pm.ResolveInfo +import android.content.res.Resources import android.content.res.XmlResourceParser +import android.graphics.* +import android.graphics.drawable.AdaptiveIconDrawable +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable 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.ktx.randomElementOrNull 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.InputStreamReader +import kotlin.math.roundToInt private val SUPPORTED_GRAYSCALE_MAP_PROVIDERS = arrayOf( "com.google.android.apps.nexuslauncher", // Pixel Launcher @@ -25,22 +34,203 @@ private val SUPPORTED_GRAYSCALE_MAP_PROVIDERS = arrayOf( class IconPackManager( - val context: Context + private val context: Context, + private val appDatabase: AppDatabase, ) { suspend fun getInstalledIconPacks(): List { return withContext(Dispatchers.IO) { - AppDatabase.getInstance(context).iconDao().getInstalledIconPacks().map { + appDatabase.iconDao().getInstalledIconPacks().map { IconPack(it) } } } - @Synchronized suspend fun updateIconPacks() { withContext(Dispatchers.IO) { UpdateIconPacksWorker(context).doWork() } } + + suspend fun getIcon(iconPack: String, componentName: ComponentName, size: Int): LauncherIcon? { + 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 icon = iconDao.getIcon(componentName.flattenToString(), iconPack) + ?: return null + + val drawableName = icon.drawable ?: return null + + if (icon.type == "calendar") { + return getIconPackCalendarIcon(context, iconPack, drawableName) + } + val resId = res.getIdentifier(drawableName, "drawable", iconPack).takeIf { it != 0 } + ?: return null + val drawable = ResourcesCompat.getDrawable(res, resId, context.theme) ?: return null + return when (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 + ) + } + } + } + + suspend fun generateIcon( + context: Context, + iconPack: String, + baseIcon: Drawable, + size: Int + ): LauncherIcon? { + val back = getIconBack(iconPack) + val upon = getIconUpon(iconPack) + val mask = getIconMask(iconPack) + val scale = getPackScale(iconPack) + + if (back == null && upon == null && mask == null) { + return null + } + + 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 = baseIcon.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 StaticLauncherIcon( + foregroundLayer = StaticIconLayer( + icon = BitmapDrawable(context.resources, bitmap), + scale = 1f, + ), + backgroundLayer = TransparentLayer + ) + } + + private suspend fun getIconBack(iconPack: String): String? { + val iconDao = AppDatabase.getInstance(context).iconDao() + val iconbacks = iconDao.getIconBacks(iconPack) + return iconbacks.randomElementOrNull() + } + + private suspend fun getIconUpon(iconPack: String): String? { + val iconDao = AppDatabase.getInstance(context).iconDao() + val iconupons = iconDao.getIconUpons(iconPack) + return iconupons.randomElementOrNull() + } + + private suspend fun getIconMask(iconPack: String): String? { + val iconDao = AppDatabase.getInstance(context).iconDao() + val iconmasks = iconDao.getIconMasks(iconPack) + return iconmasks.randomElementOrNull() + } + + private suspend fun getPackScale(iconPack: String): Float { + val iconDao = AppDatabase.getInstance(context).iconDao() + return iconDao.getScale(iconPack) ?: 1f + } + + private fun getIconPackCalendarIcon( + context: Context, + iconPack: String, + baseIconName: String + ): DynamicCalendarIcon? { + 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 DynamicCalendarIcon( + resources = resources, + resourceIds = drawableIds + ) + } + + } 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 f9df413f..f5545497 100644 --- a/icons/src/main/java/de/mm20/launcher2/icons/IconRepository.kt +++ b/icons/src/main/java/de/mm20/launcher2/icons/IconRepository.kt @@ -73,7 +73,8 @@ class IconRepository( providers.add( IconPackIconProvider( context, - settings.iconPack + settings.iconPack, + iconPackManager ) ) } 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 f2f7d597..e8fb6ae5 100644 --- a/icons/src/main/java/de/mm20/launcher2/icons/Module.kt +++ b/icons/src/main/java/de/mm20/launcher2/icons/Module.kt @@ -4,6 +4,6 @@ import org.koin.android.ext.koin.androidContext import org.koin.dsl.module val iconsModule = module { - single { IconPackManager(androidContext()) } + single { IconPackManager(androidContext(), get()) } single { IconRepository(androidContext(), get(), get(), get()) } } \ 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 index e36fb377..48525f9d 100644 --- a/icons/src/main/java/de/mm20/launcher2/icons/providers/IconPackIconProvider.kt +++ b/icons/src/main/java/de/mm20/launcher2/icons/providers/IconPackIconProvider.kt @@ -18,192 +18,27 @@ import de.mm20.launcher2.ktx.randomElementOrNull import de.mm20.launcher2.preferences.Settings import de.mm20.launcher2.search.data.LauncherApp import de.mm20.launcher2.search.data.Searchable +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import kotlin.math.roundToInt class IconPackIconProvider( private val context: Context, - private val iconPack: String + private val iconPack: String, + private val iconPackManager: IconPackManager, ): 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 (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 = getScale() - ), - backgroundLayer = TransparentLayer - ) - } - } - } - - private fun getScale(): Float { - return 1f - } - - 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 StaticLauncherIcon( - foregroundLayer = StaticIconLayer( - icon = BitmapDrawable(context.resources, bitmap), - scale = getScale(), - ), - backgroundLayer = TransparentLayer - ) - } - - 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 - ): DynamicCalendarIcon? { - 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 DynamicCalendarIcon( - resources = resources, - resourceIds = drawableIds - ) + return iconPackManager.getIcon(iconPack, component, size) + ?: iconPackManager.generateIcon( + context, + iconPack, + baseIcon = withContext(Dispatchers.IO) { + searchable.launcherActivityInfo.getIcon(context.resources.displayMetrics.densityDpi) + }, + size = size, + ) } } \ No newline at end of file