Revert "Pinning components in ChooserActivity"

This reverts commit ec6bc41e1835804533fd21b3ef8047ecd893bcfd.

Bug 26842512

Change-Id: I9fc775d21081885d0e26fca4ade412a18da45b7c
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
new file mode 100644
index 0000000..2733391
--- /dev/null
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -0,0 +1,1422 @@
+/*
+ * Copyright (C) 2008 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.animation.ObjectAnimator;
+import android.annotation.NonNull;
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.IntentSender.SendIntentException;
+import android.content.ServiceConnection;
+import android.content.pm.ActivityInfo;
+import android.content.pm.LabeledIntent;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.database.DataSetObserver;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.DocumentsContract;
+import android.service.chooser.ChooserTarget;
+import android.service.chooser.ChooserTargetService;
+import android.service.chooser.IChooserTargetResult;
+import android.service.chooser.IChooserTargetService;
+import android.text.TextUtils;
+import android.util.FloatProperty;
+import android.util.Log;
+import android.util.Slog;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.widget.AbsListView;
+import android.widget.BaseAdapter;
+import android.widget.ListView;
+import com.android.internal.R;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.MetricsProto.MetricsEvent;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+public class ChooserActivity extends ResolverActivity {
+    private static final String TAG = "ChooserActivity";
+
+    private static final boolean DEBUG = false;
+
+    private static final int QUERY_TARGET_SERVICE_LIMIT = 5;
+    private static final int WATCHDOG_TIMEOUT_MILLIS = 5000;
+
+    private Bundle mReplacementExtras;
+    private IntentSender mChosenComponentSender;
+    private IntentSender mRefinementIntentSender;
+    private RefinementResultReceiver mRefinementResultReceiver;
+
+    private Intent mReferrerFillInIntent;
+
+    private ChooserListAdapter mChooserListAdapter;
+    private ChooserRowAdapter mChooserRowAdapter;
+
+    private final List<ChooserTargetServiceConnection> mServiceConnections = new ArrayList<>();
+
+    private static final int CHOOSER_TARGET_SERVICE_RESULT = 1;
+    private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT = 2;
+
+    private final Handler mChooserHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case CHOOSER_TARGET_SERVICE_RESULT:
+                    if (DEBUG) Log.d(TAG, "CHOOSER_TARGET_SERVICE_RESULT");
+                    if (isDestroyed()) break;
+                    final ServiceResultInfo sri = (ServiceResultInfo) msg.obj;
+                    if (!mServiceConnections.contains(sri.connection)) {
+                        Log.w(TAG, "ChooserTargetServiceConnection " + sri.connection
+                                + " returned after being removed from active connections."
+                                + " Have you considered returning results faster?");
+                        break;
+                    }
+                    if (sri.resultTargets != null) {
+                        mChooserListAdapter.addServiceResults(sri.originalTarget,
+                                sri.resultTargets);
+                    }
+                    unbindService(sri.connection);
+                    sri.connection.destroy();
+                    mServiceConnections.remove(sri.connection);
+                    if (mServiceConnections.isEmpty()) {
+                        mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
+                        sendVoiceChoicesIfNeeded();
+                    }
+                    break;
+
+                case CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT:
+                    if (DEBUG) {
+                        Log.d(TAG, "CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT; unbinding services");
+                    }
+                    unbindRemainingServices();
+                    sendVoiceChoicesIfNeeded();
+                    break;
+
+                default:
+                    super.handleMessage(msg);
+            }
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        Intent intent = getIntent();
+        Parcelable targetParcelable = intent.getParcelableExtra(Intent.EXTRA_INTENT);
+        if (!(targetParcelable instanceof Intent)) {
+            Log.w("ChooserActivity", "Target is not an intent: " + targetParcelable);
+            finish();
+            super.onCreate(null);
+            return;
+        }
+        Intent target = (Intent) targetParcelable;
+        if (target != null) {
+            modifyTargetIntent(target);
+        }
+        Parcelable[] targetsParcelable
+                = intent.getParcelableArrayExtra(Intent.EXTRA_ALTERNATE_INTENTS);
+        if (targetsParcelable != null) {
+            final boolean offset = target == null;
+            Intent[] additionalTargets =
+                    new Intent[offset ? targetsParcelable.length - 1 : targetsParcelable.length];
+            for (int i = 0; i < targetsParcelable.length; i++) {
+                if (!(targetsParcelable[i] instanceof Intent)) {
+                    Log.w(TAG, "EXTRA_ALTERNATE_INTENTS array entry #" + i + " is not an Intent: "
+                            + targetsParcelable[i]);
+                    finish();
+                    super.onCreate(null);
+                    return;
+                }
+                final Intent additionalTarget = (Intent) targetsParcelable[i];
+                if (i == 0 && target == null) {
+                    target = additionalTarget;
+                    modifyTargetIntent(target);
+                } else {
+                    additionalTargets[offset ? i - 1 : i] = additionalTarget;
+                    modifyTargetIntent(additionalTarget);
+                }
+            }
+            setAdditionalTargets(additionalTargets);
+        }
+
+        mReplacementExtras = intent.getBundleExtra(Intent.EXTRA_REPLACEMENT_EXTRAS);
+        CharSequence title = intent.getCharSequenceExtra(Intent.EXTRA_TITLE);
+        int defaultTitleRes = 0;
+        if (title == null) {
+            defaultTitleRes = com.android.internal.R.string.chooseActivity;
+        }
+        Parcelable[] pa = intent.getParcelableArrayExtra(Intent.EXTRA_INITIAL_INTENTS);
+        Intent[] initialIntents = null;
+        if (pa != null) {
+            initialIntents = new Intent[pa.length];
+            for (int i=0; i<pa.length; i++) {
+                if (!(pa[i] instanceof Intent)) {
+                    Log.w(TAG, "Initial intent #" + i + " not an Intent: " + pa[i]);
+                    finish();
+                    super.onCreate(null);
+                    return;
+                }
+                final Intent in = (Intent) pa[i];
+                modifyTargetIntent(in);
+                initialIntents[i] = in;
+            }
+        }
+
+        mReferrerFillInIntent = new Intent().putExtra(Intent.EXTRA_REFERRER, getReferrer());
+
+        mChosenComponentSender = intent.getParcelableExtra(
+                Intent.EXTRA_CHOSEN_COMPONENT_INTENT_SENDER);
+        mRefinementIntentSender = intent.getParcelableExtra(
+                Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER);
+        setSafeForwardingMode(true);
+        super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents,
+                null, false);
+
+        MetricsLogger.action(this, MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN);
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        if (mRefinementResultReceiver != null) {
+            mRefinementResultReceiver.destroy();
+            mRefinementResultReceiver = null;
+        }
+        unbindRemainingServices();
+        mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_RESULT);
+    }
+
+    @Override
+    public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) {
+        Intent result = defIntent;
+        if (mReplacementExtras != null) {
+            final Bundle replExtras = mReplacementExtras.getBundle(aInfo.packageName);
+            if (replExtras != null) {
+                result = new Intent(defIntent);
+                result.putExtras(replExtras);
+            }
+        }
+        if (aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_PARENT)
+                || aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE)) {
+            result = Intent.createChooser(result,
+                    getIntent().getCharSequenceExtra(Intent.EXTRA_TITLE));
+        }
+        return result;
+    }
+
+    @Override
+    void onActivityStarted(TargetInfo cti) {
+        if (mChosenComponentSender != null) {
+            final ComponentName target = cti.getResolvedComponentName();
+            if (target != null) {
+                final Intent fillIn = new Intent().putExtra(Intent.EXTRA_CHOSEN_COMPONENT, target);
+                try {
+                    mChosenComponentSender.sendIntent(this, Activity.RESULT_OK, fillIn, null, null);
+                } catch (IntentSender.SendIntentException e) {
+                    Slog.e(TAG, "Unable to launch supplied IntentSender to report "
+                            + "the chosen component: " + e);
+                }
+            }
+        }
+    }
+
+    @Override
+    void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter,
+            boolean alwaysUseOption) {
+        final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null;
+        mChooserListAdapter = (ChooserListAdapter) adapter;
+        mChooserRowAdapter = new ChooserRowAdapter(mChooserListAdapter);
+        mChooserRowAdapter.registerDataSetObserver(new OffsetDataSetObserver(adapterView));
+        adapterView.setAdapter(mChooserRowAdapter);
+        if (listView != null) {
+            listView.setItemsCanFocus(true);
+        }
+    }
+
+    @Override
+    int getLayoutResource() {
+        return R.layout.chooser_grid;
+    }
+
+    @Override
+    boolean shouldGetActivityMetadata() {
+        return true;
+    }
+
+    @Override
+    boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
+        final Intent intent = target.getResolvedIntent();
+        final ResolveInfo resolve = target.getResolveInfo();
+
+        // When GET_CONTENT is handled by the DocumentsUI system component,
+        // we're okay automatically launching it, since it offers it's own
+        // intent disambiguation UI.
+        if (intent != null && Intent.ACTION_GET_CONTENT.equals(intent.getAction())
+                && resolve != null && resolve.priority > 0
+                && resolve.activityInfo != null && DocumentsContract.PACKAGE_DOCUMENTS_UI
+                        .equals(resolve.activityInfo.packageName)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    private void modifyTargetIntent(Intent in) {
+        final String action = in.getAction();
+        if (Intent.ACTION_SEND.equals(action) ||
+                Intent.ACTION_SEND_MULTIPLE.equals(action)) {
+            in.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT |
+                    Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+        }
+    }
+
+    @Override
+    protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) {
+        if (mRefinementIntentSender != null) {
+            final Intent fillIn = new Intent();
+            final List<Intent> sourceIntents = target.getAllSourceIntents();
+            if (!sourceIntents.isEmpty()) {
+                fillIn.putExtra(Intent.EXTRA_INTENT, sourceIntents.get(0));
+                if (sourceIntents.size() > 1) {
+                    final Intent[] alts = new Intent[sourceIntents.size() - 1];
+                    for (int i = 1, N = sourceIntents.size(); i < N; i++) {
+                        alts[i - 1] = sourceIntents.get(i);
+                    }
+                    fillIn.putExtra(Intent.EXTRA_ALTERNATE_INTENTS, alts);
+                }
+                if (mRefinementResultReceiver != null) {
+                    mRefinementResultReceiver.destroy();
+                }
+                mRefinementResultReceiver = new RefinementResultReceiver(this, target, null);
+                fillIn.putExtra(Intent.EXTRA_RESULT_RECEIVER,
+                        mRefinementResultReceiver);
+                try {
+                    mRefinementIntentSender.sendIntent(this, 0, fillIn, null, null);
+                    return false;
+                } catch (SendIntentException e) {
+                    Log.e(TAG, "Refinement IntentSender failed to send", e);
+                }
+            }
+        }
+        return super.onTargetSelected(target, alwaysCheck);
+    }
+
+    @Override
+    void startSelected(int which, boolean always, boolean filtered) {
+        super.startSelected(which, always, filtered);
+
+        if (mChooserListAdapter != null) {
+            // Log the index of which type of target the user picked.
+            // Lower values mean the ranking was better.
+            int cat = 0;
+            int value = which;
+            switch (mChooserListAdapter.getPositionTargetType(which)) {
+                case ChooserListAdapter.TARGET_CALLER:
+                    cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET;
+                    break;
+                case ChooserListAdapter.TARGET_SERVICE:
+                    cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET;
+                    value -= mChooserListAdapter.getCallerTargetCount();
+                    break;
+                case ChooserListAdapter.TARGET_STANDARD:
+                    cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_STANDARD_TARGET;
+                    value -= mChooserListAdapter.getCallerTargetCount()
+                            + mChooserListAdapter.getServiceTargetCount();
+                    break;
+            }
+
+            if (cat != 0) {
+                MetricsLogger.action(this, cat, value);
+            }
+        }
+    }
+
+    void queryTargetServices(ChooserListAdapter adapter) {
+        final PackageManager pm = getPackageManager();
+        int targetsToQuery = 0;
+        for (int i = 0, N = adapter.getDisplayResolveInfoCount(); i < N; i++) {
+            final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
+            if (adapter.getScore(dri) == 0) {
+                // A score of 0 means the app hasn't been used in some time;
+                // don't query it as it's not likely to be relevant.
+                continue;
+            }
+            final ActivityInfo ai = dri.getResolveInfo().activityInfo;
+            final Bundle md = ai.metaData;
+            final String serviceName = md != null ? convertServiceName(ai.packageName,
+                    md.getString(ChooserTargetService.META_DATA_NAME)) : null;
+            if (serviceName != null) {
+                final ComponentName serviceComponent = new ComponentName(
+                        ai.packageName, serviceName);
+                final Intent serviceIntent = new Intent(ChooserTargetService.SERVICE_INTERFACE)
+                        .setComponent(serviceComponent);
+
+                if (DEBUG) {
+                    Log.d(TAG, "queryTargets found target with service " + serviceComponent);
+                }
+
+                try {
+                    final String perm = pm.getServiceInfo(serviceComponent, 0).permission;
+                    if (!ChooserTargetService.BIND_PERMISSION.equals(perm)) {
+                        Log.w(TAG, "ChooserTargetService " + serviceComponent + " does not require"
+                                + " permission " + ChooserTargetService.BIND_PERMISSION
+                                + " - this service will not be queried for ChooserTargets."
+                                + " add android:permission=\""
+                                + ChooserTargetService.BIND_PERMISSION + "\""
+                                + " to the <service> tag for " + serviceComponent
+                                + " in the manifest.");
+                        continue;
+                    }
+                } catch (NameNotFoundException e) {
+                    Log.e(TAG, "Could not look up service " + serviceComponent, e);
+                    continue;
+                }
+
+                final ChooserTargetServiceConnection conn =
+                        new ChooserTargetServiceConnection(this, dri);
+                if (bindServiceAsUser(serviceIntent, conn, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND,
+                        UserHandle.CURRENT)) {
+                    if (DEBUG) {
+                        Log.d(TAG, "Binding service connection for target " + dri
+                                + " intent " + serviceIntent);
+                    }
+                    mServiceConnections.add(conn);
+                    targetsToQuery++;
+                }
+            }
+            if (targetsToQuery >= QUERY_TARGET_SERVICE_LIMIT) {
+                if (DEBUG) Log.d(TAG, "queryTargets hit query target limit "
+                        + QUERY_TARGET_SERVICE_LIMIT);
+                break;
+            }
+        }
+
+        if (!mServiceConnections.isEmpty()) {
+            if (DEBUG) Log.d(TAG, "queryTargets setting watchdog timer for "
+                    + WATCHDOG_TIMEOUT_MILLIS + "ms");
+            mChooserHandler.sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT,
+                    WATCHDOG_TIMEOUT_MILLIS);
+        } else {
+            sendVoiceChoicesIfNeeded();
+        }
+    }
+
+    private String convertServiceName(String packageName, String serviceName) {
+        if (TextUtils.isEmpty(serviceName)) {
+            return null;
+        }
+
+        final String fullName;
+        if (serviceName.startsWith(".")) {
+            // Relative to the app package. Prepend the app package name.
+            fullName = packageName + serviceName;
+        } else if (serviceName.indexOf('.') >= 0) {
+            // Fully qualified package name.
+            fullName = serviceName;
+        } else {
+            fullName = null;
+        }
+        return fullName;
+    }
+
+    void unbindRemainingServices() {
+        if (DEBUG) {
+            Log.d(TAG, "unbindRemainingServices, " + mServiceConnections.size() + " left");
+        }
+        for (int i = 0, N = mServiceConnections.size(); i < N; i++) {
+            final ChooserTargetServiceConnection conn = mServiceConnections.get(i);
+            if (DEBUG) Log.d(TAG, "unbinding " + conn);
+            unbindService(conn);
+            conn.destroy();
+        }
+        mServiceConnections.clear();
+        mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
+    }
+
+    void onSetupVoiceInteraction() {
+        // Do nothing. We'll send the voice stuff ourselves.
+    }
+
+    void onRefinementResult(TargetInfo selectedTarget, Intent matchingIntent) {
+        if (mRefinementResultReceiver != null) {
+            mRefinementResultReceiver.destroy();
+            mRefinementResultReceiver = null;
+        }
+
+        if (selectedTarget == null) {
+            Log.e(TAG, "Refinement result intent did not match any known targets; canceling");
+        } else if (!checkTargetSourceIntent(selectedTarget, matchingIntent)) {
+            Log.e(TAG, "onRefinementResult: Selected target " + selectedTarget
+                    + " cannot match refined source intent " + matchingIntent);
+        } else if (super.onTargetSelected(selectedTarget.cloneFilledIn(matchingIntent, 0), false)) {
+            finish();
+            return;
+        }
+        onRefinementCanceled();
+    }
+
+    void onRefinementCanceled() {
+        if (mRefinementResultReceiver != null) {
+            mRefinementResultReceiver.destroy();
+            mRefinementResultReceiver = null;
+        }
+        finish();
+    }
+
+    boolean checkTargetSourceIntent(TargetInfo target, Intent matchingIntent) {
+        final List<Intent> targetIntents = target.getAllSourceIntents();
+        for (int i = 0, N = targetIntents.size(); i < N; i++) {
+            final Intent targetIntent = targetIntents.get(i);
+            if (targetIntent.filterEquals(matchingIntent)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    void filterServiceTargets(String packageName, List<ChooserTarget> targets) {
+        if (targets == null) {
+            return;
+        }
+
+        final PackageManager pm = getPackageManager();
+        for (int i = targets.size() - 1; i >= 0; i--) {
+            final ChooserTarget target = targets.get(i);
+            final ComponentName targetName = target.getComponentName();
+            if (packageName != null && packageName.equals(targetName.getPackageName())) {
+                // Anything from the original target's package is fine.
+                continue;
+            }
+
+            boolean remove;
+            try {
+                final ActivityInfo ai = pm.getActivityInfo(targetName, 0);
+                remove = !ai.exported || ai.permission != null;
+            } catch (NameNotFoundException e) {
+                Log.e(TAG, "Target " + target + " returned by " + packageName
+                        + " component not found");
+                remove = true;
+            }
+
+            if (remove) {
+                targets.remove(i);
+            }
+        }
+    }
+
+    @Override
+    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);
+        if (DEBUG) Log.d(TAG, "Adapter created; querying services");
+        queryTargetServices(adapter);
+        return adapter;
+    }
+
+    final class ChooserTargetInfo implements TargetInfo {
+        private final DisplayResolveInfo mSourceInfo;
+        private final ResolveInfo mBackupResolveInfo;
+        private final ChooserTarget mChooserTarget;
+        private Drawable mBadgeIcon = null;
+        private CharSequence mBadgeContentDescription;
+        private Drawable mDisplayIcon;
+        private final Intent mFillInIntent;
+        private final int mFillInFlags;
+        private final float mModifiedScore;
+
+        public ChooserTargetInfo(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);
+                    }
+                }
+            }
+            final Icon icon = chooserTarget.getIcon();
+            // TODO do this in the background
+            mDisplayIcon = icon != null ? icon.loadDrawable(ChooserActivity.this) : null;
+
+            if (sourceInfo != null) {
+                mBackupResolveInfo = null;
+            } else {
+                mBackupResolveInfo = getPackageManager().resolveActivity(getResolvedIntent(), 0);
+            }
+
+            mFillInIntent = null;
+            mFillInFlags = 0;
+        }
+
+        private ChooserTargetInfo(ChooserTargetInfo 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;
+        }
+
+        public float getModifiedScore() {
+            return mModifiedScore;
+        }
+
+        @Override
+        public Intent getResolvedIntent() {
+            if (mSourceInfo != null) {
+                return mSourceInfo.getResolvedIntent();
+            }
+            return getTargetIntent();
+        }
+
+        @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 = mSourceInfo != null
+                    ? mSourceInfo.getResolvedIntent() : getTargetIntent();
+            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(Activity activity, Bundle options, int userId) {
+            final Intent intent = getBaseIntentToSend();
+            if (intent == null) {
+                return false;
+            }
+            intent.setComponent(mChooserTarget.getComponentName());
+            intent.putExtras(mChooserTarget.getIntentExtras());
+            activity.startActivityAsCaller(intent, options, true, userId);
+            return true;
+        }
+
+        @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 mChooserTarget.getTitle();
+        }
+
+        @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;
+        }
+
+        @Override
+        public Drawable getBadgeIcon() {
+            return mBadgeIcon;
+        }
+
+        @Override
+        public CharSequence getBadgeContentDescription() {
+            return mBadgeContentDescription;
+        }
+
+        @Override
+        public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
+            return new ChooserTargetInfo(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;
+        }
+    }
+
+    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;
+
+        private static final int MAX_SERVICE_TARGETS = 8;
+
+        private final List<ChooserTargetInfo> mServiceTargets = new ArrayList<>();
+        private final List<TargetInfo> mCallerTargets = new ArrayList<>();
+
+        private float mLateFee = 1.f;
+
+        private final BaseChooserTargetComparator mBaseTargetComparator
+                = new BaseChooserTargetComparator();
+
+        public ChooserListAdapter(Context context, List<Intent> payloadIntents,
+                Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
+                boolean filterLastUsed) {
+            // 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);
+
+            if (initialIntents != null) {
+                final PackageManager pm = getPackageManager();
+                for (int i = 0; i < initialIntents.length; i++) {
+                    final Intent ii = initialIntents[i];
+                    if (ii == null) {
+                        continue;
+                    }
+                    final ActivityInfo ai = ii.resolveActivityInfo(pm, 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;
+                    }
+                    mCallerTargets.add(new DisplayResolveInfo(ii, ri,
+                            ri.loadLabel(pm), null, ii));
+                }
+            }
+        }
+
+        @Override
+        public boolean showsExtendedInfo(TargetInfo info) {
+            // We have badges so we don't need this text shown.
+            return false;
+        }
+
+        @Override
+        public View onCreateView(ViewGroup parent) {
+            return mInflater.inflate(
+                    com.android.internal.R.layout.resolve_grid_item, parent, false);
+        }
+
+        @Override
+        public void onListRebuilt() {
+            if (mServiceTargets != null) {
+                pruneServiceTargets();
+            }
+        }
+
+        @Override
+        public boolean shouldGetResolvedFilter() {
+            return true;
+        }
+
+        @Override
+        public int getCount() {
+            return super.getCount() + getServiceTargetCount() + getCallerTargetCount();
+        }
+
+        @Override
+        public int getUnfilteredCount() {
+            return super.getUnfilteredCount() + getServiceTargetCount() + getCallerTargetCount();
+        }
+
+        public int getCallerTargetCount() {
+            return mCallerTargets.size();
+        }
+
+        public int getServiceTargetCount() {
+            return Math.min(mServiceTargets.size(), MAX_SERVICE_TARGETS);
+        }
+
+        public int getStandardTargetCount() {
+            return super.getCount();
+        }
+
+        public int getPositionTargetType(int position) {
+            int offset = 0;
+
+            final int callerTargetCount = getCallerTargetCount();
+            if (position < callerTargetCount) {
+                return TARGET_CALLER;
+            }
+            offset += callerTargetCount;
+
+            final int serviceTargetCount = getServiceTargetCount();
+            if (position - offset < serviceTargetCount) {
+                return TARGET_SERVICE;
+            }
+            offset += serviceTargetCount;
+
+            final int standardTargetCount = super.getCount();
+            if (position - offset < standardTargetCount) {
+                return TARGET_STANDARD;
+            }
+
+            return TARGET_BAD;
+        }
+
+        @Override
+        public TargetInfo getItem(int position) {
+            return targetInfoForPosition(position, true);
+        }
+
+        @Override
+        public TargetInfo targetInfoForPosition(int position, boolean filtered) {
+            int offset = 0;
+
+            final int callerTargetCount = getCallerTargetCount();
+            if (position < callerTargetCount) {
+                return mCallerTargets.get(position);
+            }
+            offset += callerTargetCount;
+
+            final int serviceTargetCount = getServiceTargetCount();
+            if (position - offset < serviceTargetCount) {
+                return mServiceTargets.get(position - offset);
+            }
+            offset += serviceTargetCount;
+
+            return filtered ? super.getItem(position - offset)
+                    : getDisplayInfoAt(position - offset);
+        }
+
+        public void addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets) {
+            if (DEBUG) Log.d(TAG, "addServiceResults " + origTarget + ", " + targets.size()
+                    + " targets");
+            final float parentScore = getScore(origTarget);
+            Collections.sort(targets, mBaseTargetComparator);
+            float lastScore = 0;
+            for (int i = 0, N = targets.size(); i < N; i++) {
+                final ChooserTarget target = targets.get(i);
+                float targetScore = target.getScore();
+                targetScore *= parentScore;
+                targetScore *= mLateFee;
+                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;
+                }
+                insertServiceTarget(new ChooserTargetInfo(origTarget, target, targetScore));
+
+                if (DEBUG) {
+                    Log.d(TAG, " => " + target.toString() + " score=" + targetScore
+                            + " base=" + target.getScore()
+                            + " lastScore=" + lastScore
+                            + " parentScore=" + parentScore
+                            + " lateFee=" + mLateFee);
+                }
+
+                lastScore = targetScore;
+            }
+
+            mLateFee *= 0.95f;
+
+            notifyDataSetChanged();
+        }
+
+        private void insertServiceTarget(ChooserTargetInfo chooserTargetInfo) {
+            final float newScore = chooserTargetInfo.getModifiedScore();
+            for (int i = 0, N = mServiceTargets.size(); i < N; i++) {
+                final ChooserTargetInfo serviceTarget = mServiceTargets.get(i);
+                if (newScore > serviceTarget.getModifiedScore()) {
+                    mServiceTargets.add(i, chooserTargetInfo);
+                    return;
+                }
+            }
+            mServiceTargets.add(chooserTargetInfo);
+        }
+
+        private void pruneServiceTargets() {
+            if (DEBUG) Log.d(TAG, "pruneServiceTargets");
+            for (int i = mServiceTargets.size() - 1; i >= 0; i--) {
+                final ChooserTargetInfo cti = mServiceTargets.get(i);
+                if (!hasResolvedTarget(cti.getResolveInfo())) {
+                    if (DEBUG) Log.d(TAG, " => " + i + " " + cti);
+                    mServiceTargets.remove(i);
+                }
+            }
+        }
+    }
+
+    static class BaseChooserTargetComparator implements Comparator<ChooserTarget> {
+        @Override
+        public int compare(ChooserTarget lhs, ChooserTarget rhs) {
+            // Descending order
+            return (int) Math.signum(rhs.getScore() - lhs.getScore());
+        }
+    }
+
+    static class RowScale {
+        private static final int DURATION = 400;
+
+        float mScale;
+        ChooserRowAdapter mAdapter;
+        private final ObjectAnimator mAnimator;
+
+        public static final FloatProperty<RowScale> PROPERTY =
+                new FloatProperty<RowScale>("scale") {
+            @Override
+            public void setValue(RowScale object, float value) {
+                object.mScale = value;
+                object.mAdapter.notifyDataSetChanged();
+            }
+
+            @Override
+            public Float get(RowScale object) {
+                return object.mScale;
+            }
+        };
+
+        public RowScale(@NonNull ChooserRowAdapter adapter, float from, float to) {
+            mAdapter = adapter;
+            mScale = from;
+            if (from == to) {
+                mAnimator = null;
+                return;
+            }
+
+            mAnimator = ObjectAnimator.ofFloat(this, PROPERTY, from, to).setDuration(DURATION);
+        }
+
+        public RowScale setInterpolator(Interpolator interpolator) {
+            if (mAnimator != null) {
+                mAnimator.setInterpolator(interpolator);
+            }
+            return this;
+        }
+
+        public float get() {
+            return mScale;
+        }
+
+        public void startAnimation() {
+            if (mAnimator != null) {
+                mAnimator.start();
+            }
+        }
+
+        public void cancelAnimation() {
+            if (mAnimator != null) {
+                mAnimator.cancel();
+            }
+        }
+    }
+
+    class ChooserRowAdapter extends BaseAdapter {
+        private ChooserListAdapter mChooserListAdapter;
+        private final LayoutInflater mLayoutInflater;
+        private final int mColumnCount = 4;
+        private RowScale[] mServiceTargetScale;
+        private final Interpolator mInterpolator;
+
+        public ChooserRowAdapter(ChooserListAdapter wrappedAdapter) {
+            mChooserListAdapter = wrappedAdapter;
+            mLayoutInflater = LayoutInflater.from(ChooserActivity.this);
+
+            mInterpolator = AnimationUtils.loadInterpolator(ChooserActivity.this,
+                    android.R.interpolator.decelerate_quint);
+
+            wrappedAdapter.registerDataSetObserver(new DataSetObserver() {
+                @Override
+                public void onChanged() {
+                    super.onChanged();
+                    final int rcount = getServiceTargetRowCount();
+                    if (mServiceTargetScale == null
+                            || mServiceTargetScale.length != rcount) {
+                        RowScale[] old = mServiceTargetScale;
+                        int oldRCount = old != null ? old.length : 0;
+                        mServiceTargetScale = new RowScale[rcount];
+                        if (old != null && rcount > 0) {
+                            System.arraycopy(old, 0, mServiceTargetScale, 0,
+                                    Math.min(old.length, rcount));
+                        }
+
+                        for (int i = rcount; i < oldRCount; i++) {
+                            old[i].cancelAnimation();
+                        }
+
+                        for (int i = oldRCount; i < rcount; i++) {
+                            final RowScale rs = new RowScale(ChooserRowAdapter.this, 0.f, 1.f)
+                                    .setInterpolator(mInterpolator);
+                            mServiceTargetScale[i] = rs;
+                        }
+
+                        // Start the animations in a separate loop.
+                        // The process of starting animations will result in
+                        // binding views to set up initial values, and we must
+                        // have ALL of the new RowScale objects created above before
+                        // we get started.
+                        for (int i = oldRCount; i < rcount; i++) {
+                            mServiceTargetScale[i].startAnimation();
+                        }
+                    }
+
+                    notifyDataSetChanged();
+                }
+
+                @Override
+                public void onInvalidated() {
+                    super.onInvalidated();
+                    notifyDataSetInvalidated();
+                    if (mServiceTargetScale != null) {
+                        for (RowScale rs : mServiceTargetScale) {
+                            rs.cancelAnimation();
+                        }
+                    }
+                }
+            });
+        }
+
+        private float getRowScale(int rowPosition) {
+            final int start = getCallerTargetRowCount();
+            final int end = start + getServiceTargetRowCount();
+            if (rowPosition >= start && rowPosition < end) {
+                return mServiceTargetScale[rowPosition - start].get();
+            }
+            return 1.f;
+        }
+
+        @Override
+        public int getCount() {
+            return (int) (
+                    getCallerTargetRowCount()
+                    + getServiceTargetRowCount()
+                    + Math.ceil((float) mChooserListAdapter.getStandardTargetCount() / mColumnCount)
+            );
+        }
+
+        public int getCallerTargetRowCount() {
+            return (int) Math.ceil(
+                    (float) mChooserListAdapter.getCallerTargetCount() / mColumnCount);
+        }
+
+        public int getServiceTargetRowCount() {
+            return (int) Math.ceil(
+                    (float) mChooserListAdapter.getServiceTargetCount() / mColumnCount);
+        }
+
+        @Override
+        public Object getItem(int position) {
+            // We have nothing useful to return here.
+            return position;
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return position;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            final RowViewHolder holder;
+            if (convertView == null) {
+                holder = createViewHolder(parent);
+            } else {
+                holder = (RowViewHolder) convertView.getTag();
+            }
+            bindViewHolder(position, holder);
+
+            return holder.row;
+        }
+
+        RowViewHolder createViewHolder(ViewGroup parent) {
+            final ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
+                    parent, false);
+            final RowViewHolder holder = new RowViewHolder(row, mColumnCount);
+            final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+
+            for (int i = 0; i < mColumnCount; i++) {
+                final View v = mChooserListAdapter.createView(row);
+                final int column = i;
+                v.setOnClickListener(new OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        startSelected(holder.itemIndices[column], false, true);
+                    }
+                });
+                v.setOnLongClickListener(new OnLongClickListener() {
+                    @Override
+                    public boolean onLongClick(View v) {
+                        showAppDetails(
+                                mChooserListAdapter.resolveInfoForPosition(
+                                        holder.itemIndices[column], true));
+                        return true;
+                    }
+                });
+                row.addView(v);
+                holder.cells[i] = v;
+
+                // Force height to be a given so we don't have visual disruption during scaling.
+                LayoutParams lp = v.getLayoutParams();
+                v.measure(spec, spec);
+                if (lp == null) {
+                    lp = new LayoutParams(LayoutParams.MATCH_PARENT, v.getMeasuredHeight());
+                    row.setLayoutParams(lp);
+                } else {
+                    lp.height = v.getMeasuredHeight();
+                }
+            }
+
+            // Pre-measure so we can scale later.
+            holder.measure();
+            LayoutParams lp = row.getLayoutParams();
+            if (lp == null) {
+                lp = new LayoutParams(LayoutParams.MATCH_PARENT, holder.measuredRowHeight);
+                row.setLayoutParams(lp);
+            } else {
+                lp.height = holder.measuredRowHeight;
+            }
+            row.setTag(holder);
+            return holder;
+        }
+
+        void bindViewHolder(int rowPosition, RowViewHolder holder) {
+            final int start = getFirstRowPosition(rowPosition);
+            final int startType = mChooserListAdapter.getPositionTargetType(start);
+
+            int end = start + mColumnCount - 1;
+            while (mChooserListAdapter.getPositionTargetType(end) != startType && end >= start) {
+                end--;
+            }
+
+            if (startType == ChooserListAdapter.TARGET_SERVICE) {
+                holder.row.setBackgroundColor(
+                        getColor(R.color.chooser_service_row_background_color));
+            } else {
+                holder.row.setBackgroundColor(Color.TRANSPARENT);
+            }
+
+            final int oldHeight = holder.row.getLayoutParams().height;
+            holder.row.getLayoutParams().height = Math.max(1,
+                    (int) (holder.measuredRowHeight * getRowScale(rowPosition)));
+            if (holder.row.getLayoutParams().height != oldHeight) {
+                holder.row.requestLayout();
+            }
+
+            for (int i = 0; i < mColumnCount; i++) {
+                final View v = holder.cells[i];
+                if (start + i <= end) {
+                    v.setVisibility(View.VISIBLE);
+                    holder.itemIndices[i] = start + i;
+                    mChooserListAdapter.bindView(holder.itemIndices[i], v);
+                } else {
+                    v.setVisibility(View.GONE);
+                }
+            }
+        }
+
+        int getFirstRowPosition(int row) {
+            final int callerCount = mChooserListAdapter.getCallerTargetCount();
+            final int callerRows = (int) Math.ceil((float) callerCount / mColumnCount);
+
+            if (row < callerRows) {
+                return row * mColumnCount;
+            }
+
+            final int serviceCount = mChooserListAdapter.getServiceTargetCount();
+            final int serviceRows = (int) Math.ceil((float) serviceCount / mColumnCount);
+
+            if (row < callerRows + serviceRows) {
+                return callerCount + (row - callerRows) * mColumnCount;
+            }
+
+            return callerCount + serviceCount
+                    + (row - callerRows - serviceRows) * mColumnCount;
+        }
+    }
+
+    static class RowViewHolder {
+        final View[] cells;
+        final ViewGroup row;
+        int measuredRowHeight;
+        int[] itemIndices;
+
+        public RowViewHolder(ViewGroup row, int cellCount) {
+            this.row = row;
+            this.cells = new View[cellCount];
+            this.itemIndices = new int[cellCount];
+        }
+
+        public void measure() {
+            final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+            row.measure(spec, spec);
+            measuredRowHeight = row.getMeasuredHeight();
+        }
+    }
+
+    static class ChooserTargetServiceConnection implements ServiceConnection {
+        private final DisplayResolveInfo mOriginalTarget;
+        private ComponentName mConnectedComponent;
+        private ChooserActivity mChooserActivity;
+        private final Object mLock = new Object();
+
+        private final IChooserTargetResult mChooserTargetResult = new IChooserTargetResult.Stub() {
+            @Override
+            public void sendResult(List<ChooserTarget> targets) throws RemoteException {
+                synchronized (mLock) {
+                    if (mChooserActivity == null) {
+                        Log.e(TAG, "destroyed ChooserTargetServiceConnection received result from "
+                                + mConnectedComponent + "; ignoring...");
+                        return;
+                    }
+                    mChooserActivity.filterServiceTargets(
+                            mOriginalTarget.getResolveInfo().activityInfo.packageName, targets);
+                    final Message msg = Message.obtain();
+                    msg.what = CHOOSER_TARGET_SERVICE_RESULT;
+                    msg.obj = new ServiceResultInfo(mOriginalTarget, targets,
+                            ChooserTargetServiceConnection.this);
+                    mChooserActivity.mChooserHandler.sendMessage(msg);
+                }
+            }
+        };
+
+        public ChooserTargetServiceConnection(ChooserActivity chooserActivity,
+                DisplayResolveInfo dri) {
+            mChooserActivity = chooserActivity;
+            mOriginalTarget = dri;
+        }
+
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            if (DEBUG) Log.d(TAG, "onServiceConnected: " + name);
+            synchronized (mLock) {
+                if (mChooserActivity == null) {
+                    Log.e(TAG, "destroyed ChooserTargetServiceConnection got onServiceConnected");
+                    return;
+                }
+
+                final IChooserTargetService icts = IChooserTargetService.Stub.asInterface(service);
+                try {
+                    icts.getChooserTargets(mOriginalTarget.getResolvedComponentName(),
+                            mOriginalTarget.getResolveInfo().filter, mChooserTargetResult);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e);
+                    mChooserActivity.unbindService(this);
+                    destroy();
+                    mChooserActivity.mServiceConnections.remove(this);
+                }
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            if (DEBUG) Log.d(TAG, "onServiceDisconnected: " + name);
+            synchronized (mLock) {
+                if (mChooserActivity == null) {
+                    Log.e(TAG,
+                            "destroyed ChooserTargetServiceConnection got onServiceDisconnected");
+                    return;
+                }
+
+                mChooserActivity.unbindService(this);
+                destroy();
+                mChooserActivity.mServiceConnections.remove(this);
+                if (mChooserActivity.mServiceConnections.isEmpty()) {
+                    mChooserActivity.mChooserHandler.removeMessages(
+                            CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
+                    mChooserActivity.sendVoiceChoicesIfNeeded();
+                }
+                mConnectedComponent = null;
+            }
+        }
+
+        public void destroy() {
+            synchronized (mLock) {
+                mChooserActivity = null;
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "ChooserTargetServiceConnection{service="
+                    + mConnectedComponent + ", activity="
+                    + mOriginalTarget.getResolveInfo().activityInfo.toString() + "}";
+        }
+    }
+
+    static class ServiceResultInfo {
+        public final DisplayResolveInfo originalTarget;
+        public final List<ChooserTarget> resultTargets;
+        public final ChooserTargetServiceConnection connection;
+
+        public ServiceResultInfo(DisplayResolveInfo ot, List<ChooserTarget> rt,
+                ChooserTargetServiceConnection c) {
+            originalTarget = ot;
+            resultTargets = rt;
+            connection = c;
+        }
+    }
+
+    static class RefinementResultReceiver extends ResultReceiver {
+        private ChooserActivity mChooserActivity;
+        private TargetInfo mSelectedTarget;
+
+        public RefinementResultReceiver(ChooserActivity host, TargetInfo target,
+                Handler handler) {
+            super(handler);
+            mChooserActivity = host;
+            mSelectedTarget = target;
+        }
+
+        @Override
+        protected void onReceiveResult(int resultCode, Bundle resultData) {
+            if (mChooserActivity == null) {
+                Log.e(TAG, "Destroyed RefinementResultReceiver received a result");
+                return;
+            }
+            if (resultData == null) {
+                Log.e(TAG, "RefinementResultReceiver received null resultData");
+                return;
+            }
+
+            switch (resultCode) {
+                case RESULT_CANCELED:
+                    mChooserActivity.onRefinementCanceled();
+                    break;
+                case RESULT_OK:
+                    Parcelable intentParcelable = resultData.getParcelable(Intent.EXTRA_INTENT);
+                    if (intentParcelable instanceof Intent) {
+                        mChooserActivity.onRefinementResult(mSelectedTarget,
+                                (Intent) intentParcelable);
+                    } else {
+                        Log.e(TAG, "RefinementResultReceiver received RESULT_OK but no Intent"
+                                + " in resultData with key Intent.EXTRA_INTENT");
+                    }
+                    break;
+                default:
+                    Log.w(TAG, "Unknown result code " + resultCode
+                            + " sent to RefinementResultReceiver");
+                    break;
+            }
+        }
+
+        public void destroy() {
+            mChooserActivity = null;
+            mSelectedTarget = null;
+        }
+    }
+
+    class OffsetDataSetObserver extends DataSetObserver {
+        private final AbsListView mListView;
+        private int mCachedViewType = -1;
+        private View mCachedView;
+
+        public OffsetDataSetObserver(AbsListView listView) {
+            mListView = listView;
+        }
+
+        @Override
+        public void onChanged() {
+            if (mResolverDrawerLayout == null) {
+                return;
+            }
+
+            final int chooserTargetRows = mChooserRowAdapter.getServiceTargetRowCount();
+            int offset = 0;
+            for (int i = 0; i < chooserTargetRows; i++)  {
+                final int pos = mChooserRowAdapter.getCallerTargetRowCount() + i;
+                final int vt = mChooserRowAdapter.getItemViewType(pos);
+                if (vt != mCachedViewType) {
+                    mCachedView = null;
+                }
+                final View v = mChooserRowAdapter.getView(pos, mCachedView, mListView);
+                int height = ((RowViewHolder) (v.getTag())).measuredRowHeight;
+
+                offset += (int) (height * mChooserRowAdapter.getRowScale(pos));
+
+                if (vt >= 0) {
+                    mCachedViewType = vt;
+                    mCachedView = v;
+                } else {
+                    mCachedViewType = -1;
+                }
+            }
+
+            mResolverDrawerLayout.setCollapsibleHeightReserved(offset);
+        }
+    }
+}
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index 1244987..dbec740 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -100,7 +100,7 @@
             final boolean shouldShowDisclosure = ri == null || ri.activityInfo == null ||
                     !"android".equals(ri.activityInfo.packageName) ||
                     !(ResolverActivity.class.getName().equals(ri.activityInfo.name)
-                    || "com.android.systemui.chooser.ChooserActivity".equals(ri.activityInfo.name));
+                    || ChooserActivity.class.getName().equals(ri.activityInfo.name));
 
             try {
                 startActivityAsCaller(newIntent, null, false, targetUserId);
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 7eb8708..ec148c55 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -22,7 +22,6 @@
 import android.app.VoiceInteractor.PickOptionRequest;
 import android.app.VoiceInteractor.PickOptionRequest.Option;
 import android.app.VoiceInteractor.Prompt;
-import android.content.pm.ComponentInfo;
 import android.os.AsyncTask;
 import android.provider.Settings;
 import android.text.TextUtils;
@@ -337,12 +336,12 @@
     /**
      * Perform any initialization needed for voice interaction.
      */
