Initial commit
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="de.mm20.launcher2.icons">
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="org.adw.ActivityStarter.THEMES" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="com.novalauncher.THEME" />
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
@@ -0,0 +1,65 @@
|
||||
package de.mm20.launcher2.icons
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.drawable.AdaptiveIconDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import de.mm20.launcher2.ktx.getDrawableOrNull
|
||||
import java.util.*
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
class CalendarDynamicLauncherIcon(
|
||||
context: Context,
|
||||
foreground: Drawable,
|
||||
background: Drawable?,
|
||||
foregroundScale: Float,
|
||||
backgroundScale: Float,
|
||||
badgeNumber: Float = 0f,
|
||||
val packageName: String,
|
||||
val drawableIds: IntArray,
|
||||
autoGenerateBackgroundMode: Int
|
||||
) : DynamicLauncherIcon(
|
||||
foreground,
|
||||
background,
|
||||
foregroundScale,
|
||||
backgroundScale,
|
||||
/** Not needed, we already have a background **/
|
||||
autoGenerateBackgroundMode,
|
||||
badgeNumber,
|
||||
null
|
||||
) {
|
||||
|
||||
init {
|
||||
DynamicIconController.getInstance(context).registerIcon(this)
|
||||
update(context)
|
||||
}
|
||||
|
||||
var currentDay = 0
|
||||
override fun update(context: Context) {
|
||||
val calendar = Calendar.getInstance()
|
||||
val day = calendar[Calendar.DAY_OF_MONTH]
|
||||
if (day == currentDay || drawableIds.size < currentDay) return
|
||||
val resources = try {
|
||||
context.packageManager.getResourcesForApplication(packageName)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
return
|
||||
}
|
||||
Executors.newSingleThreadExecutor().execute {
|
||||
val currentDayDrawable = resources.getDrawableOrNull(drawableIds[day - 1])
|
||||
?: return@execute
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && currentDayDrawable is AdaptiveIconDrawable) {
|
||||
foreground = currentDayDrawable.foreground
|
||||
background = currentDayDrawable.background
|
||||
foregroundScale = 1.5f
|
||||
backgroundScale = 1.5f
|
||||
} else {
|
||||
foregroundScale = 1f
|
||||
backgroundScale = 1f
|
||||
background = null
|
||||
foreground = currentDayDrawable
|
||||
}
|
||||
}
|
||||
currentDay = day
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package de.mm20.launcher2.icons
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.LayerDrawable
|
||||
import android.graphics.drawable.RotateDrawable
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import java.util.*
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
class ClockDynamicLauncherIcon(
|
||||
context: Context,
|
||||
foreground: LayerDrawable,
|
||||
background: Drawable?,
|
||||
foregroundScale: Float,
|
||||
backgroundScale: Float,
|
||||
badgeNumber: Float,
|
||||
val hourLayer: Int,
|
||||
val minuteLayer: Int,
|
||||
val secondLayer: Int
|
||||
) : DynamicLauncherIcon(
|
||||
foreground,
|
||||
background,
|
||||
foregroundScale,
|
||||
backgroundScale,
|
||||
/** Not needed, we already have a background **/
|
||||
LauncherIcon.BACKGROUND_WHITE,
|
||||
badgeNumber,
|
||||
null
|
||||
) {
|
||||
|
||||
init {
|
||||
foreground.also {
|
||||
it.setDrawable(secondLayer, ColorDrawable(0))
|
||||
(it.getDrawable(hourLayer) as? RotateDrawable)?.fromDegrees = 0f
|
||||
(it.getDrawable(hourLayer) as? RotateDrawable)?.toDegrees = 360f
|
||||
(it.getDrawable(minuteLayer) as? RotateDrawable)?.fromDegrees = 0f
|
||||
(it.getDrawable(minuteLayer) as? RotateDrawable)?.toDegrees = 360f
|
||||
}
|
||||
DynamicIconController.getInstance(context).registerIcon(this)
|
||||
update(context)
|
||||
}
|
||||
|
||||
override fun update(context: Context) {
|
||||
val calendar = Calendar.getInstance()
|
||||
val hourDegrees = calendar[Calendar.HOUR] / 12f * 10000 + calendar[Calendar.MINUTE] / 60f * 10000f / 12
|
||||
val minuteDegrees = calendar[Calendar.MINUTE] / 60f * 10000
|
||||
(foreground as LayerDrawable).also {
|
||||
(it.getDrawable(hourLayer) as? RotateDrawable)?.level = hourDegrees.roundToInt()
|
||||
(it.getDrawable(minuteLayer) as? RotateDrawable)?.level = minuteDegrees.roundToInt()
|
||||
}
|
||||
notifyCallbacks()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package de.mm20.launcher2.icons
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.Application
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleObserver
|
||||
import androidx.lifecycle.OnLifecycleEvent
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
class DynamicIconController(val context: Context): LifecycleObserver {
|
||||
|
||||
private var timeReceiver: BroadcastReceiver? = null
|
||||
private val registeredIcons = mutableListOf<WeakReference<DynamicLauncherIcon>>()
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
|
||||
fun resume() {
|
||||
timeReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent?) {
|
||||
updateAllIcons(context)
|
||||
}
|
||||
}
|
||||
val filter = IntentFilter(Intent.ACTION_TIME_TICK).also {
|
||||
it.addAction(Intent.ACTION_TIME_CHANGED)
|
||||
it.addAction(Intent.ACTION_TIMEZONE_CHANGED)
|
||||
}
|
||||
context.registerReceiver(timeReceiver, filter)
|
||||
updateAllIcons(context)
|
||||
}
|
||||
|
||||
private fun updateAllIcons(context: Context) {
|
||||
val iterator = registeredIcons.iterator()
|
||||
while (iterator.hasNext()) {
|
||||
val iconRef = iterator.next()
|
||||
if (iconRef.get() != null) iconRef.get()?.update(context)
|
||||
else iterator.remove()
|
||||
}
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
|
||||
fun pause() {
|
||||
try {
|
||||
context.unregisterReceiver(timeReceiver)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
|
||||
}
|
||||
timeReceiver = null
|
||||
}
|
||||
|
||||
fun registerIcon(icon: DynamicLauncherIcon) {
|
||||
registeredIcons.add(WeakReference(icon))
|
||||
}
|
||||
|
||||
companion object {
|
||||
private lateinit var instance: DynamicIconController
|
||||
|
||||
fun getInstance(context: Context): DynamicIconController {
|
||||
if(!::instance.isInitialized) instance = DynamicIconController(context.applicationContext)
|
||||
return instance
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package de.mm20.launcher2.icons
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Drawable
|
||||
|
||||
abstract class DynamicLauncherIcon(
|
||||
foreground: Drawable,
|
||||
background: Drawable?,
|
||||
foregroundScale: Float,
|
||||
backgroundScale: Float,
|
||||
autoGenerateBackgroundMode: Int,
|
||||
badgeNumber: Float,
|
||||
badgeDrawable: Drawable?)
|
||||
: LauncherIcon(
|
||||
foreground,
|
||||
background,
|
||||
foregroundScale,
|
||||
backgroundScale,
|
||||
autoGenerateBackgroundMode
|
||||
) {
|
||||
abstract fun update(context: Context)
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package de.mm20.launcher2.icons
|
||||
|
||||
import android.content.ComponentName
|
||||
import de.mm20.launcher2.database.entities.IconEntity
|
||||
|
||||
data class Icon(
|
||||
val type: String,
|
||||
val componentName: ComponentName?,
|
||||
val drawable: String?,
|
||||
val iconPack: String,
|
||||
val scale: Float? = null
|
||||
) {
|
||||
constructor(entity: IconEntity) : this(
|
||||
type = entity.type,
|
||||
componentName = entity.componentName,
|
||||
drawable = entity.drawable,
|
||||
iconPack = entity.iconPack,
|
||||
scale = entity.scale
|
||||
)
|
||||
|
||||
fun toDatabaseEntity(): IconEntity {
|
||||
return IconEntity(
|
||||
type = type,
|
||||
componentName = componentName,
|
||||
drawable = drawable,
|
||||
iconPack = iconPack,
|
||||
scale = scale
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package de.mm20.launcher2.icons
|
||||
|
||||
import de.mm20.launcher2.database.entities.IconPackEntity
|
||||
|
||||
data class IconPack(
|
||||
val name: String,
|
||||
val packageName: String,
|
||||
val version: String,
|
||||
var scale: Float = 1f
|
||||
) {
|
||||
constructor(entity: IconPackEntity) : this(
|
||||
name = entity.name,
|
||||
packageName = entity.packageName,
|
||||
version = entity.packageName,
|
||||
scale = entity.scale
|
||||
)
|
||||
|
||||
fun toDatabaseEntity(): IconPackEntity {
|
||||
return IconPackEntity(
|
||||
name = name,
|
||||
scale = scale,
|
||||
version = version,
|
||||
packageName = packageName
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,517 @@
|
||||
package de.mm20.launcher2.icons
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.LauncherActivityInfo
|
||||
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.ColorDrawable
|
||||
import android.graphics.drawable.LayerDrawable
|
||||
import android.os.Build
|
||||
import android.os.UserHandle
|
||||
import android.util.DisplayMetrics
|
||||
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.dp
|
||||
import de.mm20.launcher2.ktx.obtainTypedArrayOrNull
|
||||
import de.mm20.launcher2.ktx.randomElementOrNull
|
||||
import de.mm20.launcher2.preferences.IconShape
|
||||
import de.mm20.launcher2.preferences.LauncherPreferences
|
||||
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
|
||||
|
||||
|
||||
class IconPackManager private constructor(val context: Context) {
|
||||
var selectedIconPack: String
|
||||
get() {
|
||||
return context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE)
|
||||
.getString(KEY_ICON_PACK, "")!!
|
||||
}
|
||||
set(value) {
|
||||
Log.d("MM20", "Selected icon pack: $value")
|
||||
context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
.putString(KEY_ICON_PACK, value)
|
||||
.apply()
|
||||
}
|
||||
|
||||
|
||||
fun selectIconPack(iconPack: String) {
|
||||
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)
|
||||
}
|
||||
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 { return it }
|
||||
}
|
||||
getCalendarIcon(context, activity)?.let { 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): CalendarDynamicLauncherIcon? {
|
||||
val component = ComponentName(activity.applicationInfo.packageName, activity.name)
|
||||
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()
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
else -> 0.8f
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
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<IconPack> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
AppDatabase.getInstance(context).iconDao().getInstalledIconPacks().map {
|
||||
IconPack(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val GOOGLE_DESK_CLOCK_PACKAGE_NAME = "com.google.android.deskclock"
|
||||
const val GOOGLE_CALENDAR_PACKAGE_NAME = "com.google.android.calendar"
|
||||
|
||||
private lateinit var instance: IconPackManager
|
||||
fun getInstance(context: Context): IconPackManager {
|
||||
if (!::instance.isInitialized) instance = IconPackManager(context.applicationContext)
|
||||
return instance
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
suspend fun updateIconPacks() {
|
||||
withContext(Dispatchers.IO) {
|
||||
UpdateIconPacksWorker(context).doWork()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class UpdateIconPacksWorker(val context: Context) {
|
||||
|
||||
fun doWork() {
|
||||
val packs = loadInstalledPacks(context).map { it.activityInfo.packageName }
|
||||
val iconDao = AppDatabase.getInstance(context).iconDao()
|
||||
iconDao.uninstallIconPacksExcept(packs)
|
||||
|
||||
for (pack in packs) {
|
||||
try {
|
||||
val packInfo = context.packageManager.getPackageInfo(pack, 0)
|
||||
val iconPack = IconPack(
|
||||
name = packInfo.applicationInfo.loadLabel(context.packageManager).toString(),
|
||||
packageName = pack,
|
||||
version = packInfo.versionName
|
||||
)
|
||||
//if (iconDao.isInstalled(iconPack)) continue
|
||||
installIconPack(iconPack)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadInstalledPacks(context: Context): List<ResolveInfo> {
|
||||
val packs = mutableListOf<ResolveInfo>()
|
||||
val pm = context.packageManager
|
||||
var intent = Intent("org.adw.ActivityStarter.THEMES")
|
||||
val adwPacks = pm.queryIntentActivities(intent, 0)
|
||||
packs.addAll(adwPacks)
|
||||
intent = Intent("com.novalauncher.THEME")
|
||||
val novaPacks = pm.queryIntentActivities(intent, 0)
|
||||
novaPacks.forEach {
|
||||
if (packs.none { p -> p.activityInfo.packageName == it.activityInfo.packageName }) packs.add(it)
|
||||
}
|
||||
packs.sortWith(ResolveInfo.DisplayNameComparator(pm))
|
||||
return packs
|
||||
}
|
||||
|
||||
private fun installIconPack(iconPack: IconPack) {
|
||||
val pkgName = iconPack.packageName
|
||||
try {
|
||||
val res = context.packageManager.getResourcesForApplication(pkgName)
|
||||
val parser: XmlPullParser
|
||||
var inStream: InputStreamReader? = null
|
||||
val xmlId = res.getIdentifier("appfilter", "xml", pkgName)
|
||||
if (xmlId != 0) parser = res.getXml(xmlId)
|
||||
else {
|
||||
val rawId = res.getIdentifier("appfilter", "raw", pkgName)
|
||||
if (rawId == 0) {
|
||||
Log.e("MM20", "Icon pack $pkgName has no appfilter.xml, neither in xml nor in raw")
|
||||
return
|
||||
}
|
||||
parser = XmlPullParserFactory.newInstance().newPullParser()
|
||||
inStream = res.openRawResource(rawId).reader()
|
||||
parser.setInput(inStream)
|
||||
}
|
||||
|
||||
val icons = mutableListOf<Icon>()
|
||||
val iconDao = AppDatabase.getInstance(context).iconDao()
|
||||
|
||||
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 icon = Icon(
|
||||
componentName = componentName,
|
||||
drawable = drawable,
|
||||
iconPack = pkgName,
|
||||
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 icon = Icon(
|
||||
componentName = componentName,
|
||||
drawable = drawable,
|
||||
iconPack = pkgName,
|
||||
type = "calendar"
|
||||
)
|
||||
icons.add(icon)
|
||||
}
|
||||
"iconback" -> {
|
||||
for (i in 0 until parser.attributeCount) {
|
||||
if (parser.getAttributeName(i).startsWith("img")) {
|
||||
val drawable = parser.getAttributeValue(i)
|
||||
val icon = Icon(
|
||||
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 = Icon(
|
||||
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 = Icon(
|
||||
componentName = null,
|
||||
drawable = drawable,
|
||||
iconPack = pkgName,
|
||||
type = "iconmask"
|
||||
)
|
||||
icons.add(icon)
|
||||
}
|
||||
}
|
||||
}
|
||||
"scale" -> {
|
||||
val scale = parser.getAttributeValue(null, "factor")?.toFloatOrNull()
|
||||
?: continue@loop
|
||||
iconPack.scale = scale
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
iconDao.installIconPack(iconPack.toDatabaseEntity(), icons.map { it.toDatabaseEntity() })
|
||||
|
||||
(parser as? XmlResourceParser)?.close()
|
||||
inStream?.close()
|
||||
|
||||
Log.d("MM20", "Icon pack has been installed successfully")
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
Log.e("MM20", "Could not install icon pack $pkgName: package not found.")
|
||||
} catch (e: XmlPullParserException) {
|
||||
CrashReporter.logException(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val PREFERENCE_NAME = "icon_pack"
|
||||
private const val KEY_ICON_PACK = "icon_pack"
|
||||
private const val KEY_VERSION = "version"
|
||||
private const val KEY_ICONSCALE = "iconscale"
|
||||
@@ -0,0 +1,67 @@
|
||||
package de.mm20.launcher2.icons
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import android.util.LruCache
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
|
||||
class IconRepository private constructor(val context: Context) {
|
||||
|
||||
private val scope = CoroutineScope(Job() + Dispatchers.Main)
|
||||
|
||||
private val cache = LruCache<String, LauncherIcon>(200)
|
||||
|
||||
fun getIcon(searchable: Searchable, size: Int): Flow<LauncherIcon> = flow {
|
||||
var icon = cache.get(searchable.key)
|
||||
if (icon != null) {
|
||||
emit(icon)
|
||||
return@flow
|
||||
}
|
||||
val placeholderIcon = withContext(Dispatchers.IO) {
|
||||
searchable.getPlaceholderIcon(context)
|
||||
}
|
||||
emit(placeholderIcon)
|
||||
icon = withContext(Dispatchers.IO) {
|
||||
searchable.loadIconAsync(context, size)
|
||||
}
|
||||
if (icon != null) {
|
||||
cache.put(searchable.key, icon)
|
||||
emit(icon)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the icon for the given Searchable if it was requested earlier and is still in cache.
|
||||
* Returns `null` otherwise.
|
||||
*/
|
||||
fun getIconIfCached(searchable: Searchable): LauncherIcon? {
|
||||
return cache[searchable.key]
|
||||
}
|
||||
|
||||
fun requestIconPackListUpdate() {
|
||||
scope.launch {
|
||||
IconPackManager.getInstance(context).updateIconPacks()
|
||||
}
|
||||
}
|
||||
|
||||
fun removeIconFromCache(searchable: Searchable) {
|
||||
cache.remove(searchable.key)
|
||||
}
|
||||
|
||||
fun clearCache() {
|
||||
cache.evictAll()
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
private lateinit var instance: IconRepository
|
||||
|
||||
fun getInstance(context: Context): IconRepository {
|
||||
if (!::instance.isInitialized) instance = IconRepository(context.applicationContext)
|
||||
return instance
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user