From b0802dc1fceb3da127a4e5c1193fc2a83d3db4a3 Mon Sep 17 00:00:00 2001
From: arangelov
Date: Fri, 18 Oct 2019 18:03:44 +0100
Subject: [PATCH] Separate adapter logic from ChooserActivity and
ResolverActivity
Test: atest com.android.internal.app.ChooserActivityTest
Test: atest com.android.internal.app.ResolverActivityTest
Bug: 142537267
Bug: 142538125
Change-Id: Ia4a61e3dc7cbac5c31e0d5b126e033a043c81d90
---
.../android/internal/app/ChooserActivity.java | 1008 ++-------------
.../android/internal/app/ChooserFlags.java | 38 +
.../internal/app/ChooserListAdapter.java | 539 ++++++++
.../internal/app/ResolverActivity.java | 1148 ++---------------
.../internal/app/ResolverListAdapter.java | 862 +++++++++++++
.../internal/app/ResolverListController.java | 3 +-
.../internal/app/SimpleIconFactory.java | 2 +-
.../app/chooser/ChooserTargetInfo.java | 53 +
.../app/chooser/DisplayResolveInfo.java | 182 +++
.../app/chooser/NotSelectableTargetInfo.java | 88 ++
.../app/chooser/SelectableTargetInfo.java | 316 +++++
.../internal/app/chooser/TargetInfo.java | 128 ++
.../internal/app/ChooserActivityTest.java | 25 +-
.../internal/app/ChooserWrapperActivity.java | 12 +-
.../internal/app/ResolverActivityTest.java | 12 +-
.../internal/app/ResolverWrapperActivity.java | 43 +-
.../internal/app/ResolverWrapperAdapter.java | 71 +
.../systemui/usb/UsbResolverActivity.java | 1 +
18 files changed, 2542 insertions(+), 1989 deletions(-)
create mode 100644 core/java/com/android/internal/app/ChooserFlags.java
create mode 100644 core/java/com/android/internal/app/ChooserListAdapter.java
create mode 100644 core/java/com/android/internal/app/ResolverListAdapter.java
create mode 100644 core/java/com/android/internal/app/chooser/ChooserTargetInfo.java
create mode 100644 core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
create mode 100644 core/java/com/android/internal/app/chooser/NotSelectableTargetInfo.java
create mode 100644 core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
create mode 100644 core/java/com/android/internal/app/chooser/TargetInfo.java
create mode 100644 core/tests/coretests/src/com/android/internal/app/ResolverWrapperAdapter.java
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 3b82f186d80e9..b1752a47ea939 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -45,8 +45,6 @@ import android.content.IntentSender.SendIntentException;
import android.content.ServiceConnection;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
-import android.content.pm.LabeledIntent;
-import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
@@ -61,9 +59,7 @@ import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.drawable.AnimatedVectorDrawable;
-import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
import android.metrics.LogMaker;
import android.net.Uri;
import android.os.AsyncTask;
@@ -85,7 +81,6 @@ import android.service.chooser.ChooserTarget;
import android.service.chooser.ChooserTargetService;
import android.service.chooser.IChooserTargetResult;
import android.service.chooser.IChooserTargetService;
-import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.HashedStringCache;
@@ -110,6 +105,14 @@ import android.widget.Toast;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.ResolverListAdapter.ActivityInfoPresentationGetter;
+import com.android.internal.app.ResolverListAdapter.ViewHolder;
+import com.android.internal.app.chooser.ChooserTargetInfo;
+import com.android.internal.app.chooser.DisplayResolveInfo;
+import com.android.internal.app.chooser.NotSelectableTargetInfo;
+import com.android.internal.app.chooser.SelectableTargetInfo;
+import com.android.internal.app.chooser.SelectableTargetInfo.SelectableTargetInfoCommunicator;
+import com.android.internal.app.chooser.TargetInfo;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.content.PackageMonitor;
import com.android.internal.logging.MetricsLogger;
@@ -124,7 +127,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.text.Collator;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
@@ -138,7 +140,9 @@ import java.util.Set;
* for example, those generated by @see android.content.Intent#createChooser(Intent, CharSequence).
*
*/
-public class ChooserActivity extends ResolverActivity {
+public class ChooserActivity extends ResolverActivity implements
+ ChooserListAdapter.ChooserListCommunicator,
+ SelectableTargetInfoCommunicator {
private static final String TAG = "ChooserActivity";
@@ -154,12 +158,6 @@ public class ChooserActivity extends ResolverActivity {
private static final boolean DEBUG = false;
- /**
- * If {@link #USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS} and this is set to true,
- * {@link AppPredictionManager} will be queried for direct share targets.
- */
- // TODO(b/123089490): Replace with system flag
- private static final boolean USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS = true;
private static final boolean USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES = true;
// TODO(b/123088566) Share these in a better way.
private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share";
@@ -167,24 +165,21 @@ public class ChooserActivity extends ResolverActivity {
private static final int APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
public static final String APP_PREDICTION_INTENT_FILTER_KEY = "intent_filter";
+ @VisibleForTesting
+ public static final int LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS = 250;
+
private boolean mIsAppPredictorComponentAvailable;
private AppPredictor mAppPredictor;
private AppPredictor.Callback mAppPredictorCallback;
private Map mDirectShareAppTargetCache;
- /**
- * If set to true, use ShortcutManager to retrieve the matching direct share targets, instead of
- * binding to every ChooserTargetService implementation.
- */
- // TODO(b/121287573): Replace with a system flag (setprop?)
- private static final boolean USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS = true;
- private static final boolean USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS = true;
-
public static final int TARGET_TYPE_DEFAULT = 0;
public static final int TARGET_TYPE_CHOOSER_TARGET = 1;
public static final int TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER = 2;
public static final int TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE = 3;
+ private static final boolean USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS = true;
+
@IntDef(flag = false, prefix = { "TARGET_TYPE_" }, value = {
TARGET_TYPE_DEFAULT,
TARGET_TYPE_CHOOSER_TARGET,
@@ -233,10 +228,6 @@ public class ChooserActivity extends ResolverActivity {
private int mCurrAvailableWidth = 0;
- /** {@link ChooserActivity#getBaseScore} */
- public static final float CALLER_TARGET_SCORE_BOOST = 900.f;
- /** {@link ChooserActivity#getBaseScore} */
- public static final float SHORTCUT_TARGET_SCORE_BOOST = 90.f;
private static final String TARGET_DETAILS_FRAGMENT_TAG = "targetDetailsFragment";
// TODO: Update to handle landscape instead of using static value
private static final int MAX_RANKED_TARGETS = 4;
@@ -246,14 +237,9 @@ public class ChooserActivity extends ResolverActivity {
private static final int MAX_LOG_RANK_POSITION = 12;
- @VisibleForTesting
- public static final int LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS = 250;
-
private static final int MAX_EXTRA_INITIAL_INTENTS = 2;
private static final int MAX_EXTRA_CHOOSER_TARGETS = 2;
- private boolean mListViewDataChanged = false;
-
@Retention(SOURCE)
@IntDef({CONTENT_PREVIEW_FILE, CONTENT_PREVIEW_IMAGE, CONTENT_PREVIEW_TEXT})
private @interface ContentPreviewType {
@@ -266,9 +252,6 @@ public class ChooserActivity extends ResolverActivity {
private static final int CONTENT_PREVIEW_TEXT = 3;
protected MetricsLogger mMetricsLogger;
- // Sorted list of DisplayResolveInfos for the alphabetical app section.
- private List mSortedList = new ArrayList<>();
-
private ContentPreviewCoordinator mPreviewCoord;
private class ContentPreviewCoordinator {
@@ -645,8 +628,7 @@ public class ChooserActivity extends ResolverActivity {
if (isFinishing() || isDestroyed()) {
return;
}
- // May be null if there are no apps to perform share/open action.
- if (mChooserListAdapter == null) {
+ if (mChooserListAdapter.getCount() == 0) {
return;
}
if (resultList.isEmpty()) {
@@ -775,7 +757,7 @@ public class ChooserActivity extends ResolverActivity {
@Override
public void onSomePackagesChanged() {
mAdapter.handlePackagesChanged();
- bindProfileView();
+ updateProfileViewButton();
}
};
}
@@ -1191,7 +1173,7 @@ public class ChooserActivity extends ResolverActivity {
}
}
- @Override
+ @Override // ResolverListCommunicator
public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) {
Intent result = defIntent;
if (mReplacementExtras != null) {
@@ -1231,9 +1213,8 @@ public class ChooserActivity extends ResolverActivity {
}
@Override
- public void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter) {
+ public void onPrepareAdapterView(AbsListView adapterView, ResolverListAdapter adapter) {
final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null;
- mChooserListAdapter = (ChooserListAdapter) adapter;
if (mCallerChooserTargets != null && mCallerChooserTargets.length > 0) {
mChooserListAdapter.addServiceResults(null, Lists.newArrayList(mCallerChooserTargets),
TARGET_TYPE_DEFAULT);
@@ -1244,12 +1225,18 @@ public class ChooserActivity extends ResolverActivity {
}
}
+ @Override
+ protected boolean rebuildList() {
+ mChooserListAdapter = (ChooserListAdapter) mAdapter;
+ return rebuildListInternal();
+ }
+
@Override
public int getLayoutResource() {
return R.layout.chooser_grid;
}
- @Override
+ @Override // ResolverListCommunicator
public boolean shouldGetActivityMetadata() {
return true;
}
@@ -1328,7 +1315,7 @@ public class ChooserActivity extends ResolverActivity {
final long selectionCost = System.currentTimeMillis() - mChooserShownTime;
super.startSelected(which, always, filtered);
- if (mChooserListAdapter != null) {
+ if (mChooserListAdapter.getCount() > 0) {
// Log the index of which type of target the user picked.
// Lower values mean the ranking was better.
int cat = 0;
@@ -1342,7 +1329,7 @@ public class ChooserActivity extends ResolverActivity {
// Log the package name + target name to answer the question if most users
// share to mostly the same person or to a bunch of different people.
ChooserTarget target =
- mChooserListAdapter.mServiceTargets.get(value).getChooserTarget();
+ mChooserListAdapter.getChooserTargetForValue(value);
directTargetHashed = HashedStringCache.getInstance().hashString(
this,
TAG,
@@ -1428,7 +1415,7 @@ public class ChooserActivity extends ResolverActivity {
continue;
}
final ActivityInfo ai = dri.getResolveInfo().activityInfo;
- if (USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS
+ if (ChooserFlags.USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS
&& sm.hasShareTargets(ai.packageName)) {
// Share targets will be queried from ShortcutManager
continue;
@@ -1817,8 +1804,8 @@ public class ChooserActivity extends ResolverActivity {
*/
@Nullable
private AppPredictor getAppPredictorForDirectShareIfEnabled() {
- return USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS && !ActivityManager.isLowRamDeviceStatic()
- ? getAppPredictor() : null;
+ return ChooserFlags.USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS
+ && !ActivityManager.isLowRamDeviceStatic() ? getAppPredictor() : null;
}
/**
@@ -1900,24 +1887,18 @@ public class ChooserActivity extends ResolverActivity {
}
}
- private void updateAlphabeticalList() {
- mSortedList.clear();
- mSortedList.addAll(getDisplayList());
- Collections.sort(mSortedList, new AzInfoComparator(ChooserActivity.this));
- }
-
/**
* Sort intents alphabetically based on display label.
*/
- class AzInfoComparator implements Comparator {
+ static class AzInfoComparator implements Comparator {
Collator mCollator;
AzInfoComparator(Context context) {
mCollator = Collator.getInstance(context.getResources().getConfiguration().locale);
}
@Override
- public int compare(ResolverActivity.DisplayResolveInfo lhsp,
- ResolverActivity.DisplayResolveInfo rhsp) {
+ public int compare(
+ DisplayResolveInfo lhsp, DisplayResolveInfo rhsp) {
return mCollator.compare(lhsp.getDisplayLabel(), rhsp.getDisplayLabel());
}
}
@@ -1955,12 +1936,12 @@ public class ChooserActivity extends ResolverActivity {
}
@Override
- public ResolveListAdapter createAdapter(Context context, List payloadIntents,
- Intent[] initialIntents, List rList, int launchedFromUid,
- boolean filterLastUsed) {
- final ChooserListAdapter adapter = new ChooserListAdapter(context, payloadIntents,
- initialIntents, rList, launchedFromUid, filterLastUsed, createListController());
- return adapter;
+ public ResolverListAdapter createAdapter(Context context, List payloadIntents,
+ Intent[] initialIntents, List rList,
+ boolean filterLastUsed, boolean useLayoutForBrowsables) {
+ return new ChooserListAdapter(context, payloadIntents,
+ initialIntents, rList, filterLastUsed, createListController(),
+ useLayoutForBrowsables, this, this);
}
@VisibleForTesting
@@ -1999,347 +1980,21 @@ public class ChooserActivity extends ResolverActivity {
return null;
}
- interface ChooserTargetInfo extends TargetInfo {
- float getModifiedScore();
-
- ChooserTarget getChooserTarget();
-
- /**
- * Do not label as 'equals', since this doesn't quite work
- * as intended with java 8.
- */
- default boolean isSimilar(ChooserTargetInfo other) {
- if (other == null) return false;
-
- ChooserTarget ct1 = getChooserTarget();
- ChooserTarget ct2 = other.getChooserTarget();
-
- // If either is null, there is not enough info to make an informed decision
- // about equality, so just exit
- if (ct1 == null || ct2 == null) return false;
-
- if (ct1.getComponentName().equals(ct2.getComponentName())
- && TextUtils.equals(getDisplayLabel(), other.getDisplayLabel())
- && TextUtils.equals(getExtendedInfo(), other.getExtendedInfo())) {
- return true;
- }
-
- return false;
- }
- }
-
- /**
- * Distinguish between targets that selectable by the user, vs those that are
- * placeholders for the system while information is loading in an async manner.
- */
- abstract class NotSelectableTargetInfo implements ChooserTargetInfo {
-
- public Intent getResolvedIntent() {
- return null;
- }
-
- public ComponentName getResolvedComponentName() {
- return null;
- }
-
- public boolean start(Activity activity, Bundle options) {
- return false;
- }
-
- public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
- return false;
- }
-
- public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
- return false;
- }
-
- public ResolveInfo getResolveInfo() {
- return null;
- }
-
- public CharSequence getDisplayLabel() {
- return null;
- }
-
- public CharSequence getExtendedInfo() {
- return null;
- }
-
- public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
- return null;
- }
-
- public List getAllSourceIntents() {
- return null;
- }
-
- public float getModifiedScore() {
- return -0.1f;
- }
-
- public ChooserTarget getChooserTarget() {
- return null;
- }
-
- public boolean isSuspended() {
- return false;
- }
- }
-
- final class PlaceHolderTargetInfo extends NotSelectableTargetInfo {
- public Drawable getDisplayIcon() {
+ static final class PlaceHolderTargetInfo extends NotSelectableTargetInfo {
+ public Drawable getDisplayIcon(Context context) {
AnimatedVectorDrawable avd = (AnimatedVectorDrawable)
- getDrawable(R.drawable.chooser_direct_share_icon_placeholder);
+ context.getDrawable(R.drawable.chooser_direct_share_icon_placeholder);
avd.start(); // Start animation after generation
return avd;
}
}
-
- final class EmptyTargetInfo extends NotSelectableTargetInfo {
- public Drawable getDisplayIcon() {
+ static final class EmptyTargetInfo extends NotSelectableTargetInfo {
+ public Drawable getDisplayIcon(Context context) {
return null;
}
}
- final class SelectableTargetInfo implements ChooserTargetInfo {
- private final DisplayResolveInfo mSourceInfo;
- private final ResolveInfo mBackupResolveInfo;
- private final ChooserTarget mChooserTarget;
- private final String mDisplayLabel;
- private Drawable mBadgeIcon = null;
- private CharSequence mBadgeContentDescription;
- private Drawable mDisplayIcon;
- private final Intent mFillInIntent;
- private final int mFillInFlags;
- private final float mModifiedScore;
- private boolean mIsSuspended = false;
-
- SelectableTargetInfo(DisplayResolveInfo sourceInfo, ChooserTarget chooserTarget,
- float modifiedScore) {
- mSourceInfo = sourceInfo;
- mChooserTarget = chooserTarget;
- mModifiedScore = modifiedScore;
- if (sourceInfo != null) {
- final ResolveInfo ri = sourceInfo.getResolveInfo();
- if (ri != null) {
- final ActivityInfo ai = ri.activityInfo;
- if (ai != null && ai.applicationInfo != null) {
- final PackageManager pm = getPackageManager();
- mBadgeIcon = pm.getApplicationIcon(ai.applicationInfo);
- mBadgeContentDescription = pm.getApplicationLabel(ai.applicationInfo);
- mIsSuspended =
- (ai.applicationInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0;
- }
- }
- }
- // TODO(b/121287224): do this in the background thread, and only for selected targets
- mDisplayIcon = getChooserTargetIconDrawable(chooserTarget);
-
- if (sourceInfo != null) {
- mBackupResolveInfo = null;
- } else {
- mBackupResolveInfo = getPackageManager().resolveActivity(getResolvedIntent(), 0);
- }
-
- mFillInIntent = null;
- mFillInFlags = 0;
-
- mDisplayLabel = sanitizeDisplayLabel(chooserTarget.getTitle());
- }
-
- private SelectableTargetInfo(SelectableTargetInfo other, Intent fillInIntent, int flags) {
- mSourceInfo = other.mSourceInfo;
- mBackupResolveInfo = other.mBackupResolveInfo;
- mChooserTarget = other.mChooserTarget;
- mBadgeIcon = other.mBadgeIcon;
- mBadgeContentDescription = other.mBadgeContentDescription;
- mDisplayIcon = other.mDisplayIcon;
- mFillInIntent = fillInIntent;
- mFillInFlags = flags;
- mModifiedScore = other.mModifiedScore;
-
- mDisplayLabel = sanitizeDisplayLabel(mChooserTarget.getTitle());
- }
-
- private String sanitizeDisplayLabel(CharSequence label) {
- SpannableStringBuilder sb = new SpannableStringBuilder(label);
- sb.clearSpans();
- return sb.toString();
- }
-
- public boolean isSuspended() {
- return mIsSuspended;
- }
-
- /**
- * Since ShortcutInfos are returned by ShortcutManager, we can cache the shortcuts and skip
- * the call to LauncherApps#getShortcuts(ShortcutQuery).
- */
- // TODO(121287224): Refactor code to apply the suggestion above
- private Drawable getChooserTargetIconDrawable(ChooserTarget target) {
- Drawable directShareIcon = null;
-
- // First get the target drawable and associated activity info
- final Icon icon = target.getIcon();
- if (icon != null) {
- directShareIcon = icon.loadDrawable(ChooserActivity.this);
- } else if (USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS) {
- Bundle extras = target.getIntentExtras();
- if (extras != null && extras.containsKey(Intent.EXTRA_SHORTCUT_ID)) {
- CharSequence shortcutId = extras.getCharSequence(Intent.EXTRA_SHORTCUT_ID);
- LauncherApps launcherApps = (LauncherApps) getSystemService(
- Context.LAUNCHER_APPS_SERVICE);
- final LauncherApps.ShortcutQuery q = new LauncherApps.ShortcutQuery();
- q.setPackage(target.getComponentName().getPackageName());
- q.setShortcutIds(Arrays.asList(shortcutId.toString()));
- q.setQueryFlags(LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC);
- final List shortcuts = launcherApps.getShortcuts(q, getUser());
- if (shortcuts != null && shortcuts.size() > 0) {
- directShareIcon = launcherApps.getShortcutIconDrawable(shortcuts.get(0), 0);
- }
- }
- }
-
- if (directShareIcon == null) return null;
-
- ActivityInfo info = null;
- try {
- info = mPm.getActivityInfo(target.getComponentName(), 0);
- } catch (NameNotFoundException error) {
- Log.e(TAG, "Could not find activity associated with ChooserTarget");
- }
-
- if (info == null) return null;
-
- // Now fetch app icon and raster with no badging even in work profile
- Bitmap appIcon = makePresentationGetter(info).getIconBitmap(
- UserHandle.getUserHandleForUid(UserHandle.myUserId()));
-
- // Raster target drawable with appIcon as a badge
- SimpleIconFactory sif = SimpleIconFactory.obtain(ChooserActivity.this);
- Bitmap directShareBadgedIcon = sif.createAppBadgedIconBitmap(directShareIcon, appIcon);
- sif.recycle();
-
- return new BitmapDrawable(getResources(), directShareBadgedIcon);
- }
-
- public float getModifiedScore() {
- return mModifiedScore;
- }
-
- @Override
- public Intent getResolvedIntent() {
- if (mSourceInfo != null) {
- return mSourceInfo.getResolvedIntent();
- }
-
- final Intent targetIntent = new Intent(getTargetIntent());
- targetIntent.setComponent(mChooserTarget.getComponentName());
- targetIntent.putExtras(mChooserTarget.getIntentExtras());
- return targetIntent;
- }
-
- @Override
- public ComponentName getResolvedComponentName() {
- if (mSourceInfo != null) {
- return mSourceInfo.getResolvedComponentName();
- } else if (mBackupResolveInfo != null) {
- return new ComponentName(mBackupResolveInfo.activityInfo.packageName,
- mBackupResolveInfo.activityInfo.name);
- }
- return null;
- }
-
- private Intent getBaseIntentToSend() {
- Intent result = getResolvedIntent();
- if (result == null) {
- Log.e(TAG, "ChooserTargetInfo: no base intent available to send");
- } else {
- result = new Intent(result);
- if (mFillInIntent != null) {
- result.fillIn(mFillInIntent, mFillInFlags);
- }
- result.fillIn(mReferrerFillInIntent, 0);
- }
- return result;
- }
-
- @Override
- public boolean start(Activity activity, Bundle options) {
- throw new RuntimeException("ChooserTargets should be started as caller.");
- }
-
- @Override
- public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
- final Intent intent = getBaseIntentToSend();
- if (intent == null) {
- return false;
- }
- intent.setComponent(mChooserTarget.getComponentName());
- intent.putExtras(mChooserTarget.getIntentExtras());
-
- // Important: we will ignore the target security checks in ActivityManager
- // if and only if the ChooserTarget's target package is the same package
- // where we got the ChooserTargetService that provided it. This lets a
- // ChooserTargetService provide a non-exported or permission-guarded target
- // to the chooser for the user to pick.
- //
- // If mSourceInfo is null, we got this ChooserTarget from the caller or elsewhere
- // so we'll obey the caller's normal security checks.
- final boolean ignoreTargetSecurity = mSourceInfo != null
- && mSourceInfo.getResolvedComponentName().getPackageName()
- .equals(mChooserTarget.getComponentName().getPackageName());
- return activity.startAsCallerImpl(intent, options, ignoreTargetSecurity, userId);
- }
-
- @Override
- public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
- throw new RuntimeException("ChooserTargets should be started as caller.");
- }
-
- @Override
- public ResolveInfo getResolveInfo() {
- return mSourceInfo != null ? mSourceInfo.getResolveInfo() : mBackupResolveInfo;
- }
-
- @Override
- public CharSequence getDisplayLabel() {
- return mDisplayLabel;
- }
-
- @Override
- public CharSequence getExtendedInfo() {
- // ChooserTargets have badge icons, so we won't show the extended info to disambiguate.
- return null;
- }
-
- @Override
- public Drawable getDisplayIcon() {
- return mDisplayIcon;
- }
-
- public ChooserTarget getChooserTarget() {
- return mChooserTarget;
- }
-
- @Override
- public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
- return new SelectableTargetInfo(this, fillInIntent, flags);
- }
-
- @Override
- public List getAllSourceIntents() {
- final List results = new ArrayList<>();
- if (mSourceInfo != null) {
- // We only queried the service for the first one in our sourceinfo.
- results.add(mSourceInfo.getAllSourceIntents().get(0));
- }
- return results;
- }
- }
-
private void handleScroll(View view, int x, int y, int oldx, int oldy) {
if (mChooserRowAdapter != null) {
mChooserRowAdapter.handleScroll(view, y, oldy);
@@ -2408,7 +2063,8 @@ public class ChooserActivity extends ResolverActivity {
boolean isExpandable = getResources().getConfiguration().orientation
== Configuration.ORIENTATION_PORTRAIT && !isInMultiWindowMode();
- if (directShareHeight != 0 && isSendAction(getTargetIntent()) && isExpandable) {
+ if (directShareHeight != 0 && isSendAction(getTargetIntent())
+ && isExpandable) {
// make sure to leave room for direct share 4->8 expansion
int requiredExpansionHeight =
(int) (directShareHeight / DIRECT_SHARE_EXPANSION_RATE);
@@ -2424,498 +2080,6 @@ public class ChooserActivity extends ResolverActivity {
}
}
- public class ChooserListAdapter extends ResolveListAdapter {
- public static final int TARGET_BAD = -1;
- public static final int TARGET_CALLER = 0;
- public static final int TARGET_SERVICE = 1;
- public static final int TARGET_STANDARD = 2;
- public static final int TARGET_STANDARD_AZ = 3;
-
- private static final int MAX_SUGGESTED_APP_TARGETS = 4;
- private static final int MAX_CHOOSER_TARGETS_PER_APP = 2;
-
- private static final int MAX_SERVICE_TARGETS = 8;
-
- private final int mMaxShortcutTargetsPerApp =
- getResources().getInteger(R.integer.config_maxShortcutTargetsPerApp);
-
- private int mNumShortcutResults = 0;
-
- // Reserve spots for incoming direct share targets by adding placeholders
- private ChooserTargetInfo mPlaceHolderTargetInfo = new PlaceHolderTargetInfo();
- private final List mServiceTargets = new ArrayList<>();
- private final List mCallerTargets = new ArrayList<>();
-
- private final BaseChooserTargetComparator mBaseTargetComparator
- = new BaseChooserTargetComparator();
-
- public ChooserListAdapter(Context context, List payloadIntents,
- Intent[] initialIntents, List rList, int launchedFromUid,
- boolean filterLastUsed, ResolverListController resolverListController) {
- // Don't send the initial intents through the shared ResolverActivity path,
- // we want to separate them into a different section.
- super(context, payloadIntents, null, rList, launchedFromUid, filterLastUsed,
- resolverListController);
-
- createPlaceHolders();
-
- if (initialIntents != null) {
- final PackageManager pm = getPackageManager();
- for (int i = 0; i < initialIntents.length; i++) {
- final Intent ii = initialIntents[i];
- if (ii == null) {
- continue;
- }
-
- // We reimplement Intent#resolveActivityInfo here because if we have an
- // implicit intent, we want the ResolveInfo returned by PackageManager
- // instead of one we reconstruct ourselves. The ResolveInfo returned might
- // have extra metadata and resolvePackageName set and we want to respect that.
- ResolveInfo ri = null;
- ActivityInfo ai = null;
- final ComponentName cn = ii.getComponent();
- if (cn != null) {
- try {
- ai = pm.getActivityInfo(ii.getComponent(), 0);
- ri = new ResolveInfo();
- ri.activityInfo = ai;
- } catch (PackageManager.NameNotFoundException ignored) {
- // ai will == null below
- }
- }
- if (ai == null) {
- ri = pm.resolveActivity(ii, PackageManager.MATCH_DEFAULT_ONLY);
- ai = ri != null ? ri.activityInfo : null;
- }
- if (ai == null) {
- Log.w(TAG, "No activity found for " + ii);
- continue;
- }
- UserManager userManager =
- (UserManager) getSystemService(Context.USER_SERVICE);
- if (ii instanceof LabeledIntent) {
- LabeledIntent li = (LabeledIntent) ii;
- ri.resolvePackageName = li.getSourcePackage();
- ri.labelRes = li.getLabelResource();
- ri.nonLocalizedLabel = li.getNonLocalizedLabel();
- ri.icon = li.getIconResource();
- ri.iconResourceId = ri.icon;
- }
- if (userManager.isManagedProfile()) {
- ri.noResourceId = true;
- ri.icon = 0;
- }
- mCallerTargets.add(new DisplayResolveInfo(ii, ri, ii));
- }
- }
- }
-
- @Override
- public void handlePackagesChanged() {
- if (DEBUG) {
- Log.d(TAG, "clearing queryTargets on package change");
- }
- createPlaceHolders();
- mServicesRequested.clear();
- notifyDataSetChanged();
-
- super.handlePackagesChanged();
- }
-
- @Override
- public void notifyDataSetChanged() {
- if (!mListViewDataChanged) {
- mChooserHandler.sendEmptyMessageDelayed(ChooserHandler.LIST_VIEW_UPDATE_MESSAGE,
- LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
- mListViewDataChanged = true;
- }
- }
-
- private void refreshListView() {
- if (mListViewDataChanged) {
- super.notifyDataSetChanged();
- }
- mListViewDataChanged = false;
- }
-
-
- private void createPlaceHolders() {
- mNumShortcutResults = 0;
- mServiceTargets.clear();
- for (int i = 0; i < MAX_SERVICE_TARGETS; i++) {
- mServiceTargets.add(mPlaceHolderTargetInfo);
- }
- }
-
- @Override
- public View onCreateView(ViewGroup parent) {
- return mInflater.inflate(
- com.android.internal.R.layout.resolve_grid_item, parent, false);
- }
-
- @Override
- protected void onBindView(View view, TargetInfo info) {
- super.onBindView(view, info);
-
- // If target is loading, show a special placeholder shape in the label, make unclickable
- final ViewHolder holder = (ViewHolder) view.getTag();
- if (info instanceof PlaceHolderTargetInfo) {
- final int maxWidth = getResources().getDimensionPixelSize(
- R.dimen.chooser_direct_share_label_placeholder_max_width);
- holder.text.setMaxWidth(maxWidth);
- holder.text.setBackground(getResources().getDrawable(
- R.drawable.chooser_direct_share_label_placeholder, getTheme()));
- // Prevent rippling by removing background containing ripple
- holder.itemView.setBackground(null);
- } else {
- holder.text.setMaxWidth(Integer.MAX_VALUE);
- holder.text.setBackground(null);
- holder.itemView.setBackground(holder.defaultItemViewBackground);
- }
- }
-
- /**
- * Rather than fully sorting the input list, this sorting task will put the top k elements
- * in the head of input list and fill the tail with other elements in undetermined order.
- */
- @Override
- AsyncTask,
- Void,
- List> createSortingTask() {
- return new AsyncTask,
- Void,
- List>() {
- @Override
- protected List doInBackground(
- List... params) {
- mResolverListController.topK(params[0],
- getMaxRankedTargets());
- return params[0];
- }
-
- @Override
- protected void onPostExecute(List sortedComponents) {
- processSortedList(sortedComponents);
- bindProfileView();
- notifyDataSetChanged();
- }
- };
- }
-
- @Override
- public void onListRebuilt() {
- if (getDisplayList() == null || getDisplayList().isEmpty()) {
- notifyDataSetChanged();
- } else {
- new AsyncTask() {
- @Override
- protected Void doInBackground(Void... voids) {
- updateAlphabeticalList();
- return null;
- }
-
- @Override
- protected void onPostExecute(Void aVoid) {
- notifyDataSetChanged();
- }
- }.execute();
- }
-
- // don't support direct share on low ram devices
- if (ActivityManager.isLowRamDeviceStatic()) {
- return;
- }
-
- if (USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS
- || USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
- if (DEBUG) {
- Log.d(TAG, "querying direct share targets from ShortcutManager");
- }
-
- queryDirectShareTargets(this, false);
- }
- if (USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS) {
- if (DEBUG) {
- Log.d(TAG, "List built querying services");
- }
-
- queryTargetServices(this);
- }
- }
-
- @Override
- public boolean shouldGetResolvedFilter() {
- return true;
- }
-
- @Override
- public int getCount() {
- return getRankedTargetCount() + getAlphaTargetCount()
- + getSelectableServiceTargetCount() + getCallerTargetCount();
- }
-
- @Override
- public int getUnfilteredCount() {
- int appTargets = super.getUnfilteredCount();
- if (appTargets > getMaxRankedTargets()) {
- appTargets = appTargets + getMaxRankedTargets();
- }
- return appTargets + getSelectableServiceTargetCount() + getCallerTargetCount();
- }
-
-
- public int getCallerTargetCount() {
- return Math.min(mCallerTargets.size(), MAX_SUGGESTED_APP_TARGETS);
- }
-
- /**
- * Filter out placeholders and non-selectable service targets
- */
- public int getSelectableServiceTargetCount() {
- int count = 0;
- for (ChooserTargetInfo info : mServiceTargets) {
- if (info instanceof SelectableTargetInfo) {
- count++;
- }
- }
- return count;
- }
-
- public int getServiceTargetCount() {
- if (isSendAction(getTargetIntent()) && !ActivityManager.isLowRamDeviceStatic()) {
- return Math.min(mServiceTargets.size(), MAX_SERVICE_TARGETS);
- }
-
- return 0;
- }
-
- int getAlphaTargetCount() {
- int standardCount = super.getCount();
- return standardCount > getMaxRankedTargets() ? standardCount : 0;
- }
-
- int getRankedTargetCount() {
- int spacesAvailable = getMaxRankedTargets() - getCallerTargetCount();
- return Math.min(spacesAvailable, super.getCount());
- }
-
- private int getMaxRankedTargets() {
- return mChooserRowAdapter == null ? 4 : mChooserRowAdapter.getMaxTargetsPerRow();
- }
-
- public int getPositionTargetType(int position) {
- int offset = 0;
-
- final int serviceTargetCount = getServiceTargetCount();
- if (position < serviceTargetCount) {
- return TARGET_SERVICE;
- }
- offset += serviceTargetCount;
-
- final int callerTargetCount = getCallerTargetCount();
- if (position - offset < callerTargetCount) {
- return TARGET_CALLER;
- }
- offset += callerTargetCount;
-
- final int rankedTargetCount = getRankedTargetCount();
- if (position - offset < rankedTargetCount) {
- return TARGET_STANDARD;
- }
- offset += rankedTargetCount;
-
- final int standardTargetCount = getAlphaTargetCount();
- if (position - offset < standardTargetCount) {
- return TARGET_STANDARD_AZ;
- }
-
- return TARGET_BAD;
- }
-
- @Override
- public TargetInfo getItem(int position) {
- return targetInfoForPosition(position, true);
- }
-
-
- /**
- * Find target info for a given position.
- * Since ChooserActivity displays several sections of content, determine which
- * section provides this item.
- */
- @Override
- public TargetInfo targetInfoForPosition(int position, boolean filtered) {
- int offset = 0;
-
- // Direct share targets
- final int serviceTargetCount = filtered ? getServiceTargetCount() :
- getSelectableServiceTargetCount();
- if (position < serviceTargetCount) {
- return mServiceTargets.get(position);
- }
- offset += serviceTargetCount;
-
- // Targets provided by calling app
- final int callerTargetCount = getCallerTargetCount();
- if (position - offset < callerTargetCount) {
- return mCallerTargets.get(position - offset);
- }
- offset += callerTargetCount;
-
- // Ranked standard app targets
- final int rankedTargetCount = getRankedTargetCount();
- if (position - offset < rankedTargetCount) {
- return filtered ? super.getItem(position - offset)
- : getDisplayResolveInfo(position - offset);
- }
- offset += rankedTargetCount;
-
- // Alphabetical complete app target list.
- if (position - offset < getAlphaTargetCount() && !mSortedList.isEmpty()) {
- return mSortedList.get(position - offset);
- }
-
- return null;
- }
-
-
- /**
- * Evaluate targets for inclusion in the direct share area. May not be included
- * if score is too low.
- */
- public void addServiceResults(DisplayResolveInfo origTarget, List targets,
- @ShareTargetType int targetType) {
- if (DEBUG) {
- Log.d(TAG, "addServiceResults " + origTarget + ", " + targets.size()
- + " targets");
- }
-
- if (targets.size() == 0) {
- return;
- }
-
- final float baseScore = getBaseScore(origTarget, targetType);
- Collections.sort(targets, mBaseTargetComparator);
-
- final boolean isShortcutResult =
- (targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER
- || targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE);
- final int maxTargets = isShortcutResult ? mMaxShortcutTargetsPerApp
- : MAX_CHOOSER_TARGETS_PER_APP;
- float lastScore = 0;
- boolean shouldNotify = false;
- for (int i = 0, count = Math.min(targets.size(), maxTargets); i < count; i++) {
- final ChooserTarget target = targets.get(i);
- float targetScore = target.getScore();
- targetScore *= baseScore;
- if (i > 0 && targetScore >= lastScore) {
- // Apply a decay so that the top app can't crowd out everything else.
- // This incents ChooserTargetServices to define what's truly better.
- targetScore = lastScore * 0.95f;
- }
- boolean isInserted = insertServiceTarget(
- new SelectableTargetInfo(origTarget, target, targetScore));
-
- if (isInserted && isShortcutResult) {
- mNumShortcutResults++;
- }
-
- shouldNotify |= isInserted;
-
- if (DEBUG) {
- Log.d(TAG, " => " + target.toString() + " score=" + targetScore
- + " base=" + target.getScore()
- + " lastScore=" + lastScore
- + " baseScore=" + baseScore);
- }
-
- lastScore = targetScore;
- }
-
- if (shouldNotify) {
- notifyDataSetChanged();
- }
- }
-
- private int getNumShortcutResults() {
- return mNumShortcutResults;
- }
-
- /**
- * Use the scoring system along with artificial boosts to create up to 4 distinct buckets:
- *
- * - App-supplied targets
- *
- Shortcuts ranked via App Prediction Manager
- *
- Shortcuts ranked via legacy heuristics
- *
- Legacy direct share targets
- *
- */
- public float getBaseScore(DisplayResolveInfo target, @ShareTargetType int targetType) {
- if (target == null) {
- return CALLER_TARGET_SCORE_BOOST;
- }
-
- if (targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE) {
- return SHORTCUT_TARGET_SCORE_BOOST;
- }
-
- float score = super.getScore(target);
- if (targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER) {
- return score * SHORTCUT_TARGET_SCORE_BOOST;
- }
-
- return score;
- }
-
- /**
- * Calling this marks service target loading complete, and will attempt to no longer
- * update the direct share area.
- */
- public void completeServiceTargetLoading() {
- mServiceTargets.removeIf(o -> o instanceof PlaceHolderTargetInfo);
-
- if (mServiceTargets.isEmpty()) {
- mServiceTargets.add(new EmptyTargetInfo());
- }
- notifyDataSetChanged();
- }
-
- private boolean insertServiceTarget(ChooserTargetInfo chooserTargetInfo) {
- // Avoid inserting any potentially late results
- if (mServiceTargets.size() == 1
- && mServiceTargets.get(0) instanceof EmptyTargetInfo) {
- return false;
- }
-
- // Check for duplicates and abort if found
- for (ChooserTargetInfo otherTargetInfo : mServiceTargets) {
- if (chooserTargetInfo.isSimilar(otherTargetInfo)) {
- return false;
- }
- }
-
- int currentSize = mServiceTargets.size();
- final float newScore = chooserTargetInfo.getModifiedScore();
- for (int i = 0; i < Math.min(currentSize, MAX_SERVICE_TARGETS); i++) {
- final ChooserTargetInfo serviceTarget = mServiceTargets.get(i);
- if (serviceTarget == null) {
- mServiceTargets.set(i, chooserTargetInfo);
- return true;
- } else if (newScore > serviceTarget.getModifiedScore()) {
- mServiceTargets.add(i, chooserTargetInfo);
- return true;
- }
- }
-
- if (currentSize < MAX_SERVICE_TARGETS) {
- mServiceTargets.add(chooserTargetInfo);
- return true;
- }
-
- return false;
- }
- }
-
static class BaseChooserTargetComparator implements Comparator {
@Override
public int compare(ChooserTarget lhs, ChooserTarget rhs) {
@@ -2924,8 +2088,77 @@ public class ChooserActivity extends ResolverActivity {
}
}
+ @Override // ResolverListCommunicator
+ public void onHandlePackagesChanged() {
+ mServicesRequested.clear();
+ mAdapter.notifyDataSetChanged();
+ super.onHandlePackagesChanged();
+ }
- private boolean isSendAction(Intent targetIntent) {
+ @Override // SelectableTargetInfoCommunicator
+ public ActivityInfoPresentationGetter makePresentationGetter(ActivityInfo info) {
+ return mChooserListAdapter.makePresentationGetter(info);
+ }
+
+ @Override // SelectableTargetInfoCommunicator
+ public Intent getReferrerFillInIntent() {
+ return mReferrerFillInIntent;
+ }
+
+ @Override // ChooserListCommunicator
+ public int getMaxRankedTargets() {
+ return mChooserRowAdapter == null ? 4 : mChooserRowAdapter.getMaxTargetsPerRow();
+ }
+
+ @Override // ChooserListCommunicator
+ public void sendListViewUpdateMessage() {
+ mChooserHandler.sendEmptyMessageDelayed(ChooserHandler.LIST_VIEW_UPDATE_MESSAGE,
+ LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+ }
+
+ @Override
+ public void onListRebuilt() {
+ if (mChooserListAdapter.mDisplayList == null
+ || mChooserListAdapter.mDisplayList.isEmpty()) {
+ mChooserListAdapter.notifyDataSetChanged();
+ } else {
+ new AsyncTask() {
+ @Override
+ protected Void doInBackground(Void... voids) {
+ mChooserListAdapter.updateAlphabeticalList();
+ return null;
+ }
+ @Override
+ protected void onPostExecute(Void aVoid) {
+ mChooserListAdapter.notifyDataSetChanged();
+ }
+ }.execute();
+ }
+
+ // don't support direct share on low ram devices
+ if (ActivityManager.isLowRamDeviceStatic()) {
+ return;
+ }
+
+ if (ChooserFlags.USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS
+ || ChooserFlags.USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
+ if (DEBUG) {
+ Log.d(TAG, "querying direct share targets from ShortcutManager");
+ }
+
+ queryDirectShareTargets(mChooserListAdapter, false);
+ }
+ if (USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS) {
+ if (DEBUG) {
+ Log.d(TAG, "List built querying services");
+ }
+
+ queryTargetServices(mChooserListAdapter);
+ }
+ }
+
+ @Override // ChooserListCommunicator
+ public boolean isSendAction(Intent targetIntent) {
if (targetIntent == null) {
return false;
}
@@ -3080,7 +2313,8 @@ public class ChooserActivity extends ResolverActivity {
// There can be at most one row in the listview, that is internally
// a ViewGroup with 2 rows
public int getServiceTargetRowCount() {
- if (isSendAction(getTargetIntent()) && !ActivityManager.isLowRamDeviceStatic()) {
+ if (isSendAction(getTargetIntent())
+ && !ActivityManager.isLowRamDeviceStatic()) {
return 1;
}
return 0;
@@ -3177,7 +2411,7 @@ public class ChooserActivity extends ResolverActivity {
getResources().getDrawable(R.drawable.chooser_row_layer_list, null));
mProfileView = profileRow.findViewById(R.id.profile_button);
mProfileView.setOnClickListener(ChooserActivity.this::onProfileClick);
- bindProfileView();
+ updateProfileViewButton();
return profileRow;
}
diff --git a/core/java/com/android/internal/app/ChooserFlags.java b/core/java/com/android/internal/app/ChooserFlags.java
new file mode 100644
index 0000000000000..f1f1dbf49b8b1
--- /dev/null
+++ b/core/java/com/android/internal/app/ChooserFlags.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2019 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.internal.app;
+
+import android.app.prediction.AppPredictionManager;
+
+/**
+ * Common flags for {@link ChooserListAdapter} and {@link ChooserActivity}.
+ */
+public class ChooserFlags {
+ /**
+ * If set to true, use ShortcutManager to retrieve the matching direct share targets, instead of
+ * binding to every ChooserTargetService implementation.
+ */
+ // TODO(b/121287573): Replace with a system flag (setprop?)
+ public static final boolean USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS = true;
+
+ /**
+ * If {@link ChooserFlags#USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS} and this is set to true,
+ * {@link AppPredictionManager} will be queried for direct share targets.
+ */
+ // TODO(b/123089490): Replace with system flag
+ static final boolean USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS = true;
+}
diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java
new file mode 100644
index 0000000000000..6eb470fef2bc8
--- /dev/null
+++ b/core/java/com/android/internal/app/ChooserListAdapter.java
@@ -0,0 +1,539 @@
+/*
+ * Copyright (C) 2019 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.internal.app;
+
+import static com.android.internal.app.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE;
+import static com.android.internal.app.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER;
+
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.LabeledIntent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.AsyncTask;
+import android.os.UserManager;
+import android.service.chooser.ChooserTarget;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.internal.R;
+import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
+import com.android.internal.app.chooser.ChooserTargetInfo;
+import com.android.internal.app.chooser.DisplayResolveInfo;
+import com.android.internal.app.chooser.SelectableTargetInfo;
+import com.android.internal.app.chooser.TargetInfo;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class ChooserListAdapter extends ResolverListAdapter {
+ private static final String TAG = "ChooserListAdapter";
+ private static final boolean DEBUG = false;
+
+ public static final int TARGET_BAD = -1;
+ public static final int TARGET_CALLER = 0;
+ public static final int TARGET_SERVICE = 1;
+ public static final int TARGET_STANDARD = 2;
+ public static final int TARGET_STANDARD_AZ = 3;
+
+ private static final int MAX_SUGGESTED_APP_TARGETS = 4;
+ private static final int MAX_CHOOSER_TARGETS_PER_APP = 2;
+
+ static final int MAX_SERVICE_TARGETS = 8;
+
+ /** {@link #getBaseScore} */
+ public static final float CALLER_TARGET_SCORE_BOOST = 900.f;
+ /** {@link #getBaseScore} */
+ public static final float SHORTCUT_TARGET_SCORE_BOOST = 90.f;
+
+ private final int mMaxShortcutTargetsPerApp;
+ private final ChooserListCommunicator mChooserListCommunicator;
+ private final SelectableTargetInfo.SelectableTargetInfoCommunicator
+ mSelectableTargetInfoComunicator;
+
+ private int mNumShortcutResults = 0;
+
+ // Reserve spots for incoming direct share targets by adding placeholders
+ private ChooserTargetInfo
+ mPlaceHolderTargetInfo = new ChooserActivity.PlaceHolderTargetInfo();
+ private final List mServiceTargets = new ArrayList<>();
+ private final List mCallerTargets = new ArrayList<>();
+
+ private final ChooserActivity.BaseChooserTargetComparator mBaseTargetComparator =
+ new ChooserActivity.BaseChooserTargetComparator();
+ private boolean mListViewDataChanged = false;
+
+ // Sorted list of DisplayResolveInfos for the alphabetical app section.
+ private List mSortedList = new ArrayList<>();
+
+ public ChooserListAdapter(Context context, List payloadIntents,
+ Intent[] initialIntents, List rList,
+ boolean filterLastUsed, ResolverListController resolverListController,
+ boolean useLayoutForBrowsables,
+ ChooserListCommunicator chooserListCommunicator,
+ SelectableTargetInfo.SelectableTargetInfoCommunicator selectableTargetInfoComunicator) {
+ // Don't send the initial intents through the shared ResolverActivity path,
+ // we want to separate them into a different section.
+ super(context, payloadIntents, null, rList, filterLastUsed,
+ resolverListController, useLayoutForBrowsables,
+ chooserListCommunicator);
+
+ createPlaceHolders();
+ mMaxShortcutTargetsPerApp =
+ context.getResources().getInteger(R.integer.config_maxShortcutTargetsPerApp);
+ mChooserListCommunicator = chooserListCommunicator;
+ mSelectableTargetInfoComunicator = selectableTargetInfoComunicator;
+
+ if (initialIntents != null) {
+ final PackageManager pm = context.getPackageManager();
+ for (int i = 0; i < initialIntents.length; i++) {
+ final Intent ii = initialIntents[i];
+ if (ii == null) {
+ continue;
+ }
+
+ // We reimplement Intent#resolveActivityInfo here because if we have an
+ // implicit intent, we want the ResolveInfo returned by PackageManager
+ // instead of one we reconstruct ourselves. The ResolveInfo returned might
+ // have extra metadata and resolvePackageName set and we want to respect that.
+ ResolveInfo ri = null;
+ ActivityInfo ai = null;
+ final ComponentName cn = ii.getComponent();
+ if (cn != null) {
+ try {
+ ai = pm.getActivityInfo(ii.getComponent(), 0);
+ ri = new ResolveInfo();
+ ri.activityInfo = ai;
+ } catch (PackageManager.NameNotFoundException ignored) {
+ // ai will == null below
+ }
+ }
+ if (ai == null) {
+ ri = pm.resolveActivity(ii, PackageManager.MATCH_DEFAULT_ONLY);
+ ai = ri != null ? ri.activityInfo : null;
+ }
+ if (ai == null) {
+ Log.w(TAG, "No activity found for " + ii);
+ continue;
+ }
+ UserManager userManager =
+ (UserManager) context.getSystemService(Context.USER_SERVICE);
+ if (ii instanceof LabeledIntent) {
+ LabeledIntent li = (LabeledIntent) ii;
+ ri.resolvePackageName = li.getSourcePackage();
+ ri.labelRes = li.getLabelResource();
+ ri.nonLocalizedLabel = li.getNonLocalizedLabel();
+ ri.icon = li.getIconResource();
+ ri.iconResourceId = ri.icon;
+ }
+ if (userManager.isManagedProfile()) {
+ ri.noResourceId = true;
+ ri.icon = 0;
+ }
+ mCallerTargets.add(new DisplayResolveInfo(ii, ri, ii, makePresentationGetter(ri)));
+ }
+ }
+ }
+
+ @Override
+ public void handlePackagesChanged() {
+ if (DEBUG) {
+ Log.d(TAG, "clearing queryTargets on package change");
+ }
+ createPlaceHolders();
+ mChooserListCommunicator.onHandlePackagesChanged();
+
+ }
+
+ @Override
+ public void notifyDataSetChanged() {
+ if (!mListViewDataChanged) {
+ mChooserListCommunicator.sendListViewUpdateMessage();
+ mListViewDataChanged = true;
+ }
+ }
+
+ void refreshListView() {
+ if (mListViewDataChanged) {
+ super.notifyDataSetChanged();
+ }
+ mListViewDataChanged = false;
+ }
+
+
+ private void createPlaceHolders() {
+ mNumShortcutResults = 0;
+ mServiceTargets.clear();
+ for (int i = 0; i < MAX_SERVICE_TARGETS; i++) {
+ mServiceTargets.add(mPlaceHolderTargetInfo);
+ }
+ }
+
+ @Override
+ public View onCreateView(ViewGroup parent) {
+ return mInflater.inflate(
+ com.android.internal.R.layout.resolve_grid_item, parent, false);
+ }
+
+ @Override
+ protected void onBindView(View view, TargetInfo info) {
+ super.onBindView(view, info);
+
+ // If target is loading, show a special placeholder shape in the label, make unclickable
+ final ViewHolder holder = (ViewHolder) view.getTag();
+ if (info instanceof ChooserActivity.PlaceHolderTargetInfo) {
+ final int maxWidth = mContext.getResources().getDimensionPixelSize(
+ R.dimen.chooser_direct_share_label_placeholder_max_width);
+ holder.text.setMaxWidth(maxWidth);
+ holder.text.setBackground(mContext.getResources().getDrawable(
+ R.drawable.chooser_direct_share_label_placeholder, mContext.getTheme()));
+ // Prevent rippling by removing background containing ripple
+ holder.itemView.setBackground(null);
+ } else {
+ holder.text.setMaxWidth(Integer.MAX_VALUE);
+ holder.text.setBackground(null);
+ holder.itemView.setBackground(holder.defaultItemViewBackground);
+ }
+ }
+
+ void updateAlphabeticalList() {
+ mSortedList.clear();
+ mSortedList.addAll(mDisplayList);
+ Collections.sort(mSortedList, new ChooserActivity.AzInfoComparator(mContext));
+ }
+
+ @Override
+ public boolean shouldGetResolvedFilter() {
+ return true;
+ }
+
+ @Override
+ public int getCount() {
+ return getRankedTargetCount() + getAlphaTargetCount()
+ + getSelectableServiceTargetCount() + getCallerTargetCount();
+ }
+
+ @Override
+ public int getUnfilteredCount() {
+ int appTargets = super.getUnfilteredCount();
+ if (appTargets > mChooserListCommunicator.getMaxRankedTargets()) {
+ appTargets = appTargets + mChooserListCommunicator.getMaxRankedTargets();
+ }
+ return appTargets + getSelectableServiceTargetCount() + getCallerTargetCount();
+ }
+
+
+ public int getCallerTargetCount() {
+ return Math.min(mCallerTargets.size(), MAX_SUGGESTED_APP_TARGETS);
+ }
+
+ /**
+ * Filter out placeholders and non-selectable service targets
+ */
+ public int getSelectableServiceTargetCount() {
+ int count = 0;
+ for (ChooserTargetInfo info : mServiceTargets) {
+ if (info instanceof SelectableTargetInfo) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ public int getServiceTargetCount() {
+ if (mChooserListCommunicator.isSendAction(mChooserListCommunicator.getTargetIntent())
+ && !ActivityManager.isLowRamDeviceStatic()) {
+ return Math.min(mServiceTargets.size(), MAX_SERVICE_TARGETS);
+ }
+
+ return 0;
+ }
+
+ int getAlphaTargetCount() {
+ int standardCount = super.getCount();
+ return standardCount > mChooserListCommunicator.getMaxRankedTargets() ? standardCount : 0;
+ }
+
+ int getRankedTargetCount() {
+ int spacesAvailable =
+ mChooserListCommunicator.getMaxRankedTargets() - getCallerTargetCount();
+ return Math.min(spacesAvailable, super.getCount());
+ }
+
+ public int getPositionTargetType(int position) {
+ int offset = 0;
+
+ final int serviceTargetCount = getServiceTargetCount();
+ if (position < serviceTargetCount) {
+ return TARGET_SERVICE;
+ }
+ offset += serviceTargetCount;
+
+ final int callerTargetCount = getCallerTargetCount();
+ if (position - offset < callerTargetCount) {
+ return TARGET_CALLER;
+ }
+ offset += callerTargetCount;
+
+ final int rankedTargetCount = getRankedTargetCount();
+ if (position - offset < rankedTargetCount) {
+ return TARGET_STANDARD;
+ }
+ offset += rankedTargetCount;
+
+ final int standardTargetCount = getAlphaTargetCount();
+ if (position - offset < standardTargetCount) {
+ return TARGET_STANDARD_AZ;
+ }
+
+ return TARGET_BAD;
+ }
+
+ @Override
+ public TargetInfo getItem(int position) {
+ return targetInfoForPosition(position, true);
+ }
+
+
+ /**
+ * Find target info for a given position.
+ * Since ChooserActivity displays several sections of content, determine which
+ * section provides this item.
+ */
+ @Override
+ public TargetInfo targetInfoForPosition(int position, boolean filtered) {
+ int offset = 0;
+
+ // Direct share targets
+ final int serviceTargetCount = filtered ? getServiceTargetCount() :
+ getSelectableServiceTargetCount();
+ if (position < serviceTargetCount) {
+ return mServiceTargets.get(position);
+ }
+ offset += serviceTargetCount;
+
+ // Targets provided by calling app
+ final int callerTargetCount = getCallerTargetCount();
+ if (position - offset < callerTargetCount) {
+ return mCallerTargets.get(position - offset);
+ }
+ offset += callerTargetCount;
+
+ // Ranked standard app targets
+ final int rankedTargetCount = getRankedTargetCount();
+ if (position - offset < rankedTargetCount) {
+ return filtered ? super.getItem(position - offset)
+ : getDisplayResolveInfo(position - offset);
+ }
+ offset += rankedTargetCount;
+
+ // Alphabetical complete app target list.
+ if (position - offset < getAlphaTargetCount() && !mSortedList.isEmpty()) {
+ return mSortedList.get(position - offset);
+ }
+
+ return null;
+ }
+
+
+ /**
+ * Evaluate targets for inclusion in the direct share area. May not be included
+ * if score is too low.
+ */
+ public void addServiceResults(DisplayResolveInfo origTarget, List targets,
+ @ChooserActivity.ShareTargetType int targetType) {
+ if (DEBUG) {
+ Log.d(TAG, "addServiceResults " + origTarget + ", " + targets.size()
+ + " targets");
+ }
+
+ if (targets.size() == 0) {
+ return;
+ }
+
+ final float baseScore = getBaseScore(origTarget, targetType);
+ Collections.sort(targets, mBaseTargetComparator);
+
+ final boolean isShortcutResult =
+ (targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER
+ || targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE);
+ final int maxTargets = isShortcutResult ? mMaxShortcutTargetsPerApp
+ : MAX_CHOOSER_TARGETS_PER_APP;
+ float lastScore = 0;
+ boolean shouldNotify = false;
+ for (int i = 0, count = Math.min(targets.size(), maxTargets); i < count; i++) {
+ final ChooserTarget target = targets.get(i);
+ float targetScore = target.getScore();
+ targetScore *= baseScore;
+ if (i > 0 && targetScore >= lastScore) {
+ // Apply a decay so that the top app can't crowd out everything else.
+ // This incents ChooserTargetServices to define what's truly better.
+ targetScore = lastScore * 0.95f;
+ }
+ boolean isInserted = insertServiceTarget(new SelectableTargetInfo(
+ mContext, origTarget, target, targetScore, mSelectableTargetInfoComunicator));
+
+ if (isInserted && isShortcutResult) {
+ mNumShortcutResults++;
+ }
+
+ shouldNotify |= isInserted;
+
+ if (DEBUG) {
+ Log.d(TAG, " => " + target.toString() + " score=" + targetScore
+ + " base=" + target.getScore()
+ + " lastScore=" + lastScore
+ + " baseScore=" + baseScore);
+ }
+
+ lastScore = targetScore;
+ }
+
+ if (shouldNotify) {
+ notifyDataSetChanged();
+ }
+ }
+
+ int getNumShortcutResults() {
+ return mNumShortcutResults;
+ }
+
+ /**
+ * Use the scoring system along with artificial boosts to create up to 4 distinct buckets:
+ *
+ * - App-supplied targets
+ *
- Shortcuts ranked via App Prediction Manager
+ *
- Shortcuts ranked via legacy heuristics
+ *
- Legacy direct share targets
+ *
+ */
+ public float getBaseScore(
+ DisplayResolveInfo target,
+ @ChooserActivity.ShareTargetType int targetType) {
+ if (target == null) {
+ return CALLER_TARGET_SCORE_BOOST;
+ }
+
+ if (targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE) {
+ return SHORTCUT_TARGET_SCORE_BOOST;
+ }
+
+ float score = super.getScore(target);
+ if (targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER) {
+ return score * SHORTCUT_TARGET_SCORE_BOOST;
+ }
+
+ return score;
+ }
+
+ /**
+ * Calling this marks service target loading complete, and will attempt to no longer
+ * update the direct share area.
+ */
+ public void completeServiceTargetLoading() {
+ mServiceTargets.removeIf(o -> o instanceof ChooserActivity.PlaceHolderTargetInfo);
+
+ if (mServiceTargets.isEmpty()) {
+ mServiceTargets.add(new ChooserActivity.EmptyTargetInfo());
+ }
+ notifyDataSetChanged();
+ }
+
+ private boolean insertServiceTarget(ChooserTargetInfo chooserTargetInfo) {
+ // Avoid inserting any potentially late results
+ if (mServiceTargets.size() == 1
+ && mServiceTargets.get(0) instanceof ChooserActivity.EmptyTargetInfo) {
+ return false;
+ }
+
+ // Check for duplicates and abort if found
+ for (ChooserTargetInfo otherTargetInfo : mServiceTargets) {
+ if (chooserTargetInfo.isSimilar(otherTargetInfo)) {
+ return false;
+ }
+ }
+
+ int currentSize = mServiceTargets.size();
+ final float newScore = chooserTargetInfo.getModifiedScore();
+ for (int i = 0; i < Math.min(currentSize, MAX_SERVICE_TARGETS); i++) {
+ final ChooserTargetInfo serviceTarget = mServiceTargets.get(i);
+ if (serviceTarget == null) {
+ mServiceTargets.set(i, chooserTargetInfo);
+ return true;
+ } else if (newScore > serviceTarget.getModifiedScore()) {
+ mServiceTargets.add(i, chooserTargetInfo);
+ return true;
+ }
+ }
+
+ if (currentSize < MAX_SERVICE_TARGETS) {
+ mServiceTargets.add(chooserTargetInfo);
+ return true;
+ }
+
+ return false;
+ }
+
+ public ChooserTarget getChooserTargetForValue(int value) {
+ return mServiceTargets.get(value).getChooserTarget();
+ }
+
+ /**
+ * Rather than fully sorting the input list, this sorting task will put the top k elements
+ * in the head of input list and fill the tail with other elements in undetermined order.
+ */
+ @Override
+ AsyncTask,
+ Void,
+ List> createSortingTask() {
+ return new AsyncTask,
+ Void,
+ List>() {
+ @Override
+ protected List doInBackground(
+ List... params) {
+ mResolverListController.topK(params[0],
+ mChooserListCommunicator.getMaxRankedTargets());
+ return params[0];
+ }
+ @Override
+ protected void onPostExecute(List sortedComponents) {
+ processSortedList(sortedComponents);
+ mChooserListCommunicator.updateProfileViewButton();
+ notifyDataSetChanged();
+ }
+ };
+ }
+
+ /**
+ * Necessary methods to communicate between {@link ChooserListAdapter}
+ * and {@link ChooserActivity}.
+ */
+ interface ChooserListCommunicator extends ResolverListCommunicator {
+
+ int getMaxRankedTargets();
+
+ void sendListViewUpdateMessage();
+
+ boolean isSendAction(Intent targetIntent);
+ }
+}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 74996e9fc2127..c1c6ac9dfa893 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -23,7 +23,6 @@ import android.annotation.StringRes;
import android.annotation.UiThread;
import android.annotation.UnsupportedAppUsage;
import android.app.Activity;
-import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.ActivityThread;
import android.app.VoiceInteractor.PickOptionRequest;
@@ -35,26 +34,18 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
-import android.content.pm.LabeledIntent;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.ColorMatrix;
-import android.graphics.ColorMatrixColorFilter;
import android.graphics.Insets;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
import android.net.Uri;
-import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.PatternMatcher;
-import android.os.Process;
import android.os.RemoteException;
import android.os.StrictMode;
import android.os.UserHandle;
@@ -71,7 +62,6 @@ import android.view.ViewGroup.LayoutParams;
import android.view.WindowInsets;
import android.widget.AbsListView;
import android.widget.AdapterView;
-import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ListView;
@@ -81,6 +71,8 @@ import android.widget.Toast;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.chooser.DisplayResolveInfo;
+import com.android.internal.app.chooser.TargetInfo;
import com.android.internal.content.PackageMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
@@ -99,32 +91,33 @@ import java.util.Set;
* which to go to. It is not normally used directly by application developers.
*/
@UiThread
-public class ResolverActivity extends Activity {
-
- // Temporary flag for new chooser delegate behavior.
- boolean mEnableChooserDelegate = true;
+public class ResolverActivity extends Activity implements
+ ResolverListAdapter.ResolverListCommunicator {
@UnsupportedAppUsage
- protected ResolveListAdapter mAdapter;
+ protected ResolverListAdapter mAdapter;
private boolean mSafeForwardingMode;
protected AbsListView mAdapterView;
private Button mAlwaysButton;
private Button mOnceButton;
protected View mProfileView;
- private int mIconDpi;
private int mLastSelected = AbsListView.INVALID_POSITION;
private boolean mResolvingHome = false;
private int mProfileSwitchMessageId = -1;
private int mLayoutId;
- private final ArrayList mIntents = new ArrayList<>();
+ @VisibleForTesting
+ protected final ArrayList mIntents = new ArrayList<>();
private PickTargetOptionRequest mPickOptionRequest;
private String mReferrerPackage;
private CharSequence mTitle;
private int mDefaultTitleResId;
- private boolean mUseLayoutForBrowsables;
+
+ @VisibleForTesting
+ protected boolean mUseLayoutForBrowsables;
// Whether or not this activity supports choosing a default handler for the intent.
- private boolean mSupportsAlwaysUseOption;
+ @VisibleForTesting
+ protected boolean mSupportsAlwaysUseOption;
protected ResolverDrawerLayout mResolverDrawerLayout;
@UnsupportedAppUsage
protected PackageManager mPm;
@@ -132,12 +125,9 @@ public class ResolverActivity extends Activity {
private static final String TAG = "ResolverActivity";
private static final boolean DEBUG = false;
- private Runnable mPostListReadyRunnable;
private boolean mRegistered;
- private ColorMatrixColorFilter mSuspendedMatrixColorFilter;
-
protected Insets mSystemWindowInsets = null;
private Space mFooterSpacer = null;
@@ -233,7 +223,7 @@ public class ResolverActivity extends Activity {
@Override
public void onSomePackagesChanged() {
mAdapter.handlePackagesChanged();
- bindProfileView();
+ updateProfileViewButton();
}
@Override
@@ -316,9 +306,6 @@ public class ResolverActivity extends Activity {
mRegistered = true;
mReferrerPackage = getReferrerPackageName();
- final ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
- mIconDpi = am.getLauncherLargeIconDensity();
-
// Add our initial intent as the first item, regardless of what else has already been added.
mIntents.add(0, new Intent(intent));
mTitle = title;
@@ -330,7 +317,17 @@ public class ResolverActivity extends Activity {
mSupportsAlwaysUseOption = supportsAlwaysUseOption;
- if (configureContentView(mIntents, initialIntents, rList)) {
+ // The last argument of createAdapter is whether to do special handling
+ // of the last used choice to highlight it in the list. We need to always
+ // turn this off when running under voice interaction, since it results in
+ // a more complicated UI that the current voice interaction flow is not able
+ // to handle.
+ boolean filterLastUsed = mSupportsAlwaysUseOption && !isVoiceInteraction();
+ mAdapter = createAdapter(this, mIntents, initialIntents, rList,
+ filterLastUsed, mUseLayoutForBrowsables);
+ configureContentView();
+
+ if (rebuildList()) {
return;
}
@@ -356,11 +353,9 @@ public class ResolverActivity extends Activity {
mProfileView = findViewById(R.id.profile_button);
if (mProfileView != null) {
mProfileView.setOnClickListener(this::onProfileClick);
- bindProfileView();
+ updateProfileViewButton();
}
- initSuspendedColorMatrix();
-
final Set categories = intent.getCategories();
MetricsLogger.action(this, mAdapter.hasFilteredItem()
? MetricsProto.MetricsEvent.ACTION_SHOW_APP_DISAMBIG_APP_FEATURED
@@ -423,25 +418,7 @@ public class ResolverActivity extends Activity {
}
}
- private void initSuspendedColorMatrix() {
- int grayValue = 127;
- float scale = 0.5f; // half bright
-
- ColorMatrix tempBrightnessMatrix = new ColorMatrix();
- float[] mat = tempBrightnessMatrix.getArray();
- mat[0] = scale;
- mat[6] = scale;
- mat[12] = scale;
- mat[4] = grayValue;
- mat[9] = grayValue;
- mat[14] = grayValue;
-
- ColorMatrix matrix = new ColorMatrix();
- matrix.setSaturation(0.0f);
- matrix.preConcat(tempBrightnessMatrix);
- mSuspendedMatrixColorFilter = new ColorMatrixColorFilter(matrix);
- }
-
+ @Override // ResolverListCommunicator
public void sendVoiceChoicesIfNeeded() {
if (!isVoiceInteraction()) {
// Clearly not needed.
@@ -476,6 +453,7 @@ public class ResolverActivity extends Activity {
}
}
+ @Override // SelectableTargetInfoCommunicator ResolverListCommunicator
public Intent getTargetIntent() {
return mIntents.isEmpty() ? null : mIntents.get(0);
}
@@ -492,7 +470,8 @@ public class ResolverActivity extends Activity {
return R.layout.resolver_list;
}
- protected void bindProfileView() {
+ @Override // ResolverListCommunicator
+ public void updateProfileViewButton() {
if (mProfileView == null) {
return;
}
@@ -583,187 +562,6 @@ public class ResolverActivity extends Activity {
}
}
-
- /**
- * 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
- * exception for applications that hold the right permission. Always attempts to use available
- * resources over PackageManager loading mechanisms so badging can be done by iconloader. Uses
- * Strings to strip creative formatting.
- */
- private abstract static class TargetPresentationGetter {
- @Nullable abstract Drawable getIconSubstituteInternal();
- @Nullable abstract String getAppSubLabelInternal();
-
- private Context mCtx;
- private final int mIconDpi;
- private final boolean mHasSubstitutePermission;
- private final ApplicationInfo mAi;
-
- protected PackageManager mPm;
-
- TargetPresentationGetter(Context ctx, int iconDpi, ApplicationInfo ai) {
- mCtx = ctx;
- mPm = ctx.getPackageManager();
- mAi = ai;
- mIconDpi = iconDpi;
- mHasSubstitutePermission = PackageManager.PERMISSION_GRANTED == mPm.checkPermission(
- android.Manifest.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON,
- mAi.packageName);
- }
-
- public Drawable getIcon(UserHandle userHandle) {
- return new BitmapDrawable(mCtx.getResources(), getIconBitmap(userHandle));
- }
-
- public Bitmap getIconBitmap(UserHandle userHandle) {
- Drawable dr = null;
- if (mHasSubstitutePermission) {
- dr = getIconSubstituteInternal();
- }
-
- if (dr == null) {
- try {
- if (mAi.icon != 0) {
- dr = loadIconFromResource(mPm.getResourcesForApplication(mAi), mAi.icon);
- }
- } catch (NameNotFoundException ignore) {
- }
- }
-
- // Fall back to ApplicationInfo#loadIcon if nothing has been loaded
- if (dr == null) {
- dr = mAi.loadIcon(mPm);
- }
-
- SimpleIconFactory sif = SimpleIconFactory.obtain(mCtx);
- Bitmap icon = sif.createUserBadgedIconBitmap(dr, userHandle);
- sif.recycle();
-
- return icon;
- }
-
- public String getLabel() {
- String label = null;
- // Apps with the substitute permission will always show the sublabel as their label
- if (mHasSubstitutePermission) {
- label = getAppSubLabelInternal();
- }
-
- if (label == null) {
- label = (String) mAi.loadLabel(mPm);
- }
-
- return label;
- }
-
- public String getSubLabel() {
- // Apps with the substitute permission will never have a sublabel
- if (mHasSubstitutePermission) return null;
- return getAppSubLabelInternal();
- }
-
- protected String loadLabelFromResource(Resources res, int resId) {
- return res.getString(resId);
- }
-
- @Nullable
- protected Drawable loadIconFromResource(Resources res, int resId) {
- return res.getDrawableForDensity(resId, mIconDpi);
- }
-
- }
-
- /**
- * Loads the icon and label for the provided ResolveInfo.
- */
- @VisibleForTesting
- public static class ResolveInfoPresentationGetter extends ActivityInfoPresentationGetter {
- private final ResolveInfo mRi;
- public ResolveInfoPresentationGetter(Context ctx, int iconDpi, ResolveInfo ri) {
- super(ctx, iconDpi, ri.activityInfo);
- mRi = ri;
- }
-
- @Override
- Drawable getIconSubstituteInternal() {
- Drawable dr = null;
- try {
- // Do not use ResolveInfo#getIconResource() as it defaults to the app
- if (mRi.resolvePackageName != null && mRi.icon != 0) {
- dr = loadIconFromResource(
- mPm.getResourcesForApplication(mRi.resolvePackageName), mRi.icon);
- }
- } catch (NameNotFoundException e) {
- Log.e(TAG, "SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON permission granted but "
- + "couldn't find resources for package", e);
- }
-
- // Fall back to ActivityInfo if no icon is found via ResolveInfo
- if (dr == null) dr = super.getIconSubstituteInternal();
-
- return dr;
- }
-
- @Override
- String getAppSubLabelInternal() {
- // Will default to app name if no intent filter or activity label set, make sure to
- // check if subLabel matches label before final display
- return (String) mRi.loadLabel(mPm);
- }
- }
-
- ResolveInfoPresentationGetter makePresentationGetter(ResolveInfo ri) {
- return new ResolveInfoPresentationGetter(this, mIconDpi, ri);
- }
-
- /**
- * Loads the icon and label for the provided ActivityInfo.
- */
- @VisibleForTesting
- public static class ActivityInfoPresentationGetter extends TargetPresentationGetter {
- private final ActivityInfo mActivityInfo;
- public ActivityInfoPresentationGetter(Context ctx, int iconDpi,
- ActivityInfo activityInfo) {
- super(ctx, iconDpi, activityInfo.applicationInfo);
- mActivityInfo = activityInfo;
- }
-
- @Override
- Drawable getIconSubstituteInternal() {
- Drawable dr = null;
- try {
- // Do not use ActivityInfo#getIconResource() as it defaults to the app
- if (mActivityInfo.icon != 0) {
- dr = loadIconFromResource(
- mPm.getResourcesForApplication(mActivityInfo.applicationInfo),
- mActivityInfo.icon);
- }
- } catch (NameNotFoundException e) {
- Log.e(TAG, "SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON permission granted but "
- + "couldn't find resources for package", e);
- }
-
- return dr;
- }
-
- @Override
- String getAppSubLabelInternal() {
- // Will default to app name if no activity label set, make sure to check if subLabel
- // matches label before final display
- return (String) mActivityInfo.loadLabel(mPm);
- }
- }
-
- protected ActivityInfoPresentationGetter makePresentationGetter(ActivityInfo ai) {
- return new ActivityInfoPresentationGetter(this, mIconDpi, ai);
- }
-
- Drawable loadIconForResolveInfo(ResolveInfo ri) {
- // Load icons based on the current process. If in work profile icons should be badged.
- return makePresentationGetter(ri).getIcon(Process.myUserHandle());
- }
-
@Override
protected void onRestart() {
super.onRestart();
@@ -772,7 +570,7 @@ public class ResolverActivity extends Activity {
mRegistered = true;
}
mAdapter.handlePackagesChanged();
- bindProfileView();
+ updateProfileViewButton();
}
@Override
@@ -804,12 +602,8 @@ public class ResolverActivity extends Activity {
if (!isChangingConfigurations() && mPickOptionRequest != null) {
mPickOptionRequest.cancel();
}
- if (mPostListReadyRunnable != null) {
- getMainThreadHandler().removeCallbacks(mPostListReadyRunnable);
- mPostListReadyRunnable = null;
- }
- if (mAdapter != null && mAdapter.mResolverListController != null) {
- mAdapter.mResolverListController.destroy();
+ if (mAdapter != null) {
+ mAdapter.onDestroy();
}
}
@@ -950,10 +744,30 @@ public class ResolverActivity extends Activity {
/**
* Replace me in subclasses!
*/
+ @Override // ResolverListCommunicator
public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) {
return defIntent;
}
+ @Override // ResolverListCommunicator
+ public void onPostListReady() {
+ setHeader();
+ resetButtonBar();
+ onListRebuilt();
+ }
+
+ protected void onListRebuilt() {
+ int count = mAdapter.getUnfilteredCount();
+ if (count == 1 && mAdapter.getOtherProfile() == null) {
+ // Only one target, so we're a candidate to auto-launch!
+ final TargetInfo target = mAdapter.targetInfoForPosition(0, false);
+ if (shouldAutoLaunchSingleChoice(target)) {
+ safelyStartActivity(target);
+ finish();
+ }
+ }
+ }
+
protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) {
final ResolveInfo ri = target.getResolveInfo();
final Intent intent = target != null ? target.getResolvedIntent() : null;
@@ -1049,8 +863,8 @@ public class ResolverActivity extends Activity {
// If we don't add back in the component for forwarding the intent to a managed
// profile, the preferred activity may not be updated correctly (as the set of
// components we tell it we knew about will have changed).
- final boolean needToAddBackProfileForwardingComponent
- = mAdapter.mOtherProfile != null;
+ final boolean needToAddBackProfileForwardingComponent =
+ mAdapter.getOtherProfile() != null;
if (!needToAddBackProfileForwardingComponent) {
set = new ComponentName[N];
} else {
@@ -1066,8 +880,8 @@ public class ResolverActivity extends Activity {
}
if (needToAddBackProfileForwardingComponent) {
- set[N] = mAdapter.mOtherProfile.getResolvedComponentName();
- final int otherProfileMatch = mAdapter.mOtherProfile.getResolveInfo().match;
+ set[N] = mAdapter.getOtherProfile().getResolvedComponentName();
+ final int otherProfileMatch = mAdapter.getOtherProfile().getResolveInfo().match;
if (otherProfileMatch > bestMatch) bestMatch = otherProfileMatch;
}
@@ -1169,7 +983,7 @@ public class ResolverActivity extends Activity {
}
- boolean startAsCallerImpl(Intent intent, Bundle options, boolean ignoreTargetSecurity,
+ public boolean startAsCallerImpl(Intent intent, Bundle options, boolean ignoreTargetSecurity,
int userId) {
// Pass intent to delegate chooser activity with permission token.
// TODO: This should move to a trampoline Activity in the system when the ChooserActivity
@@ -1205,6 +1019,7 @@ public class ResolverActivity extends Activity {
// Do nothing
}
+ @Override // ResolverListCommunicator
public boolean shouldGetActivityMetadata() {
return false;
}
@@ -1220,11 +1035,11 @@ public class ResolverActivity extends Activity {
startActivity(in);
}
- public ResolveListAdapter createAdapter(Context context, List payloadIntents,
- Intent[] initialIntents, List rList, int launchedFromUid,
- boolean filterLastUsed) {
- return new ResolveListAdapter(context, payloadIntents, initialIntents, rList,
- launchedFromUid, filterLastUsed, createListController());
+ public ResolverListAdapter createAdapter(Context context, List payloadIntents,
+ Intent[] initialIntents, List rList,
+ boolean filterLastUsed, boolean useLayoutForBrowsables) {
+ return new ResolverListAdapter(context, payloadIntents, initialIntents, rList,
+ filterLastUsed, createListController(), useLayoutForBrowsables, this);
}
@VisibleForTesting
@@ -1238,26 +1053,34 @@ public class ResolverActivity extends Activity {
}
/**
- * Returns true if the activity is finishing and creation should halt
+ * Sets up the content view.
*/
- public boolean configureContentView(List payloadIntents, Intent[] initialIntents,
- List rList) {
- // The last argument of createAdapter is whether to do special handling
- // of the last used choice to highlight it in the list. We need to always
- // turn this off when running under voice interaction, since it results in
- // a more complicated UI that the current voice interaction flow is not able
- // to handle.
- mAdapter = createAdapter(this, payloadIntents, initialIntents, rList,
- mLaunchedFromUid, mSupportsAlwaysUseOption && !isVoiceInteraction());
- boolean rebuildCompleted = mAdapter.rebuildList();
-
+ private void configureContentView() {
+ if (mAdapter == null) {
+ throw new IllegalStateException("mAdapter cannot be null.");
+ }
if (useLayoutWithDefault()) {
mLayoutId = R.layout.resolver_list_with_default;
} else {
mLayoutId = getLayoutResource();
}
setContentView(mLayoutId);
+ mAdapterView = findViewById(R.id.resolver_list);
+ }
+ /**
+ * Returns true if the activity is finishing and creation should halt.
+ *
Subclasses must call rebuildListInternal at the end of rebuildList.
+ */
+ protected boolean rebuildList() {
+ return rebuildListInternal();
+ }
+
+ /**
+ * Returns true if the activity is finishing and creation should halt.
+ */
+ final boolean rebuildListInternal() {
+ boolean rebuildCompleted = mAdapter.rebuildList();
int count = mAdapter.getUnfilteredCount();
// We only rebuild asynchronously when we have multiple elements to sort. In the case where
@@ -1276,10 +1099,7 @@ public class ResolverActivity extends Activity {
}
}
-
- mAdapterView = findViewById(R.id.resolver_list);
-
- if (count == 0 && mAdapter.mPlaceholderCount == 0) {
+ if (count == 0 && mAdapter.getPlaceholderCount() == 0) {
final TextView emptyView = findViewById(R.id.empty);
emptyView.setVisibility(View.VISIBLE);
mAdapterView.setVisibility(View.GONE);
@@ -1290,7 +1110,7 @@ public class ResolverActivity extends Activity {
return false;
}
- public void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter) {
+ public void onPrepareAdapterView(AbsListView adapterView, ResolverListAdapter adapter) {
final boolean useHeader = adapter.hasFilteredItem();
final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null;
@@ -1317,7 +1137,7 @@ public class ResolverActivity extends Activity {
* Configure the area above the app selection list (title, content preview, etc).
*/
public void setHeader() {
- if (mAdapter.getCount() == 0 && mAdapter.mPlaceholderCount == 0) {
+ if (mAdapter.getCount() == 0 && mAdapter.getPlaceholderCount() == 0) {
final TextView titleView = findViewById(R.id.title);
if (titleView != null) {
titleView.setVisibility(View.GONE);
@@ -1337,9 +1157,8 @@ public class ResolverActivity extends Activity {
}
final ImageView iconView = findViewById(R.id.icon);
- final DisplayResolveInfo iconInfo = mAdapter.getFilteredItem();
- if (iconView != null && iconInfo != null) {
- new LoadIconTask(iconInfo, iconView).execute();
+ if (iconView != null) {
+ mAdapter.loadFilteredItemIconTaskAsync(iconView);
}
}
@@ -1382,7 +1201,8 @@ public class ResolverActivity extends Activity {
}
}
- private boolean useLayoutWithDefault() {
+ @Override // ResolverListCommunicator
+ public boolean useLayoutWithDefault() {
return mSupportsAlwaysUseOption && mAdapter.hasFilteredItem();
}
@@ -1397,704 +1217,20 @@ public class ResolverActivity extends Activity {
/**
* Check a simple match for the component of two ResolveInfos.
*/
- static boolean resolveInfoMatch(ResolveInfo lhs, ResolveInfo rhs) {
+ @Override // ResolverListCommunicator
+ public boolean resolveInfoMatch(ResolveInfo lhs, ResolveInfo rhs) {
return lhs == null ? rhs == null
: lhs.activityInfo == null ? rhs.activityInfo == null
: Objects.equals(lhs.activityInfo.name, rhs.activityInfo.name)
&& Objects.equals(lhs.activityInfo.packageName, rhs.activityInfo.packageName);
}
- public final class DisplayResolveInfo implements TargetInfo {
- private final ResolveInfo mResolveInfo;
- private CharSequence mDisplayLabel;
- private Drawable mDisplayIcon;
- private Drawable mBadge;
- private CharSequence mExtendedInfo;
- private final Intent mResolvedIntent;
- private final List mSourceIntents = new ArrayList<>();
- private boolean mIsSuspended;
-
- public DisplayResolveInfo(Intent originalIntent, ResolveInfo pri, Intent pOrigIntent) {
- this(originalIntent, pri, null /*mDisplayLabel*/, null /*mExtendedInfo*/, pOrigIntent);
+ @Override // ResolverListCommunicator
+ public void onHandlePackagesChanged() {
+ if (mAdapter.getCount() == 0) {
+ // We no longer have any items... just finish the activity.
+ finish();
}
-
- public DisplayResolveInfo(Intent originalIntent, ResolveInfo pri, CharSequence pLabel,
- CharSequence pInfo, Intent pOrigIntent) {
- mSourceIntents.add(originalIntent);
- mResolveInfo = pri;
- mDisplayLabel = pLabel;
- mExtendedInfo = pInfo;
-
- final Intent intent = new Intent(pOrigIntent != null ? pOrigIntent :
- getReplacementIntent(pri.activityInfo, getTargetIntent()));
- intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT
- | Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
- final ActivityInfo ai = mResolveInfo.activityInfo;
- intent.setComponent(new ComponentName(ai.applicationInfo.packageName, ai.name));
-
- mIsSuspended = (ai.applicationInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0;
-
- mResolvedIntent = intent;
- }
-
- private DisplayResolveInfo(DisplayResolveInfo other, Intent fillInIntent, int flags) {
- mSourceIntents.addAll(other.getAllSourceIntents());
- mResolveInfo = other.mResolveInfo;
- mDisplayLabel = other.mDisplayLabel;
- mDisplayIcon = other.mDisplayIcon;
- mExtendedInfo = other.mExtendedInfo;
- mResolvedIntent = new Intent(other.mResolvedIntent);
- mResolvedIntent.fillIn(fillInIntent, flags);
- }
-
- public ResolveInfo getResolveInfo() {
- return mResolveInfo;
- }
-
- public CharSequence getDisplayLabel() {
- if (mDisplayLabel == null) {
- ResolveInfoPresentationGetter pg = makePresentationGetter(mResolveInfo);
- mDisplayLabel = pg.getLabel();
- mExtendedInfo = pg.getSubLabel();
- }
- return mDisplayLabel;
- }
-
- public boolean hasDisplayLabel() {
- return mDisplayLabel != null;
- }
-
- public void setDisplayLabel(CharSequence displayLabel) {
- mDisplayLabel = displayLabel;
- }
-
- public void setExtendedInfo(CharSequence extendedInfo) {
- mExtendedInfo = extendedInfo;
- }
-
- public Drawable getDisplayIcon() {
- return mDisplayIcon;
- }
-
- @Override
- public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
- return new DisplayResolveInfo(this, fillInIntent, flags);
- }
-
- @Override
- public List getAllSourceIntents() {
- return mSourceIntents;
- }
-
- public void addAlternateSourceIntent(Intent alt) {
- mSourceIntents.add(alt);
- }
-
- public void setDisplayIcon(Drawable icon) {
- mDisplayIcon = icon;
- }
-
- public boolean hasDisplayIcon() {
- return mDisplayIcon != null;
- }
-
- public CharSequence getExtendedInfo() {
- return mExtendedInfo;
- }
-
- public Intent getResolvedIntent() {
- return mResolvedIntent;
- }
-
- @Override
- public ComponentName getResolvedComponentName() {
- return new ComponentName(mResolveInfo.activityInfo.packageName,
- mResolveInfo.activityInfo.name);
- }
-
- @Override
- public boolean start(Activity activity, Bundle options) {
- activity.startActivity(mResolvedIntent, options);
- return true;
- }
-
- @Override
- public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
- if (mEnableChooserDelegate) {
- return activity.startAsCallerImpl(mResolvedIntent, options, false, userId);
- } else {
- activity.startActivityAsCaller(mResolvedIntent, options, null, false, userId);
- return true;
- }
- }
-
- @Override
- public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
- activity.startActivityAsUser(mResolvedIntent, options, user);
- return false;
- }
-
- public boolean isSuspended() {
- return mIsSuspended;
- }
- }
-
- List getDisplayList() {
- return mAdapter.mDisplayList;
- }
-
- /**
- * A single target as represented in the chooser.
- */
- public interface TargetInfo {
- /**
- * Get the resolved intent that represents this target. Note that this may not be the
- * intent that will be launched by calling one of the start methods provided;
- * this is the intent that will be credited with the launch.
- *
- * @return the resolved intent for this target
- */
- Intent getResolvedIntent();
-
- /**
- * Get the resolved component name that represents this target. Note that this may not
- * be the component that will be directly launched by calling one of the start
- * methods provided; this is the component that will be credited with the launch.
- *
- * @return the resolved ComponentName for this target
- */
- ComponentName getResolvedComponentName();
-
- /**
- * Start the activity referenced by this target.
- *
- * @param activity calling Activity performing the launch
- * @param options ActivityOptions bundle
- * @return true if the start completed successfully
- */
- boolean start(Activity activity, Bundle options);
-
- /**
- * Start the activity referenced by this target as if the ResolverActivity's caller
- * was performing the start operation.
- *
- * @param activity calling Activity (actually) performing the launch
- * @param options ActivityOptions bundle
- * @param userId userId to start as or {@link UserHandle#USER_NULL} for activity's caller
- * @return true if the start completed successfully
- */
- boolean startAsCaller(ResolverActivity activity, Bundle options, int userId);
-
- /**
- * Start the activity referenced by this target as a given user.
- *
- * @param activity calling activity performing the launch
- * @param options ActivityOptions bundle
- * @param user handle for the user to start the activity as
- * @return true if the start completed successfully
- */
- boolean startAsUser(Activity activity, Bundle options, UserHandle user);
-
- /**
- * Return the ResolveInfo about how and why this target matched the original query
- * for available targets.
- *
- * @return ResolveInfo representing this target's match
- */
- ResolveInfo getResolveInfo();
-
- /**
- * Return the human-readable text label for this target.
- *
- * @return user-visible target label
- */
- CharSequence getDisplayLabel();
-
- /**
- * Return any extended info for this target. This may be used to disambiguate
- * otherwise identical targets.
- *
- * @return human-readable disambig string or null if none present
- */
- CharSequence getExtendedInfo();
-
- /**
- * @return The drawable that should be used to represent this target including badge
- */
- Drawable getDisplayIcon();
-
- /**
- * Clone this target with the given fill-in information.
- */
- TargetInfo cloneFilledIn(Intent fillInIntent, int flags);
-
- /**
- * @return the list of supported source intents deduped against this single target
- */
- List getAllSourceIntents();
-
- /**
- * @return true if this target can be selected by the user
- */
- boolean isSuspended();
- }
-
- public class ResolveListAdapter extends BaseAdapter {
- private final List mIntents;
- private final Intent[] mInitialIntents;
- private final List mBaseResolveList;
- protected ResolveInfo mLastChosen;
- private DisplayResolveInfo mOtherProfile;
- ResolverListController mResolverListController;
- private int mPlaceholderCount;
- private boolean mAllTargetsAreBrowsers = false;
-
- protected final LayoutInflater mInflater;
-
- // This one is the list that the Adapter will actually present.
- List mDisplayList;
- List mUnfilteredResolveList;
-
- private int mLastChosenPosition = -1;
- private boolean mFilterLastUsed;
-
- public ResolveListAdapter(Context context, List payloadIntents,
- Intent[] initialIntents, List rList, int launchedFromUid,
- boolean filterLastUsed,
- ResolverListController resolverListController) {
- mIntents = payloadIntents;
- mInitialIntents = initialIntents;
- mBaseResolveList = rList;
- mLaunchedFromUid = launchedFromUid;
- mInflater = LayoutInflater.from(context);
- mDisplayList = new ArrayList<>();
- mFilterLastUsed = filterLastUsed;
- mResolverListController = resolverListController;
- }
-
- public void handlePackagesChanged() {
- rebuildList();
- if (getCount() == 0) {
- // We no longer have any items... just finish the activity.
- finish();
- }
- }
-
- public void setPlaceholderCount(int count) {
- mPlaceholderCount = count;
- }
-
- public int getPlaceholderCount() { return mPlaceholderCount; }
-
- @Nullable
- public DisplayResolveInfo getFilteredItem() {
- if (mFilterLastUsed && mLastChosenPosition >= 0) {
- // Not using getItem since it offsets to dodge this position for the list
- return mDisplayList.get(mLastChosenPosition);
- }
- return null;
- }
-
- public DisplayResolveInfo getOtherProfile() {
- return mOtherProfile;
- }
-
- public int getFilteredPosition() {
- if (mFilterLastUsed && mLastChosenPosition >= 0) {
- return mLastChosenPosition;
- }
- return AbsListView.INVALID_POSITION;
- }
-
- public boolean hasFilteredItem() {
- return mFilterLastUsed && mLastChosen != null;
- }
-
- public float getScore(DisplayResolveInfo target) {
- return mResolverListController.getScore(target);
- }
-
- public void updateModel(ComponentName componentName) {
- mResolverListController.updateModel(componentName);
- }
-
- public void updateChooserCounts(String packageName, int userId, String action) {
- mResolverListController.updateChooserCounts(packageName, userId, action);
- }
-
- /**
- * @return true if all items in the display list are defined as browsers by
- * ResolveInfo.handleAllWebDataURI
- */
- public boolean areAllTargetsBrowsers() {
- return mAllTargetsAreBrowsers;
- }
-
- /**
- * Rebuild the list of resolvers. In some cases some parts will need some asynchronous work
- * to complete.
- *
- * @return Whether or not the list building is completed.
- */
- protected boolean rebuildList() {
- List currentResolveList = null;
- // Clear the value of mOtherProfile from previous call.
- mOtherProfile = null;
- mLastChosen = null;
- mLastChosenPosition = -1;
- mAllTargetsAreBrowsers = false;
- mDisplayList.clear();
- if (mBaseResolveList != null) {
- currentResolveList = mUnfilteredResolveList = new ArrayList<>();
- mResolverListController.addResolveListDedupe(currentResolveList,
- getTargetIntent(),
- mBaseResolveList);
- } else {
- currentResolveList = mUnfilteredResolveList =
- mResolverListController.getResolversForIntent(shouldGetResolvedFilter(),
- shouldGetActivityMetadata(),
- mIntents);
- if (currentResolveList == null) {
- processSortedList(currentResolveList);
- return true;
- }
- List originalList =
- mResolverListController.filterIneligibleActivities(currentResolveList,
- true);
- if (originalList != null) {
- mUnfilteredResolveList = originalList;
- }
- }
-
- // So far we only support a single other profile at a time.
- // The first one we see gets special treatment.
- for (ResolvedComponentInfo info : currentResolveList) {
- if (info.getResolveInfoAt(0).targetUserId != UserHandle.USER_CURRENT) {
- mOtherProfile = new DisplayResolveInfo(info.getIntentAt(0),
- info.getResolveInfoAt(0),
- info.getResolveInfoAt(0).loadLabel(mPm),
- info.getResolveInfoAt(0).loadLabel(mPm),
- getReplacementIntent(info.getResolveInfoAt(0).activityInfo,
- info.getIntentAt(0)));
- currentResolveList.remove(info);
- break;
- }
- }
-
- if (mOtherProfile == null) {
- try {
- mLastChosen = mResolverListController.getLastChosen();
- } catch (RemoteException re) {
- Log.d(TAG, "Error calling getLastChosenActivity\n" + re);
- }
- }
-
- int N;
- if ((currentResolveList != null) && ((N = currentResolveList.size()) > 0)) {
- // We only care about fixing the unfilteredList if the current resolve list and
- // current resolve list are currently the same.
- List originalList =
- mResolverListController.filterLowPriority(currentResolveList,
- mUnfilteredResolveList == currentResolveList);
- if (originalList != null) {
- mUnfilteredResolveList = originalList;
- }
-
- if (currentResolveList.size() > 1) {
- int placeholderCount = currentResolveList.size();
- if (useLayoutWithDefault()) {
- --placeholderCount;
- }
- setPlaceholderCount(placeholderCount);
- createSortingTask().execute(currentResolveList);
- postListReadyRunnable();
- return false;
- } else {
- processSortedList(currentResolveList);
- return true;
- }
- } else {
- processSortedList(currentResolveList);
- return true;
- }
- }
-
- AsyncTask,
- Void,
- List> createSortingTask() {
- return new AsyncTask,
- Void,
- List>() {
- @Override
- protected List doInBackground(
- List... params) {
- mResolverListController.sort(params[0]);
- return params[0];
- }
-
- @Override
- protected void onPostExecute(List sortedComponents) {
- processSortedList(sortedComponents);
- bindProfileView();
- notifyDataSetChanged();
- }
- };
- }
-
- void processSortedList(List sortedComponents) {
- int N;
- if (sortedComponents != null && (N = sortedComponents.size()) != 0) {
- mAllTargetsAreBrowsers = mUseLayoutForBrowsables;
-
- // First put the initial items at the top.
- if (mInitialIntents != null) {
- for (int i = 0; i < mInitialIntents.length; i++) {
- Intent ii = mInitialIntents[i];
- if (ii == null) {
- continue;
- }
- ActivityInfo ai = ii.resolveActivityInfo(
- getPackageManager(), 0);
- if (ai == null) {
- Log.w(TAG, "No activity found for " + ii);
- continue;
- }
- ResolveInfo ri = new ResolveInfo();
- ri.activityInfo = ai;
- UserManager userManager =
- (UserManager) getSystemService(Context.USER_SERVICE);
- if (ii instanceof LabeledIntent) {
- LabeledIntent li = (LabeledIntent) ii;
- ri.resolvePackageName = li.getSourcePackage();
- ri.labelRes = li.getLabelResource();
- ri.nonLocalizedLabel = li.getNonLocalizedLabel();
- ri.icon = li.getIconResource();
- ri.iconResourceId = ri.icon;
- }
- if (userManager.isManagedProfile()) {
- ri.noResourceId = true;
- ri.icon = 0;
- }
- mAllTargetsAreBrowsers &= ri.handleAllWebDataURI;
-
- addResolveInfo(new DisplayResolveInfo(ii, ri,
- ri.loadLabel(getPackageManager()), null, ii));
- }
- }
-
-
- for (ResolvedComponentInfo rci : sortedComponents) {
- final ResolveInfo ri = rci.getResolveInfoAt(0);
- if (ri != null) {
- mAllTargetsAreBrowsers &= ri.handleAllWebDataURI;
- addResolveInfoWithAlternates(rci);
- }
- }
- }
-
- sendVoiceChoicesIfNeeded();
- postListReadyRunnable();
- }
-
-
-
- /**
- * Some necessary methods for creating the list are initiated in onCreate and will also
- * determine the layout known. We therefore can't update the UI inline and post to the
- * handler thread to update after the current task is finished.
- */
- private void postListReadyRunnable() {
- if (mPostListReadyRunnable == null) {
- mPostListReadyRunnable = new Runnable() {
- @Override
- public void run() {
- setHeader();
- resetButtonBar();
- onListRebuilt();
- mPostListReadyRunnable = null;
- }
- };
- getMainThreadHandler().post(mPostListReadyRunnable);
- }
- }
-
- public void onListRebuilt() {
- int count = getUnfilteredCount();
- if (count == 1 && getOtherProfile() == null) {
- // Only one target, so we're a candidate to auto-launch!
- final TargetInfo target = targetInfoForPosition(0, false);
- if (shouldAutoLaunchSingleChoice(target)) {
- safelyStartActivity(target);
- finish();
- }
- }
- }
-
- public boolean shouldGetResolvedFilter() {
- return mFilterLastUsed;
- }
-
- private void addResolveInfoWithAlternates(ResolvedComponentInfo rci) {
- final int count = rci.getCount();
- final Intent intent = rci.getIntentAt(0);
- final ResolveInfo add = rci.getResolveInfoAt(0);
- final Intent replaceIntent = getReplacementIntent(add.activityInfo, intent);
- final DisplayResolveInfo dri = new DisplayResolveInfo(intent, add, replaceIntent);
- addResolveInfo(dri);
- if (replaceIntent == intent) {
- // Only add alternates if we didn't get a specific replacement from
- // the caller. If we have one it trumps potential alternates.
- for (int i = 1, N = count; i < N; i++) {
- final Intent altIntent = rci.getIntentAt(i);
- dri.addAlternateSourceIntent(altIntent);
- }
- }
- updateLastChosenPosition(add);
- }
-
- private void updateLastChosenPosition(ResolveInfo info) {
- // If another profile is present, ignore the last chosen entry.
- if (mOtherProfile != null) {
- mLastChosenPosition = -1;
- return;
- }
- if (mLastChosen != null
- && mLastChosen.activityInfo.packageName.equals(info.activityInfo.packageName)
- && mLastChosen.activityInfo.name.equals(info.activityInfo.name)) {
- mLastChosenPosition = mDisplayList.size() - 1;
- }
- }
-
- // We assume that at this point we've already filtered out the only intent for a different
- // targetUserId which we're going to use.
- private void addResolveInfo(DisplayResolveInfo dri) {
- if (dri != null && dri.mResolveInfo != null
- && dri.mResolveInfo.targetUserId == UserHandle.USER_CURRENT) {
- // Checks if this info is already listed in display.
- for (DisplayResolveInfo existingInfo : mDisplayList) {
- if (resolveInfoMatch(dri.mResolveInfo, existingInfo.mResolveInfo)) {
- return;
- }
- }
- mDisplayList.add(dri);
- }
- }
-
- @Nullable
- public ResolveInfo resolveInfoForPosition(int position, boolean filtered) {
- TargetInfo target = targetInfoForPosition(position, filtered);
- if (target != null) {
- return target.getResolveInfo();
- }
- return null;
- }
-
- @Nullable
- public TargetInfo targetInfoForPosition(int position, boolean filtered) {
- if (filtered) {
- return getItem(position);
- }
- if (mDisplayList.size() > position) {
- return mDisplayList.get(position);
- }
- return null;
- }
-
- public int getCount() {
- int totalSize = mDisplayList == null || mDisplayList.isEmpty() ? mPlaceholderCount :
- mDisplayList.size();
- if (mFilterLastUsed && mLastChosenPosition >= 0) {
- totalSize--;
- }
- return totalSize;
- }
-
- public int getUnfilteredCount() {
- return mDisplayList.size();
- }
-
- @Nullable
- public TargetInfo getItem(int position) {
- if (mFilterLastUsed && mLastChosenPosition >= 0 && position >= mLastChosenPosition) {
- position++;
- }
- if (mDisplayList.size() > position) {
- return mDisplayList.get(position);
- } else {
- return null;
- }
- }
-
- public long getItemId(int position) {
- return position;
- }
-
- public int getDisplayResolveInfoCount() {
- return mDisplayList.size();
- }
-
- public DisplayResolveInfo getDisplayResolveInfo(int index) {
- // Used to query services. We only query services for primary targets, not alternates.
- return mDisplayList.get(index);
- }
-
- public final View getView(int position, View convertView, ViewGroup parent) {
- View view = convertView;
- if (view == null) {
- view = createView(parent);
- }
- onBindView(view, getItem(position));
- return view;
- }
-
- public final View createView(ViewGroup parent) {
- final View view = onCreateView(parent);
- final ViewHolder holder = new ViewHolder(view);
- view.setTag(holder);
- return view;
- }
-
- public View onCreateView(ViewGroup parent) {
- return mInflater.inflate(
- com.android.internal.R.layout.resolve_list_item, parent, false);
- }
-
- public final void bindView(int position, View view) {
- onBindView(view, getItem(position));
- }
-
- protected void onBindView(View view, TargetInfo info) {
- final ViewHolder holder = (ViewHolder) view.getTag();
- if (info == null) {
- holder.icon.setImageDrawable(
- getDrawable(R.drawable.resolver_icon_placeholder));
- return;
- }
-
- if (info instanceof DisplayResolveInfo
- && !((DisplayResolveInfo) info).hasDisplayLabel()) {
- getLoadLabelTask((DisplayResolveInfo) info, holder).execute();
- } else {
- holder.bindLabel(info.getDisplayLabel(), info.getExtendedInfo());
- }
-
- if (info.isSuspended()) {
- holder.icon.setColorFilter(mSuspendedMatrixColorFilter);
- } else {
- holder.icon.setColorFilter(null);
- }
-
- if (info instanceof DisplayResolveInfo
- && !((DisplayResolveInfo) info).hasDisplayIcon()) {
- new LoadIconTask((DisplayResolveInfo) info, holder.icon).execute();
- } else {
- holder.icon.setImageDrawable(info.getDisplayIcon());
- }
- }
- }
-
- protected LoadLabelTask getLoadLabelTask(DisplayResolveInfo info, ViewHolder holder) {
- return new LoadLabelTask(info, holder);
}
@VisibleForTesting
@@ -2144,41 +1280,6 @@ public class ResolverActivity extends Activity {
}
}
- static class ViewHolder {
- public View itemView;
- public Drawable defaultItemViewBackground;
-
- public TextView text;
- public TextView text2;
- public ImageView icon;
-
- public ViewHolder(View view) {
- itemView = view;
- defaultItemViewBackground = view.getBackground();
- text = (TextView) view.findViewById(com.android.internal.R.id.text1);
- text2 = (TextView) view.findViewById(com.android.internal.R.id.text2);
- icon = (ImageView) view.findViewById(R.id.icon);
- }
-
- public void bindLabel(CharSequence label, CharSequence subLabel) {
- if (!TextUtils.equals(text.getText(), label)) {
- text.setText(label);
- }
-
- // Always show a subLabel for visual consistency across list items. Show an empty
- // subLabel if the subLabel is the same as the label
- if (TextUtils.equals(label, subLabel)) {
- subLabel = null;
- }
-
- if (!TextUtils.equals(text2.getText(), subLabel)
- && !TextUtils.isEmpty(subLabel)) {
- text2.setVisibility(View.VISIBLE);
- text2.setText(subLabel);
- }
- }
- }
-
class ItemClickListener implements AdapterView.OnItemClickListener,
AdapterView.OnItemLongClickListener {
@Override
@@ -2229,61 +1330,6 @@ public class ResolverActivity extends Activity {
}
- protected class LoadLabelTask extends AsyncTask {
- private final DisplayResolveInfo mDisplayResolveInfo;
- private final ViewHolder mHolder;
-
- protected LoadLabelTask(DisplayResolveInfo dri, ViewHolder holder) {
- mDisplayResolveInfo = dri;
- mHolder = holder;
- }
-
-
- @Override
- protected CharSequence[] doInBackground(Void... voids) {
- ResolveInfoPresentationGetter pg =
- makePresentationGetter(mDisplayResolveInfo.mResolveInfo);
- return new CharSequence[] {
- pg.getLabel(),
- pg.getSubLabel()
- };
- }
-
- @Override
- protected void onPostExecute(CharSequence[] result) {
- mDisplayResolveInfo.setDisplayLabel(result[0]);
- mDisplayResolveInfo.setExtendedInfo(result[1]);
- mHolder.bindLabel(result[0], result[1]);
- }
- }
-
- class LoadIconTask extends AsyncTask {
- protected final DisplayResolveInfo mDisplayResolveInfo;
- private final ResolveInfo mResolveInfo;
- private final ImageView mTargetView;
-
- LoadIconTask(DisplayResolveInfo dri, ImageView target) {
- mDisplayResolveInfo = dri;
- mResolveInfo = dri.getResolveInfo();
- mTargetView = target;
- }
-
- @Override
- protected Drawable doInBackground(Void... params) {
- return loadIconForResolveInfo(mResolveInfo);
- }
-
- @Override
- protected void onPostExecute(Drawable d) {
- if (mAdapter.getOtherProfile() == mDisplayResolveInfo) {
- bindProfileView();
- } else {
- mDisplayResolveInfo.setDisplayIcon(d);
- mTargetView.setImageDrawable(d);
- }
- }
- }
-
static final boolean isSpecificUriMatch(int match) {
match = match&IntentFilter.MATCH_CATEGORY_MASK;
return match >= IntentFilter.MATCH_CATEGORY_HOST
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
new file mode 100644
index 0000000000000..4076ddaa71b00
--- /dev/null
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -0,0 +1,862 @@
+/*
+ * Copyright (C) 2019 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.internal.app;
+
+import static android.content.Context.ACTIVITY_SERVICE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LabeledIntent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
+import com.android.internal.app.chooser.DisplayResolveInfo;
+import com.android.internal.app.chooser.TargetInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ResolverListAdapter extends BaseAdapter {
+ private static final String TAG = "ResolverListAdapter";
+
+ private final List mIntents;
+ private final Intent[] mInitialIntents;
+ private final List mBaseResolveList;
+ private final PackageManager mPm;
+ protected final Context mContext;
+ private final ColorMatrixColorFilter mSuspendedMatrixColorFilter;
+ private final boolean mUseLayoutForBrowsables;
+ private final int mIconDpi;
+ protected ResolveInfo mLastChosen;
+ private DisplayResolveInfo mOtherProfile;
+ ResolverListController mResolverListController;
+ private int mPlaceholderCount;
+ private boolean mAllTargetsAreBrowsers = false;
+
+ protected final LayoutInflater mInflater;
+
+ // This one is the list that the Adapter will actually present.
+ List mDisplayList;
+ List mUnfilteredResolveList;
+
+ private int mLastChosenPosition = -1;
+ private boolean mFilterLastUsed;
+ private final ResolverListCommunicator mResolverListCommunicator;
+ private Runnable mPostListReadyRunnable;
+
+ public ResolverListAdapter(Context context, List payloadIntents,
+ Intent[] initialIntents, List rList,
+ boolean filterLastUsed,
+ ResolverListController resolverListController,
+ boolean useLayoutForBrowsables,
+ ResolverListCommunicator resolverListCommunicator) {
+ mContext = context;
+ mIntents = payloadIntents;
+ mInitialIntents = initialIntents;
+ mBaseResolveList = rList;
+ mInflater = LayoutInflater.from(context);
+ mPm = context.getPackageManager();
+ mDisplayList = new ArrayList<>();
+ mFilterLastUsed = filterLastUsed;
+ mResolverListController = resolverListController;
+ mSuspendedMatrixColorFilter = createSuspendedColorMatrix();
+ mUseLayoutForBrowsables = useLayoutForBrowsables;
+ mResolverListCommunicator = resolverListCommunicator;
+ final ActivityManager am = (ActivityManager) mContext.getSystemService(ACTIVITY_SERVICE);
+ mIconDpi = am.getLauncherLargeIconDensity();
+ }
+
+ public void handlePackagesChanged() {
+ rebuildList();
+ mResolverListCommunicator.onHandlePackagesChanged();
+ }
+
+ public void setPlaceholderCount(int count) {
+ mPlaceholderCount = count;
+ }
+
+ public int getPlaceholderCount() {
+ return mPlaceholderCount;
+ }
+
+ @Nullable
+ public DisplayResolveInfo getFilteredItem() {
+ if (mFilterLastUsed && mLastChosenPosition >= 0) {
+ // Not using getItem since it offsets to dodge this position for the list
+ return mDisplayList.get(mLastChosenPosition);
+ }
+ return null;
+ }
+
+ public DisplayResolveInfo getOtherProfile() {
+ return mOtherProfile;
+ }
+
+ public int getFilteredPosition() {
+ if (mFilterLastUsed && mLastChosenPosition >= 0) {
+ return mLastChosenPosition;
+ }
+ return AbsListView.INVALID_POSITION;
+ }
+
+ public boolean hasFilteredItem() {
+ return mFilterLastUsed && mLastChosen != null;
+ }
+
+ public float getScore(DisplayResolveInfo target) {
+ return mResolverListController.getScore(target);
+ }
+
+ public void updateModel(ComponentName componentName) {
+ mResolverListController.updateModel(componentName);
+ }
+
+ public void updateChooserCounts(String packageName, int userId, String action) {
+ mResolverListController.updateChooserCounts(packageName, userId, action);
+ }
+
+ /**
+ * @return true if all items in the display list are defined as browsers by
+ * ResolveInfo.handleAllWebDataURI
+ */
+ public boolean areAllTargetsBrowsers() {
+ return mAllTargetsAreBrowsers;
+ }
+
+ /**
+ * Rebuild the list of resolvers. In some cases some parts will need some asynchronous work
+ * to complete.
+ *
+ * @return Whether or not the list building is completed.
+ */
+ protected boolean rebuildList() {
+ List currentResolveList = null;
+ // Clear the value of mOtherProfile from previous call.
+ mOtherProfile = null;
+ mLastChosen = null;
+ mLastChosenPosition = -1;
+ mAllTargetsAreBrowsers = false;
+ mDisplayList.clear();
+ if (mBaseResolveList != null) {
+ currentResolveList = mUnfilteredResolveList = new ArrayList<>();
+ mResolverListController.addResolveListDedupe(currentResolveList,
+ mResolverListCommunicator.getTargetIntent(),
+ mBaseResolveList);
+ } else {
+ currentResolveList = mUnfilteredResolveList =
+ mResolverListController.getResolversForIntent(shouldGetResolvedFilter(),
+ mResolverListCommunicator.shouldGetActivityMetadata(),
+ mIntents);
+ if (currentResolveList == null) {
+ processSortedList(currentResolveList);
+ return true;
+ }
+ List originalList =
+ mResolverListController.filterIneligibleActivities(currentResolveList,
+ true);
+ if (originalList != null) {
+ mUnfilteredResolveList = originalList;
+ }
+ }
+
+ // So far we only support a single other profile at a time.
+ // The first one we see gets special treatment.
+ for (ResolvedComponentInfo info : currentResolveList) {
+ ResolveInfo resolveInfo = info.getResolveInfoAt(0);
+ if (resolveInfo.targetUserId != UserHandle.USER_CURRENT) {
+ Intent pOrigIntent = mResolverListCommunicator.getReplacementIntent(
+ resolveInfo.activityInfo,
+ info.getIntentAt(0));
+ Intent replacementIntent = mResolverListCommunicator.getReplacementIntent(
+ resolveInfo.activityInfo,
+ mResolverListCommunicator.getTargetIntent());
+ mOtherProfile = new DisplayResolveInfo(info.getIntentAt(0),
+ resolveInfo,
+ resolveInfo.loadLabel(mPm),
+ resolveInfo.loadLabel(mPm),
+ pOrigIntent != null ? pOrigIntent : replacementIntent,
+ makePresentationGetter(resolveInfo));
+ currentResolveList.remove(info);
+ break;
+ }
+ }
+
+ if (mOtherProfile == null) {
+ try {
+ mLastChosen = mResolverListController.getLastChosen();
+ } catch (RemoteException re) {
+ Log.d(TAG, "Error calling getLastChosenActivity\n" + re);
+ }
+ }
+
+ int n;
+ if ((currentResolveList != null) && ((n = currentResolveList.size()) > 0)) {
+ // We only care about fixing the unfilteredList if the current resolve list and
+ // current resolve list are currently the same.
+ List originalList =
+ mResolverListController.filterLowPriority(currentResolveList,
+ mUnfilteredResolveList == currentResolveList);
+ if (originalList != null) {
+ mUnfilteredResolveList = originalList;
+ }
+
+ if (currentResolveList.size() > 1) {
+ int placeholderCount = currentResolveList.size();
+ if (mResolverListCommunicator.useLayoutWithDefault()) {
+ --placeholderCount;
+ }
+ setPlaceholderCount(placeholderCount);
+ createSortingTask().execute(currentResolveList);
+ postListReadyRunnable();
+ return false;
+ } else {
+ processSortedList(currentResolveList);
+ return true;
+ }
+ } else {
+ processSortedList(currentResolveList);
+ return true;
+ }
+ }
+
+ AsyncTask,
+ Void,
+ List> createSortingTask() {
+ return new AsyncTask,
+ Void,
+ List>() {
+ @Override
+ protected List doInBackground(
+ List... params) {
+ mResolverListController.sort(params[0]);
+ return params[0];
+ }
+ @Override
+ protected void onPostExecute(List sortedComponents) {
+ processSortedList(sortedComponents);
+ mResolverListCommunicator.updateProfileViewButton();
+ notifyDataSetChanged();
+ }
+ };
+ }
+
+
+ protected void processSortedList(List sortedComponents) {
+ int n;
+ if (sortedComponents != null && (n = sortedComponents.size()) != 0) {
+ mAllTargetsAreBrowsers = mUseLayoutForBrowsables;
+
+ // First put the initial items at the top.
+ if (mInitialIntents != null) {
+ for (int i = 0; i < mInitialIntents.length; i++) {
+ Intent ii = mInitialIntents[i];
+ if (ii == null) {
+ continue;
+ }
+ ActivityInfo ai = ii.resolveActivityInfo(
+ mPm, 0);
+ if (ai == null) {
+ Log.w(TAG, "No activity found for " + ii);
+ continue;
+ }
+ ResolveInfo ri = new ResolveInfo();
+ ri.activityInfo = ai;
+ UserManager userManager =
+ (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ if (ii instanceof LabeledIntent) {
+ LabeledIntent li = (LabeledIntent) ii;
+ ri.resolvePackageName = li.getSourcePackage();
+ ri.labelRes = li.getLabelResource();
+ ri.nonLocalizedLabel = li.getNonLocalizedLabel();
+ ri.icon = li.getIconResource();
+ ri.iconResourceId = ri.icon;
+ }
+ if (userManager.isManagedProfile()) {
+ ri.noResourceId = true;
+ ri.icon = 0;
+ }
+ mAllTargetsAreBrowsers &= ri.handleAllWebDataURI;
+
+ addResolveInfo(new DisplayResolveInfo(ii, ri,
+ ri.loadLabel(mPm), null, ii, makePresentationGetter(ri)));
+ }
+ }
+
+
+ for (ResolvedComponentInfo rci : sortedComponents) {
+ final ResolveInfo ri = rci.getResolveInfoAt(0);
+ if (ri != null) {
+ mAllTargetsAreBrowsers &= ri.handleAllWebDataURI;
+ addResolveInfoWithAlternates(rci);
+ }
+ }
+ }
+
+ mResolverListCommunicator.sendVoiceChoicesIfNeeded();
+ postListReadyRunnable();
+ }
+
+ /**
+ * Some necessary methods for creating the list are initiated in onCreate and will also
+ * determine the layout known. We therefore can't update the UI inline and post to the
+ * handler thread to update after the current task is finished.
+ */
+ private void postListReadyRunnable() {
+ if (mPostListReadyRunnable == null) {
+ mPostListReadyRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mResolverListCommunicator.onPostListReady();
+ mPostListReadyRunnable = null;
+ }
+ };
+ mContext.getMainThreadHandler().post(mPostListReadyRunnable);
+ }
+ }
+
+ public boolean shouldGetResolvedFilter() {
+ return mFilterLastUsed;
+ }
+
+ private void addResolveInfoWithAlternates(ResolvedComponentInfo rci) {
+ final int count = rci.getCount();
+ final Intent intent = rci.getIntentAt(0);
+ final ResolveInfo add = rci.getResolveInfoAt(0);
+ final Intent replaceIntent =
+ mResolverListCommunicator.getReplacementIntent(add.activityInfo, intent);
+ final Intent defaultIntent = mResolverListCommunicator.getReplacementIntent(
+ add.activityInfo, mResolverListCommunicator.getTargetIntent());
+ final DisplayResolveInfo
+ dri = new DisplayResolveInfo(intent, add,
+ replaceIntent != null ? replaceIntent : defaultIntent, makePresentationGetter(add));
+ addResolveInfo(dri);
+ if (replaceIntent == intent) {
+ // Only add alternates if we didn't get a specific replacement from
+ // the caller. If we have one it trumps potential alternates.
+ for (int i = 1, n = count; i < n; i++) {
+ final Intent altIntent = rci.getIntentAt(i);
+ dri.addAlternateSourceIntent(altIntent);
+ }
+ }
+ updateLastChosenPosition(add);
+ }
+
+ private void updateLastChosenPosition(ResolveInfo info) {
+ // If another profile is present, ignore the last chosen entry.
+ if (mOtherProfile != null) {
+ mLastChosenPosition = -1;
+ return;
+ }
+ if (mLastChosen != null
+ && mLastChosen.activityInfo.packageName.equals(info.activityInfo.packageName)
+ && mLastChosen.activityInfo.name.equals(info.activityInfo.name)) {
+ mLastChosenPosition = mDisplayList.size() - 1;
+ }
+ }
+
+ // We assume that at this point we've already filtered out the only intent for a different
+ // targetUserId which we're going to use.
+ private void addResolveInfo(DisplayResolveInfo dri) {
+ if (dri != null && dri.getResolveInfo() != null
+ && dri.getResolveInfo().targetUserId == UserHandle.USER_CURRENT) {
+ // Checks if this info is already listed in display.
+ for (DisplayResolveInfo existingInfo : mDisplayList) {
+ if (mResolverListCommunicator
+ .resolveInfoMatch(dri.getResolveInfo(), existingInfo.getResolveInfo())) {
+ return;
+ }
+ }
+ mDisplayList.add(dri);
+ }
+ }
+
+ @Nullable
+ public ResolveInfo resolveInfoForPosition(int position, boolean filtered) {
+ TargetInfo target = targetInfoForPosition(position, filtered);
+ if (target != null) {
+ return target.getResolveInfo();
+ }
+ return null;
+ }
+
+ @Nullable
+ public TargetInfo targetInfoForPosition(int position, boolean filtered) {
+ if (filtered) {
+ return getItem(position);
+ }
+ if (mDisplayList.size() > position) {
+ return mDisplayList.get(position);
+ }
+ return null;
+ }
+
+ public int getCount() {
+ int totalSize = mDisplayList == null || mDisplayList.isEmpty() ? mPlaceholderCount :
+ mDisplayList.size();
+ if (mFilterLastUsed && mLastChosenPosition >= 0) {
+ totalSize--;
+ }
+ return totalSize;
+ }
+
+ public int getUnfilteredCount() {
+ return mDisplayList.size();
+ }
+
+ @Nullable
+ public TargetInfo getItem(int position) {
+ if (mFilterLastUsed && mLastChosenPosition >= 0 && position >= mLastChosenPosition) {
+ position++;
+ }
+ if (mDisplayList.size() > position) {
+ return mDisplayList.get(position);
+ } else {
+ return null;
+ }
+ }
+
+ public long getItemId(int position) {
+ return position;
+ }
+
+ public int getDisplayResolveInfoCount() {
+ return mDisplayList.size();
+ }
+
+ public DisplayResolveInfo getDisplayResolveInfo(int index) {
+ // Used to query services. We only query services for primary targets, not alternates.
+ return mDisplayList.get(index);
+ }
+
+ public final View getView(int position, View convertView, ViewGroup parent) {
+ View view = convertView;
+ if (view == null) {
+ view = createView(parent);
+ }
+ onBindView(view, getItem(position));
+ return view;
+ }
+
+ public final View createView(ViewGroup parent) {
+ final View view = onCreateView(parent);
+ final ViewHolder holder = new ViewHolder(view);
+ view.setTag(holder);
+ return view;
+ }
+
+ public View onCreateView(ViewGroup parent) {
+ return mInflater.inflate(
+ com.android.internal.R.layout.resolve_list_item, parent, false);
+ }
+
+ public final void bindView(int position, View view) {
+ onBindView(view, getItem(position));
+ }
+
+ protected void onBindView(View view, TargetInfo info) {
+ final ViewHolder holder = (ViewHolder) view.getTag();
+ if (info == null) {
+ holder.icon.setImageDrawable(
+ mContext.getDrawable(R.drawable.resolver_icon_placeholder));
+ return;
+ }
+
+ if (info instanceof DisplayResolveInfo
+ && !((DisplayResolveInfo) info).hasDisplayLabel()) {
+ getLoadLabelTask((DisplayResolveInfo) info, holder).execute();
+ } else {
+ holder.bindLabel(info.getDisplayLabel(), info.getExtendedInfo());
+ }
+
+ if (info.isSuspended()) {
+ holder.icon.setColorFilter(mSuspendedMatrixColorFilter);
+ } else {
+ holder.icon.setColorFilter(null);
+ }
+
+ if (info instanceof DisplayResolveInfo
+ && !((DisplayResolveInfo) info).hasDisplayIcon()) {
+ new ResolverListAdapter.LoadIconTask((DisplayResolveInfo) info, holder.icon).execute();
+ } else {
+ holder.icon.setImageDrawable(info.getDisplayIcon(mContext));
+ }
+ }
+
+ protected LoadLabelTask getLoadLabelTask(DisplayResolveInfo info, ViewHolder holder) {
+ return new LoadLabelTask(info, holder);
+ }
+
+ public void onDestroy() {
+ if (mPostListReadyRunnable != null) {
+ mContext.getMainThreadHandler().removeCallbacks(mPostListReadyRunnable);
+ mPostListReadyRunnable = null;
+ }
+ if (mResolverListController != null) {
+ mResolverListController.destroy();
+ }
+ }
+
+ private ColorMatrixColorFilter createSuspendedColorMatrix() {
+ int grayValue = 127;
+ float scale = 0.5f; // half bright
+
+ ColorMatrix tempBrightnessMatrix = new ColorMatrix();
+ float[] mat = tempBrightnessMatrix.getArray();
+ mat[0] = scale;
+ mat[6] = scale;
+ mat[12] = scale;
+ mat[4] = grayValue;
+ mat[9] = grayValue;
+ mat[14] = grayValue;
+
+ ColorMatrix matrix = new ColorMatrix();
+ matrix.setSaturation(0.0f);
+ matrix.preConcat(tempBrightnessMatrix);
+ return new ColorMatrixColorFilter(matrix);
+ }
+
+ ActivityInfoPresentationGetter makePresentationGetter(ActivityInfo ai) {
+ return new ActivityInfoPresentationGetter(mContext, mIconDpi, ai);
+ }
+
+ ResolveInfoPresentationGetter makePresentationGetter(ResolveInfo ri) {
+ return new ResolveInfoPresentationGetter(mContext, mIconDpi, ri);
+ }
+
+ Drawable loadIconForResolveInfo(ResolveInfo ri) {
+ // Load icons based on the current process. If in work profile icons should be badged.
+ return makePresentationGetter(ri).getIcon(Process.myUserHandle());
+ }
+
+ void loadFilteredItemIconTaskAsync(@NonNull ImageView iconView) {
+ final DisplayResolveInfo iconInfo = getFilteredItem();
+ if (iconView != null && iconInfo != null) {
+ new LoadIconTask(iconInfo, iconView).execute();
+ }
+ }
+
+ /**
+ * Necessary methods to communicate between {@link ResolverListAdapter}
+ * and {@link ResolverActivity}.
+ */
+ interface ResolverListCommunicator {
+
+ boolean resolveInfoMatch(ResolveInfo lhs, ResolveInfo rhs);
+
+ Intent getReplacementIntent(ActivityInfo activityInfo, Intent defIntent);
+
+ void onPostListReady();
+
+ void sendVoiceChoicesIfNeeded();
+
+ void updateProfileViewButton();
+
+ boolean useLayoutWithDefault();
+
+ boolean shouldGetActivityMetadata();
+
+ Intent getTargetIntent();
+
+ void onHandlePackagesChanged();
+ }
+
+ static class ViewHolder {
+ public View itemView;
+ public Drawable defaultItemViewBackground;
+
+ public TextView text;
+ public TextView text2;
+ public ImageView icon;
+
+ ViewHolder(View view) {
+ itemView = view;
+ defaultItemViewBackground = view.getBackground();
+ text = (TextView) view.findViewById(com.android.internal.R.id.text1);
+ text2 = (TextView) view.findViewById(com.android.internal.R.id.text2);
+ icon = (ImageView) view.findViewById(R.id.icon);
+ }
+
+ public void bindLabel(CharSequence label, CharSequence subLabel) {
+ if (!TextUtils.equals(text.getText(), label)) {
+ text.setText(label);
+ }
+
+ // Always show a subLabel for visual consistency across list items. Show an empty
+ // subLabel if the subLabel is the same as the label
+ if (TextUtils.equals(label, subLabel)) {
+ subLabel = null;
+ }
+
+ if (!TextUtils.equals(text2.getText(), subLabel)
+ && !TextUtils.isEmpty(subLabel)) {
+ text2.setVisibility(View.VISIBLE);
+ text2.setText(subLabel);
+ }
+ }
+ }
+
+ protected class LoadLabelTask extends AsyncTask {
+ private final DisplayResolveInfo mDisplayResolveInfo;
+ private final ViewHolder mHolder;
+
+ protected LoadLabelTask(DisplayResolveInfo dri, ViewHolder holder) {
+ mDisplayResolveInfo = dri;
+ mHolder = holder;
+ }
+
+ @Override
+ protected CharSequence[] doInBackground(Void... voids) {
+ ResolveInfoPresentationGetter pg =
+ makePresentationGetter(mDisplayResolveInfo.getResolveInfo());
+ return new CharSequence[] {
+ pg.getLabel(),
+ pg.getSubLabel()
+ };
+ }
+
+ @Override
+ protected void onPostExecute(CharSequence[] result) {
+ mDisplayResolveInfo.setDisplayLabel(result[0]);
+ mDisplayResolveInfo.setExtendedInfo(result[1]);
+ mHolder.bindLabel(result[0], result[1]);
+ }
+ }
+
+ class LoadIconTask extends AsyncTask {
+ protected final com.android.internal.app.chooser.DisplayResolveInfo mDisplayResolveInfo;
+ private final ResolveInfo mResolveInfo;
+ private final ImageView mTargetView;
+
+ LoadIconTask(DisplayResolveInfo dri, ImageView target) {
+ mDisplayResolveInfo = dri;
+ mResolveInfo = dri.getResolveInfo();
+ mTargetView = target;
+ }
+
+ @Override
+ protected Drawable doInBackground(Void... params) {
+ return loadIconForResolveInfo(mResolveInfo);
+ }
+
+ @Override
+ protected void onPostExecute(Drawable d) {
+ if (getOtherProfile() == mDisplayResolveInfo) {
+ mResolverListCommunicator.updateProfileViewButton();
+ } else {
+ mDisplayResolveInfo.setDisplayIcon(d);
+ mTargetView.setImageDrawable(d);
+ }
+ }
+ }
+
+ /**
+ * Loads the icon and label for the provided ResolveInfo.
+ */
+ @VisibleForTesting
+ public static class ResolveInfoPresentationGetter extends ActivityInfoPresentationGetter {
+ private final ResolveInfo mRi;
+ public ResolveInfoPresentationGetter(Context ctx, int iconDpi, ResolveInfo ri) {
+ super(ctx, iconDpi, ri.activityInfo);
+ mRi = ri;
+ }
+
+ @Override
+ Drawable getIconSubstituteInternal() {
+ Drawable dr = null;
+ try {
+ // Do not use ResolveInfo#getIconResource() as it defaults to the app
+ if (mRi.resolvePackageName != null && mRi.icon != 0) {
+ dr = loadIconFromResource(
+ mPm.getResourcesForApplication(mRi.resolvePackageName), mRi.icon);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON permission granted but "
+ + "couldn't find resources for package", e);
+ }
+
+ // Fall back to ActivityInfo if no icon is found via ResolveInfo
+ if (dr == null) dr = super.getIconSubstituteInternal();
+
+ return dr;
+ }
+
+ @Override
+ String getAppSubLabelInternal() {
+ // Will default to app name if no intent filter or activity label set, make sure to
+ // check if subLabel matches label before final display
+ return (String) mRi.loadLabel(mPm);
+ }
+ }
+
+ /**
+ * Loads the icon and label for the provided ActivityInfo.
+ */
+ @VisibleForTesting
+ public static class ActivityInfoPresentationGetter extends
+ TargetPresentationGetter {
+ private final ActivityInfo mActivityInfo;
+ public ActivityInfoPresentationGetter(Context ctx, int iconDpi,
+ ActivityInfo activityInfo) {
+ super(ctx, iconDpi, activityInfo.applicationInfo);
+ mActivityInfo = activityInfo;
+ }
+
+ @Override
+ Drawable getIconSubstituteInternal() {
+ Drawable dr = null;
+ try {
+ // Do not use ActivityInfo#getIconResource() as it defaults to the app
+ if (mActivityInfo.icon != 0) {
+ dr = loadIconFromResource(
+ mPm.getResourcesForApplication(mActivityInfo.applicationInfo),
+ mActivityInfo.icon);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON permission granted but "
+ + "couldn't find resources for package", e);
+ }
+
+ return dr;
+ }
+
+ @Override
+ String getAppSubLabelInternal() {
+ // Will default to app name if no activity label set, make sure to check if subLabel
+ // matches label before final display
+ return (String) mActivityInfo.loadLabel(mPm);
+ }
+ }
+
+ /**
+ * 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
+ * exception for applications that hold the right permission. Always attempts to use available
+ * resources over PackageManager loading mechanisms so badging can be done by iconloader. Uses
+ * Strings to strip creative formatting.
+ */
+ private abstract static class TargetPresentationGetter {
+ @Nullable abstract Drawable getIconSubstituteInternal();
+ @Nullable abstract String getAppSubLabelInternal();
+
+ private Context mCtx;
+ private final int mIconDpi;
+ private final boolean mHasSubstitutePermission;
+ private final ApplicationInfo mAi;
+
+ protected PackageManager mPm;
+
+ TargetPresentationGetter(Context ctx, int iconDpi, ApplicationInfo ai) {
+ mCtx = ctx;
+ mPm = ctx.getPackageManager();
+ mAi = ai;
+ mIconDpi = iconDpi;
+ mHasSubstitutePermission = PackageManager.PERMISSION_GRANTED == mPm.checkPermission(
+ android.Manifest.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON,
+ mAi.packageName);
+ }
+
+ public Drawable getIcon(UserHandle userHandle) {
+ return new BitmapDrawable(mCtx.getResources(), getIconBitmap(userHandle));
+ }
+
+ public Bitmap getIconBitmap(UserHandle userHandle) {
+ Drawable dr = null;
+ if (mHasSubstitutePermission) {
+ dr = getIconSubstituteInternal();
+ }
+
+ if (dr == null) {
+ try {
+ if (mAi.icon != 0) {
+ dr = loadIconFromResource(mPm.getResourcesForApplication(mAi), mAi.icon);
+ }
+ } catch (PackageManager.NameNotFoundException ignore) {
+ }
+ }
+
+ // Fall back to ApplicationInfo#loadIcon if nothing has been loaded
+ if (dr == null) {
+ dr = mAi.loadIcon(mPm);
+ }
+
+ SimpleIconFactory sif = SimpleIconFactory.obtain(mCtx);
+ Bitmap icon = sif.createUserBadgedIconBitmap(dr, userHandle);
+ sif.recycle();
+
+ return icon;
+ }
+
+ public String getLabel() {
+ String label = null;
+ // Apps with the substitute permission will always show the sublabel as their label
+ if (mHasSubstitutePermission) {
+ label = getAppSubLabelInternal();
+ }
+
+ if (label == null) {
+ label = (String) mAi.loadLabel(mPm);
+ }
+
+ return label;
+ }
+
+ public String getSubLabel() {
+ // Apps with the substitute permission will never have a sublabel
+ if (mHasSubstitutePermission) return null;
+ return getAppSubLabelInternal();
+ }
+
+ protected String loadLabelFromResource(Resources res, int resId) {
+ return res.getString(resId);
+ }
+
+ @Nullable
+ protected Drawable loadIconFromResource(Resources res, int resId) {
+ return res.getDrawableForDensity(resId, mIconDpi);
+ }
+
+ }
+}
diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java
index 28a8a86313723..6cc60b786e551 100644
--- a/core/java/com/android/internal/app/ResolverListController.java
+++ b/core/java/com/android/internal/app/ResolverListController.java
@@ -31,6 +31,7 @@ import android.os.RemoteException;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.chooser.DisplayResolveInfo;
import java.util.ArrayList;
import java.util.Collections;
@@ -332,7 +333,7 @@ public class ResolverListController {
}
@VisibleForTesting
- public float getScore(ResolverActivity.DisplayResolveInfo target) {
+ public float getScore(DisplayResolveInfo target) {
return mResolverComparator.getScore(target.getResolvedComponentName());
}
diff --git a/core/java/com/android/internal/app/SimpleIconFactory.java b/core/java/com/android/internal/app/SimpleIconFactory.java
index 7a4e76fa8c173..d618cdf86865a 100644
--- a/core/java/com/android/internal/app/SimpleIconFactory.java
+++ b/core/java/com/android/internal/app/SimpleIconFactory.java
@@ -214,7 +214,7 @@ public class SimpleIconFactory {
* @deprecated Do not use, functionality will be replaced by iconloader lib eventually.
*/
@Deprecated
- Bitmap createAppBadgedIconBitmap(@Nullable Drawable icon, Bitmap renderedAppIcon) {
+ public Bitmap createAppBadgedIconBitmap(@Nullable Drawable icon, Bitmap renderedAppIcon) {
// If no icon is provided use the system default
if (icon == null) {
icon = getFullResDefaultActivityIcon(mFillResIconDpi);
diff --git a/core/java/com/android/internal/app/chooser/ChooserTargetInfo.java b/core/java/com/android/internal/app/chooser/ChooserTargetInfo.java
new file mode 100644
index 0000000000000..a2d0953b56207
--- /dev/null
+++ b/core/java/com/android/internal/app/chooser/ChooserTargetInfo.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2019 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.internal.app.chooser;
+
+import android.service.chooser.ChooserTarget;
+import android.text.TextUtils;
+
+/**
+ * A TargetInfo for Direct Share. Includes a {@link ChooserTarget} representing the
+ * Direct Share deep link into an application.
+ */
+public interface ChooserTargetInfo extends TargetInfo {
+ float getModifiedScore();
+
+ ChooserTarget getChooserTarget();
+
+ /**
+ * Do not label as 'equals', since this doesn't quite work
+ * as intended with java 8.
+ */
+ default boolean isSimilar(ChooserTargetInfo other) {
+ if (other == null) return false;
+
+ ChooserTarget ct1 = getChooserTarget();
+ ChooserTarget ct2 = other.getChooserTarget();
+
+ // If either is null, there is not enough info to make an informed decision
+ // about equality, so just exit
+ if (ct1 == null || ct2 == null) return false;
+
+ if (ct1.getComponentName().equals(ct2.getComponentName())
+ && TextUtils.equals(getDisplayLabel(), other.getDisplayLabel())
+ && TextUtils.equals(getExtendedInfo(), other.getExtendedInfo())) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
new file mode 100644
index 0000000000000..c77444e949ed1
--- /dev/null
+++ b/core/java/com/android/internal/app/chooser/DisplayResolveInfo.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2019 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.internal.app.chooser;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.UserHandle;
+
+import com.android.internal.app.ResolverActivity;
+import com.android.internal.app.ResolverListAdapter.ResolveInfoPresentationGetter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A TargetInfo plus additional information needed to render it (such as icon and label) and
+ * resolve it to an activity.
+ */
+public class DisplayResolveInfo implements TargetInfo {
+ // Temporary flag for new chooser delegate behavior.
+ private static final boolean ENABLE_CHOOSER_DELEGATE = true;
+
+ private final ResolveInfo mResolveInfo;
+ private CharSequence mDisplayLabel;
+ private Drawable mDisplayIcon;
+ private CharSequence mExtendedInfo;
+ private final Intent mResolvedIntent;
+ private final List mSourceIntents = new ArrayList<>();
+ private boolean mIsSuspended;
+ private ResolveInfoPresentationGetter mResolveInfoPresentationGetter;
+
+ public DisplayResolveInfo(Intent originalIntent, ResolveInfo pri, Intent pOrigIntent,
+ ResolveInfoPresentationGetter resolveInfoPresentationGetter) {
+ this(originalIntent, pri, null /*mDisplayLabel*/, null /*mExtendedInfo*/, pOrigIntent,
+ resolveInfoPresentationGetter);
+ }
+
+ public DisplayResolveInfo(Intent originalIntent, ResolveInfo pri, CharSequence pLabel,
+ CharSequence pInfo, @NonNull Intent resolvedIntent,
+ @Nullable ResolveInfoPresentationGetter resolveInfoPresentationGetter) {
+ mSourceIntents.add(originalIntent);
+ mResolveInfo = pri;
+ mDisplayLabel = pLabel;
+ mExtendedInfo = pInfo;
+ mResolveInfoPresentationGetter = resolveInfoPresentationGetter;
+
+ final Intent intent = new Intent(resolvedIntent);
+ intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT
+ | Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
+ final ActivityInfo ai = mResolveInfo.activityInfo;
+ intent.setComponent(new ComponentName(ai.applicationInfo.packageName, ai.name));
+
+ mIsSuspended = (ai.applicationInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0;
+
+ mResolvedIntent = intent;
+ }
+
+ private DisplayResolveInfo(DisplayResolveInfo other, Intent fillInIntent, int flags,
+ ResolveInfoPresentationGetter resolveInfoPresentationGetter) {
+ mSourceIntents.addAll(other.getAllSourceIntents());
+ mResolveInfo = other.mResolveInfo;
+ mDisplayLabel = other.mDisplayLabel;
+ mDisplayIcon = other.mDisplayIcon;
+ mExtendedInfo = other.mExtendedInfo;
+ mResolvedIntent = new Intent(other.mResolvedIntent);
+ mResolvedIntent.fillIn(fillInIntent, flags);
+ mResolveInfoPresentationGetter = resolveInfoPresentationGetter;
+ }
+
+ public ResolveInfo getResolveInfo() {
+ return mResolveInfo;
+ }
+
+ public CharSequence getDisplayLabel() {
+ if (mDisplayLabel == null && mResolveInfoPresentationGetter != null) {
+ mDisplayLabel = mResolveInfoPresentationGetter.getLabel();
+ mExtendedInfo = mResolveInfoPresentationGetter.getSubLabel();
+ }
+ return mDisplayLabel;
+ }
+
+ public boolean hasDisplayLabel() {
+ return mDisplayLabel != null;
+ }
+
+ public void setDisplayLabel(CharSequence displayLabel) {
+ mDisplayLabel = displayLabel;
+ }
+
+ public void setExtendedInfo(CharSequence extendedInfo) {
+ mExtendedInfo = extendedInfo;
+ }
+
+ public Drawable getDisplayIcon(Context context) {
+ return mDisplayIcon;
+ }
+
+ @Override
+ public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
+ return new DisplayResolveInfo(this, fillInIntent, flags, mResolveInfoPresentationGetter);
+ }
+
+ @Override
+ public List getAllSourceIntents() {
+ return mSourceIntents;
+ }
+
+ public void addAlternateSourceIntent(Intent alt) {
+ mSourceIntents.add(alt);
+ }
+
+ public void setDisplayIcon(Drawable icon) {
+ mDisplayIcon = icon;
+ }
+
+ public boolean hasDisplayIcon() {
+ return mDisplayIcon != null;
+ }
+
+ public CharSequence getExtendedInfo() {
+ return mExtendedInfo;
+ }
+
+ public Intent getResolvedIntent() {
+ return mResolvedIntent;
+ }
+
+ @Override
+ public ComponentName getResolvedComponentName() {
+ return new ComponentName(mResolveInfo.activityInfo.packageName,
+ mResolveInfo.activityInfo.name);
+ }
+
+ @Override
+ public boolean start(Activity activity, Bundle options) {
+ activity.startActivity(mResolvedIntent, options);
+ return true;
+ }
+
+ @Override
+ public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
+ if (ENABLE_CHOOSER_DELEGATE) {
+ return activity.startAsCallerImpl(mResolvedIntent, options, false, userId);
+ } else {
+ activity.startActivityAsCaller(mResolvedIntent, options, null, false, userId);
+ return true;
+ }
+ }
+
+ @Override
+ public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
+ activity.startActivityAsUser(mResolvedIntent, options, user);
+ return false;
+ }
+
+ public boolean isSuspended() {
+ return mIsSuspended;
+ }
+}
diff --git a/core/java/com/android/internal/app/chooser/NotSelectableTargetInfo.java b/core/java/com/android/internal/app/chooser/NotSelectableTargetInfo.java
new file mode 100644
index 0000000000000..22cbdaa662677
--- /dev/null
+++ b/core/java/com/android/internal/app/chooser/NotSelectableTargetInfo.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2019 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.internal.app.chooser;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.service.chooser.ChooserTarget;
+
+import com.android.internal.app.ResolverActivity;
+
+import java.util.List;
+
+/**
+ * Distinguish between targets that selectable by the user, vs those that are
+ * placeholders for the system while information is loading in an async manner.
+ */
+public abstract class NotSelectableTargetInfo implements ChooserTargetInfo {
+
+ public Intent getResolvedIntent() {
+ return null;
+ }
+
+ public ComponentName getResolvedComponentName() {
+ return null;
+ }
+
+ public boolean start(Activity activity, Bundle options) {
+ return false;
+ }
+
+ public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
+ return false;
+ }
+
+ public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
+ return false;
+ }
+
+ public ResolveInfo getResolveInfo() {
+ return null;
+ }
+
+ public CharSequence getDisplayLabel() {
+ return null;
+ }
+
+ public CharSequence getExtendedInfo() {
+ return null;
+ }
+
+ public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
+ return null;
+ }
+
+ public List getAllSourceIntents() {
+ return null;
+ }
+
+ public float getModifiedScore() {
+ return -0.1f;
+ }
+
+ public ChooserTarget getChooserTarget() {
+ return null;
+ }
+
+ public boolean isSuspended() {
+ return false;
+ }
+}
diff --git a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
new file mode 100644
index 0000000000000..1cc4857b39fe3
--- /dev/null
+++ b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2019 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.internal.app.chooser;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+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.Bundle;
+import android.os.UserHandle;
+import android.service.chooser.ChooserTarget;
+import android.text.SpannableStringBuilder;
+import android.util.Log;
+
+import com.android.internal.app.ChooserActivity;
+import com.android.internal.app.ChooserFlags;
+import com.android.internal.app.ResolverActivity;
+import com.android.internal.app.ResolverListAdapter.ActivityInfoPresentationGetter;
+import com.android.internal.app.SimpleIconFactory;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Live target, currently selectable by the user.
+ * @see NotSelectableTargetInfo
+ */
+public final class SelectableTargetInfo implements ChooserTargetInfo {
+ private static final String TAG = "SelectableTargetInfo";
+
+ private final Context mContext;
+ private final DisplayResolveInfo mSourceInfo;
+ private final ResolveInfo mBackupResolveInfo;
+ private final ChooserTarget mChooserTarget;
+ private final String mDisplayLabel;
+ private final PackageManager mPm;
+ private final SelectableTargetInfoCommunicator mSelectableTargetInfoCommunicator;
+ private Drawable mBadgeIcon = null;
+ private CharSequence mBadgeContentDescription;
+ private Drawable mDisplayIcon;
+ private final Intent mFillInIntent;
+ private final int mFillInFlags;
+ private final float mModifiedScore;
+ private boolean mIsSuspended = false;
+
+ public SelectableTargetInfo(Context context, DisplayResolveInfo sourceInfo,
+ ChooserTarget chooserTarget,
+ float modifiedScore, SelectableTargetInfoCommunicator selectableTargetInfoComunicator) {
+ mContext = context;
+ mSourceInfo = sourceInfo;
+ mChooserTarget = chooserTarget;
+ mModifiedScore = modifiedScore;
+ mPm = mContext.getPackageManager();
+ mSelectableTargetInfoCommunicator = selectableTargetInfoComunicator;
+ if (sourceInfo != null) {
+ final ResolveInfo ri = sourceInfo.getResolveInfo();
+ if (ri != null) {
+ final ActivityInfo ai = ri.activityInfo;
+ if (ai != null && ai.applicationInfo != null) {
+ final PackageManager pm = mContext.getPackageManager();
+ mBadgeIcon = pm.getApplicationIcon(ai.applicationInfo);
+ mBadgeContentDescription = pm.getApplicationLabel(ai.applicationInfo);
+ mIsSuspended =
+ (ai.applicationInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0;
+ }
+ }
+ }
+ // TODO(b/121287224): do this in the background thread, and only for selected targets
+ mDisplayIcon = getChooserTargetIconDrawable(chooserTarget);
+
+ if (sourceInfo != null) {
+ mBackupResolveInfo = null;
+ } else {
+ mBackupResolveInfo =
+ mContext.getPackageManager().resolveActivity(getResolvedIntent(), 0);
+ }
+
+ mFillInIntent = null;
+ mFillInFlags = 0;
+
+ mDisplayLabel = sanitizeDisplayLabel(chooserTarget.getTitle());
+ }
+
+ private SelectableTargetInfo(SelectableTargetInfo other,
+ Intent fillInIntent, int flags) {
+ mContext = other.mContext;
+ mPm = other.mPm;
+ mSelectableTargetInfoCommunicator = other.mSelectableTargetInfoCommunicator;
+ mSourceInfo = other.mSourceInfo;
+ mBackupResolveInfo = other.mBackupResolveInfo;
+ mChooserTarget = other.mChooserTarget;
+ mBadgeIcon = other.mBadgeIcon;
+ mBadgeContentDescription = other.mBadgeContentDescription;
+ mDisplayIcon = other.mDisplayIcon;
+ mFillInIntent = fillInIntent;
+ mFillInFlags = flags;
+ mModifiedScore = other.mModifiedScore;
+
+ mDisplayLabel = sanitizeDisplayLabel(mChooserTarget.getTitle());
+ }
+
+ private String sanitizeDisplayLabel(CharSequence label) {
+ SpannableStringBuilder sb = new SpannableStringBuilder(label);
+ sb.clearSpans();
+ return sb.toString();
+ }
+
+ public boolean isSuspended() {
+ return mIsSuspended;
+ }
+
+ /**
+ * Since ShortcutInfos are returned by ShortcutManager, we can cache the shortcuts and skip
+ * the call to LauncherApps#getShortcuts(ShortcutQuery).
+ */
+ // TODO(121287224): Refactor code to apply the suggestion above
+ private Drawable getChooserTargetIconDrawable(ChooserTarget target) {
+ Drawable directShareIcon = null;
+
+ // First get the target drawable and associated activity info
+ final Icon icon = target.getIcon();
+ if (icon != null) {
+ directShareIcon = icon.loadDrawable(mContext);
+ } else if (ChooserFlags.USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS) {
+ Bundle extras = target.getIntentExtras();
+ if (extras != null && extras.containsKey(Intent.EXTRA_SHORTCUT_ID)) {
+ CharSequence shortcutId = extras.getCharSequence(Intent.EXTRA_SHORTCUT_ID);
+ LauncherApps launcherApps = (LauncherApps) mContext.getSystemService(
+ Context.LAUNCHER_APPS_SERVICE);
+ final LauncherApps.ShortcutQuery q = new LauncherApps.ShortcutQuery();
+ q.setPackage(target.getComponentName().getPackageName());
+ q.setShortcutIds(Arrays.asList(shortcutId.toString()));
+ q.setQueryFlags(LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC);
+ final List shortcuts =
+ launcherApps.getShortcuts(q, mContext.getUser());
+ if (shortcuts != null && shortcuts.size() > 0) {
+ directShareIcon = launcherApps.getShortcutIconDrawable(shortcuts.get(0), 0);
+ }
+ }
+ }
+
+ if (directShareIcon == null) return null;
+
+ ActivityInfo info = null;
+ try {
+ info = mPm.getActivityInfo(target.getComponentName(), 0);
+ } catch (PackageManager.NameNotFoundException error) {
+ Log.e(TAG, "Could not find activity associated with ChooserTarget");
+ }
+
+ if (info == null) return null;
+
+ // Now fetch app icon and raster with no badging even in work profile
+ Bitmap appIcon = mSelectableTargetInfoCommunicator.makePresentationGetter(info)
+ .getIconBitmap(UserHandle.getUserHandleForUid(UserHandle.myUserId()));
+
+ // Raster target drawable with appIcon as a badge
+ SimpleIconFactory sif = SimpleIconFactory.obtain(mContext);
+ Bitmap directShareBadgedIcon = sif.createAppBadgedIconBitmap(directShareIcon, appIcon);
+ sif.recycle();
+
+ return new BitmapDrawable(mContext.getResources(), directShareBadgedIcon);
+ }
+
+ public float getModifiedScore() {
+ return mModifiedScore;
+ }
+
+ @Override
+ public Intent getResolvedIntent() {
+ if (mSourceInfo != null) {
+ return mSourceInfo.getResolvedIntent();
+ }
+
+ final Intent targetIntent = new Intent(mSelectableTargetInfoCommunicator.getTargetIntent());
+ targetIntent.setComponent(mChooserTarget.getComponentName());
+ targetIntent.putExtras(mChooserTarget.getIntentExtras());
+ return targetIntent;
+ }
+
+ @Override
+ public ComponentName getResolvedComponentName() {
+ if (mSourceInfo != null) {
+ return mSourceInfo.getResolvedComponentName();
+ } else if (mBackupResolveInfo != null) {
+ return new ComponentName(mBackupResolveInfo.activityInfo.packageName,
+ mBackupResolveInfo.activityInfo.name);
+ }
+ return null;
+ }
+
+ private Intent getBaseIntentToSend() {
+ Intent result = getResolvedIntent();
+ if (result == null) {
+ Log.e(TAG, "ChooserTargetInfo: no base intent available to send");
+ } else {
+ result = new Intent(result);
+ if (mFillInIntent != null) {
+ result.fillIn(mFillInIntent, mFillInFlags);
+ }
+ result.fillIn(mSelectableTargetInfoCommunicator.getReferrerFillInIntent(), 0);
+ }
+ return result;
+ }
+
+ @Override
+ public boolean start(Activity activity, Bundle options) {
+ throw new RuntimeException("ChooserTargets should be started as caller.");
+ }
+
+ @Override
+ public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
+ final Intent intent = getBaseIntentToSend();
+ if (intent == null) {
+ return false;
+ }
+ intent.setComponent(mChooserTarget.getComponentName());
+ intent.putExtras(mChooserTarget.getIntentExtras());
+
+ // Important: we will ignore the target security checks in ActivityManager
+ // if and only if the ChooserTarget's target package is the same package
+ // where we got the ChooserTargetService that provided it. This lets a
+ // ChooserTargetService provide a non-exported or permission-guarded target
+ // to the chooser for the user to pick.
+ //
+ // If mSourceInfo is null, we got this ChooserTarget from the caller or elsewhere
+ // so we'll obey the caller's normal security checks.
+ final boolean ignoreTargetSecurity = mSourceInfo != null
+ && mSourceInfo.getResolvedComponentName().getPackageName()
+ .equals(mChooserTarget.getComponentName().getPackageName());
+ return activity.startAsCallerImpl(intent, options, ignoreTargetSecurity, userId);
+ }
+
+ @Override
+ public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
+ throw new RuntimeException("ChooserTargets should be started as caller.");
+ }
+
+ @Override
+ public ResolveInfo getResolveInfo() {
+ return mSourceInfo != null ? mSourceInfo.getResolveInfo() : mBackupResolveInfo;
+ }
+
+ @Override
+ public CharSequence getDisplayLabel() {
+ return mDisplayLabel;
+ }
+
+ @Override
+ public CharSequence getExtendedInfo() {
+ // ChooserTargets have badge icons, so we won't show the extended info to disambiguate.
+ return null;
+ }
+
+ @Override
+ public Drawable getDisplayIcon(Context context) {
+ return mDisplayIcon;
+ }
+
+ public ChooserTarget getChooserTarget() {
+ return mChooserTarget;
+ }
+
+ @Override
+ public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
+ return new SelectableTargetInfo(this, fillInIntent, flags);
+ }
+
+ @Override
+ public List getAllSourceIntents() {
+ final List results = new ArrayList<>();
+ if (mSourceInfo != null) {
+ // We only queried the service for the first one in our sourceinfo.
+ results.add(mSourceInfo.getAllSourceIntents().get(0));
+ }
+ return results;
+ }
+
+ /**
+ * Necessary methods to communicate between {@link SelectableTargetInfo}
+ * and {@link ResolverActivity} or {@link ChooserActivity}.
+ */
+ public interface SelectableTargetInfoCommunicator {
+
+ ActivityInfoPresentationGetter makePresentationGetter(ActivityInfo info);
+
+ Intent getTargetIntent();
+
+ Intent getReferrerFillInIntent();
+ }
+}
diff --git a/core/java/com/android/internal/app/chooser/TargetInfo.java b/core/java/com/android/internal/app/chooser/TargetInfo.java
new file mode 100644
index 0000000000000..b59def174828d
--- /dev/null
+++ b/core/java/com/android/internal/app/chooser/TargetInfo.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2019 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.internal.app.chooser;
+
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.UserHandle;
+
+import com.android.internal.app.ResolverActivity;
+
+import java.util.List;
+
+/**
+ * A single target as represented in the chooser.
+ */
+public interface TargetInfo {
+ /**
+ * Get the resolved intent that represents this target. Note that this may not be the
+ * intent that will be launched by calling one of the start methods provided;
+ * this is the intent that will be credited with the launch.
+ *
+ * @return the resolved intent for this target
+ */
+ Intent getResolvedIntent();
+
+ /**
+ * Get the resolved component name that represents this target. Note that this may not
+ * be the component that will be directly launched by calling one of the start
+ * methods provided; this is the component that will be credited with the launch.
+ *
+ * @return the resolved ComponentName for this target
+ */
+ ComponentName getResolvedComponentName();
+
+ /**
+ * Start the activity referenced by this target.
+ *
+ * @param activity calling Activity performing the launch
+ * @param options ActivityOptions bundle
+ * @return true if the start completed successfully
+ */
+ boolean start(Activity activity, Bundle options);
+
+ /**
+ * Start the activity referenced by this target as if the ResolverActivity's caller
+ * was performing the start operation.
+ *
+ * @param activity calling Activity (actually) performing the launch
+ * @param options ActivityOptions bundle
+ * @param userId userId to start as or {@link UserHandle#USER_NULL} for activity's caller
+ * @return true if the start completed successfully
+ */
+ boolean startAsCaller(ResolverActivity activity, Bundle options, int userId);
+
+ /**
+ * Start the activity referenced by this target as a given user.
+ *
+ * @param activity calling activity performing the launch
+ * @param options ActivityOptions bundle
+ * @param user handle for the user to start the activity as
+ * @return true if the start completed successfully
+ */
+ boolean startAsUser(Activity activity, Bundle options, UserHandle user);
+
+ /**
+ * Return the ResolveInfo about how and why this target matched the original query
+ * for available targets.
+ *
+ * @return ResolveInfo representing this target's match
+ */
+ ResolveInfo getResolveInfo();
+
+ /**
+ * Return the human-readable text label for this target.
+ *
+ * @return user-visible target label
+ */
+ CharSequence getDisplayLabel();
+
+ /**
+ * Return any extended info for this target. This may be used to disambiguate
+ * otherwise identical targets.
+ *
+ * @return human-readable disambig string or null if none present
+ */
+ CharSequence getExtendedInfo();
+
+ /**
+ * @return The drawable that should be used to represent this target including badge
+ * @param context
+ */
+ Drawable getDisplayIcon(Context context);
+
+ /**
+ * Clone this target with the given fill-in information.
+ */
+ TargetInfo cloneFilledIn(Intent fillInIntent, int flags);
+
+ /**
+ * @return the list of supported source intents deduped against this single target
+ */
+ List getAllSourceIntents();
+
+ /**
+ * @return true if this target can be selected by the user
+ */
+ boolean isSuspended();
+}
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index 5ea91da98fd33..d427cbda7fb66 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -24,12 +24,12 @@ import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
-import static com.android.internal.app.ChooserActivity.CALLER_TARGET_SCORE_BOOST;
-import static com.android.internal.app.ChooserActivity.SHORTCUT_TARGET_SCORE_BOOST;
import static com.android.internal.app.ChooserActivity.TARGET_TYPE_CHOOSER_TARGET;
import static com.android.internal.app.ChooserActivity.TARGET_TYPE_DEFAULT;
import static com.android.internal.app.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE;
import static com.android.internal.app.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER;
+import static com.android.internal.app.ChooserListAdapter.CALLER_TARGET_SCORE_BOOST;
+import static com.android.internal.app.ChooserListAdapter.SHORTCUT_TARGET_SCORE_BOOST;
import static com.android.internal.app.ChooserWrapperActivity.sOverrides;
import static org.hamcrest.CoreMatchers.is;
@@ -69,6 +69,7 @@ import androidx.test.rule.ActivityTestRule;
import com.android.internal.R;
import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
+import com.android.internal.app.chooser.DisplayResolveInfo;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -819,17 +820,18 @@ public class ChooserActivityTest {
when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
- when(sOverrides.resolverListController.getScore(Mockito.isA(
- ResolverActivity.DisplayResolveInfo.class))).thenReturn(testBaseScore);
+ when(sOverrides.resolverListController.getScore(Mockito.isA(DisplayResolveInfo.class)))
+ .thenReturn(testBaseScore);
final ChooserWrapperActivity activity = mActivityRule
.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
- final ResolverActivity.DisplayResolveInfo testDri =
+ final DisplayResolveInfo testDri =
activity.createTestDisplayResolveInfo(sendIntent,
- ResolverDataProvider.createResolveInfo(3, 0), "testLabel", "testInfo", sendIntent);
- final ChooserActivity.ChooserListAdapter adapter = activity.getAdapter();
+ ResolverDataProvider.createResolveInfo(3, 0), "testLabel", "testInfo", sendIntent,
+ /* resolveInfoPresentationGetter */ null);
+ final ChooserListAdapter adapter = activity.getAdapter();
assertThat(adapter.getBaseScore(null, 0), is(CALLER_TARGET_SCORE_BOOST));
assertThat(adapter.getBaseScore(testDri, TARGET_TYPE_DEFAULT), is(testBaseScore));
@@ -970,7 +972,8 @@ public class ChooserActivityTest {
ri,
"testLabel",
"testInfo",
- sendIntent),
+ sendIntent,
+ /* resolveInfoPresentationGetter */ null),
serviceTargets,
TARGET_TYPE_CHOOSER_TARGET)
);
@@ -1036,7 +1039,8 @@ public class ChooserActivityTest {
ri,
"testLabel",
"testInfo",
- sendIntent),
+ sendIntent,
+ /* resolveInfoPresentationGetter */ null),
serviceTargets,
TARGET_TYPE_CHOOSER_TARGET)
);
@@ -1097,7 +1101,8 @@ public class ChooserActivityTest {
ri,
"testLabel",
"testInfo",
- sendIntent),
+ sendIntent,
+ /* resolveInfoPresentationGetter */ null),
serviceTargets,
TARGET_TYPE_CHOOSER_TARGET)
);
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
index 1d567c73f3761..03705d0599e50 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
@@ -18,6 +18,7 @@ package com.android.internal.app;
import static org.mockito.Mockito.mock;
+import android.annotation.Nullable;
import android.app.usage.UsageStatsManager;
import android.content.ContentResolver;
import android.content.Context;
@@ -30,6 +31,9 @@ import android.graphics.Bitmap;
import android.net.Uri;
import android.util.Size;
+import com.android.internal.app.ResolverListAdapter.ResolveInfoPresentationGetter;
+import com.android.internal.app.chooser.DisplayResolveInfo;
+import com.android.internal.app.chooser.TargetInfo;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -64,7 +68,7 @@ public class ChooserWrapperActivity extends ChooserActivity {
}
@Override
- public void safelyStartActivity(TargetInfo cti) {
+ public void safelyStartActivity(com.android.internal.app.chooser.TargetInfo cti) {
if (sOverrides.onSafelyStartCallback != null &&
sOverrides.onSafelyStartCallback.apply(cti)) {
return;
@@ -133,8 +137,10 @@ public class ChooserWrapperActivity extends ChooserActivity {
}
public DisplayResolveInfo createTestDisplayResolveInfo(Intent originalIntent, ResolveInfo pri,
- CharSequence pLabel, CharSequence pInfo, Intent pOrigIntent) {
- return new DisplayResolveInfo(originalIntent, pri, pLabel, pInfo, pOrigIntent);
+ CharSequence pLabel, CharSequence pInfo, Intent replacementIntent,
+ @Nullable ResolveInfoPresentationGetter resolveInfoPresentationGetter) {
+ return new DisplayResolveInfo(originalIntent, pri, pLabel, pInfo, replacementIntent,
+ resolveInfoPresentationGetter);
}
/**
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
index 0fa29bf4436a3..a401e21df8054 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -43,10 +43,10 @@ import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
-import com.android.internal.app.ResolverActivity.ActivityInfoPresentationGetter;
-import com.android.internal.app.ResolverActivity.ResolveInfoPresentationGetter;
import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
import com.android.internal.app.ResolverDataProvider.PackageManagerMockedInfo;
+import com.android.internal.app.ResolverListAdapter.ActivityInfoPresentationGetter;
+import com.android.internal.app.ResolverListAdapter.ResolveInfoPresentationGetter;
import com.android.internal.widget.ResolverDrawerLayout;
import org.junit.Before;
@@ -83,7 +83,7 @@ public class ResolverActivityTest {
Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
- Espresso.registerIdlingResources(activity.getLabelIdlingResource());
+ Espresso.registerIdlingResources(activity.getAdapter().getLabelIdlingResource());
waitForIdle();
assertThat(activity.getAdapter().getCount(), is(2));
@@ -216,7 +216,7 @@ public class ResolverActivityTest {
Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
- Espresso.registerIdlingResources(activity.getLabelIdlingResource());
+ Espresso.registerIdlingResources(activity.getAdapter().getLabelIdlingResource());
waitForIdle();
// The other entry is filtered to the last used slot
@@ -254,7 +254,7 @@ public class ResolverActivityTest {
Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
- Espresso.registerIdlingResources(activity.getLabelIdlingResource());
+ Espresso.registerIdlingResources(activity.getAdapter().getLabelIdlingResource());
waitForIdle();
// The other entry is filtered to the other profile slot
@@ -300,7 +300,7 @@ public class ResolverActivityTest {
.thenReturn(resolvedComponentInfos.get(1).getResolveInfoAt(0));
final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
- Espresso.registerIdlingResources(activity.getLabelIdlingResource());
+ Espresso.registerIdlingResources(activity.getAdapter().getLabelIdlingResource());
waitForIdle();
// The other entry is filtered to the other profile slot
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
index 9082543e8ebc2..39cc83c3bc433 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
@@ -19,10 +19,14 @@ package com.android.internal.app;
import static org.mockito.Mockito.mock;
import android.app.usage.UsageStatsManager;
+import android.content.Context;
+import android.content.Intent;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
-import androidx.test.espresso.idling.CountingIdlingResource;
+import com.android.internal.app.chooser.TargetInfo;
+import java.util.List;
import java.util.function.Function;
/*
@@ -31,15 +35,17 @@ import java.util.function.Function;
public class ResolverWrapperActivity extends ResolverActivity {
static final OverrideData sOverrides = new OverrideData();
private UsageStatsManager mUsm;
- private CountingIdlingResource mLabelIdlingResource =
- new CountingIdlingResource("LoadLabelTask");
- public CountingIdlingResource getLabelIdlingResource() {
- return mLabelIdlingResource;
+ @Override
+ public ResolverListAdapter createAdapter(Context context, List payloadIntents,
+ Intent[] initialIntents, List rList, boolean filterLastUsed,
+ boolean useLayoutForBrowsables) {
+ return new ResolverWrapperAdapter(context, payloadIntents, initialIntents, rList,
+ filterLastUsed, createListController(), useLayoutForBrowsables, this);
}
- ResolveListAdapter getAdapter() {
- return mAdapter;
+ ResolverWrapperAdapter getAdapter() {
+ return (ResolverWrapperAdapter) mAdapter;
}
@Override
@@ -72,11 +78,6 @@ public class ResolverWrapperActivity extends ResolverActivity {
return super.getPackageManager();
}
- @Override
- protected LoadLabelTask getLoadLabelTask(DisplayResolveInfo info, ViewHolder holder) {
- return new LoadLabelWrapperTask(info, holder);
- }
-
/**
* We cannot directly mock the activity created since instrumentation creates it.
*
@@ -96,22 +97,4 @@ public class ResolverWrapperActivity extends ResolverActivity {
resolverListController = mock(ResolverListController.class);
}
}
-
- class LoadLabelWrapperTask extends LoadLabelTask {
-
- protected LoadLabelWrapperTask(DisplayResolveInfo dri, ViewHolder holder) {
- super(dri, holder);
- }
-
- @Override
- protected void onPreExecute() {
- mLabelIdlingResource.increment();
- }
-
- @Override
- protected void onPostExecute(CharSequence[] result) {
- super.onPostExecute(result);
- mLabelIdlingResource.decrement();
- }
- }
}
\ No newline at end of file
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperAdapter.java b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperAdapter.java
new file mode 100644
index 0000000000000..e41df4186a12b
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperAdapter.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2019 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.internal.app;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+
+import androidx.test.espresso.idling.CountingIdlingResource;
+
+import com.android.internal.app.chooser.DisplayResolveInfo;
+
+import java.util.List;
+
+public class ResolverWrapperAdapter extends ResolverListAdapter {
+
+ private CountingIdlingResource mLabelIdlingResource =
+ new CountingIdlingResource("LoadLabelTask");
+
+ public ResolverWrapperAdapter(Context context,
+ List payloadIntents,
+ Intent[] initialIntents,
+ List rList, boolean filterLastUsed,
+ ResolverListController resolverListController, boolean useLayoutForBrowsables,
+ ResolverListCommunicator resolverListCommunicator) {
+ super(context, payloadIntents, initialIntents, rList, filterLastUsed,
+ resolverListController,
+ useLayoutForBrowsables, resolverListCommunicator);
+ }
+
+ public CountingIdlingResource getLabelIdlingResource() {
+ return mLabelIdlingResource;
+ }
+
+ @Override
+ protected LoadLabelTask getLoadLabelTask(DisplayResolveInfo info, ViewHolder holder) {
+ return new LoadLabelWrapperTask(info, holder);
+ }
+
+ class LoadLabelWrapperTask extends LoadLabelTask {
+
+ protected LoadLabelWrapperTask(DisplayResolveInfo dri, ViewHolder holder) {
+ super(dri, holder);
+ }
+
+ @Override
+ protected void onPreExecute() {
+ mLabelIdlingResource.increment();
+ }
+
+ @Override
+ protected void onPostExecute(CharSequence[] result) {
+ super.onPostExecute(result);
+ mLabelIdlingResource.decrement();
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java
index fa3ff64e5e180..0b273274f86d3 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbResolverActivity.java
@@ -35,6 +35,7 @@ import android.util.Log;
import android.widget.CheckBox;
import com.android.internal.app.ResolverActivity;
+import com.android.internal.app.chooser.TargetInfo;
import com.android.systemui.R;
import java.util.ArrayList;