Merge "Moving IconProvider extensions to a separate subclass instead of routing via APIWrapper" into main

This commit is contained in:
Sunny Goyal
2025-04-08 09:46:28 -07:00
committed by Android (Google) Code Review
6 changed files with 189 additions and 58 deletions
@@ -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
@@ -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<LauncherModel>,
private val iconCacheProvider: Provider<IconCache>,
pluginManagerWrapper: PluginManagerWrapper,
lifecycle: DaggerSingletonTracker,
) : LauncherIconProvider(ctx, themeManager), PluginListener<IconProcessorPlugin> {
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<IconProcessorPlugin>?,
) {
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<IconProcessorPlugin>?,
) {
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)
}
}
@@ -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()
}
@@ -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<String, ThemeData> 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<String, ThemeData> getThemedIconMap() {
if (mThemedIconMap != null) {
return mThemedIconMap;
@@ -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
*/
@@ -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<String, UserHandle> 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);
}