Skip to content

Commit

Permalink
Early implementation of custom icon handling
Browse files Browse the repository at this point in the history
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 is not saved to a backup,
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.
  • Loading branch information
F4uzan committed Dec 16, 2020
1 parent ae096bd commit 43d00c4
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 3 deletions.
45 changes: 44 additions & 1 deletion app/src/main/java/mono/hg/helpers/LauncherIconHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.*

Expand Down Expand Up @@ -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))
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 =
Expand Down
49 changes: 48 additions & 1 deletion app/src/main/java/mono/hg/pages/AppsListPage.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package mono.hg.pages

import android.app.Activity.RESULT_OK
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
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
Expand Down Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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<Bitmap>("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
}
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -494,7 +539,8 @@ class AppsListPage : GenericPage() {
private fun addApp(list: MutableList<App>, 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
}

Expand Down Expand Up @@ -631,5 +677,6 @@ class AppsListPage : GenericPage() {

companion object {
private const val SHORTCUT_MENU_GROUP = 247
private const val SET_ICON_REQUEST = 8000
}
}
5 changes: 5 additions & 0 deletions app/src/main/res/menu/menu_app.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
android:title="@string/action_actions">

<menu android:id="@+id/menu_app_actions">
<item
android:id="@+id/action_icon"
android:orderInCategory="100"
android:title="@string/action_icon"
app:showAsAction="never" />

<item
android:id="@+id/action_unpin"
Expand Down
6 changes: 5 additions & 1 deletion app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<string name="action_web_provider_add">Add</string>
<string name="action_web_provider_reset">Reset</string>
<!-- App menu actions -->
<string name="action_icon">Icon</string>
<string name="action_pin">Pin</string>
<string name="action_unpin">Unpin</string>
<string name="action_hide">Hide</string>
Expand All @@ -29,9 +30,12 @@
<string name="search_web_hint">Search for \'%1$s\' online</string>
<string name="search_web_button_prompt">With</string>
<string name="search_web_button">Go</string>
<!-- Dialog titles -->
<string name="dialog_title_add_provider">Add provider</string>
<string name="dialog_title_edit_provider">Edit provider</string>
<string name="dialog_title_shorthand">Set shorthand</string>
<string name="dialog_title_set_icon">Select icon pack</string>
<!-- Dialog actions -->
<string name="dialog_action_close">Close</string>
<string name="dialog_action_add">Add</string>
<string name="dialog_action_edit">Edit</string>
Expand All @@ -40,7 +44,7 @@
<string name="err_invalid_url">Invalid URL.</string>
<string name="err_provider_exists">Search engine name must be unique.</string>
<string name="err_no_method_panel">This is only possible on newer Android.</string>
<!-- Start up dialogue, shown to new user -->
<!-- Start up dialog, shown to new user -->
<string name="dialog_start_hint_title">Welcome!</string>
<string name="dialog_start_hint_message_one">This is your desktop. In here, you can…\n
• Tap to open the app list\n
Expand Down

0 comments on commit 43d00c4

Please sign in to comment.