diff --git a/quickstep/src/com/android/launcher3/dagger/Modules.kt b/quickstep/src/com/android/launcher3/dagger/Modules.kt index 9723b42c62..f16c849637 100644 --- a/quickstep/src/com/android/launcher3/dagger/Modules.kt +++ b/quickstep/src/com/android/launcher3/dagger/Modules.kt @@ -16,6 +16,8 @@ package com.android.launcher3.dagger +import com.android.launcher3.icons.LauncherIconProvider +import com.android.launcher3.icons.LauncherIconProviderImpl import com.android.launcher3.uioverrides.QuickstepWidgetHolder.QuickstepWidgetHolderFactory import com.android.launcher3.uioverrides.SystemApiWrapper import com.android.launcher3.uioverrides.plugins.PluginManagerWrapperImpl @@ -41,6 +43,9 @@ abstract class WindowManagerProxyModule { @Module abstract class ApiWrapperModule { @Binds abstract fun bindApiWrapper(systemApiWrapper: SystemApiWrapper): ApiWrapper + + @Binds + abstract fun bindIconProvider(iconProviderImpl: LauncherIconProviderImpl): LauncherIconProvider } @Module diff --git a/quickstep/src/com/android/launcher3/icons/LauncherIconProviderImpl.kt b/quickstep/src/com/android/launcher3/icons/LauncherIconProviderImpl.kt new file mode 100644 index 0000000000..f67c9541ac --- /dev/null +++ b/quickstep/src/com/android/launcher3/icons/LauncherIconProviderImpl.kt @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.icons + +import android.content.Context +import android.content.pm.ApplicationInfo +import android.content.pm.PackageItemInfo +import android.content.res.Resources.NotFoundException +import android.graphics.drawable.AdaptiveIconDrawable +import android.graphics.drawable.Drawable +import android.util.Log +import com.android.launcher3.LauncherModel +import com.android.launcher3.dagger.ApplicationContext +import com.android.launcher3.dagger.LauncherAppSingleton +import com.android.launcher3.graphics.ShapeDelegate.Circle +import com.android.launcher3.graphics.ThemeManager +import com.android.launcher3.icons.cache.CachingLogic +import com.android.launcher3.icons.cache.LauncherActivityCachingLogic +import com.android.launcher3.util.ComponentKey +import com.android.launcher3.util.DaggerSingletonTracker +import com.android.launcher3.util.Executors.MODEL_EXECUTOR +import com.android.launcher3.util.PluginManagerWrapper +import com.android.systemui.plugins.IconProcessorPlugin +import com.android.systemui.plugins.PluginLifecycleManager +import com.android.systemui.plugins.PluginListener +import javax.inject.Inject +import javax.inject.Provider + +/** Extension of LauncherIconProvider with system APIs and plugin support */ +private const val TAG = "LauncherIconProviderImpl" + +@LauncherAppSingleton +class LauncherIconProviderImpl +@Inject +constructor( + @ApplicationContext ctx: Context, + themeManager: ThemeManager, + private val modelProvider: Provider, + private val iconCacheProvider: Provider, + pluginManagerWrapper: PluginManagerWrapper, + lifecycle: DaggerSingletonTracker, +) : LauncherIconProvider(ctx, themeManager), PluginListener { + + init { + pluginManagerWrapper.addPluginListener(this, IconProcessorPlugin::class.java) + lifecycle.addCloseable { pluginManagerWrapper.removePluginListener(this) } + } + + private var processor: IconProcessorPlugin? = null + + override fun getApplicationInfoHash(appInfo: ApplicationInfo): String = + (appInfo.sourceDir?.hashCode() ?: 0).toString() + " " + appInfo.longVersionCode + + override fun loadPackageIcon( + info: PackageItemInfo, + appInfo: ApplicationInfo, + density: Int, + ): Drawable? { + fun Drawable.preprocess(resId: Int) = + processor?.preprocessDrawable(this, resId, appInfo) ?: this + + try { + val resources = mContext.packageManager.getResourcesForApplication(appInfo) + // Try to load the package item icon first + if (info !== appInfo && info.icon != 0) { + try { + val icon = resources.getDrawableForDensity(info.icon, density, null) + if (icon != null) return icon.preprocess(info.icon) + } catch (_: NotFoundException) {} + } + // Load the fallback app icon + if (appInfo.icon != 0) { + // Tries to load the round icon res, if the app defines it as an adaptive icon + if (mThemeManager.iconShape is Circle) { + if (appInfo.roundIconRes != 0 && appInfo.roundIconRes != appInfo.icon) { + try { + val d = + resources.getDrawableForDensity(appInfo.roundIconRes, density, null) + if (d is AdaptiveIconDrawable) return d.preprocess(appInfo.roundIconRes) + } catch (_: NotFoundException) {} + } + } + + try { + return resources + .getDrawableForDensity(appInfo.icon, density, null) + ?.preprocess(appInfo.icon) + } catch (_: NotFoundException) {} + } + } catch (_: Exception) {} + return null + } + + override fun onPluginLoaded( + plugin: IconProcessorPlugin?, + pluginContext: Context?, + manager: PluginLifecycleManager?, + ) { + plugin?.setIconChangeNotifier { pkg, userHandle -> + modelProvider.get().onAppIconChanged(pkg, userHandle) + } + processor = plugin + Log.d(TAG, "Plugin connected $plugin") + MODEL_EXECUTOR.execute { + iconCacheProvider.get().clearMemoryCache() + modelProvider.get().reloadIfActive() + } + } + + override fun onPluginUnloaded( + plugin: IconProcessorPlugin?, + manager: PluginLifecycleManager?, + ) { + processor = null + Log.d(TAG, "Plugin disconnected") + } + + override fun notifyIconLoaded(icon: BitmapInfo, key: ComponentKey, logic: CachingLogic<*>) { + if (logic == LauncherActivityCachingLogic) + processor?.notifyAppIconLoaded(key.componentName, key.user, icon.flags) + } +} diff --git a/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt b/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt index 1f34969a49..9df40e7749 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt +++ b/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt @@ -23,7 +23,6 @@ import android.content.IIntentReceiver import android.content.IIntentSender import android.content.Intent import android.content.pm.ActivityInfo -import android.content.pm.ApplicationInfo import android.content.pm.LauncherActivityInfo import android.content.pm.LauncherApps import android.content.pm.ShortcutInfo @@ -198,11 +197,6 @@ open class SystemApiWrapper @Inject constructor(@ApplicationContext context: Con } } - override fun getApplicationInfoHash(appInfo: ApplicationInfo): String = - (appInfo.sourceDir?.hashCode() ?: 0).toString() + " " + appInfo.longVersionCode - - override fun getRoundIconRes(appInfo: ApplicationInfo) = appInfo.roundIconRes - override fun isFileDrawable(shortcutInfo: ShortcutInfo) = shortcutInfo.hasIconFile() || shortcutInfo.hasIconUri() } diff --git a/src/com/android/launcher3/icons/LauncherIconProvider.java b/src/com/android/launcher3/icons/LauncherIconProvider.java index 71ed3b49c6..ea3bd8a11a 100644 --- a/src/com/android/launcher3/icons/LauncherIconProvider.java +++ b/src/com/android/launcher3/icons/LauncherIconProvider.java @@ -16,25 +16,17 @@ package com.android.launcher3.icons; import android.content.Context; -import android.content.pm.ApplicationInfo; import android.content.res.Resources; import android.content.res.XmlResourceParser; -import android.graphics.drawable.AdaptiveIconDrawable; -import android.graphics.drawable.Drawable; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - import com.android.launcher3.R; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dagger.ApplicationContext; import com.android.launcher3.dagger.LauncherAppSingleton; -import com.android.launcher3.graphics.ShapeDelegate; import com.android.launcher3.graphics.ThemeManager; -import com.android.launcher3.util.ApiWrapper; import org.xmlpull.v1.XmlPullParser; @@ -58,17 +50,14 @@ public class LauncherIconProvider extends IconProvider { private Map mThemedIconMap; - private final ApiWrapper mApiWrapper; - private final ThemeManager mThemeManager; + protected final ThemeManager mThemeManager; @Inject public LauncherIconProvider( @ApplicationContext Context context, - ThemeManager themeManager, - ApiWrapper apiWrapper) { + ThemeManager themeManager) { super(context); mThemeManager = themeManager; - mApiWrapper = apiWrapper; mThemedIconMap = FeatureFlags.USE_LOCAL_ICON_OVERRIDES.get() ? null : DISABLED_MAP; } @@ -83,29 +72,6 @@ public class LauncherIconProvider extends IconProvider { mSystemState += "," + mThemeManager.getIconState().toUniqueId(); } - @Override - protected String getApplicationInfoHash(@NonNull ApplicationInfo appInfo) { - return mApiWrapper.getApplicationInfoHash(appInfo); - } - - @Nullable - @Override - protected Drawable loadAppInfoIcon(ApplicationInfo info, Resources resources, int density) { - // Tries to load the round icon res, if the app defines it as an adaptive icon - if (mThemeManager.getIconShape() instanceof ShapeDelegate.Circle) { - int roundIconRes = mApiWrapper.getRoundIconRes(info); - if (roundIconRes != 0 && roundIconRes != info.icon) { - try { - Drawable d = resources.getDrawableForDensity(roundIconRes, density); - if (d instanceof AdaptiveIconDrawable) { - return d; - } - } catch (Resources.NotFoundException exc) { } - } - } - return super.loadAppInfoIcon(info, resources, density); - } - private Map getThemedIconMap() { if (mThemedIconMap != null) { return mThemedIconMap; diff --git a/src/com/android/launcher3/util/ApiWrapper.java b/src/com/android/launcher3/util/ApiWrapper.java index 0510d591e4..9d7dfdeb7b 100644 --- a/src/com/android/launcher3/util/ApiWrapper.java +++ b/src/com/android/launcher3/util/ApiWrapper.java @@ -23,7 +23,6 @@ import android.app.Person; import android.app.role.RoleManager; import android.content.Context; import android.content.Intent; -import android.content.pm.ApplicationInfo; import android.content.pm.LauncherActivityInfo; import android.content.pm.ShortcutInfo; import android.graphics.drawable.ColorDrawable; @@ -201,21 +200,6 @@ public class ApiWrapper { } } - /** - * Returns a hash to uniquely identify a particular version of appInfo - */ - public String getApplicationInfoHash(@NonNull ApplicationInfo appInfo) { - // The hashString in source dir changes with every install - return appInfo.sourceDir; - } - - /** - * Returns the round icon resource Id if defined by the app - */ - public int getRoundIconRes(@NonNull ApplicationInfo appInfo) { - return 0; - } - /** * Checks if the shortcut is using an icon with file or URI source */ diff --git a/src_plugins/com/android/systemui/plugins/IconProcessorPlugin.java b/src_plugins/com/android/systemui/plugins/IconProcessorPlugin.java new file mode 100644 index 0000000000..c1eda095af --- /dev/null +++ b/src_plugins/com/android/systemui/plugins/IconProcessorPlugin.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.plugins; + +import android.content.ComponentName; +import android.content.pm.ApplicationInfo; +import android.graphics.drawable.Drawable; +import android.os.UserHandle; + +import com.android.systemui.plugins.annotations.ProvidesInterface; + +import java.util.function.BiConsumer; + +/** + * Implement this interface to process Launcher icon drawables before they are displayed. + */ +@ProvidesInterface(action = IconProcessorPlugin.ACTION, version = IconProcessorPlugin.VERSION) +public interface IconProcessorPlugin extends Plugin { + String ACTION = "com.android.systemui.action.ICON_WRAPPER_PLUGIN"; + int VERSION = 1; + + /** + * Sets a callback to be called with packageName and userHandle, whenever an icon changes + */ + void setIconChangeNotifier(BiConsumer callback); + + /** Preprocess the provided drawable and returns the modified drawable */ + Drawable preprocessDrawable(Drawable original, int resId, ApplicationInfo appInfo); + + /** Notifies when an app icon is loaded from cache */ + void notifyAppIconLoaded(ComponentName cn, UserHandle user, int bitmapInfoFlags); +}