diff options
7 files changed, 105 insertions, 40 deletions
diff --git a/java/src/com/android/intentresolver/ChooserTargetActionsDialogFragment.java b/java/src/com/android/intentresolver/ChooserTargetActionsDialogFragment.java index ff0bda01..8070fc84 100644 --- a/java/src/com/android/intentresolver/ChooserTargetActionsDialogFragment.java +++ b/java/src/com/android/intentresolver/ChooserTargetActionsDialogFragment.java @@ -281,7 +281,11 @@ public class ChooserTargetActionsDialogFragment extends DialogFragment final int iconDpi = am.getLauncherLargeIconDensity(); // Use the matching application icon and label for the title, any TargetInfo will do - return new TargetPresentationGetter.Factory(getContext(), iconDpi) + final Context context = getContext(); + return new TargetPresentationGetter.Factory( + () -> SimpleIconFactory.obtain(context), + context.getPackageManager(), + iconDpi) .makePresentationGetter(mTargetInfos.get(0).getResolveInfo()); } diff --git a/java/src/com/android/intentresolver/SimpleIconFactory.java b/java/src/com/android/intentresolver/SimpleIconFactory.java index f4871e36..afb7d19e 100644 --- a/java/src/com/android/intentresolver/SimpleIconFactory.java +++ b/java/src/com/android/intentresolver/SimpleIconFactory.java @@ -64,7 +64,7 @@ import java.util.Optional; * possibly badged. It is intended to be used only by Sharesheet for the Q release with custom code. */ @Deprecated -public class SimpleIconFactory { +public class SimpleIconFactory implements AutoCloseable { private static final SynchronizedPool<SimpleIconFactory> sPool = @@ -139,6 +139,11 @@ public class SimpleIconFactory { "Expected theme to define iconfactoryBadgeSize."); } + @Override + public void close() { + recycle(); + } + /** * Recycles the SimpleIconFactory so others may use it. * @@ -146,9 +151,11 @@ public class SimpleIconFactory { */ @Deprecated public void recycle() { - // Return to default background color - setWrapperBackgroundColor(Color.WHITE); - sPool.release(this); + if (sPoolEnabled) { + // Return to default background color + setWrapperBackgroundColor(Color.WHITE); + sPool.release(this); + } } /** diff --git a/java/src/com/android/intentresolver/TargetPresentationGetter.java b/java/src/com/android/intentresolver/TargetPresentationGetter.java index ac74366e..3a7f807d 100644 --- a/java/src/com/android/intentresolver/TargetPresentationGetter.java +++ b/java/src/com/android/intentresolver/TargetPresentationGetter.java @@ -16,7 +16,6 @@ package com.android.intentresolver; -import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -30,6 +29,8 @@ import android.util.Log; import androidx.annotation.Nullable; +import javax.inject.Provider; + /** * Loads the icon and label for the provided ApplicationInfo. Defaults to using the application icon * and label over any IntentFilter or Activity icon to increase user understanding, with an @@ -48,22 +49,29 @@ public abstract class TargetPresentationGetter { /** Helper to build appropriate type-specific {@link TargetPresentationGetter} instances. */ public static class Factory { - private final Context mContext; + private final Provider<SimpleIconFactory> mIconFactoryProvider; + private final PackageManager mPackageManager; private final int mIconDpi; - public Factory(Context context, int iconDpi) { - mContext = context; + public Factory( + Provider<SimpleIconFactory> iconfactoryProvider, + PackageManager packageManager, + int iconDpi) { + mIconFactoryProvider = iconfactoryProvider; + mPackageManager = packageManager; mIconDpi = iconDpi; } /** Make a {@link TargetPresentationGetter} for an {@link ActivityInfo}. */ public TargetPresentationGetter makePresentationGetter(ActivityInfo activityInfo) { - return new ActivityInfoPresentationGetter(mContext, mIconDpi, activityInfo); + return new ActivityInfoPresentationGetter( + mIconFactoryProvider, mPackageManager, mIconDpi, activityInfo); } /** Make a {@link TargetPresentationGetter} for a {@link ResolveInfo}. */ public TargetPresentationGetter makePresentationGetter(ResolveInfo resolveInfo) { - return new ResolveInfoPresentationGetter(mContext, mIconDpi, resolveInfo); + return new ResolveInfoPresentationGetter( + mIconFactoryProvider, mPackageManager, mIconDpi, resolveInfo); } } @@ -76,7 +84,7 @@ public abstract class TargetPresentationGetter { @Nullable protected abstract String getAppLabelForSubstitutePermission(); - private final Context mContext; + private final Provider<SimpleIconFactory> mIconFactoryProvider; private final int mIconDpi; private final boolean mHasSubstitutePermission; private final ApplicationInfo mAppInfo; @@ -107,9 +115,10 @@ public abstract class TargetPresentationGetter { drawable = mAppInfo.loadIcon(mPm); } - SimpleIconFactory iconFactory = SimpleIconFactory.obtain(mContext); - Bitmap icon = iconFactory.createUserBadgedIconBitmap(drawable, userHandle); - iconFactory.recycle(); + Bitmap icon; + try (SimpleIconFactory iconFactory = mIconFactoryProvider.get()) { + icon = iconFactory.createUserBadgedIconBitmap(drawable, userHandle); + } return icon; } @@ -159,9 +168,13 @@ public abstract class TargetPresentationGetter { return res.getDrawableForDensity(resId, mIconDpi); } - private TargetPresentationGetter(Context context, int iconDpi, ApplicationInfo appInfo) { - mContext = context; - mPm = context.getPackageManager(); + private TargetPresentationGetter( + Provider<SimpleIconFactory> iconfactoryProvider, + PackageManager packageManager, + int iconDpi, + ApplicationInfo appInfo) { + mIconFactoryProvider = iconfactoryProvider; + mPm = packageManager; mAppInfo = appInfo; mIconDpi = iconDpi; mHasSubstitutePermission = (PackageManager.PERMISSION_GRANTED == mPm.checkPermission( @@ -174,8 +187,11 @@ public abstract class TargetPresentationGetter { private final ResolveInfo mResolveInfo; ResolveInfoPresentationGetter( - Context context, int iconDpi, ResolveInfo resolveInfo) { - super(context, iconDpi, resolveInfo.activityInfo); + Provider<SimpleIconFactory> iconfactoryProvider, + PackageManager packageManager, + int iconDpi, + ResolveInfo resolveInfo) { + super(iconfactoryProvider, packageManager, iconDpi, resolveInfo.activityInfo); mResolveInfo = resolveInfo; } @@ -221,8 +237,11 @@ public abstract class TargetPresentationGetter { private final ActivityInfo mActivityInfo; ActivityInfoPresentationGetter( - Context context, int iconDpi, ActivityInfo activityInfo) { - super(context, iconDpi, activityInfo.applicationInfo); + Provider<SimpleIconFactory> iconfactoryProvider, + PackageManager packageManager, + int iconDpi, + ActivityInfo activityInfo) { + super(iconfactoryProvider, packageManager, iconDpi, activityInfo.applicationInfo); mActivityInfo = activityInfo; } diff --git a/java/src/com/android/intentresolver/icons/DefaultTargetDataLoader.kt b/java/src/com/android/intentresolver/icons/DefaultTargetDataLoader.kt index e80a4a7c..1ff1ddfa 100644 --- a/java/src/com/android/intentresolver/icons/DefaultTargetDataLoader.kt +++ b/java/src/com/android/intentresolver/icons/DefaultTargetDataLoader.kt @@ -16,7 +16,6 @@ package com.android.intentresolver.icons -import android.app.ActivityManager import android.content.Context import android.graphics.Bitmap import android.graphics.drawable.BitmapDrawable @@ -30,6 +29,7 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import com.android.intentresolver.Flags.targetHoverAndKeyboardFocusStates import com.android.intentresolver.R +import com.android.intentresolver.SimpleIconFactory import com.android.intentresolver.TargetPresentationGetter import com.android.intentresolver.chooser.DisplayResolveInfo import com.android.intentresolver.chooser.SelectableTargetInfo @@ -40,6 +40,7 @@ import dagger.assisted.AssistedInject import dagger.hilt.android.qualifiers.ActivityContext import java.util.concurrent.atomic.AtomicInteger import java.util.function.Consumer +import javax.inject.Provider import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.asExecutor @@ -50,14 +51,10 @@ class DefaultTargetDataLoader constructor( @ActivityContext private val context: Context, @ActivityOwned private val lifecycle: Lifecycle, + private val iconFactoryProvider: Provider<SimpleIconFactory>, + private val presentationFactory: TargetPresentationGetter.Factory, @Assisted private val isAudioCaptureDevice: Boolean, ) : TargetDataLoader { - private val presentationFactory = - TargetPresentationGetter.Factory( - context, - context.getSystemService(ActivityManager::class.java)?.launcherLargeIconDensity - ?: error("Unable to access ActivityManager"), - ) private val nextTaskId = AtomicInteger(0) @GuardedBy("self") private val activeTasks = SparseArray<AsyncTask<*, *, *>>() private val executor = Dispatchers.IO.asExecutor() @@ -98,6 +95,7 @@ constructor( context.createContextAsUser(userHandle, 0), info, presentationFactory, + iconFactoryProvider, ) { bitmap -> removeTask(taskId) callback.accept(bitmap?.toDrawable() ?: loadIconPlaceholder()) diff --git a/java/src/com/android/intentresolver/icons/LoadDirectShareIconTask.java b/java/src/com/android/intentresolver/icons/LoadDirectShareIconTask.java index 641a0d6a..01f9330e 100644 --- a/java/src/com/android/intentresolver/icons/LoadDirectShareIconTask.java +++ b/java/src/com/android/intentresolver/icons/LoadDirectShareIconTask.java @@ -38,19 +38,24 @@ import com.android.intentresolver.util.UriFilters; import java.util.function.Consumer; +import javax.inject.Provider; + /** * Loads direct share targets icons. */ class LoadDirectShareIconTask extends BaseLoadIconTask { private static final String TAG = "DirectShareIconTask"; private final SelectableTargetInfo mTargetInfo; + private final Provider<SimpleIconFactory> mIconFactoryProvider; LoadDirectShareIconTask( Context context, SelectableTargetInfo targetInfo, TargetPresentationGetter.Factory presentationFactory, + Provider<SimpleIconFactory> iconFactoryProvider, Consumer<Bitmap> callback) { super(context, presentationFactory, callback); + mIconFactoryProvider = iconFactoryProvider; mTargetInfo = targetInfo; } @@ -121,9 +126,10 @@ class LoadDirectShareIconTask extends BaseLoadIconTask { Bitmap appIcon = mPresentationFactory.makePresentationGetter(info).getIconBitmap(null); // Raster target drawable with appIcon as a badge - SimpleIconFactory sif = SimpleIconFactory.obtain(context); - Bitmap directShareBadgedIcon = sif.createAppBadgedIconBitmap(directShareIcon, appIcon); - sif.recycle(); + Bitmap directShareBadgedIcon; + try (SimpleIconFactory sif = mIconFactoryProvider.get()) { + directShareBadgedIcon = sif.createAppBadgedIconBitmap(directShareIcon, appIcon); + } return directShareBadgedIcon; } diff --git a/java/src/com/android/intentresolver/icons/TargetDataLoaderModule.kt b/java/src/com/android/intentresolver/icons/TargetDataLoaderModule.kt index 21ff654f..d6d4aae1 100644 --- a/java/src/com/android/intentresolver/icons/TargetDataLoaderModule.kt +++ b/java/src/com/android/intentresolver/icons/TargetDataLoaderModule.kt @@ -16,18 +16,39 @@ package com.android.intentresolver.icons +import android.app.ActivityManager import android.content.Context +import android.content.pm.PackageManager +import com.android.intentresolver.SimpleIconFactory +import com.android.intentresolver.TargetPresentationGetter import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.components.ActivityComponent import dagger.hilt.android.qualifiers.ActivityContext import dagger.hilt.android.scopes.ActivityScoped +import javax.inject.Provider @Module @InstallIn(ActivityComponent::class) object TargetDataLoaderModule { @Provides + fun simpleIconFactory(@ActivityContext context: Context): SimpleIconFactory = + SimpleIconFactory.obtain(context) + + @Provides + fun presentationGetterFactory( + iconFactoryProvider: Provider<SimpleIconFactory>, + packageManager: PackageManager, + activityManager: ActivityManager, + ): TargetPresentationGetter.Factory = + TargetPresentationGetter.Factory( + iconFactoryProvider, + packageManager, + activityManager.launcherLargeIconDensity, + ) + + @Provides @ActivityScoped @Caching fun cachingTargetDataLoader( diff --git a/tests/unit/src/com/android/intentresolver/TargetPresentationGetterTest.kt b/tests/unit/src/com/android/intentresolver/TargetPresentationGetterTest.kt index 92848b2c..b5b05eb9 100644 --- a/tests/unit/src/com/android/intentresolver/TargetPresentationGetterTest.kt +++ b/tests/unit/src/com/android/intentresolver/TargetPresentationGetterTest.kt @@ -32,32 +32,42 @@ class TargetPresentationGetterTest { withSubstitutePermission: Boolean, appLabel: String, activityLabel: String, - resolveInfoLabel: String + resolveInfoLabel: String, ): TargetPresentationGetter { val testPackageInfo = ResolverDataProvider.createPackageManagerMockedInfo( withSubstitutePermission, appLabel, activityLabel, - resolveInfoLabel + resolveInfoLabel, + ) + val factory = + TargetPresentationGetter.Factory( + { SimpleIconFactory.obtain(testPackageInfo.ctx) }, + testPackageInfo.ctx.packageManager, + 100, ) - val factory = TargetPresentationGetter.Factory(testPackageInfo.ctx, 100) return factory.makePresentationGetter(testPackageInfo.resolveInfo) } fun makeActivityInfoPresentationGetter( withSubstitutePermission: Boolean, appLabel: String?, - activityLabel: String? + activityLabel: String?, ): TargetPresentationGetter { val testPackageInfo = ResolverDataProvider.createPackageManagerMockedInfo( withSubstitutePermission, appLabel, activityLabel, - "" + "", + ) + val factory = + TargetPresentationGetter.Factory( + { SimpleIconFactory.obtain(testPackageInfo.ctx) }, + testPackageInfo.ctx.packageManager, + 100, ) - val factory = TargetPresentationGetter.Factory(testPackageInfo.ctx, 100) return factory.makePresentationGetter(testPackageInfo.activityInfo) } @@ -158,7 +168,7 @@ class TargetPresentationGetterTest { false, "app_label", "activity_label", - "resolve_info_label" + "resolve_info_label", ) assertThat(presentationGetter.getLabel()).isEqualTo("app_label") assertThat(presentationGetter.getSubLabel()).isEqualTo("resolve_info_label") @@ -192,7 +202,7 @@ class TargetPresentationGetterTest { true, "app_label", "activity_label", - "resolve_info_label" + "resolve_info_label", ) assertThat(presentationGetter.getLabel()).isEqualTo("activity_label") assertThat(presentationGetter.getSubLabel()).isEqualTo("resolve_info_label") |