diff --git a/app/src/main/java/cu/axel/smartdock/activities/LauncherActivity.kt b/app/src/main/java/cu/axel/smartdock/activities/LauncherActivity.kt index 4f53e0ec..75716a22 100644 --- a/app/src/main/java/cu/axel/smartdock/activities/LauncherActivity.kt +++ b/app/src/main/java/cu/axel/smartdock/activities/LauncherActivity.kt @@ -6,6 +6,7 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import android.content.SharedPreferences +import android.content.SharedPreferences.OnSharedPreferenceChangeListener import android.content.pm.ShortcutInfo import android.net.Uri import android.os.Bundle @@ -41,6 +42,7 @@ import cu.axel.smartdock.utils.AppUtils import cu.axel.smartdock.utils.ColorUtils import cu.axel.smartdock.utils.DeepShortcutManager import cu.axel.smartdock.utils.DeviceUtils +import cu.axel.smartdock.utils.IconPackUtils import cu.axel.smartdock.utils.Utils import java.io.BufferedReader import java.io.File @@ -51,7 +53,9 @@ import java.io.IOException const val LAUNCHER_ACTION = "launcher_action" const val LAUNCHER_RESUMED = "launcher_resumed" -open class LauncherActivity : AppCompatActivity(), OnAppClickListener { +open class LauncherActivity : AppCompatActivity(), OnAppClickListener, + OnSharedPreferenceChangeListener { + private var iconPackUtils: IconPackUtils? = null private lateinit var serviceBtn: MaterialButton private lateinit var appsGv: RecyclerView private lateinit var notesEt: EditText @@ -68,7 +72,7 @@ open class LauncherActivity : AppCompatActivity(), OnAppClickListener { notesEt = findViewById(R.id.notes_et) sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this) serviceBtn - .setOnClickListener { startActivity(Intent(this, MainActivity::class.java)) } + .setOnClickListener { startActivity(Intent(this, MainActivity::class.java)) } backgroundLayout.setOnLongClickListener { val view = LayoutInflater.from(this).inflate(R.layout.task_list, null) val layoutParams = Utils.makeWindowParams(-2, -2, this) @@ -92,8 +96,16 @@ open class LauncherActivity : AppCompatActivity(), OnAppClickListener { actionsLv.adapter = AppActionsAdapter(this, actions) actionsLv.setOnItemClickListener { adapterView, _, position, _ -> val action = adapterView.getItemAtPosition(position) as Action - if (action.text == getString(R.string.change_wallpaper)) startActivityForResult(Intent.createChooser(Intent(Intent.ACTION_SET_WALLPAPER), - getString(R.string.change_wallpaper)), 18) else if (action.text == getString(R.string.display_settings)) startActivity(Intent(Settings.ACTION_DISPLAY_SETTINGS)) + if (action.text == getString(R.string.change_wallpaper)) startActivityForResult( + Intent.createChooser( + Intent(Intent.ACTION_SET_WALLPAPER), + getString(R.string.change_wallpaper) + ), 18 + ) else if (action.text == getString(R.string.display_settings)) startActivity( + Intent( + Settings.ACTION_DISPLAY_SETTINGS + ) + ) windowManager.removeView(view) } windowManager.addView(view, layoutParams) @@ -104,30 +116,40 @@ open class LauncherActivity : AppCompatActivity(), OnAppClickListener { y = event.y false } - ContextCompat.registerReceiver(this, object : BroadcastReceiver() { - override fun onReceive(p1: Context, intent: Intent) { - when (intent.getStringExtra("action")) { - DOCK_SERVICE_CONNECTED -> serviceBtn.visibility = View.GONE - DESKTOP_APP_PINNED -> loadDesktopApps() + ContextCompat.registerReceiver( + this, object : BroadcastReceiver() { + override fun onReceive(p1: Context, intent: Intent) { + when (intent.getStringExtra("action")) { + DOCK_SERVICE_CONNECTED -> serviceBtn.visibility = View.GONE + DESKTOP_APP_PINNED -> loadDesktopApps() + } } - } - }, IntentFilter(DOCK_SERVICE_ACTION), - ContextCompat.RECEIVER_NOT_EXPORTED) + }, IntentFilter(DOCK_SERVICE_ACTION), + ContextCompat.RECEIVER_NOT_EXPORTED + ) + + if (sharedPreferences.getString("icon_pack", "")!!.isNotEmpty()) { + iconPackUtils = IconPackUtils(this) + } } fun loadDesktopApps() { - appsGv.adapter = AppAdapter(this, - AppUtils.getPinnedApps(this, AppUtils.DESKTOP_LIST), this, true) + appsGv.adapter = AppAdapter( + this, + AppUtils.getPinnedApps(this, AppUtils.DESKTOP_LIST), this, true, iconPackUtils + ) } override fun onResume() { super.onResume() - sendBroadcast(Intent(LAUNCHER_ACTION) + sendBroadcast( + Intent(LAUNCHER_ACTION) .setPackage(packageName) .putExtra("action", LAUNCHER_RESUMED) ) - serviceBtn.visibility = if (DeviceUtils.isAccessibilityServiceEnabled(this)) View.GONE else View.VISIBLE + serviceBtn.visibility = + if (DeviceUtils.isAccessibilityServiceEnabled(this)) View.GONE else View.VISIBLE loadDesktopApps() @@ -176,17 +198,21 @@ open class LauncherActivity : AppCompatActivity(), OnAppClickListener { } private fun launchApp(mode: String?, app: String) { - sendBroadcast(Intent(LAUNCHER_ACTION) + sendBroadcast( + Intent(LAUNCHER_ACTION) .setPackage(packageName) .putExtra("action", ACTION_LAUNCH_APP) .putExtra("mode", mode) - .putExtra("app", app)) + .putExtra("app", app) + ) } private fun getAppActions(app: String): ArrayList { val actions = ArrayList() if (DeepShortcutManager.hasHostPermission(this)) { - if (DeepShortcutManager.getShortcuts(app, this)!!.isNotEmpty()) actions.add(Action(R.drawable.ic_shortcuts, getString(R.string.shortcuts))) + if (DeepShortcutManager.getShortcuts(app, this)!! + .isNotEmpty() + ) actions.add(Action(R.drawable.ic_shortcuts, getString(R.string.shortcuts))) } actions.add(Action(R.drawable.ic_manage, getString(R.string.manage))) actions.add(Action(R.drawable.ic_launch_mode, getString(R.string.open_as))) @@ -201,7 +227,8 @@ open class LauncherActivity : AppCompatActivity(), OnAppClickListener { val layoutParams = Utils.makeWindowParams(-2, -2, this) ColorUtils.applyMainColor(this, sharedPreferences, view) layoutParams.gravity = Gravity.TOP or Gravity.START - layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH + layoutParams.flags = + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH val location = IntArray(2) anchor.getLocationOnScreen(location) layoutParams.x = location[0] @@ -223,14 +250,24 @@ open class LauncherActivity : AppCompatActivity(), OnAppClickListener { actions.add(Action(R.drawable.ic_arrow_back, "")) actions.add(Action(R.drawable.ic_info, getString(R.string.app_info))) if (!AppUtils.isSystemApp(this, app.packageName) - || sharedPreferences.getBoolean("allow_sysapp_uninstall", false)) actions.add(Action(R.drawable.ic_uninstall, getString(R.string.uninstall))) - if (sharedPreferences.getBoolean("allow_app_freeze", false)) actions.add(Action(R.drawable.ic_freeze, getString(R.string.freeze))) + || sharedPreferences.getBoolean("allow_sysapp_uninstall", false) + ) actions.add( + Action( + R.drawable.ic_uninstall, + getString(R.string.uninstall) + ) + ) + if (sharedPreferences.getBoolean("allow_app_freeze", false)) actions.add( + Action(R.drawable.ic_freeze, getString(R.string.freeze)) + ) actionsLv.adapter = AppActionsAdapter(this, actions) } getString(R.string.shortcuts) -> { - actionsLv.adapter = AppShortcutAdapter(this, - DeepShortcutManager.getShortcuts(app.packageName, this)!!) + actionsLv.adapter = AppShortcutAdapter( + this, + DeepShortcutManager.getShortcuts(app.packageName, this)!! + ) } "" -> { @@ -243,20 +280,34 @@ open class LauncherActivity : AppCompatActivity(), OnAppClickListener { actions.add(Action(R.drawable.ic_standard, getString(R.string.standard))) actions.add(Action(R.drawable.ic_maximized, getString(R.string.maximized))) actions.add(Action(R.drawable.ic_portrait, getString(R.string.portrait))) - actions.add(Action(R.drawable.ic_fullscreen, getString(R.string.fullscreen))) + actions.add( + Action( + R.drawable.ic_fullscreen, + getString(R.string.fullscreen) + ) + ) actionsLv.adapter = AppActionsAdapter(this, actions) } getString(R.string.app_info) -> { - startActivity(Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) - .setData(Uri.parse("package:$app")).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)) + startActivity( + Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) + .setData(Uri.parse("package:$app")) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + ) windowManager.removeView(view) } getString(R.string.uninstall) -> { @Suppress("DEPRECATION") - if (AppUtils.isSystemApp(this, app.packageName)) DeviceUtils.runAsRoot("pm uninstall --user 0 $app") else startActivity(Intent(Intent.ACTION_UNINSTALL_PACKAGE, Uri.parse("package:$app")) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)) + if (AppUtils.isSystemApp( + this, + app.packageName + ) + ) DeviceUtils.runAsRoot("pm uninstall --user 0 $app") else startActivity( + Intent(Intent.ACTION_UNINSTALL_PACKAGE, Uri.parse("package:$app")) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + ) windowManager.removeView(view) } @@ -265,7 +316,8 @@ open class LauncherActivity : AppCompatActivity(), OnAppClickListener { if (status != "error") Toast.makeText(this, R.string.app_frozen, Toast.LENGTH_SHORT).show() else - Toast.makeText(this, R.string.something_wrong, Toast.LENGTH_SHORT).show() + Toast.makeText(this, R.string.something_wrong, Toast.LENGTH_SHORT) + .show() windowManager.removeView(view) loadDesktopApps() } @@ -312,4 +364,19 @@ open class LauncherActivity : AppCompatActivity(), OnAppClickListener { override fun onAppLongClicked(app: App, item: View) { showAppContextMenu(app, item) } + + override fun onSharedPreferenceChanged( + sharedPreferences: SharedPreferences, + preference: String? + ) { + if (preference == null) + return + if (preference == "icon_pack") { + val iconPack = sharedPreferences.getString("icon_pack", "")!! + iconPackUtils = if (iconPack.isNotEmpty()) { + IconPackUtils(this) + } else + null + } + } } diff --git a/app/src/main/java/cu/axel/smartdock/adapters/AppAdapter.kt b/app/src/main/java/cu/axel/smartdock/adapters/AppAdapter.kt index db1f6c6b..aa5d32a4 100644 --- a/app/src/main/java/cu/axel/smartdock/adapters/AppAdapter.kt +++ b/app/src/main/java/cu/axel/smartdock/adapters/AppAdapter.kt @@ -5,6 +5,7 @@ import android.graphics.Typeface import android.text.Spannable import android.text.SpannableString import android.text.style.StyleSpan +import android.util.Log import android.view.LayoutInflater import android.view.MotionEvent import android.view.View @@ -17,11 +18,17 @@ import androidx.recyclerview.widget.RecyclerView import cu.axel.smartdock.models.App import cu.axel.smartdock.R import cu.axel.smartdock.utils.ColorUtils +import cu.axel.smartdock.utils.IconPackUtils import cu.axel.smartdock.utils.Utils import java.util.Locale -class AppAdapter(private val context: Context, private var apps: ArrayList, - private val listener: OnAppClickListener, private val large: Boolean) : RecyclerView.Adapter() { +class AppAdapter( + private val context: Context, + private var apps: ArrayList, + private val listener: OnAppClickListener, + private val large: Boolean, + val iconPackUtils: IconPackUtils? +) : RecyclerView.Adapter() { private val allApps: ArrayList = ArrayList(apps) private var iconBackground = 0 private val iconPadding: Int @@ -35,7 +42,8 @@ class AppAdapter(private val context: Context, private var apps: ArrayList, init { val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) - iconPadding = Utils.dpToPx(context, sharedPreferences.getString("icon_padding", "5")!!.toInt()) + iconPadding = + Utils.dpToPx(context, sharedPreferences.getString("icon_padding", "5")!!.toInt()) singleLine = sharedPreferences.getBoolean("single_line_labels", true) when (sharedPreferences.getString("icon_shape", "circle")) { "circle" -> iconBackground = R.drawable.circle @@ -46,7 +54,7 @@ class AppAdapter(private val context: Context, private var apps: ArrayList, override fun onCreateViewHolder(parent: ViewGroup, arg1: Int): ViewHolder { val itemLayoutView = LayoutInflater.from(context) - .inflate(if (large) R.layout.app_entry_large else R.layout.app_entry, null) + .inflate(if (large) R.layout.app_entry_large else R.layout.app_entry, null) return ViewHolder(itemLayoutView) } @@ -54,11 +62,17 @@ class AppAdapter(private val context: Context, private var apps: ArrayList, val app = apps[position] val name = app.name if (::query.isInitialized) { - val spanStart = name.lowercase(Locale.getDefault()).indexOf(query.lowercase(Locale.getDefault())) + val spanStart = + name.lowercase(Locale.getDefault()).indexOf(query.lowercase(Locale.getDefault())) val spanEnd = spanStart + query.length if (spanStart != -1) { val spannable = SpannableString(name) - spannable.setSpan(StyleSpan(Typeface.BOLD), spanStart, spanEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + spannable.setSpan( + StyleSpan(Typeface.BOLD), + spanStart, + spanEnd, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) viewHolder.nameTv.text = spannable } else { viewHolder.nameTv.text = name @@ -66,12 +80,18 @@ class AppAdapter(private val context: Context, private var apps: ArrayList, } else { viewHolder.nameTv.text = name } - viewHolder.iconIv.setImageDrawable(app.icon) + + if (iconPackUtils != null) + viewHolder.iconIv.setImageDrawable(iconPackUtils.getAppThemedIcon(app.packageName)) + else + viewHolder.iconIv.setImageDrawable(app.icon) if (iconBackground != -1) { viewHolder.iconIv.setPadding(iconPadding, iconPadding, iconPadding, iconPadding) viewHolder.iconIv.setBackgroundResource(iconBackground) - ColorUtils.applyColor(viewHolder.iconIv, - ColorUtils.getDrawableDominantColor(viewHolder.iconIv.drawable)) + ColorUtils.applyColor( + viewHolder.iconIv, + ColorUtils.getDrawableDominantColor(viewHolder.iconIv.drawable) + ) } viewHolder.bind(app, listener) } @@ -84,11 +104,21 @@ class AppAdapter(private val context: Context, private var apps: ArrayList, val results = ArrayList() if (query.length > 1) { if (query.matches("^[0-9]+(\\.[0-9]+)?[-+/*][0-9]+(\\.[0-9]+)?".toRegex())) { - results.add(App(Utils.solve(query).toString() + "", context.packageName + ".calc", - ResourcesCompat.getDrawable(context.resources, R.drawable.ic_calculator, context.theme)!!)) + results.add( + App( + Utils.solve(query).toString() + "", context.packageName + ".calc", + ResourcesCompat.getDrawable( + context.resources, + R.drawable.ic_calculator, + context.theme + )!! + ) + ) } else { for (app in allApps) { - if (app.name.lowercase(Locale.getDefault()).contains(query.lowercase(Locale.getDefault()))) results.add(app) + if (app.name.lowercase(Locale.getDefault()) + .contains(query.lowercase(Locale.getDefault())) + ) results.add(app) } } apps = results diff --git a/app/src/main/java/cu/axel/smartdock/adapters/DockAppAdapter.kt b/app/src/main/java/cu/axel/smartdock/adapters/DockAppAdapter.kt index c9bcb0f8..a00d016d 100644 --- a/app/src/main/java/cu/axel/smartdock/adapters/DockAppAdapter.kt +++ b/app/src/main/java/cu/axel/smartdock/adapters/DockAppAdapter.kt @@ -13,10 +13,13 @@ import cu.axel.smartdock.R import cu.axel.smartdock.models.DockApp import cu.axel.smartdock.utils.AppUtils import cu.axel.smartdock.utils.ColorUtils +import cu.axel.smartdock.utils.IconPackUtils import cu.axel.smartdock.utils.Utils -class DockAppAdapter(private val context: Context, private val apps: ArrayList, - private val listener: OnDockAppClickListener) : RecyclerView.Adapter() { +class DockAppAdapter( + private val context: Context, private val apps: ArrayList, + private val listener: OnDockAppClickListener, private val iconPackUtils: IconPackUtils? +) : RecyclerView.Adapter() { private var iconBackground = 0 private val iconPadding: Int private val iconTheming: Boolean @@ -30,7 +33,8 @@ class DockAppAdapter(private val context: Context, private val apps: ArrayList iconBackground = R.drawable.circle @@ -40,7 +44,8 @@ class DockAppAdapter(private val context: Context, private val apps: ArrayList 0) { - if (tintIndicators) ColorUtils.applyColor(viewHolder.runningIndicator, - ColorUtils.manipulateColor(ColorUtils.getDrawableDominantColor(app.icon), 2f)) + if (tintIndicators) ColorUtils.applyColor( + viewHolder.runningIndicator, + ColorUtils.manipulateColor(ColorUtils.getDrawableDominantColor(app.icon), 2f) + ) if (app.tasks[0].id != -1) viewHolder.runningIndicator.alpha = 1f if (app.packageName == AppUtils.currentApp) { viewHolder.runningIndicator.layoutParams.width = Utils.dpToPx(context, 16) @@ -59,12 +66,17 @@ class DockAppAdapter(private val context: Context, private val apps: ArrayList, private val listener: OnNotificationClickListener) : RecyclerView.Adapter() { - private var iconBackground = 0 - private val iconPadding: Int - private val iconTheming: Boolean +class NotificationAdapter( + private val context: Context, + private val notifications: Array, + private val listener: OnNotificationClickListener +) : RecyclerView.Adapter() { + private var sharedPreferences: SharedPreferences = + PreferenceManager.getDefaultSharedPreferences(context) interface OnNotificationClickListener { fun onNotificationClicked(notification: StatusBarNotification, item: View) @@ -32,20 +37,10 @@ class NotificationAdapter(private val context: Context, fun onNotificationCancelClicked(notification: StatusBarNotification, item: View) } - init { - val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) - iconTheming = sharedPreferences.getString("icon_pack", "") != "" - iconPadding = Utils.dpToPx(context, sharedPreferences.getString("icon_padding", "5")!!.toInt()) - when (sharedPreferences.getString("icon_shape", "circle")) { - "circle" -> iconBackground = R.drawable.circle - "round_rect" -> iconBackground = R.drawable.round_square - "default" -> iconBackground = -1 - } - } - override fun onCreateViewHolder(parent: ViewGroup, arg1: Int): ViewHolder { - val itemLayoutView = LayoutInflater.from(parent.context).inflate(R.layout.notification_entry, parent, - false) + val itemLayoutView = LayoutInflater.from(parent.context).inflate( + R.layout.notification_entry, parent, false + ) return ViewHolder(itemLayoutView) } @@ -56,27 +51,33 @@ class NotificationAdapter(private val context: Context, val extras = notification.extras viewHolder.notifActionsLayout.removeAllViews() if (actions != null) { - val layoutParams = LinearLayout.LayoutParams(-2, -2) + val layoutParams = LinearLayout.LayoutParams( + 0, + LinearLayout.LayoutParams.MATCH_PARENT + ) layoutParams.weight = 1f - if (extras[Notification.EXTRA_MEDIA_SESSION] != null) { + if (AppUtils.isMediaNotification(notification)) { for (action in actions) { - val actionTv = ImageView(context) - try { - val res = context.packageManager.getResourcesForApplication(sbn.packageName) - val drawable = res - .getDrawable(res.getIdentifier(action.icon.toString() + "", "drawable", sbn.packageName)) - drawable.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP) - actionTv.setImageDrawable(drawable) - actionTv.setOnClickListener { - try { - action.actionIntent.send() - } catch (_: CanceledException) { - } + val actionIv = ImageView(context) + val resources = context.packageManager + .getResourcesForApplication(sbn.packageName) + val drawable = resources.getDrawable( + resources.getIdentifier( + action.icon.toString() + "", + "drawable", + sbn.packageName + ) + ) + drawable.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP) + actionIv.setImageDrawable(drawable) + actionIv.setOnClickListener { + try { + action.actionIntent.send() + } catch (_: CanceledException) { } - viewHolder.notifText.isSingleLine = true - viewHolder.notifActionsLayout.addView(actionTv, layoutParams) - } catch (_: PackageManager.NameNotFoundException) { } + viewHolder.notifText.isSingleLine = true + viewHolder.notifActionsLayout.addView(actionIv, layoutParams) } } else { for (action in actions) { @@ -95,7 +96,8 @@ class NotificationAdapter(private val context: Context, } } var notificationTitle = extras.getString(Notification.EXTRA_TITLE) - if (notificationTitle == null) notificationTitle = AppUtils.getPackageLabel(context, sbn.packageName) + if (notificationTitle == null) notificationTitle = + AppUtils.getPackageLabel(context, sbn.packageName) val notificationText = extras.getCharSequence(Notification.EXTRA_TEXT) val progress = extras.getInt(Notification.EXTRA_PROGRESS) val formattedProgress = if (progress != 0) " $progress%" else "" @@ -107,13 +109,23 @@ class NotificationAdapter(private val context: Context, if (sbn.isClearable) listener.onNotificationCancelClicked(sbn, view) } } else viewHolder.notifCancelBtn.alpha = 0f - val notificationIcon = AppUtils.getAppIcon(context, sbn.packageName) - viewHolder.notifIcon.setImageDrawable(notificationIcon) - if (iconBackground != -1) { - viewHolder.notifIcon.setPadding(iconPadding, iconPadding, iconPadding, iconPadding) - viewHolder.notifIcon.setBackgroundResource(iconBackground) - ColorUtils.applyColor(viewHolder.notifIcon, ColorUtils.getDrawableDominantColor(notificationIcon)) + + if (AppUtils.isMediaNotification(notification) && notification.getLargeIcon() != null) { + val padding = Utils.dpToPx(context, 0) + viewHolder.notifIcon.setPadding(padding, padding, padding, padding) + viewHolder.notifIcon.setImageIcon(notification.getLargeIcon()) + } else { + notification.smallIcon.setTint(Color.WHITE) + viewHolder.notifIcon.setBackgroundResource(R.drawable.circle) + ColorUtils.applySecondaryColor( + context, sharedPreferences, + viewHolder.notifIcon + ) + val padding = Utils.dpToPx(context, 14) + viewHolder.notifIcon.setPadding(padding, padding, padding, padding) + viewHolder.notifIcon.setImageIcon(notification.smallIcon) } + viewHolder.bind(sbn, listener) } diff --git a/app/src/main/java/cu/axel/smartdock/preferences/IconPackPreference.kt b/app/src/main/java/cu/axel/smartdock/preferences/IconPackPreference.kt new file mode 100644 index 00000000..40a76a5c --- /dev/null +++ b/app/src/main/java/cu/axel/smartdock/preferences/IconPackPreference.kt @@ -0,0 +1,96 @@ +package cu.axel.smartdock.preferences + +import android.content.Context +import android.content.DialogInterface +import android.content.Intent +import android.content.SharedPreferences +import android.content.pm.PackageManager +import android.content.pm.ResolveInfo +import android.util.AttributeSet +import androidx.preference.Preference +import androidx.preference.PreferenceManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import cu.axel.smartdock.R +import cu.axel.smartdock.utils.AppUtils + +private val LAUNCHER_INTENTS = arrayOf( + "com.fede.launcher.THEME_ICONPACK", + "com.anddoes.launcher.THEME", + "com.teslacoilsw.launcher.THEME", + "com.gau.go.launcherex.theme", + "org.adw.launcher.THEMES", + "org.adw.launcher.icons.ACTION_PICK_ICON", + "net.oneplus.launcher.icons.ACTION_PICK_ICON" +) + +class IconPackPreference(private val context: Context, attrs: AttributeSet?) : Preference(context, attrs) { + + private var sp: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) + + /* + These are all icon pack intents to date + It could change in the future + but by default, I don't think we even use these any more in icon packs + but we support all icon packs to date (Long live Ander Web) + */ + + init { + setTitle(R.string.icon_pack) + val iconPack = sp.getString("icon_pack", "")!! + if (iconPack.isEmpty()) { + setSummary(R.string.system) + } else { + summary = AppUtils.getPackageLabel(context, iconPack) + } + } + + override fun onClick() { + val pm: PackageManager = context.packageManager + + /* + We manually add Smart Dock context as a default item so Smart Dock has a default item to rely on + */ + val iconPackageList = ArrayList() + val iconNameList = ArrayList() + iconPackageList.add(context.packageName) + iconNameList.add(context.getString(R.string.system)) + val launcherActivities: MutableList = ArrayList() + /* + Gather all the apps installed on the device + filter all the icon pack packages to the list + */for (i in LAUNCHER_INTENTS) { + launcherActivities.addAll( + pm.queryIntentActivities( + Intent(i), + PackageManager.GET_META_DATA + ) + ) + } + for (resolveInfo in launcherActivities) { + iconPackageList.add(resolveInfo.activityInfo.packageName) + iconNameList.add( + AppUtils.getPackageLabel( + context, + resolveInfo.activityInfo.packageName + ) + ) + } + val cleanedNameList: Set = LinkedHashSet(iconNameList) + val newNameList = cleanedNameList.toTypedArray() + val dialog = MaterialAlertDialogBuilder(context) + dialog.setTitle(R.string.icon_pack) + dialog.setItems( + newNameList + ) { _: DialogInterface?, item: Int -> + if (iconPackageList[item] == context.packageName) { + sp.edit().putString("icon_pack", "").apply() + setSummary(R.string.system) + } else { + val iconPack = iconPackageList[item] + sp.edit().putString("icon_pack", iconPack).apply() + summary = AppUtils.getPackageLabel(context, iconPack) + } + } + dialog.show() + } +} diff --git a/app/src/main/java/cu/axel/smartdock/services/DockService.kt b/app/src/main/java/cu/axel/smartdock/services/DockService.kt index 3ac99694..90a34a8e 100644 --- a/app/src/main/java/cu/axel/smartdock/services/DockService.kt +++ b/app/src/main/java/cu/axel/smartdock/services/DockService.kt @@ -31,6 +31,7 @@ import android.os.Handler import android.os.Looper import android.os.Process import android.provider.Settings +import android.util.Log import android.view.ContextThemeWrapper import android.view.Display import android.view.GestureDetector @@ -87,6 +88,7 @@ import cu.axel.smartdock.utils.AppUtils import cu.axel.smartdock.utils.ColorUtils import cu.axel.smartdock.utils.DeepShortcutManager import cu.axel.smartdock.utils.DeviceUtils +import cu.axel.smartdock.utils.IconPackUtils import cu.axel.smartdock.utils.OnSwipeListener import cu.axel.smartdock.utils.Utils import cu.axel.smartdock.widgets.HoverInterceptorLayout @@ -159,6 +161,7 @@ class DockService : AccessibilityService(), OnSharedPreferenceChangeListener, On private var dockHeight: Int = 0 private lateinit var handleLayoutParams: WindowManager.LayoutParams private lateinit var launcherApps: LauncherApps + private var iconPackUtils: IconPackUtils? = null override fun onCreate() { super.onCreate() db = DBHelper(this) @@ -172,6 +175,9 @@ class DockService : AccessibilityService(), OnSharedPreferenceChangeListener, On bluetoothManager = getSystemService(BLUETOOTH_SERVICE) as BluetoothManager launcherApps = getSystemService(LAUNCHER_APPS_SERVICE) as LauncherApps dockHandler = Handler(Looper.getMainLooper()) + if (sharedPreferences.getString("icon_pack", "")!!.isNotEmpty()) { + iconPackUtils = IconPackUtils(this) + } } override fun onServiceConnected() { @@ -1171,7 +1177,7 @@ class DockService : AccessibilityService(), OnSharedPreferenceChangeListener, On //TODO: Implement efficient adapter appsGv.adapter = AppAdapter( context, apps, this@DockService, - menuFullscreen && !phoneLayout + menuFullscreen && !phoneLayout, iconPackUtils ) } } @@ -1391,10 +1397,17 @@ class DockService : AccessibilityService(), OnSharedPreferenceChangeListener, On applyTheme() else if (preference == "menu_icon_uri") updateMenuIcon() - else if (preference.startsWith("icon_") || preference == "tint_indicators") { + else if (preference.startsWith("icon_")) { + val iconPack = sharedPreferences.getString("icon_pack", "")!! + iconPackUtils = if (iconPack.isNotEmpty()) { + IconPackUtils(this) + } else + null updateRunningTasks() loadFavoriteApps() - } else if (preference == "lock_landscape") + } else if (preference == "tint_indicators") + updateRunningTasks() + else if (preference == "lock_landscape") setOrientation() else if (preference == "center_running_apps") { placeRunningApps() @@ -1495,7 +1508,7 @@ class DockService : AccessibilityService(), OnSharedPreferenceChangeListener, On if (index != -1) apps[index].addTask(task) else apps.add(DockApp(task)) } tasksGv.layoutParams.width = gridSize * apps.size - tasksGv.adapter = DockAppAdapter(context, apps, this) + tasksGv.adapter = DockAppAdapter(context, apps, this, iconPackUtils) //TODO: Move context outta here wifiBtn.setImageResource(if (wifiManager.isWifiEnabled) R.drawable.ic_wifi_on else R.drawable.ic_wifi_off) @@ -1760,7 +1773,8 @@ class DockService : AccessibilityService(), OnSharedPreferenceChangeListener, On toggleFavorites(apps.size > 0) val menuFullscreen = sharedPreferences.getBoolean("app_menu_fullscreen", false) val phoneLayout = sharedPreferences.getInt("dock_layout", -1) == 0 - favoritesGv.adapter = AppAdapter(context, apps, this, menuFullscreen && !phoneLayout) + favoritesGv.adapter = + AppAdapter(context, apps, this, menuFullscreen && !phoneLayout, iconPackUtils) } fun takeScreenshot() { diff --git a/app/src/main/java/cu/axel/smartdock/services/NotificationService.kt b/app/src/main/java/cu/axel/smartdock/services/NotificationService.kt index 99015aa7..fa1bd6e5 100644 --- a/app/src/main/java/cu/axel/smartdock/services/NotificationService.kt +++ b/app/src/main/java/cu/axel/smartdock/services/NotificationService.kt @@ -12,13 +12,11 @@ import android.content.SharedPreferences import android.content.pm.PackageManager import android.graphics.Color import android.graphics.PorterDuff -import android.os.Build import android.os.Handler import android.os.Looper import android.service.notification.NotificationListenerService import android.service.notification.StatusBarNotification import android.view.Gravity -import android.view.KeyEvent import android.view.LayoutInflater import android.view.MotionEvent import android.view.View @@ -171,26 +169,24 @@ class NotificationService : NotificationListenerService(), OnNotificationClickLi sharedPreferences, notifCancelBtn ) - val notificationIcon = AppUtils.getAppIcon(context, sbn.packageName) - notifIcon.setImageDrawable(notificationIcon) - val iconPadding = Utils.dpToPx( - context, - sharedPreferences.getString("icon_padding", "5")!!.toInt() - ) - var iconBackground = -1 - when (sharedPreferences.getString("icon_shape", "circle")) { - "circle" -> iconBackground = R.drawable.circle - "round_rect" -> iconBackground = R.drawable.round_square - } - notifIcon.setImageDrawable(notificationIcon) - if (iconBackground != -1) { - notifIcon.setPadding(iconPadding, iconPadding, iconPadding, iconPadding) - notifIcon.setBackgroundResource(iconBackground) - ColorUtils.applyColor( - notifIcon, - ColorUtils.getDrawableDominantColor(notificationIcon) + + if (AppUtils.isMediaNotification(notification) && notification.getLargeIcon() != null) { + val padding = Utils.dpToPx(context, 0) + notifIcon.setPadding(padding, padding, padding, padding) + notifIcon.setImageIcon(notification.getLargeIcon()) + notifIcon.background = null + } else { + notification.smallIcon.setTint(Color.WHITE) + notifIcon.setBackgroundResource(R.drawable.circle) + ColorUtils.applySecondaryColor( + context, sharedPreferences, + notifIcon ) + val padding = Utils.dpToPx(context, 14) + notifIcon.setPadding(padding, padding, padding, padding) + notifIcon.setImageIcon(notification.smallIcon) } + val progress = extras.getInt(Notification.EXTRA_PROGRESS) val p = if (progress != 0) " $progress%" else "" notifTitle.text = notificationTitle + p @@ -200,30 +196,29 @@ class NotificationService : NotificationListenerService(), OnNotificationClickLi if (actions != null) { val layoutParams = LinearLayout.LayoutParams(-2, -2) layoutParams.weight = 1f - if (extras[Notification.EXTRA_MEDIA_SESSION] != null) { + if (AppUtils.isMediaNotification(notification)) { for (action in actions) { - val actionTv = ImageView(this@NotificationService) + val actionIv = ImageView(this@NotificationService) try { - val res = packageManager + val resources = packageManager .getResourcesForApplication(sbn.packageName) - val drawable = res.getDrawable( - res.getIdentifier( + val drawable = resources.getDrawable( + resources.getIdentifier( action.icon.toString() + "", "drawable", sbn.packageName ) ) drawable.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP) - actionTv.setImageDrawable(drawable) - //actionTv.setImageIcon(action.getIcon()); - actionTv.setOnClickListener { + actionIv.setImageDrawable(drawable) + actionIv.setOnClickListener { try { action.actionIntent.send() } catch (_: CanceledException) { } } notifText.isSingleLine = true - notifActionsLayout!!.addView(actionTv, layoutParams) + notifActionsLayout!!.addView(actionIv, layoutParams) } catch (_: PackageManager.NameNotFoundException) { } } @@ -474,7 +469,11 @@ class NotificationService : NotificationListenerService(), OnNotificationClickLi } private fun updateNotificationPanel() { - val adapter = NotificationAdapter(context, activeNotifications, this) + val adapter = NotificationAdapter( + context, activeNotifications.sortedWith( + compareByDescending { AppUtils.isMediaNotification(it.notification) && it.isOngoing }) + .toTypedArray(), this + ) notificationsLv!!.adapter = adapter val layoutParams = notificationsLv!!.layoutParams val count = adapter.itemCount diff --git a/app/src/main/java/cu/axel/smartdock/utils/AppUtils.kt b/app/src/main/java/cu/axel/smartdock/utils/AppUtils.kt index 6f233c08..6aa6a1ed 100644 --- a/app/src/main/java/cu/axel/smartdock/utils/AppUtils.kt +++ b/app/src/main/java/cu/axel/smartdock/utils/AppUtils.kt @@ -2,6 +2,7 @@ package cu.axel.smartdock.utils import android.app.ActivityManager import android.app.ActivityOptions +import android.app.Notification import android.app.usage.UsageStats import android.app.usage.UsageStatsManager import android.content.Context @@ -33,22 +34,27 @@ object AppUtils { val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager val launcherApps = context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps var appsInfo = mutableListOf() - for (profile in userManager.userProfiles) - appsInfo.addAll(launcherApps.getActivityList(null, profile)) + for (profile in userManager.userProfiles) appsInfo.addAll( + launcherApps.getActivityList( + null, + profile + ) + ) appsInfo = appsInfo.sortedWith(compareBy { it.label.toString() }).toMutableList() - //TODO: Filter Google App for (appInfo in appsInfo) { apps.add( App( - appInfo.label.toString(), appInfo.componentName.packageName, appInfo.getIcon(0), - appInfo.componentName, appInfo.user + appInfo.label.toString(), + appInfo.componentName.packageName, + appInfo.getIcon(0), + appInfo.componentName, + appInfo.user ) ) } - return apps } @@ -60,14 +66,12 @@ object AppUtils { val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager if (file.exists()) { for (line in file.readLines()) { - if (line.isBlank()) - continue + if (line.isBlank()) continue val info = line.split(" ") val packageName = info[0] val userHandle = userManager.getUserForSerialNumber(info[1].toLong()) val list = launcherApps.getActivityList(packageName, userHandle) - if (list.isNullOrEmpty()) - unpinApp(context, packageName, type) + if (list.isNullOrEmpty()) unpinApp(context, packageName, type) appsInfo.addAll(list) } } @@ -75,12 +79,14 @@ object AppUtils { for (appInfo in appsInfo) { apps.add( App( - appInfo.label.toString(), appInfo.componentName.packageName, appInfo.getIcon(0), - appInfo.componentName, appInfo.user + appInfo.label.toString(), + appInfo.componentName.packageName, + appInfo.getIcon(0), + appInfo.componentName, + appInfo.user ) ) } - return apps } @@ -93,8 +99,7 @@ object AppUtils { fun unpinApp(context: Context, packageName: String, type: String) { val file = File(context.filesDir, type) val updatedList = file.readLines().filter { it.split(" ")[0] != packageName } - if (updatedList.isNotEmpty()) - file.writeText(updatedList.joinToString("\n") + "\n") + if (updatedList.isNotEmpty()) file.writeText(updatedList.joinToString("\n") + "\n") else { file.writeText("") } @@ -121,11 +126,9 @@ object AppUtils { fun isPinned(context: Context, app: App, type: String): Boolean { val file = File(context.filesDir, type) - if (!file.exists()) - return false + if (!file.exists()) return false file.readLines().forEach { line -> - if (line.split(" ")[0] == app.packageName) - return true + if (line.split(" ")[0] == app.packageName) return true } return false } @@ -164,9 +167,7 @@ object AppUtils { } fun getRunningTasks( - activityManager: ActivityManager, - packageManager: PackageManager, - max: Int + activityManager: ActivityManager, packageManager: PackageManager, max: Int ): ArrayList { val tasksInfo = activityManager.getRunningTasks(max) currentApp = tasksInfo[0].baseActivity!!.packageName @@ -174,9 +175,9 @@ object AppUtils { for (taskInfo in tasksInfo) { try { //Exclude systemui, launcher and other system apps from the tasklist - if (taskInfo.baseActivity!!.packageName.contains("com.android.systemui") - || taskInfo.baseActivity!!.packageName.contains("com.google.android.packageinstaller") - || taskInfo.baseActivity!!.className == "com.android.quickstep.RecentsActivity" + if (taskInfo.baseActivity!!.packageName.contains("com.android.systemui") || taskInfo.baseActivity!!.packageName.contains( + "com.google.android.packageinstaller" + ) || taskInfo.baseActivity!!.className == "com.android.quickstep.RecentsActivity" ) continue //Hack to save Dock settings activity ftom being excluded @@ -211,8 +212,7 @@ object AppUtils { val usm = context.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager val start = System.currentTimeMillis() - SystemClock.elapsedRealtime() val usageStats = usm.queryUsageStats( - UsageStatsManager.INTERVAL_BEST, start, - System.currentTimeMillis() + UsageStatsManager.INTERVAL_BEST, start, System.currentTimeMillis() ) val appTasks = ArrayList() usageStats.sortWith { usageStats1: UsageStats, usageStats2: UsageStats -> @@ -224,12 +224,13 @@ object AppUtils { val app = stat.packageName try { if (isLaunchable( - context, - app + context, app ) && app != getCurrentLauncher(context.packageManager) ) appTasks.add( AppTask( - -1, getPackageLabel(context, app), app, + -1, + getPackageLabel(context, app), + app, context.packageManager.getApplicationIcon(app) ) ) @@ -275,10 +276,7 @@ object AppUtils { } private fun makeLaunchBounds( - context: Context, - mode: String, - dockHeight: Int, - displayId: Int = Display.DEFAULT_DISPLAY + context: Context, mode: String, dockHeight: Int, displayId: Int = Display.DEFAULT_DISPLAY ): Rect { val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) var left = 0 @@ -292,10 +290,10 @@ object AppUtils { val diff = if (dockHeight - navHeight > 0) dockHeight - navHeight else 0 val usableHeight = - if (DeviceUtils.shouldApplyNavbarFix()) - deviceHeight - diff - DeviceUtils.getStatusBarHeight(context) - else - deviceHeight - dockHeight - DeviceUtils.getStatusBarHeight(context) + if (DeviceUtils.shouldApplyNavbarFix()) deviceHeight - diff - DeviceUtils.getStatusBarHeight( + context + ) + else deviceHeight - dockHeight - DeviceUtils.getStatusBarHeight(context) val scaleFactor = sharedPreferences.getString("scale_factor", "1.0")!!.toFloat() when (mode) { "standard" -> { @@ -343,10 +341,7 @@ object AppUtils { } fun makeActivityOptions( - context: Context, - mode: String, - dockHeight: Int, - displayId: Int + context: Context, mode: String, dockHeight: Int, displayId: Int ): ActivityOptions { val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) val secondary = sharedPreferences.getBoolean("prefer_last_display", false) @@ -358,37 +353,29 @@ object AppUtils { val options: ActivityOptions = ActivityOptions.makeBasic() val windowMode: Int - if (mode == "fullscreen") - windowMode = 1 + if (mode == "fullscreen") windowMode = 1 else { windowMode = if (Build.VERSION.SDK_INT >= 28) 5 else 2 options.setLaunchBounds( makeLaunchBounds( - context, - mode, - dockHeight, - display + context, mode, dockHeight, display ) ) } - if (Build.VERSION.SDK_INT > 28) - options.setLaunchDisplayId(display) + if (Build.VERSION.SDK_INT > 28) options.setLaunchDisplayId(display) val methodName = if (Build.VERSION.SDK_INT >= 28) "setLaunchWindowingMode" else "setLaunchStackId" - val method = - ActivityOptions::class.java.getMethod(methodName, Int::class.javaPrimitiveType) + val method = ActivityOptions::class.java.getMethod(methodName, Int::class.javaPrimitiveType) method.invoke(options, windowMode) return options } fun resizeTask(context: Context, mode: String, taskId: Int, dockHeight: Int) { - if (taskId < 0) - return + if (taskId < 0) return val bounds = makeLaunchBounds(context, mode, dockHeight) DeviceUtils.runAsRoot( - "am task resize " + taskId + " " + bounds.left + " " + bounds.top + " " + bounds.right - + " " + bounds.bottom + "am task resize " + taskId + " " + bounds.left + " " + bounds.top + " " + bounds.right + " " + bounds.bottom ) } @@ -399,4 +386,7 @@ object AppUtils { return -1 } + fun isMediaNotification(notification: Notification) = + notification.extras[Notification.EXTRA_TEMPLATE].toString() == "android.app.Notification\$MediaStyle" + } diff --git a/app/src/main/java/cu/axel/smartdock/utils/IconPackUtils.kt b/app/src/main/java/cu/axel/smartdock/utils/IconPackUtils.kt new file mode 100644 index 00000000..1e2c8e21 --- /dev/null +++ b/app/src/main/java/cu/axel/smartdock/utils/IconPackUtils.kt @@ -0,0 +1,355 @@ +package cu.axel.smartdock.utils + +import android.content.ComponentName +import android.content.Context +import android.content.pm.ActivityInfo +import android.content.pm.PackageManager +import android.content.res.Resources +import android.content.res.XmlResourceParser +import android.graphics.drawable.Drawable +import android.text.TextUtils +import androidx.core.content.res.ResourcesCompat +import androidx.preference.PreferenceManager +import org.xmlpull.v1.XmlPullParser +import org.xmlpull.v1.XmlPullParserException +import org.xmlpull.v1.XmlPullParserFactory +import java.io.IOException +import java.io.InputStream +import java.lang.reflect.Field +import java.util.Locale + +private const val ICON_MASK_TAG = "iconmask" +private const val ICON_BACK_TAG = "iconback" +private const val ICON_UPON_TAG = "iconupon" +private const val ICON_SCALE_TAG = "scale" + +class IconPackUtils(val context: Context) { + // Holds package/class -> drawable + private var iconPackResources: Map? = HashMap() + private var loadedIconPackName: String? = null + private var loadedIconPackResource: Resources? = null + private var mIconUpon: Drawable? = null + private var iconMask: Drawable? = null + private val iconBackList: MutableList = ArrayList() + private val iconBackStrings: MutableList = ArrayList() + private var iconScale = 0f + private var loading = false + + init { + loadIconPack() + } + + private fun getDrawableForName(name: String): Drawable? { + if (isIconPackLoaded) { + val item = iconPackResources?.get(name) + if (item.isNullOrEmpty()) { + val id = getResourceIdForDrawable(item) + if (id != 0) { + return loadedIconPackResource!!.getDrawable(id) + } + } + } + return null + } + + private fun getDrawableWithName(name: String): Drawable? { + if (isIconPackLoaded) { + val id = getResourceIdForDrawable(name) + if (id != 0) { + return loadedIconPackResource!!.getDrawable(id) + } + } + return null + } + + @Throws(XmlPullParserException::class, IOException::class) + private fun loadResourcesFromXmlParser( + parser: XmlPullParser, + iconPackResources: MutableMap + ) { + while (parser.next() != XmlPullParser.END_DOCUMENT) { + if (parser.eventType != XmlPullParser.START_TAG) { + continue + } + val name = parser.name + if (name == "item") { + var component = parser.getAttributeValue(null, "component") + val drawable = parser.getAttributeValue(null, "drawable") + // Validate component/drawable exist + if (TextUtils.isEmpty(component) || TextUtils.isEmpty(drawable)) { + continue + } + + // Validate format/length of component + if (!component.startsWith("ComponentInfo{") || !component.endsWith("}") || component.length < 16) { + continue + } + + // Sanitize stored value + component = component.substring(14, component.length - 1) + if (!component.contains("/")) { + // Package icon reference + iconPackResources[component] = drawable + } else { + val componentName = ComponentName.unflattenFromString(component) + if (componentName != null) { + iconPackResources[componentName.packageName] = drawable + iconPackResources[component] = drawable + } + } + continue + } + if (name.equals(ICON_BACK_TAG, ignoreCase = true)) { + val icon = parser.getAttributeValue(null, "img") + if (icon == null) { + for (i in 0 until parser.attributeCount) { + iconBackStrings.add(parser.getAttributeValue(i)) + } + } + continue + } + if (name.equals(ICON_MASK_TAG, ignoreCase = true) || name.equals( + ICON_UPON_TAG, + ignoreCase = true + ) + ) { + var icon = parser.getAttributeValue(null, "img") + if (icon == null) { + if (parser.attributeCount > 0) { + icon = parser.getAttributeValue(0) + } + } + iconPackResources[parser.name.lowercase(Locale.getDefault())] = icon + continue + } + if (name.equals(ICON_SCALE_TAG, ignoreCase = true)) { + var factor = parser.getAttributeValue(null, "factor") + if (factor == null) { + if (parser.attributeCount > 0) { + factor = parser.getAttributeValue(0) + } + } + if (factor != null) { + iconPackResources[parser.name.lowercase(Locale.getDefault())] = factor + } + } + } + } + + private fun loadIconPack() { + val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) + val currentIconPack = sharedPreferences.getString("icon_pack", "") + iconBackList.clear() + iconBackStrings.clear() + if (currentIconPack.isNullOrEmpty()) + return + + loading = true + iconPackResources = getIconPackResources(context, currentIconPack) + val resources: Resources + try { + resources = context.packageManager.getResourcesForApplication(currentIconPack) + } catch (e: PackageManager.NameNotFoundException) { + loading = false + return + } + loadedIconPackResource = resources + loadedIconPackName = currentIconPack + iconMask = getDrawableForName(ICON_MASK_TAG) + mIconUpon = getDrawableForName(ICON_UPON_TAG) + for (i in iconBackStrings.indices) { + val backIconString = iconBackStrings[i] + val backIcon = getDrawableWithName(backIconString) + if (backIcon != null) { + iconBackList.add(backIcon) + } + } + val scale = iconPackResources!![ICON_SCALE_TAG] + if (scale != null) { + try { + iconScale = scale.toFloat() + } catch (ignored: NumberFormatException) { + } + } + loading = false + } + + //new method from trebuchet + private fun getIconPackResources( + context: Context, + packageName: String + ): Map? { + if (packageName.isEmpty()) + return null + + val resources: Resources = try { + context.packageManager.getResourcesForApplication(packageName) + } catch (e: PackageManager.NameNotFoundException) { + return null + } + var parser: XmlPullParser? = null + var inputStream: InputStream? = null + val iconPackResources: MutableMap = HashMap() + try { + inputStream = resources.assets.open("appfilter.xml") + val factory = XmlPullParserFactory.newInstance() + parser = factory.newPullParser() + parser.setInput(inputStream, "UTF-8") + } catch (e: Exception) { + // Catch any exception since we want to fall back to parsing the xml/ + // resource in all cases + val resId = resources.getIdentifier("appfilter", "xml", packageName) + if (resId != 0) { + parser = resources.getXml(resId) + } + } + if (parser != null) { + try { + loadResourcesFromXmlParser(parser, iconPackResources) + return iconPackResources + } catch (e: XmlPullParserException) { + e.printStackTrace() + } catch (e: IOException) { + e.printStackTrace() + } finally { + // Cleanup resources + if (parser is XmlResourceParser) { + parser.close() + } + if (inputStream != null) { + try { + inputStream.close() + } catch (ignored: IOException) { + } + } + } + } + + // Application uses a different theme format (most likely launcher pro) + var arrayId = resources.getIdentifier("theme_iconpack", "array", packageName) + if (arrayId == 0) { + arrayId = resources.getIdentifier("icon_pack", "array", packageName) + } + if (arrayId != 0) { + val iconPack = resources.getStringArray(arrayId) + for (entry in iconPack) { + if (TextUtils.isEmpty(entry)) { + continue + } + val icon = entry.lowercase(Locale.getDefault()) + val entry = entry.replace("_".toRegex(), ".") + iconPackResources[entry] = icon + val activityIndex = entry.lastIndexOf(".") + if (activityIndex <= 0 || activityIndex == entry.length - 1) { + continue + } + val iconPackage = entry.substring(0, activityIndex) + if (TextUtils.isEmpty(iconPackage)) { + continue + } + iconPackResources[iconPackage] = icon + val iconActivity = entry.substring(activityIndex + 1) + if (TextUtils.isEmpty(iconActivity)) { + continue + } + iconPackResources["$iconPackage.$iconActivity"] = icon + } + } else { + loadApplicationResources(context, iconPackResources, packageName) + } + return iconPackResources + } + + private fun loadApplicationResources( + context: Context?, + iconPackResources: MutableMap, + packageName: String? + ) { + val drawableItems: Array = try { + val appContext = context!!.createPackageContext( + packageName, + Context.CONTEXT_INCLUDE_CODE or Context.CONTEXT_IGNORE_SECURITY + ) + Class.forName("$packageName.R\$drawable", true, appContext.classLoader).fields + } catch (e: Exception) { + return + } + for (f in drawableItems) { + var name = f.name + val icon = name.lowercase(Locale.getDefault()) + name = name.replace("_".toRegex(), ".") + iconPackResources[name] = icon + val activityIndex = name.lastIndexOf(".") + if (activityIndex <= 0 || activityIndex == name.length - 1) { + continue + } + val iconPackage = name.substring(0, activityIndex) + if (TextUtils.isEmpty(iconPackage)) { + continue + } + iconPackResources[iconPackage] = icon + val iconActivity = name.substring(activityIndex + 1) + if (TextUtils.isEmpty(iconActivity)) { + continue + } + iconPackResources["$iconPackage.$iconActivity"] = icon + } + } + + val isIconPackLoaded: Boolean + get() = loadedIconPackResource != null && loadedIconPackName != null && iconPackResources != null + + private fun getResourceIdForDrawable(resource: String?): Int { + return loadedIconPackResource!!.getIdentifier(resource, "drawable", loadedIconPackName) + } + + private fun getIconPackResources(id: Int, mContext: Context): Drawable? { + return ResourcesCompat.getDrawable(loadedIconPackResource!!, id, mContext.theme) + } + + fun getResourceIdForActivityIcon(info: ActivityInfo): Int { + // TODO since we are loading in background block access until load ready + if (!isIconPackLoaded || loading) { + return 0 + } + //Try to match icon class by lower case, if not fallback to exact string + //Catch added for lower case exceptions + var drawable: String? + drawable = try { + iconPackResources?.get( + info.packageName.lowercase(Locale.getDefault()) + "." + info.name.lowercase( + Locale.getDefault() + ) + ) + } catch (e: NullPointerException) { + iconPackResources?.get(info.packageName + "." + info.name) + } + if (drawable == null) { + // Icon pack doesn't have an icon for the activity, fallback to package icon + //Catch added for lower case exceptions + drawable = try { + iconPackResources?.get(info.packageName.lowercase(Locale.getDefault())) + } catch (e: NullPointerException) { + iconPackResources?.get(info.packageName) + } + if (drawable == null) { + return 0 + } + } + return getResourceIdForDrawable(drawable) + } + + fun getAppThemedIcon(packageName: String): Drawable? { + val activityInfo = ActivityInfo() + activityInfo.packageName = packageName + if (isIconPackLoaded) { + val iconId = getResourceIdForActivityIcon(activityInfo) + run { + if (iconId != 0) { + return getIconPackResources(iconId, context) + } + } + } + return AppUtils.getAppIcon(context, packageName) + } +} diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index fc066f83..5906f236 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -322,5 +322,6 @@ Algo salió mal Guardar registro Abrir aplicación de nuevo + Icon pack \ No newline at end of file diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml index d7810b2c..02d2c544 100644 --- a/app/src/main/res/values-pa/strings.xml +++ b/app/src/main/res/values-pa/strings.xml @@ -322,5 +322,6 @@ ਕੁਝ ਗਲਤ ਹੋ ਗਿਆ ਲੌਗ ਸੇਵ ਕਰੋ ਐਪ ਨੂੰ ਦੁਬਾਰਾ ਖੋਲ੍ਹੋ + Icon pack diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 18f4bfe3..6ff9ed5b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -322,5 +322,6 @@ Something went wrong Save log Open app again + Icon pack \ No newline at end of file diff --git a/app/src/main/res/xml/preferences_appearance.xml b/app/src/main/res/xml/preferences_appearance.xml index 012cc819..88cac0f0 100644 --- a/app/src/main/res/xml/preferences_appearance.xml +++ b/app/src/main/res/xml/preferences_appearance.xml @@ -17,6 +17,10 @@ android:key="dock_layout" android:title="@string/dock_layout" /> + +