summaryrefslogtreecommitdiff
path: root/java
diff options
context:
space:
mode:
author Andrey Epin <ayepin@google.com> 2024-04-24 14:49:33 -0700
committer Andrey Epin <ayepin@google.com> 2024-05-06 10:56:17 -0700
commit729b5dd9fae069c1c34eff2d655620d6b7455c4e (patch)
treefda2a9d0859cc803a13b49e24cee2126c3de0532 /java
parent6f6dd11c347de406ec1877b3634e07acb20c2113 (diff)
Do not animate target icons and labels after payload selection change.
An shared icon cache is added to prevent icons reloading for the new list adapter instance. Bug: 325465291 Test: atest IntentResolver-tests-unit Test: atest IntentResolver-tests-activity Change-Id: Ifba9ec221dd6c52f4aff1d3ff94729b61c24981a
Diffstat (limited to 'java')
-rw-r--r--java/src/com/android/intentresolver/ChooserActivity.java27
-rw-r--r--java/src/com/android/intentresolver/ChooserListAdapter.java22
-rw-r--r--java/src/com/android/intentresolver/ResolverActivity.java2
-rw-r--r--java/src/com/android/intentresolver/ResolverListAdapter.java15
-rw-r--r--java/src/com/android/intentresolver/chooser/DisplayResolveInfo.java1
-rw-r--r--java/src/com/android/intentresolver/chooser/MultiDisplayResolveInfo.java2
-rw-r--r--java/src/com/android/intentresolver/icons/CachingTargetDataLoader.kt76
-rw-r--r--java/src/com/android/intentresolver/icons/DefaultTargetDataLoader.kt5
-rw-r--r--java/src/com/android/intentresolver/icons/LabelInfo.kt2
-rw-r--r--java/src/com/android/intentresolver/icons/TargetDataLoader.kt4
-rw-r--r--java/src/com/android/intentresolver/icons/TargetDataLoaderModule.kt6
11 files changed, 132 insertions, 30 deletions
diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java
index 1922c05c..bd816a83 100644
--- a/java/src/com/android/intentresolver/ChooserActivity.java
+++ b/java/src/com/android/intentresolver/ChooserActivity.java
@@ -118,6 +118,7 @@ import com.android.intentresolver.emptystate.NoAppsAvailableEmptyStateProvider;
import com.android.intentresolver.emptystate.NoCrossProfileEmptyStateProvider;
import com.android.intentresolver.emptystate.WorkProfilePausedEmptyStateProvider;
import com.android.intentresolver.grid.ChooserGridAdapter;
+import com.android.intentresolver.icons.Caching;
import com.android.intentresolver.icons.TargetDataLoader;
import com.android.intentresolver.inject.Background;
import com.android.intentresolver.logging.EventLog;
@@ -175,6 +176,7 @@ import java.util.function.Consumer;
import java.util.function.Supplier;
import javax.inject.Inject;
+import javax.inject.Provider;
/**
* The Chooser Activity handles intent resolution specifically for sharing intents -
@@ -265,7 +267,11 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
@Inject @AppPredictionAvailable public boolean mAppPredictionAvailable;
@Inject @ImageEditor public Optional<ComponentName> mImageEditor;
@Inject @NearbyShare public Optional<ComponentName> mNearbyShare;
- @Inject public TargetDataLoader mTargetDataLoader;
+ protected TargetDataLoader mTargetDataLoader;
+ @Inject public Provider<TargetDataLoader> mTargetDataLoaderProvider;
+ @Inject
+ @Caching
+ public Provider<TargetDataLoader> mCachingTargetDataLoaderProvider;
@Inject public DevicePolicyResources mDevicePolicyResources;
@Inject public ProfilePagerResources mProfilePagerResources;
@Inject public PackageManager mPackageManager;
@@ -340,6 +346,10 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
super.onCreate(savedInstanceState);
Log.i(TAG, "onCreate");
+ mTargetDataLoader = mChooserServiceFeatureFlags.chooserPayloadToggling()
+ ? mCachingTargetDataLoaderProvider.get()
+ : mTargetDataLoaderProvider.get();
+
setTheme(R.style.Theme_DeviceDefault_Chooser);
// Initializer is invoked when this function returns, via Lifecycle.
@@ -776,6 +786,10 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
mRequest.getInitialIntents(),
mMaxTargetsPerRow);
mChooserMultiProfilePagerAdapter.setCurrentPage(currentPage);
+ for (int i = 0, count = mChooserMultiProfilePagerAdapter.getItemCount(); i < count; i++) {
+ mChooserMultiProfilePagerAdapter.getPageAdapterForIndex(i)
+ .getListAdapter().setAnimateItems(false);
+ }
if (mPersonalPackageMonitor != null) {
mPersonalPackageMonitor.unregister();
}
@@ -1384,17 +1398,6 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
return context.getSharedPreferences(PINNED_SHARED_PREFS_NAME, MODE_PRIVATE);
}
- protected ChooserMultiProfilePagerAdapter createMultiProfilePagerAdapter() {
- return createMultiProfilePagerAdapter(
- /* context = */ this,
- mProfilePagerResources,
- mViewModel.getRequest().getValue(),
- mProfiles,
- mProfileAvailability,
- mRequest.getInitialIntents(),
- mMaxTargetsPerRow);
- }
-
private ChooserMultiProfilePagerAdapter createMultiProfilePagerAdapter(
Context context,
ProfilePagerResources profilePagerResources,
diff --git a/java/src/com/android/intentresolver/ChooserListAdapter.java b/java/src/com/android/intentresolver/ChooserListAdapter.java
index 29b5698b..8b848e55 100644
--- a/java/src/com/android/intentresolver/ChooserListAdapter.java
+++ b/java/src/com/android/intentresolver/ChooserListAdapter.java
@@ -153,6 +153,8 @@ public class ChooserListAdapter extends ResolverListAdapter {
}
};
+ private boolean mAnimateItems = true;
+
public ChooserListAdapter(
Context context,
List<Intent> payloadIntents,
@@ -308,6 +310,10 @@ public class ChooserListAdapter extends ResolverListAdapter {
}
}
+ public void setAnimateItems(boolean animateItems) {
+ mAnimateItems = animateItems;
+ }
+
@Override
public void handlePackagesChanged() {
if (mPackageChangeCallback != null) {
@@ -371,18 +377,15 @@ public class ChooserListAdapter extends ResolverListAdapter {
final CharSequence displayLabel = Objects.requireNonNullElse(info.getDisplayLabel(), "");
final CharSequence extendedInfo = Objects.requireNonNullElse(info.getExtendedInfo(), "");
holder.bindLabel(displayLabel, extendedInfo);
- if (!TextUtils.isEmpty(displayLabel)) {
+ if (mAnimateItems && !TextUtils.isEmpty(displayLabel)) {
mAnimationTracker.animateLabel(holder.text, info);
}
- if (!TextUtils.isEmpty(extendedInfo) && holder.text2.getVisibility() == View.VISIBLE) {
+ if (mAnimateItems
+ && !TextUtils.isEmpty(extendedInfo)
+ && holder.text2.getVisibility() == View.VISIBLE) {
mAnimationTracker.animateLabel(holder.text2, info);
}
- holder.bindIcon(info);
- if (info.hasDisplayIcon()) {
- mAnimationTracker.animateIcon(holder.icon, info);
- }
-
if (info.isSelectableTargetInfo()) {
// direct share targets should append the application name for a better readout
DisplayResolveInfo rInfo = info.getDisplayResolveInfo();
@@ -418,6 +421,11 @@ public class ChooserListAdapter extends ResolverListAdapter {
}
}
+ holder.bindIcon(info);
+ if (mAnimateItems && info.hasDisplayIcon()) {
+ mAnimationTracker.animateIcon(holder.icon, info);
+ }
+
if (info.isPlaceHolderTargetInfo()) {
bindPlaceholder(holder);
}
diff --git a/java/src/com/android/intentresolver/ResolverActivity.java b/java/src/com/android/intentresolver/ResolverActivity.java
index 1b08d957..927f59ef 100644
--- a/java/src/com/android/intentresolver/ResolverActivity.java
+++ b/java/src/com/android/intentresolver/ResolverActivity.java
@@ -1390,7 +1390,7 @@ public class ResolverActivity extends Hilt_ResolverActivity implements
// Load the icon asynchronously
ImageView icon = findViewById(com.android.internal.R.id.icon);
- targetDataLoader.loadAppTargetIcon(
+ targetDataLoader.getOrLoadAppTargetIcon(
otherProfileResolveInfo,
inactiveAdapter.getUserHandle(),
(drawable) -> {
diff --git a/java/src/com/android/intentresolver/ResolverListAdapter.java b/java/src/com/android/intentresolver/ResolverListAdapter.java
index 2a8fcfa4..5fd37d43 100644
--- a/java/src/com/android/intentresolver/ResolverListAdapter.java
+++ b/java/src/com/android/intentresolver/ResolverListAdapter.java
@@ -739,26 +739,31 @@ public class ResolverListAdapter extends BaseAdapter {
holder.bindLabel("", "");
loadLabel(dri);
}
- holder.bindIcon(info);
if (!dri.hasDisplayIcon()) {
loadIcon(dri);
}
+ holder.bindIcon(info);
}
}
protected final void loadIcon(DisplayResolveInfo info) {
if (mRequestedIcons.add(info)) {
- mTargetDataLoader.loadAppTargetIcon(
+ Drawable icon = mTargetDataLoader.getOrLoadAppTargetIcon(
info,
getUserHandle(),
- (drawable) -> onIconLoaded(info, drawable));
+ (drawable) -> {
+ onIconLoaded(info, drawable);
+ notifyDataSetChanged();
+ });
+ if (icon != null) {
+ onIconLoaded(info, icon);
+ }
}
}
private void onIconLoaded(DisplayResolveInfo displayResolveInfo, Drawable drawable) {
if (!displayResolveInfo.hasDisplayIcon()) {
displayResolveInfo.getDisplayIconHolder().setDisplayIcon(drawable);
- notifyDataSetChanged();
}
}
@@ -822,7 +827,7 @@ public class ResolverListAdapter extends BaseAdapter {
public void loadFilteredItemIconTaskAsync(@NonNull ImageView iconView) {
final DisplayResolveInfo iconInfo = getFilteredItem();
if (iconInfo != null) {
- mTargetDataLoader.loadAppTargetIcon(
+ mTargetDataLoader.getOrLoadAppTargetIcon(
iconInfo, getUserHandle(), iconView::setImageDrawable);
}
}
diff --git a/java/src/com/android/intentresolver/chooser/DisplayResolveInfo.java b/java/src/com/android/intentresolver/chooser/DisplayResolveInfo.java
index 536f11ce..5e44c53e 100644
--- a/java/src/com/android/intentresolver/chooser/DisplayResolveInfo.java
+++ b/java/src/com/android/intentresolver/chooser/DisplayResolveInfo.java
@@ -196,6 +196,7 @@ public class DisplayResolveInfo implements TargetInfo {
}
@Override
+ @NonNull
public ComponentName getResolvedComponentName() {
return new ComponentName(mResolveInfo.activityInfo.packageName,
mResolveInfo.activityInfo.name);
diff --git a/java/src/com/android/intentresolver/chooser/MultiDisplayResolveInfo.java b/java/src/com/android/intentresolver/chooser/MultiDisplayResolveInfo.java
index 4fe28384..95cb443e 100644
--- a/java/src/com/android/intentresolver/chooser/MultiDisplayResolveInfo.java
+++ b/java/src/com/android/intentresolver/chooser/MultiDisplayResolveInfo.java
@@ -23,6 +23,7 @@ import android.os.Bundle;
import android.os.UserHandle;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.ArrayList;
@@ -123,6 +124,7 @@ public class MultiDisplayResolveInfo extends DisplayResolveInfo {
}
@Override
+ @NonNull
public ComponentName getResolvedComponentName() {
if (hasSelected()) {
return mTargetInfos.get(mSelected).getResolvedComponentName();
diff --git a/java/src/com/android/intentresolver/icons/CachingTargetDataLoader.kt b/java/src/com/android/intentresolver/icons/CachingTargetDataLoader.kt
new file mode 100644
index 00000000..b3054231
--- /dev/null
+++ b/java/src/com/android/intentresolver/icons/CachingTargetDataLoader.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2024 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.intentresolver.icons
+
+import android.content.ComponentName
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
+import androidx.collection.LruCache
+import com.android.intentresolver.chooser.DisplayResolveInfo
+import com.android.intentresolver.chooser.SelectableTargetInfo
+import java.util.function.Consumer
+import javax.annotation.concurrent.GuardedBy
+import javax.inject.Qualifier
+
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.BINARY) annotation class Caching
+
+private typealias IconCache = LruCache<ComponentName, Drawable>
+
+class CachingTargetDataLoader(
+ private val targetDataLoader: TargetDataLoader,
+ private val cacheSize: Int = 100,
+) : TargetDataLoader() {
+ @GuardedBy("self") private val perProfileIconCache = HashMap<UserHandle, IconCache>()
+
+ override fun getOrLoadAppTargetIcon(
+ info: DisplayResolveInfo,
+ userHandle: UserHandle,
+ callback: Consumer<Drawable>
+ ): Drawable? {
+ val cacheKey = info.toCacheKey()
+ return getCachedAppIcon(cacheKey, userHandle)
+ ?: targetDataLoader.getOrLoadAppTargetIcon(info, userHandle) { drawable ->
+ getProfileIconCache(userHandle).put(cacheKey, drawable)
+ callback.accept(drawable)
+ }
+ }
+
+ override fun loadDirectShareIcon(
+ info: SelectableTargetInfo,
+ userHandle: UserHandle,
+ callback: Consumer<Drawable>
+ ) = targetDataLoader.loadDirectShareIcon(info, userHandle, callback)
+
+ override fun loadLabel(info: DisplayResolveInfo, callback: Consumer<LabelInfo>) =
+ targetDataLoader.loadLabel(info, callback)
+
+ override fun getOrLoadLabel(info: DisplayResolveInfo) = targetDataLoader.getOrLoadLabel(info)
+
+ private fun getCachedAppIcon(component: ComponentName, userHandle: UserHandle): Drawable? =
+ getProfileIconCache(userHandle)[component]
+
+ private fun getProfileIconCache(userHandle: UserHandle): IconCache =
+ synchronized(perProfileIconCache) {
+ perProfileIconCache.getOrPut(userHandle) { IconCache(cacheSize) }
+ }
+
+ private fun DisplayResolveInfo.toCacheKey() =
+ ComponentName(
+ resolveInfo.activityInfo.packageName,
+ resolveInfo.activityInfo.name,
+ )
+}
diff --git a/java/src/com/android/intentresolver/icons/DefaultTargetDataLoader.kt b/java/src/com/android/intentresolver/icons/DefaultTargetDataLoader.kt
index 054fbe71..1a724d73 100644
--- a/java/src/com/android/intentresolver/icons/DefaultTargetDataLoader.kt
+++ b/java/src/com/android/intentresolver/icons/DefaultTargetDataLoader.kt
@@ -62,11 +62,11 @@ class DefaultTargetDataLoader(
)
}
- override fun loadAppTargetIcon(
+ override fun getOrLoadAppTargetIcon(
info: DisplayResolveInfo,
userHandle: UserHandle,
callback: Consumer<Drawable>,
- ) {
+ ): Drawable? {
val taskId = nextTaskId.getAndIncrement()
LoadIconTask(context, info, userHandle, presentationFactory) { result ->
removeTask(taskId)
@@ -74,6 +74,7 @@ class DefaultTargetDataLoader(
}
.also { addTask(taskId, it) }
.executeOnExecutor(executor)
+ return null
}
override fun loadDirectShareIcon(
diff --git a/java/src/com/android/intentresolver/icons/LabelInfo.kt b/java/src/com/android/intentresolver/icons/LabelInfo.kt
index a9c4cd77..4b60d607 100644
--- a/java/src/com/android/intentresolver/icons/LabelInfo.kt
+++ b/java/src/com/android/intentresolver/icons/LabelInfo.kt
@@ -16,4 +16,4 @@
package com.android.intentresolver.icons
-class LabelInfo(val label: CharSequence?, val subLabel: CharSequence?)
+data class LabelInfo(val label: CharSequence?, val subLabel: CharSequence?)
diff --git a/java/src/com/android/intentresolver/icons/TargetDataLoader.kt b/java/src/com/android/intentresolver/icons/TargetDataLoader.kt
index 07c62177..7789df44 100644
--- a/java/src/com/android/intentresolver/icons/TargetDataLoader.kt
+++ b/java/src/com/android/intentresolver/icons/TargetDataLoader.kt
@@ -25,11 +25,11 @@ import java.util.function.Consumer
/** A target data loader contract. Added to support testing. */
abstract class TargetDataLoader {
/** Load an app target icon */
- abstract fun loadAppTargetIcon(
+ abstract fun getOrLoadAppTargetIcon(
info: DisplayResolveInfo,
userHandle: UserHandle,
callback: Consumer<Drawable>,
- )
+ ): Drawable?
/** Load a shortcut icon */
abstract fun loadDirectShareIcon(
diff --git a/java/src/com/android/intentresolver/icons/TargetDataLoaderModule.kt b/java/src/com/android/intentresolver/icons/TargetDataLoaderModule.kt
index 32c040b8..9c0acb11 100644
--- a/java/src/com/android/intentresolver/icons/TargetDataLoaderModule.kt
+++ b/java/src/com/android/intentresolver/icons/TargetDataLoaderModule.kt
@@ -35,4 +35,10 @@ object TargetDataLoaderModule {
@ActivityContext context: Context,
@ActivityOwned lifecycle: Lifecycle,
): TargetDataLoader = DefaultTargetDataLoader(context, lifecycle, isAudioCaptureDevice = false)
+
+ @Provides
+ @ActivityScoped
+ @Caching
+ fun cachingTargetDataLoader(targetDataLoader: TargetDataLoader): TargetDataLoader =
+ CachingTargetDataLoader(targetDataLoader)
}