Merge "Separate adapter logic from ChooserActivity and ResolverActivity"
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 3b82f18..b1752a4 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.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.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.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 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.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 @@
  * 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 @@
 
     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 @@
     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<ChooserTarget, AppTarget> 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 @@
 
     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 @@
 
     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 @@
     private static final int CONTENT_PREVIEW_TEXT = 3;
     protected MetricsLogger mMetricsLogger;
 
-    // Sorted list of DisplayResolveInfos for the alphabetical app section.
-    private List<ResolverActivity.DisplayResolveInfo> mSortedList = new ArrayList<>();
-
     private ContentPreviewCoordinator mPreviewCoord;
 
     private class ContentPreviewCoordinator {
@@ -645,8 +628,7 @@
                 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 @@
             @Override
             public void onSomePackagesChanged() {
                 mAdapter.handlePackagesChanged();
-                bindProfileView();
+                updateProfileViewButton();
             }
         };
     }
@@ -1191,7 +1173,7 @@
         }
     }
 
-    @Override
+    @Override // ResolverListCommunicator
     public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) {
         Intent result = defIntent;
         if (mReplacementExtras != null) {
@@ -1231,9 +1213,8 @@
     }
 
     @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);
@@ -1245,11 +1226,17 @@
     }
 
     @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 @@
         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 @@
                     // 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 @@
                 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 @@
      */
     @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 @@
         }
     }
 
-    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<ResolverActivity.DisplayResolveInfo> {
+    static class AzInfoComparator implements Comparator<DisplayResolveInfo> {
         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 @@
     }
 
     @Override
-    public ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
-            Intent[] initialIntents, List<ResolveInfo> 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<Intent> payloadIntents,
+            Intent[] initialIntents, List<ResolveInfo> rList,
+            boolean filterLastUsed, boolean useLayoutForBrowsables) {
+        return new ChooserListAdapter(context, payloadIntents,
+                initialIntents, rList, filterLastUsed, createListController(),
+                useLayoutForBrowsables, this, this);
     }
 
     @VisibleForTesting
