diff options
author | 2024-10-07 15:43:44 -0700 | |
---|---|---|
committer | 2024-10-08 15:45:57 -0700 | |
commit | de09e2d769cbffdc3ce206e5bd28aec85ddaf635 (patch) | |
tree | 3ab4c3cb3941f8e2761a3d77209bf6c9df9114db /java/src/com | |
parent | f6400db571602fbbb3c5fa88276c3e5ed40792da (diff) |
CachingTargetDataLoader to cache bitmaps and not drawables
A preparation refactoring. Make ChachingTargetDataLoader to cache icon
bitmaps instead of the end drawables. Drawables carry state and the same
drawable can be used in two places in the target grid (i.e. in the
ranked targets row and in the all-target grid) yet the new hover effect
needs to be provided independently for each position.
Bug: 295175912
Test: manual basic functinality testing including Shareousel selection
change.
Test: atest IntentResolver-tests-unit
Flag: EXEMPT refactoring
Change-Id: I1c5e6333310e0984e39e22cab8cf162f2f31d6e8
Change-Id: I4a3b5dff8fcfcffe7742be8ea3bd348351332c9e
Diffstat (limited to 'java/src/com')
9 files changed, 71 insertions, 69 deletions
diff --git a/java/src/com/android/intentresolver/ChooserTargetActionsDialogFragment.java b/java/src/com/android/intentresolver/ChooserTargetActionsDialogFragment.java index ae80fad4..ff0bda01 100644 --- a/java/src/com/android/intentresolver/ChooserTargetActionsDialogFragment.java +++ b/java/src/com/android/intentresolver/ChooserTargetActionsDialogFragment.java @@ -33,6 +33,7 @@ import android.content.pm.PackageManager; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutManager; import android.graphics.Color; +import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; @@ -136,7 +137,7 @@ public class ChooserTargetActionsDialogFragment extends DialogFragment final TargetPresentationGetter pg = getProvidingAppPresentationGetter(); title.setText(isShortcutTarget() ? mShortcutTitle : pg.getLabel()); - icon.setImageDrawable(pg.getIcon(mUserHandle)); + icon.setImageDrawable(new BitmapDrawable(getResources(), pg.getIconBitmap(mUserHandle))); rv.setAdapter(new VHAdapter(items)); return v; diff --git a/java/src/com/android/intentresolver/TargetPresentationGetter.java b/java/src/com/android/intentresolver/TargetPresentationGetter.java index 910c65c9..ac74366e 100644 --- a/java/src/com/android/intentresolver/TargetPresentationGetter.java +++ b/java/src/com/android/intentresolver/TargetPresentationGetter.java @@ -23,7 +23,6 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.graphics.Bitmap; -import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.UserHandle; import android.text.TextUtils; @@ -77,7 +76,7 @@ public abstract class TargetPresentationGetter { @Nullable protected abstract String getAppLabelForSubstitutePermission(); - private Context mContext; + private final Context mContext; private final int mIconDpi; private final boolean mHasSubstitutePermission; private final ApplicationInfo mAppInfo; @@ -88,14 +87,6 @@ public abstract class TargetPresentationGetter { * Retrieve the image that should be displayed as the icon when this target is presented to the * specified {@code userHandle}. */ - public Drawable getIcon(UserHandle userHandle) { - return new BitmapDrawable(mContext.getResources(), getIconBitmap(userHandle)); - } - - /** - * Retrieve the image that should be displayed as the icon when this target is presented to the - * specified {@code userHandle}. - */ public Bitmap getIconBitmap(@Nullable UserHandle userHandle) { Drawable drawable = null; if (mHasSubstitutePermission) { diff --git a/java/src/com/android/intentresolver/icons/BaseLoadIconTask.java b/java/src/com/android/intentresolver/icons/BaseLoadIconTask.java index 2eceb89c..f09fcfc5 100644 --- a/java/src/com/android/intentresolver/icons/BaseLoadIconTask.java +++ b/java/src/com/android/intentresolver/icons/BaseLoadIconTask.java @@ -17,34 +17,31 @@ package com.android.intentresolver.icons; import android.content.Context; -import android.graphics.drawable.Drawable; +import android.graphics.Bitmap; import android.os.AsyncTask; -import com.android.intentresolver.R; +import androidx.annotation.Nullable; + import com.android.intentresolver.TargetPresentationGetter; import java.util.function.Consumer; -abstract class BaseLoadIconTask extends AsyncTask<Void, Void, Drawable> { +abstract class BaseLoadIconTask extends AsyncTask<Void, Void, Bitmap> { protected final Context mContext; protected final TargetPresentationGetter.Factory mPresentationFactory; - private final Consumer<Drawable> mCallback; + private final Consumer<Bitmap> mCallback; BaseLoadIconTask( Context context, TargetPresentationGetter.Factory presentationFactory, - Consumer<Drawable> callback) { + Consumer<Bitmap> callback) { mContext = context; mPresentationFactory = presentationFactory; mCallback = callback; } - protected final Drawable loadIconPlaceholder() { - return mContext.getDrawable(R.drawable.resolver_icon_placeholder); - } - @Override - protected final void onPostExecute(Drawable d) { + protected final void onPostExecute(@Nullable Bitmap d) { mCallback.accept(d); } } diff --git a/java/src/com/android/intentresolver/icons/CachingTargetDataLoader.kt b/java/src/com/android/intentresolver/icons/CachingTargetDataLoader.kt index 8474b4c3..b0c26777 100644 --- a/java/src/com/android/intentresolver/icons/CachingTargetDataLoader.kt +++ b/java/src/com/android/intentresolver/icons/CachingTargetDataLoader.kt @@ -17,6 +17,9 @@ package com.android.intentresolver.icons import android.content.ComponentName +import android.content.Context +import android.graphics.Bitmap +import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable import android.os.UserHandle import androidx.collection.LruCache @@ -28,23 +31,26 @@ import javax.inject.Qualifier @Qualifier @MustBeDocumented @Retention(AnnotationRetention.BINARY) annotation class Caching -private typealias IconCache = LruCache<String, Drawable> +private typealias IconCache = LruCache<String, Bitmap> class CachingTargetDataLoader( + private val context: Context, private val targetDataLoader: TargetDataLoader, private val cacheSize: Int = 100, -) : TargetDataLoader() { +) : TargetDataLoader { @GuardedBy("self") private val perProfileIconCache = HashMap<UserHandle, IconCache>() override fun getOrLoadAppTargetIcon( info: DisplayResolveInfo, userHandle: UserHandle, - callback: Consumer<Drawable> + callback: Consumer<Drawable>, ): Drawable? { val cacheKey = info.toCacheKey() - return getCachedAppIcon(cacheKey, userHandle) + return getCachedAppIcon(cacheKey, userHandle)?.let { BitmapDrawable(context.resources, it) } ?: targetDataLoader.getOrLoadAppTargetIcon(info, userHandle) { drawable -> - getProfileIconCache(userHandle).put(cacheKey, drawable) + (drawable as? BitmapDrawable)?.bitmap?.let { + getProfileIconCache(userHandle).put(cacheKey, it) + } callback.accept(drawable) } } @@ -52,13 +58,17 @@ class CachingTargetDataLoader( override fun getOrLoadDirectShareIcon( info: SelectableTargetInfo, userHandle: UserHandle, - callback: Consumer<Drawable> + callback: Consumer<Drawable>, ): Drawable? { val cacheKey = info.toCacheKey() - return cacheKey?.let { getCachedAppIcon(it, userHandle) } + return cacheKey + ?.let { getCachedAppIcon(it, userHandle) } + ?.let { BitmapDrawable(context.resources, it) } ?: targetDataLoader.getOrLoadDirectShareIcon(info, userHandle) { drawable -> if (cacheKey != null) { - getProfileIconCache(userHandle).put(cacheKey, drawable) + (drawable as? BitmapDrawable)?.bitmap?.let { + getProfileIconCache(userHandle).put(cacheKey, it) + } } callback.accept(drawable) } @@ -69,7 +79,7 @@ class CachingTargetDataLoader( override fun getOrLoadLabel(info: DisplayResolveInfo) = targetDataLoader.getOrLoadLabel(info) - private fun getCachedAppIcon(component: String, userHandle: UserHandle): Drawable? = + private fun getCachedAppIcon(component: String, userHandle: UserHandle): Bitmap? = getProfileIconCache(userHandle)[component] private fun getProfileIconCache(userHandle: UserHandle): IconCache = @@ -78,10 +88,7 @@ class CachingTargetDataLoader( } private fun DisplayResolveInfo.toCacheKey() = - ComponentName( - resolveInfo.activityInfo.packageName, - resolveInfo.activityInfo.name, - ) + ComponentName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name) .flattenToString() private fun SelectableTargetInfo.toCacheKey(): String? = diff --git a/java/src/com/android/intentresolver/icons/DefaultTargetDataLoader.kt b/java/src/com/android/intentresolver/icons/DefaultTargetDataLoader.kt index e7392f58..117c769d 100644 --- a/java/src/com/android/intentresolver/icons/DefaultTargetDataLoader.kt +++ b/java/src/com/android/intentresolver/icons/DefaultTargetDataLoader.kt @@ -18,6 +18,7 @@ package com.android.intentresolver.icons import android.app.ActivityManager import android.content.Context +import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable import android.os.AsyncTask import android.os.UserHandle @@ -26,6 +27,7 @@ import androidx.annotation.GuardedBy import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner +import com.android.intentresolver.R import com.android.intentresolver.TargetPresentationGetter import com.android.intentresolver.chooser.DisplayResolveInfo import com.android.intentresolver.chooser.SelectableTargetInfo @@ -40,12 +42,12 @@ class DefaultTargetDataLoader( private val context: Context, private val lifecycle: Lifecycle, private val isAudioCaptureDevice: Boolean, -) : TargetDataLoader() { +) : TargetDataLoader { private val presentationFactory = TargetPresentationGetter.Factory( context, context.getSystemService(ActivityManager::class.java)?.launcherLargeIconDensity - ?: error("Unable to access ActivityManager") + ?: error("Unable to access ActivityManager"), ) private val nextTaskId = AtomicInteger(0) @GuardedBy("self") private val activeTasks = SparseArray<AsyncTask<*, *, *>>() @@ -68,9 +70,11 @@ class DefaultTargetDataLoader( callback: Consumer<Drawable>, ): Drawable? { val taskId = nextTaskId.getAndIncrement() - LoadIconTask(context, info, userHandle, presentationFactory) { result -> + LoadIconTask(context, info, presentationFactory) { bitmap -> removeTask(taskId) - callback.accept(result) + callback.accept( + bitmap?.let { BitmapDrawable(context.resources, it) } ?: loadIconPlaceholder() + ) } .also { addTask(taskId, it) } .executeOnExecutor(executor) @@ -87,9 +91,11 @@ class DefaultTargetDataLoader( context.createContextAsUser(userHandle, 0), info, presentationFactory, - ) { result -> + ) { bitmap -> removeTask(taskId) - callback.accept(result) + callback.accept( + bitmap?.let { BitmapDrawable(context.resources, it) } ?: loadIconPlaceholder() + ) } .also { addTask(taskId, it) } .executeOnExecutor(executor) @@ -123,6 +129,9 @@ class DefaultTargetDataLoader( synchronized(activeTasks) { activeTasks.remove(id) } } + private fun loadIconPlaceholder(): Drawable = + requireNotNull(context.getDrawable(R.drawable.resolver_icon_placeholder)) + private fun destroy() { synchronized(activeTasks) { for (i in 0 until activeTasks.size()) { diff --git a/java/src/com/android/intentresolver/icons/LoadDirectShareIconTask.java b/java/src/com/android/intentresolver/icons/LoadDirectShareIconTask.java index e2c0362d..641a0d6a 100644 --- a/java/src/com/android/intentresolver/icons/LoadDirectShareIconTask.java +++ b/java/src/com/android/intentresolver/icons/LoadDirectShareIconTask.java @@ -23,7 +23,6 @@ import android.content.pm.LauncherApps; import android.content.pm.PackageManager; import android.content.pm.ShortcutInfo; import android.graphics.Bitmap; -import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.os.Trace; @@ -50,19 +49,20 @@ class LoadDirectShareIconTask extends BaseLoadIconTask { Context context, SelectableTargetInfo targetInfo, TargetPresentationGetter.Factory presentationFactory, - Consumer<Drawable> callback) { + Consumer<Bitmap> callback) { super(context, presentationFactory, callback); mTargetInfo = targetInfo; } @Override - protected Drawable doInBackground(Void... voids) { - Drawable drawable = null; + @Nullable + protected Bitmap doInBackground(Void... voids) { + Bitmap iconBitmap = null; Trace.beginSection("shortcut-icon"); try { final Icon icon = mTargetInfo.getChooserTargetIcon(); if (icon == null || UriFilters.hasValidIcon(icon)) { - drawable = getChooserTargetIconDrawable( + iconBitmap = getChooserTargetIconBitmap( mContext, icon, mTargetInfo.getChooserTargetComponentName(), @@ -71,25 +71,21 @@ class LoadDirectShareIconTask extends BaseLoadIconTask { Log.e(TAG, "Failed to load shortcut icon for " + mTargetInfo.getChooserTargetComponentName() + "; no access"); } - if (drawable == null) { - drawable = loadIconPlaceholder(); - } } catch (Exception e) { Log.e( TAG, "Failed to load shortcut icon for " + mTargetInfo.getChooserTargetComponentName(), e); - drawable = loadIconPlaceholder(); } finally { Trace.endSection(); } - return drawable; + return iconBitmap; } @WorkerThread @Nullable - private Drawable getChooserTargetIconDrawable( + private Bitmap getChooserTargetIconBitmap( Context context, @Nullable Icon icon, ComponentName targetComponentName, @@ -129,6 +125,6 @@ class LoadDirectShareIconTask extends BaseLoadIconTask { Bitmap directShareBadgedIcon = sif.createAppBadgedIconBitmap(directShareIcon, appIcon); sif.recycle(); - return new BitmapDrawable(context.getResources(), directShareBadgedIcon); + return directShareBadgedIcon; } } diff --git a/java/src/com/android/intentresolver/icons/LoadIconTask.java b/java/src/com/android/intentresolver/icons/LoadIconTask.java index 75132208..4573fadf 100644 --- a/java/src/com/android/intentresolver/icons/LoadIconTask.java +++ b/java/src/com/android/intentresolver/icons/LoadIconTask.java @@ -19,11 +19,12 @@ package com.android.intentresolver.icons; import android.content.ComponentName; import android.content.Context; import android.content.pm.ResolveInfo; -import android.graphics.drawable.Drawable; +import android.graphics.Bitmap; import android.os.Trace; -import android.os.UserHandle; import android.util.Log; +import androidx.annotation.Nullable; + import com.android.intentresolver.TargetPresentationGetter; import com.android.intentresolver.chooser.DisplayResolveInfo; @@ -32,38 +33,36 @@ import java.util.function.Consumer; class LoadIconTask extends BaseLoadIconTask { private static final String TAG = "IconTask"; protected final DisplayResolveInfo mDisplayResolveInfo; - private final UserHandle mUserHandle; private final ResolveInfo mResolveInfo; LoadIconTask( Context context, DisplayResolveInfo dri, - UserHandle userHandle, TargetPresentationGetter.Factory presentationFactory, - Consumer<Drawable> callback) { + Consumer<Bitmap> callback) { super(context, presentationFactory, callback); - mUserHandle = userHandle; mDisplayResolveInfo = dri; mResolveInfo = dri.getResolveInfo(); } @Override - protected Drawable doInBackground(Void... params) { + @Nullable + protected Bitmap doInBackground(Void... params) { Trace.beginSection("app-icon"); try { return loadIconForResolveInfo(mResolveInfo); } catch (Exception e) { ComponentName componentName = mDisplayResolveInfo.getResolvedComponentName(); Log.e(TAG, "Failed to load app icon for " + componentName, e); - return loadIconPlaceholder(); + return null; } finally { Trace.endSection(); } } - protected final Drawable loadIconForResolveInfo(ResolveInfo ri) { + protected final Bitmap loadIconForResolveInfo(ResolveInfo ri) { // Load icons based on userHandle from ResolveInfo. If in work profile/clone profile, icons // should be badged. - return mPresentationFactory.makePresentationGetter(ri).getIcon(ri.userHandle); + return mPresentationFactory.makePresentationGetter(ri).getIconBitmap(ri.userHandle); } } diff --git a/java/src/com/android/intentresolver/icons/TargetDataLoader.kt b/java/src/com/android/intentresolver/icons/TargetDataLoader.kt index 935b527a..7cbd040e 100644 --- a/java/src/com/android/intentresolver/icons/TargetDataLoader.kt +++ b/java/src/com/android/intentresolver/icons/TargetDataLoader.kt @@ -23,24 +23,24 @@ import com.android.intentresolver.chooser.SelectableTargetInfo import java.util.function.Consumer /** A target data loader contract. Added to support testing. */ -abstract class TargetDataLoader { +interface TargetDataLoader { /** Load an app target icon */ - abstract fun getOrLoadAppTargetIcon( + fun getOrLoadAppTargetIcon( info: DisplayResolveInfo, userHandle: UserHandle, callback: Consumer<Drawable>, ): Drawable? /** Load a shortcut icon */ - abstract fun getOrLoadDirectShareIcon( + fun getOrLoadDirectShareIcon( info: SelectableTargetInfo, userHandle: UserHandle, callback: Consumer<Drawable>, ): Drawable? /** Load target label */ - abstract fun loadLabel(info: DisplayResolveInfo, callback: Consumer<LabelInfo>) + fun loadLabel(info: DisplayResolveInfo, callback: Consumer<LabelInfo>) /** Loads DisplayResolveInfo's display label synchronously, if needed */ - abstract fun getOrLoadLabel(info: DisplayResolveInfo) + fun getOrLoadLabel(info: DisplayResolveInfo) } diff --git a/java/src/com/android/intentresolver/icons/TargetDataLoaderModule.kt b/java/src/com/android/intentresolver/icons/TargetDataLoaderModule.kt index 9c0acb11..86ebb9d9 100644 --- a/java/src/com/android/intentresolver/icons/TargetDataLoaderModule.kt +++ b/java/src/com/android/intentresolver/icons/TargetDataLoaderModule.kt @@ -39,6 +39,8 @@ object TargetDataLoaderModule { @Provides @ActivityScoped @Caching - fun cachingTargetDataLoader(targetDataLoader: TargetDataLoader): TargetDataLoader = - CachingTargetDataLoader(targetDataLoader) + fun cachingTargetDataLoader( + @ActivityContext context: Context, + targetDataLoader: TargetDataLoader, + ): TargetDataLoader = CachingTargetDataLoader(context, targetDataLoader) } |