-    public void onSetupVoiceInteraction() {
+    void onSetupVoiceInteraction() {
         // Do it right now. Subclasses may delay this and send it later.
         sendVoiceChoicesIfNeeded();
     }
 
-    public void sendVoiceChoicesIfNeeded() {
+    void sendVoiceChoicesIfNeeded() {
         if (!isVoiceInteraction()) {
             // Clearly not needed.
             return;
@@ -383,7 +382,7 @@
         return null;
     }
 
-    public int getLayoutResource() {
+    int getLayoutResource() {
         return R.layout.resolver_list;
     }
 
@@ -592,7 +591,7 @@
                 mAlwaysUseOption);
     }
 
-    public void startSelected(int which, boolean always, boolean filtered) {
+    void startSelected(int which, boolean always, boolean filtered) {
         if (isFinishing()) {
             return;
         }
@@ -762,7 +761,7 @@
         return true;
     }
 
-    public void safelyStartActivity(TargetInfo cti) {
+    void safelyStartActivity(TargetInfo cti) {
         // If needed, show that intent is forwarded
         // from managed profile to owner or other way around.
         if (mProfileSwitchMessageId != -1) {
@@ -792,26 +791,26 @@
         }
     }
 
-    public void onActivityStarted(TargetInfo cti) {
+    void onActivityStarted(TargetInfo cti) {
         // Do nothing
     }
 
-    public boolean shouldGetActivityMetadata() {
+    boolean shouldGetActivityMetadata() {
         return false;
     }
 
-    public boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
+    boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
         return true;
     }
 
-    public void showTargetDetails(ResolveInfo ri) {
+    void showAppDetails(ResolveInfo ri) {
         Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
                 .setData(Uri.fromParts("package", ri.activityInfo.packageName, null))
                 .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
         startActivity(in);
     }
 
-    public ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
+    ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
             Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
             boolean filterLastUsed) {
         return new ResolveListAdapter(context, payloadIntents, initialIntents, rList,
@@ -821,7 +820,7 @@
     /**
      * Returns true if the activity is finishing and creation should halt
      */
-    public boolean configureContentView(List<Intent> payloadIntents, Intent[] initialIntents,
+    boolean configureContentView(List<Intent> payloadIntents, Intent[] initialIntents,
             List<ResolveInfo> rList, boolean alwaysUseOption) {
         // 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
@@ -868,7 +867,7 @@
         return false;
     }
 
-    public void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter,
+    void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter,
             boolean alwaysUseOption) {
         final boolean useHeader = adapter.hasFilteredItem();
         final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null;
@@ -899,7 +898,7 @@
                 && Objects.equals(lhs.activityInfo.packageName, rhs.activityInfo.packageName);
     }
 
-    public final class DisplayResolveInfo implements TargetInfo {
+    final class DisplayResolveInfo implements TargetInfo {
         private final ResolveInfo mResolveInfo;
         private final CharSequence mDisplayLabel;
         private Drawable mDisplayIcon;
@@ -907,9 +906,8 @@
         private final CharSequence mExtendedInfo;
         private final Intent mResolvedIntent;
         private final List<Intent> mSourceIntents = new ArrayList<>();
-        private boolean mPinned;
 
-        public DisplayResolveInfo(Intent originalIntent, ResolveInfo pri, CharSequence pLabel,
+        DisplayResolveInfo(Intent originalIntent, ResolveInfo pri, CharSequence pLabel,
                 CharSequence pInfo, Intent pOrigIntent) {
             mSourceIntents.add(originalIntent);
             mResolveInfo = pri;
@@ -934,7 +932,6 @@
             mExtendedInfo = other.mExtendedInfo;
             mResolvedIntent = new Intent(other.mResolvedIntent);
             mResolvedIntent.fillIn(fillInIntent, flags);
-            mPinned = other.mPinned;
         }
 
         public ResolveInfo getResolveInfo() {
@@ -1029,15 +1026,6 @@
             activity.startActivityAsUser(mResolvedIntent, options, user);
             return false;
         }
-
-        @Override
-        public boolean isPinned() {
-            return mPinned;
-        }
-
-        public void setPinned(boolean pinned) {
-            mPinned = pinned;
-        }
     }
 
     /**
@@ -1051,7 +1039,7 @@
          *
          * @return the resolved intent for this target
          */
-        Intent getResolvedIntent();
+        public Intent getResolvedIntent();
 
         /**
          * Get the resolved component name that represents this target. Note that this may not
@@ -1060,7 +1048,7 @@
          *
          * @return the resolved ComponentName for this target
          */
-        ComponentName getResolvedComponentName();
+        public ComponentName getResolvedComponentName();
 
         /**
          * Start the activity referenced by this target.
@@ -1069,7 +1057,7 @@
          * @param options ActivityOptions bundle
          * @return true if the start completed successfully
          */
-        boolean start(Activity activity, Bundle options);
+        public boolean start(Activity activity, Bundle options);
 
         /**
          * Start the activity referenced by this target as if the ResolverActivity's caller
@@ -1080,7 +1068,7 @@
          * @param userId userId to start as or {@link UserHandle#USER_NULL} for activity's caller
          * @return true if the start completed successfully
          */
-        boolean startAsCaller(Activity activity, Bundle options, int userId);
+        public boolean startAsCaller(Activity activity, Bundle options, int userId);
 
         /**
          * Start the activity referenced by this target as a given user.
@@ -1090,7 +1078,7 @@
          * @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);
+        public boolean startAsUser(Activity activity, Bundle options, UserHandle user);
 
         /**
          * Return the ResolveInfo about how and why this target matched the original query
@@ -1098,14 +1086,14 @@
          *
          * @return ResolveInfo representing this target's match
          */
-        ResolveInfo getResolveInfo();
+        public ResolveInfo getResolveInfo();
 
         /**
          * Return the human-readable text label for this target.
          *
          * @return user-visible target label
          */
-        CharSequence getDisplayLabel();
+        public CharSequence getDisplayLabel();
 
         /**
          * Return any extended info for this target. This may be used to disambiguate
@@ -1113,40 +1101,35 @@
          *
          * @return human-readable disambig string or null if none present
          */
-        CharSequence getExtendedInfo();
+        public CharSequence getExtendedInfo();
 
         /**
          * @return The drawable that should be used to represent this target
          */
-        Drawable getDisplayIcon();
+        public Drawable getDisplayIcon();
 
         /**
          * @return The (small) icon to badge the target with
          */
-        Drawable getBadgeIcon();
+        public Drawable getBadgeIcon();
 
         /**
          * @return The content description for the badge icon
          */
-        CharSequence getBadgeContentDescription();
+        public CharSequence getBadgeContentDescription();
 
         /**
          * Clone this target with the given fill-in information.
          */
-        TargetInfo cloneFilledIn(Intent fillInIntent, int flags);
+        public 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 should be pinned to the front by the request of the user
-         */
-        boolean isPinned();
+        public List<Intent> getAllSourceIntents();
     }
 
-    public class ResolveListAdapter extends BaseAdapter {
+    class ResolveListAdapter extends BaseAdapter {
         private final List<Intent> mIntents;
         private final Intent[] mInitialIntents;
         private final List<ResolveInfo> mBaseResolveList;
@@ -1393,12 +1376,9 @@
                     }
                 }
                 if (!found) {
-                    final ComponentName name = new ComponentName(
-                            newInfo.activityInfo.packageName, newInfo.activityInfo.name);
-                    final ResolvedComponentInfo rci = new ResolvedComponentInfo(name,
-                            intent, newInfo);
-                    rci.setPinned(isComponentPinned(name));
-                    into.add(rci);
+                    into.add(new ResolvedComponentInfo(new ComponentName(
+                            newInfo.activityInfo.packageName, newInfo.activityInfo.name),
+                            intent, newInfo));
                 }
             }
         }
@@ -1474,8 +1454,6 @@
             final Intent replaceIntent = getReplacementIntent(add.activityInfo, intent);
             final DisplayResolveInfo dri = new DisplayResolveInfo(intent, add, roLabel,
                     extraInfo, replaceIntent);
-            final ComponentInfo ci = add.getComponentInfo();
-            dri.setPinned(rci.isPinned());
             addResolveInfo(dri);
             if (replaceIntent == intent) {
                 // Only add alternates if we didn't get a specific replacement from
@@ -1559,11 +1537,11 @@
             return false;
         }
 
-        public int getDisplayResolveInfoCount() {
+        protected int getDisplayResolveInfoCount() {
             return mDisplayList.size();
         }
 
-        public DisplayResolveInfo getDisplayResolveInfo(int index) {
+        protected DisplayResolveInfo getDisplayResolveInfo(int index) {
             // Used to query services. We only query services for primary targets, not alternates.
             return mDisplayList.get(index);
         }
@@ -1593,10 +1571,6 @@
             return !TextUtils.isEmpty(info.getExtendedInfo());
         }
 
-        public boolean isComponentPinned(ComponentName name) {
-            return false;
-        }
-
         public final void bindView(int position, View view) {
             onBindView(view, getItem(position));
         }
@@ -1633,7 +1607,6 @@
 
     static final class ResolvedComponentInfo {
         public final ComponentName name;
-        private boolean mPinned;
         private final List<Intent> mIntents = new ArrayList<>();
         private final List<ResolveInfo> mResolveInfos = new ArrayList<>();
 
@@ -1676,14 +1649,6 @@
             }
             return -1;
         }
-
-        public boolean isPinned() {
-            return mPinned;
-        }
-
-        public void setPinned(boolean pinned) {
-            mPinned = pinned;
-        }
     }
 
     static class ViewHolder {
@@ -1737,7 +1702,7 @@
                 return false;
             }
             ResolveInfo ri = mAdapter.resolveInfoForPosition(position, true);
-            showTargetDetails(ri);
+            showAppDetails(ri);
             return true;
         }
 
diff --git a/core/java/com/android/internal/app/ResolverComparator.java b/core/java/com/android/internal/app/ResolverComparator.java
index 964a7f5..31556e2 100644
--- a/core/java/com/android/internal/app/ResolverComparator.java
+++ b/core/java/com/android/internal/app/ResolverComparator.java
@@ -48,7 +48,7 @@
     private static final boolean DEBUG = false;
 
     // Two weeks
-    private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 7;
+    private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 14;
 
     private static final long RECENCY_TIME_PERIOD = 1000 * 60 * 60 * 12;
 
@@ -171,27 +171,15 @@
             }
         }
 
-        final boolean lPinned = lhsp.isPinned();
-        final boolean rPinned = rhsp.isPinned();
+        if (mStats != null) {
+            final ScoredTarget lhsTarget = mScoredTargets.get(new ComponentName(
+                    lhs.activityInfo.packageName, lhs.activityInfo.name));
+            final ScoredTarget rhsTarget = mScoredTargets.get(new ComponentName(
+                    rhs.activityInfo.packageName, rhs.activityInfo.name));
+            final float diff = rhsTarget.score - lhsTarget.score;
 
-        if (lPinned && !rPinned) {
-            return -1;
-        } else if (!lPinned && rPinned) {
-            return 1;
-        }
-
-        // Pinned items stay stable within a normal lexical sort and ignore scoring.
-        if (!lPinned && !rPinned) {
-            if (mStats != null) {
-                final ScoredTarget lhsTarget = mScoredTargets.get(new ComponentName(
-                        lhs.activityInfo.packageName, lhs.activityInfo.name));
-                final ScoredTarget rhsTarget = mScoredTargets.get(new ComponentName(
-                        rhs.activityInfo.packageName, rhs.activityInfo.name));
-                final float diff = rhsTarget.score - lhsTarget.score;
-
-                if (diff != 0) {
-                    return diff > 0 ? 1 : -1;
-                }
+            if (diff != 0) {
+                return diff > 0 ? 1 : -1;
             }
         }
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4ab81e9..1c3db10 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2887,6 +2887,19 @@
                  android:theme="@style/Theme.Material.DayNight.DarkActionBar"
                  android:forceDeviceEncrypted="true"
                  android:encryptionAware="true">
+        <activity android:name="com.android.internal.app.ChooserActivity"
+                android:theme="@style/Theme.DeviceDefault.Resolver"
+                android:finishOnCloseSystemDialogs="true"
+                android:excludeFromRecents="true"
+                android:documentLaunchMode="never"
+                android:relinquishTaskIdentity="true"
+                android:process=":ui">
+            <intent-filter>
+                <action android:name="android.intent.action.CHOOSER" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.VOICE" />
+            </intent-filter>
+        </activity>
         <activity android:name="com.android.internal.app.IntentForwarderActivity"
                 android:finishOnCloseSystemDialogs="true"
                 android:theme="@style/Theme.NoDisplay"