@@ -1999,347 +1980,21 @@
         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<Intent> 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<ShortcutInfo> 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<Intent> getAllSourceIntents() {
-            final List<Intent> 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 @@
 
                 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 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<ChooserTargetInfo> mServiceTargets = new ArrayList<>();
-        private final List<TargetInfo> mCallerTargets = new ArrayList<>();
-
-        private final BaseChooserTargetComparator mBaseTargetComparator
-                = new BaseChooserTargetComparator();
-
-        public ChooserListAdapter(Context context, List<Intent> payloadIntents,
-                Intent[] initialIntents, List<ResolveInfo> 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<List<ResolvedComponentInfo>,
-                Void,
-                List<ResolvedComponentInfo>> createSortingTask() {
-            return new AsyncTask<List<ResolvedComponentInfo>,
-                    Void,
-                    List<ResolvedComponentInfo>>() {
-                @Override
-                protected List<ResolvedComponentInfo> doInBackground(
-                        List<ResolvedComponentInfo>... params) {
-                    mResolverListController.topK(params[0],
-                            getMaxRankedTargets());
-                    return params[0];
-                }
-
-                @Override
-                protected void onPostExecute(List<ResolvedComponentInfo> sortedComponents) {
-                    processSortedList(sortedComponents);
-                    bindProfileView();
-                    notifyDataSetChanged();
-                }
-            };
-        }
-
-        @Override
-        public void onListRebuilt() {
-            if (getDisplayList() == null || getDisplayList().isEmpty()) {
-                notifyDataSetChanged();
-            } else {
-                new AsyncTask<Void, Void, Void>() {
-                    @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<ChooserTarget> 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:
-          * <ol>
-          *   <li>App-supplied targets
-          *   <li>Shortcuts ranked via App Prediction Manager
-          *   <li>Shortcuts ranked via legacy heuristics
-          *   <li>Legacy direct share targets
-          * </ol>
-          */
-        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<ChooserTarget> {
         @Override
         public int compare(ChooserTarget lhs, ChooserTarget rhs) {
@@ -2924,8 +2088,77 @@
         }
     }
 
+    @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<Void, Void, Void>() {
+                @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 @@
         // 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 @@
                     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 0000000..f1f1dbf
--- /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 0000000..6eb470f
--- /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<ChooserTargetInfo> mServiceTargets = new ArrayList<>();
+    private final List<TargetInfo> 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<DisplayResolveInfo> mSortedList = new ArrayList<>();
+
+    public ChooserListAdapter(Context context, List<Intent> payloadIntents,
+            Intent[] initialIntents, List<ResolveInfo> 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<ChooserTarget> 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:
+     * <ol>
+     *   <li>App-supplied targets
+     *   <li>Shortcuts ranked via App Prediction Manager
+     *   <li>Shortcuts ranked via legacy heuristics
+     *   <li>Legacy direct share targets
+     * </ol>
+     */
+    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<List<ResolvedComponentInfo>,
+                Void,
+                List<ResolvedComponentInfo>> createSortingTask() {
+        return new AsyncTask<List<ResolvedComponentInfo>,
+                Void,
+                List<ResolvedComponentInfo>>() {
+            @Override
+            protected List<ResolvedComponentInfo> doInBackground(
+                    List<ResolvedComponentInfo>... params) {
+                mResolverListController.topK(params[0],
+                        mChooserListCommunicator.getMaxRankedTargets());
+                return params[0];
+            }
+            @Override
+            protected void onPostExecute(List<ResolvedComponentInfo> 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 74996e9..c1c6ac9 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.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.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.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 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 @@
  * 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<Intent> mIntents = new ArrayList<>();
+    @VisibleForTesting
+    protected final ArrayList<Intent> 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 @@
 
     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 @@
             @Override
             public void onSomePackagesChanged() {
                 mAdapter.handlePackagesChanged();
-                bindProfileView();
+                updateProfileViewButton();
             }
 
             @Override
@@ -316,9 +306,6 @@
         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 @@
 
         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 @@
         mProfileView = findViewById(R.id.profile_button);
         if (mProfileView != null) {
             mProfileView.setOnClickListener(this::onProfileClick);
-            bindProfileView();
+            updateProfileViewButton();
         }
 
-        initSuspendedColorMatrix();
-
         final Set<String> categories = intent.getCategories();
         MetricsLogger.action(this, mAdapter.hasFilteredItem()
                 ? MetricsProto.MetricsEvent.ACTION_SHOW_APP_DISAMBIG_APP_FEATURED
@@ -423,25 +418,7 @@
         }
     }
 
-    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 @@
         }
     }
 
+    @Override // SelectableTargetInfoCommunicator ResolverListCommunicator
     public Intent getTargetIntent() {
         return mIntents.isEmpty() ? null : mIntents.get(0);
     }
@@ -492,7 +470,8 @@
         return R.layout.resolver_list;
     }
 
-    protected void bindProfileView() {
+    @Override // ResolverListCommunicator
+    public void updateProfileViewButton() {
         if (mProfileView == null) {
             return;
         }
@@ -583,187 +562,6 @@
         }
     }
 
-
-    /**
-     * 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 @@
             mRegistered = true;
         }
         mAdapter.handlePackagesChanged();
-        bindProfileView();
+        updateProfileViewButton();
     }
 
     @Override
@@ -804,12 +602,8 @@
         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 @@
     /**
      * 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 @@
                 // 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 @@
                 }
 
                 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 @@
     }
 
 
-    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 @@
         // Do nothing
     }
 
+    @Override // ResolverListCommunicator
     public boolean shouldGetActivityMetadata() {
         return false;
     }
@@ -1220,11 +1035,11 @@
         startActivity(in);
     }
 
-    public ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
-            Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
-            boolean filterLastUsed) {
-        return new ResolveListAdapter(context, payloadIntents, initialIntents, rList,
-                launchedFromUid, filterLastUsed, createListController());
+    public ResolverListAdapter createAdapter(Context context, List<Intent> payloadIntents,
+            Intent[] initialIntents, List<ResolveInfo> rList,
+            boolean filterLastUsed, boolean useLayoutForBrowsables) {
+        return new ResolverListAdapter(context, payloadIntents, initialIntents, rList,
+                filterLastUsed, createListController(), useLayoutForBrowsables, this);
     }
 
     @VisibleForTesting
@@ -1238,26 +1053,34 @@
     }
 
     /**
-     * Returns true if the activity is finishing and creation should halt
+     * Sets up the content view.
      */
-    public boolean configureContentView(List<Intent> payloadIntents, Intent[] initialIntents,
-            List<ResolveInfo> 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.
+     * </p>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 @@
             }
         }
 
-
-        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 @@
         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 @@
      * 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 @@
         }
 
         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 @@
         }
     }
 
-    private boolean useLayoutWithDefault() {
+    @Override // ResolverListCommunicator
+    public boolean useLayoutWithDefault() {
         return mSupportsAlwaysUseOption && mAdapter.hasFilteredItem();
     }
 
@@ -1397,704 +1217,20 @@
     /**
      * 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<Intent> 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<Intent> 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<DisplayResolveInfo> 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 <code>start</code> 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 <code>start</code>
-         * 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<Intent> getAllSourceIntents();
-
-        /**
-          * @return true if this target can be selected by the user
-          */
-        boolean isSuspended();
-    }
-
-    public class ResolveListAdapter extends BaseAdapter {
-        private final List<Intent> mIntents;
-        private final Intent[] mInitialIntents;
-        private final List<ResolveInfo> 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<DisplayResolveInfo> mDisplayList;
-        List<ResolvedComponentInfo> mUnfilteredResolveList;
-
-        private int mLastChosenPosition = -1;
-        private boolean mFilterLastUsed;
-
-        public ResolveListAdapter(Context context, List<Intent> payloadIntents,
-                Intent[] initialIntents, List<ResolveInfo> 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<ResolvedComponentInfo> 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<ResolvedComponentInfo> 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<ResolvedComponentInfo> 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<List<ResolvedComponentInfo>,
-                Void,
-                List<ResolvedComponentInfo>> createSortingTask() {
-            return new AsyncTask<List<ResolvedComponentInfo>,
-                    Void,
-                    List<ResolvedComponentInfo>>() {
-                @Override
-                protected List<ResolvedComponentInfo> doInBackground(
-                        List<ResolvedComponentInfo>... params) {
-                    mResolverListController.sort(params[0]);
-                    return params[0];
-                }
-
-                @Override
-                protected void onPostExecute(List<ResolvedComponentInfo> sortedComponents) {
-                    processSortedList(sortedComponents);
-                    bindProfileView();
-                    notifyDataSetChanged();
-                }
-            };
-        }
-
-        void processSortedList(List<ResolvedComponentInfo> 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 @@
         }
     }
 
-    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 @@
 
     }
 
-    protected class LoadLabelTask extends AsyncTask<Void, Void, CharSequence[]> {
-        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<Void, Void, Drawable> {
-        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 0000000..4076dda
--- /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<Intent> mIntents;
+    private final Intent[] mInitialIntents;
+    private final List<ResolveInfo> 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<DisplayResolveInfo> mDisplayList;
+    List<ResolvedComponentInfo> mUnfilteredResolveList;
+
+    private int mLastChosenPosition = -1;
+    private boolean mFilterLastUsed;
+    private final ResolverListCommunicator mResolverListCommunicator;
+    private Runnable mPostListReadyRunnable;
+
+    public ResolverListAdapter(Context context, List<Intent> payloadIntents,
+            Intent[] initialIntents, List<ResolveInfo> 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<ResolvedComponentInfo> 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<ResolvedComponentInfo> 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<ResolvedComponentInfo> 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<List<ResolvedComponentInfo>,
+            Void,
+            List<ResolvedComponentInfo>> createSortingTask() {
+        return new AsyncTask<List<ResolvedComponentInfo>,
+                Void,
+                List<ResolvedComponentInfo>>() {
+            @Override
+            protected List<ResolvedComponentInfo> doInBackground(
+                    List<ResolvedComponentInfo>... params) {
+                mResolverListController.sort(params[0]);
+                return params[0];
+            }
+            @Override
+            protected void onPostExecute(List<ResolvedComponentInfo> sortedComponents) {
+                processSortedList(sortedComponents);
+                mResolverListCommunicator.updateProfileViewButton();
+                notifyDataSetChanged();
+            }
+        };
+    }
+
+
+    protected void processSortedList(List<ResolvedComponentInfo> 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<Void, Void, CharSequence[]> {
+        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<Void, Void, Drawable> {
+        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 28a8a86..6cc60b7 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.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 @@
     }
 
     @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 7a4e76f..d618cdf 100644
--- a/core/java/com/android/internal/app/SimpleIconFactory.java
+++ b/core/java/com/android/internal/app/SimpleIconFactory.java
@@ -214,7 +214,7 @@
      * @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 0000000..a2d0953
--- /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 0000000..c77444e
--- /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<Intent> 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<Intent> 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 0000000..22cbdaa6
--- /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<Intent> 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 0000000..1cc4857
--- /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<ShortcutInfo> 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<Intent> getAllSourceIntents() {
+        final List<Intent> 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 0000000..b59def1
--- /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 <code>start</code> 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 <code>start</code>
+     * 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<Intent> 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 5ea91da..d427cbd 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.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 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 @@
         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 @@
                                 ri,
                                 "testLabel",
                                 "testInfo",
-                                sendIntent),
+                                sendIntent,
+                                /* resolveInfoPresentationGetter */ null),
                         serviceTargets,
                         TARGET_TYPE_CHOOSER_TARGET)
         );
@@ -1036,7 +1039,8 @@
                                 ri,
                                 "testLabel",
                                 "testInfo",
-                                sendIntent),
+                                sendIntent,
+                                /* resolveInfoPresentationGetter */ null),
                         serviceTargets,
                         TARGET_TYPE_CHOOSER_TARGET)
         );
@@ -1097,7 +1101,8 @@
                                 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 1d567c7..03705d0 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 @@
 
 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.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 @@
     }
 
     @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 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 0fa29bf..a401e21 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.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 @@
                 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 @@
                 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 @@
                 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 @@
                 .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 9082543..39cc83c 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 @@
 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 @@
 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<Intent> payloadIntents,
+            Intent[] initialIntents, List<ResolveInfo> 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 @@
         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.
      * <p>
@@ -96,22 +97,4 @@
             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 0000000..e41df41
--- /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<Intent> payloadIntents,
+            Intent[] initialIntents,
+            List<ResolveInfo> 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 fa3ff64..0b27327 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.widget.CheckBox;
 
 import com.android.internal.app.ResolverActivity;
+import com.android.internal.app.chooser.TargetInfo;
 import com.android.systemui.R;
 
 import java.util.ArrayList;