From 3d1835fd0600374d55379ff544d1795d924c486d Mon Sep 17 00:00:00 2001 From: F4uzan Date: Wed, 16 Dec 2020 12:38:43 +0000 Subject: [PATCH] Early implementation of custom icon handling As requested by #86, this feature allows the user to be able to select an icon from icon packs, replacing the default and/or existing icons provided by the current icon pack. This implementation caches the icon into a PNG, saving it to the launcher's files directory. This means that it can't be saved to when backing up the launcher configuration, but it will persist even if the source of this icon (the icon pack where icon came from) is removed. At the moment, there should be a consideration on the use of simple index instead of having to cache an entire file to save on space, but this works for now. --- .../mono/hg/helpers/LauncherIconHelper.kt | 45 ++++++++++++++++- .../main/java/mono/hg/pages/AppsListPage.kt | 49 ++++++++++++++++++- app/src/main/res/menu/menu_app.xml | 5 ++ app/src/main/res/values/strings.xml | 6 ++- 4 files changed, 102 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/mono/hg/helpers/LauncherIconHelper.kt b/app/src/main/java/mono/hg/helpers/LauncherIconHelper.kt index 1825ecb8..6eddaaaf 100644 --- a/app/src/main/java/mono/hg/helpers/LauncherIconHelper.kt +++ b/app/src/main/java/mono/hg/helpers/LauncherIconHelper.kt @@ -7,6 +7,7 @@ import android.content.pm.LauncherApps import android.content.pm.PackageManager import android.content.res.Resources import android.graphics.Bitmap +import android.graphics.BitmapFactory import android.graphics.BlurMaskFilter import android.graphics.Canvas import android.graphics.Color @@ -26,6 +27,8 @@ import mono.hg.utils.Utils.LogLevel import org.xmlpull.v1.XmlPullParser import org.xmlpull.v1.XmlPullParserException import org.xmlpull.v1.XmlPullParserFactory +import java.io.File +import java.io.FileOutputStream import java.io.IOException import java.util.* @@ -97,6 +100,29 @@ object LauncherIconHelper { return getDefaultIconDrawable(activity, componentName, user) } + /** + * Caches an icon to the launcher's files directory. + * + * This is only used for custom icon. + * Most icons are not cached to save on space. + * + * @param context Context required to retrieve the path to the files directory. + * @param bitmap The Bitmap to cache. + * @param componentName The component name that will use this cached Bitmap. + * This component name will be reduced to package name. + */ + fun cacheIcon(context: Context, bitmap: Bitmap, componentName: String) { + try { + FileOutputStream(context.filesDir.path + File.separatorChar + componentName).apply { + bitmap.compress(Bitmap.CompressFormat.PNG, 100, this) // Quality is unused here. + flush() + close() + } + } catch (e: Exception) { + Utils.sendLog(3, e.toString()) + } + } + private fun drawAdaptiveShadow(resources: Resources, icon: Drawable): BitmapDrawable { return BitmapDrawable(resources, addShadow(icon, icon.intrinsicHeight, icon.intrinsicWidth)) } @@ -324,8 +350,21 @@ object LauncherIconHelper { val iconPackageName = PreferenceHelper.preference.getString("icon_pack", "default") ?: "default" val defaultIcon: Drawable? = getDefaultIconDrawable(activity, appPackageName, user) + val customIconPath = + activity.filesDir.path + File.separatorChar + AppUtils.getPackageName(appPackageName) try { + // If there is a custom icon set, use that over the default one. + with(File(customIconPath)) { + if (exists() && ! isDirectory) { + BitmapFactory.Options().apply { + inPreferredConfig = Bitmap.Config.ARGB_8888 + }.also { + return BitmapDrawable(activity.resources, BitmapFactory.decodeFile(this.path, it)) + } + } + } + val iconRes = if ("default" != iconPackageName) { packageManager.getResourcesForApplication(iconPackageName) } else { @@ -430,7 +469,11 @@ object LauncherIconHelper { return BitmapDrawable(resources, result) } - private fun getDefaultIconDrawable(activity: Activity, appPackageName: String, user:Long): Drawable? { + private fun getDefaultIconDrawable( + activity: Activity, + appPackageName: String, + user: Long + ): Drawable? { return if (Utils.atLeastLollipop()) { try { val launcher = diff --git a/app/src/main/java/mono/hg/pages/AppsListPage.kt b/app/src/main/java/mono/hg/pages/AppsListPage.kt index 45f0ba83..0f0380bd 100644 --- a/app/src/main/java/mono/hg/pages/AppsListPage.kt +++ b/app/src/main/java/mono/hg/pages/AppsListPage.kt @@ -1,5 +1,6 @@ package mono.hg.pages +import android.app.Activity.RESULT_OK import android.content.BroadcastReceiver import android.content.Context import android.content.Intent @@ -7,6 +8,8 @@ import android.content.IntentFilter import android.content.pm.LauncherApps import android.content.pm.PackageManager import android.content.res.ColorStateList +import android.graphics.Bitmap +import android.graphics.drawable.BitmapDrawable import android.net.Uri import android.os.Bundle import android.os.UserManager @@ -58,6 +61,11 @@ import kotlin.collections.ArrayList * This is the generic implementation of an app list that handles the required features. */ class AppsListPage : GenericPage() { + /* + * Index of an app that is currently being edited. + */ + private var editingAppPosition: Int = -1 + /* * Adapter for installed apps. */ @@ -309,6 +317,29 @@ class AppsListPage : GenericPage() { } } + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + + // Update the icon on the selected app. + if (resultCode == RESULT_OK && requestCode == SET_ICON_REQUEST) { + data?.getParcelableExtra("icon")?.let { bitmap -> + appsAdapter.getItem(editingAppPosition).apply { + this?.packageName?.let { + LauncherIconHelper.cacheIcon( + requireContext(), + bitmap, + AppUtils.getPackageName(it) + ) + } + this?.icon = BitmapDrawable(resources, bitmap) + }?.let { appsAdapter.updateItem(it) } + } + + // Reset the index once we're done. + editingAppPosition = -1 + } + } + override fun isAcceptingSearch(): Boolean { return true } @@ -363,6 +394,20 @@ class AppsListPage : GenericPage() { show() setOnMenuItemClickListener { item -> when (item.itemId) { + R.id.action_icon -> { + // Update the index first. + editingAppPosition = appsAdapter.getGlobalPositionOf(app) + + // Load the picker intent. + // TODO: We should use more intent actions here. + Intent("org.adw.launcher.icons.ACTION_PICK_ICON").apply { + startActivityForResult( + Intent.createChooser( + this, getString(R.string.dialog_title_set_icon) + ), SET_ICON_REQUEST + ) + } + } R.id.action_pin -> getLauncherActivity().pinAppHere(app.userPackageName, user) R.id.action_info -> AppUtils.openAppDetails( requireActivity(), @@ -494,7 +539,8 @@ class AppsListPage : GenericPage() { private fun addApp(list: MutableList, componentName: String, user: Long) { // Don't add the app if it has the launcher's package name or if it's hidden. if (componentName.contains(requireContext().packageName) || - excludedAppsList.contains(componentName)) { + excludedAppsList.contains(componentName) + ) { return } @@ -631,5 +677,6 @@ class AppsListPage : GenericPage() { companion object { private const val SHORTCUT_MENU_GROUP = 247 + private const val SET_ICON_REQUEST = 8000 } } \ No newline at end of file diff --git a/app/src/main/res/menu/menu_app.xml b/app/src/main/res/menu/menu_app.xml index 9fd6b5f1..411215a9 100644 --- a/app/src/main/res/menu/menu_app.xml +++ b/app/src/main/res/menu/menu_app.xml @@ -10,6 +10,11 @@ android:title="@string/action_actions"> + Add Reset + Icon Pin Unpin Hide @@ -29,9 +30,12 @@ Search for \'%1$s\' online With Go + Add provider Edit provider Set shorthand + Select icon pack + Close Add Edit @@ -40,7 +44,7 @@ Invalid URL. Search engine name must be unique. This is only possible on newer Android. - + Welcome! This is your desktop. In here, you can…\n • Tap to open the app list\n