Home | History | Annotate | Download | only in app
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.internal.app;
     18 
     19 import android.annotation.Nullable;
     20 import android.annotation.StringRes;
     21 import android.annotation.UiThread;
     22 import android.app.Activity;
     23 import android.app.ActivityManager;
     24 import android.app.ActivityThread;
     25 import android.app.VoiceInteractor.PickOptionRequest;
     26 import android.app.VoiceInteractor.PickOptionRequest.Option;
     27 import android.app.VoiceInteractor.Prompt;
     28 import android.content.ComponentName;
     29 import android.content.Context;
     30 import android.content.Intent;
     31 import android.content.IntentFilter;
     32 import android.content.pm.ActivityInfo;
     33 import android.content.pm.ApplicationInfo;
     34 import android.content.pm.LabeledIntent;
     35 import android.content.pm.PackageManager;
     36 import android.content.pm.PackageManager.NameNotFoundException;
     37 import android.content.pm.ResolveInfo;
     38 import android.content.pm.UserInfo;
     39 import android.content.res.Configuration;
     40 import android.content.res.Resources;
     41 import android.graphics.drawable.Drawable;
     42 import android.net.Uri;
     43 import android.os.AsyncTask;
     44 import android.os.Build;
     45 import android.os.Bundle;
     46 import android.os.PatternMatcher;
     47 import android.os.RemoteException;
     48 import android.os.StrictMode;
     49 import android.os.UserHandle;
     50 import android.os.UserManager;
     51 import android.provider.MediaStore;
     52 import android.provider.Settings;
     53 import android.text.TextUtils;
     54 import android.util.IconDrawableFactory;
     55 import android.util.Log;
     56 import android.util.Slog;
     57 import android.view.LayoutInflater;
     58 import android.view.View;
     59 import android.view.ViewGroup;
     60 import android.widget.AbsListView;
     61 import android.widget.AdapterView;
     62 import android.widget.BaseAdapter;
     63 import android.widget.Button;
     64 import android.widget.ImageView;
     65 import android.widget.ListView;
     66 import android.widget.TextView;
     67 import android.widget.Toast;
     68 import com.android.internal.R;
     69 import com.android.internal.annotations.VisibleForTesting;
     70 import com.android.internal.content.PackageMonitor;
     71 import com.android.internal.logging.MetricsLogger;
     72 import com.android.internal.logging.nano.MetricsProto;
     73 import com.android.internal.widget.ResolverDrawerLayout;
     74 
     75 import java.util.ArrayList;
     76 import java.util.Arrays;
     77 import java.util.HashSet;
     78 import java.util.Iterator;
     79 import java.util.List;
     80 import java.util.Objects;
     81 import java.util.Set;
     82 
     83 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
     84 
     85 /**
     86  * This activity is displayed when the system attempts to start an Intent for
     87  * which there is more than one matching activity, allowing the user to decide
     88  * which to go to.  It is not normally used directly by application developers.
     89  */
     90 @UiThread
     91 public class ResolverActivity extends Activity {
     92 
     93     protected ResolveListAdapter mAdapter;
     94     private boolean mSafeForwardingMode;
     95     private AbsListView mAdapterView;
     96     private Button mAlwaysButton;
     97     private Button mOnceButton;
     98     private View mProfileView;
     99     private int mIconDpi;
    100     private int mLastSelected = AbsListView.INVALID_POSITION;
    101     private boolean mResolvingHome = false;
    102     private int mProfileSwitchMessageId = -1;
    103     private int mLayoutId;
    104     private final ArrayList<Intent> mIntents = new ArrayList<>();
    105     private PickTargetOptionRequest mPickOptionRequest;
    106     private String mReferrerPackage;
    107     private CharSequence mTitle;
    108     private int mDefaultTitleResId;
    109 
    110     // Whether or not this activity supports choosing a default handler for the intent.
    111     private boolean mSupportsAlwaysUseOption;
    112     protected ResolverDrawerLayout mResolverDrawerLayout;
    113     protected PackageManager mPm;
    114     protected int mLaunchedFromUid;
    115 
    116     private static final String TAG = "ResolverActivity";
    117     private static final boolean DEBUG = false;
    118     private Runnable mPostListReadyRunnable;
    119 
    120     private boolean mRegistered;
    121 
    122     /** See {@link #setRetainInOnStop}. */
    123     private boolean mRetainInOnStop;
    124 
    125     IconDrawableFactory mIconFactory;
    126 
    127     private final PackageMonitor mPackageMonitor = new PackageMonitor() {
    128         @Override public void onSomePackagesChanged() {
    129             mAdapter.handlePackagesChanged();
    130             if (mProfileView != null) {
    131                 bindProfileView();
    132             }
    133         }
    134 
    135         @Override
    136         public boolean onPackageChanged(String packageName, int uid, String[] components) {
    137             // We care about all package changes, not just the whole package itself which is
    138             // default behavior.
    139             return true;
    140         }
    141     };
    142 
    143     /**
    144      * Get the string resource to be used as a label for the link to the resolver activity for an
    145      * action.
    146      *
    147      * @param action The action to resolve
    148      *
    149      * @return The string resource to be used as a label
    150      */
    151     public static @StringRes int getLabelRes(String action) {
    152         return ActionTitle.forAction(action).labelRes;
    153     }
    154 
    155     private enum ActionTitle {
    156         VIEW(Intent.ACTION_VIEW,
    157                 com.android.internal.R.string.whichViewApplication,
    158                 com.android.internal.R.string.whichViewApplicationNamed,
    159                 com.android.internal.R.string.whichViewApplicationLabel),
    160         EDIT(Intent.ACTION_EDIT,
    161                 com.android.internal.R.string.whichEditApplication,
    162                 com.android.internal.R.string.whichEditApplicationNamed,
    163                 com.android.internal.R.string.whichEditApplicationLabel),
    164         SEND(Intent.ACTION_SEND,
    165                 com.android.internal.R.string.whichSendApplication,
    166                 com.android.internal.R.string.whichSendApplicationNamed,
    167                 com.android.internal.R.string.whichSendApplicationLabel),
    168         SENDTO(Intent.ACTION_SENDTO,
    169                 com.android.internal.R.string.whichSendToApplication,
    170                 com.android.internal.R.string.whichSendToApplicationNamed,
    171                 com.android.internal.R.string.whichSendToApplicationLabel),
    172         SEND_MULTIPLE(Intent.ACTION_SEND_MULTIPLE,
    173                 com.android.internal.R.string.whichSendApplication,
    174                 com.android.internal.R.string.whichSendApplicationNamed,
    175                 com.android.internal.R.string.whichSendApplicationLabel),
    176         CAPTURE_IMAGE(MediaStore.ACTION_IMAGE_CAPTURE,
    177                 com.android.internal.R.string.whichImageCaptureApplication,
    178                 com.android.internal.R.string.whichImageCaptureApplicationNamed,
    179                 com.android.internal.R.string.whichImageCaptureApplicationLabel),
    180         DEFAULT(null,
    181                 com.android.internal.R.string.whichApplication,
    182                 com.android.internal.R.string.whichApplicationNamed,
    183                 com.android.internal.R.string.whichApplicationLabel),
    184         HOME(Intent.ACTION_MAIN,
    185                 com.android.internal.R.string.whichHomeApplication,
    186                 com.android.internal.R.string.whichHomeApplicationNamed,
    187                 com.android.internal.R.string.whichHomeApplicationLabel);
    188 
    189         public final String action;
    190         public final int titleRes;
    191         public final int namedTitleRes;
    192         public final @StringRes int labelRes;
    193 
    194         ActionTitle(String action, int titleRes, int namedTitleRes, @StringRes int labelRes) {
    195             this.action = action;
    196             this.titleRes = titleRes;
    197             this.namedTitleRes = namedTitleRes;
    198             this.labelRes = labelRes;
    199         }
    200 
    201         public static ActionTitle forAction(String action) {
    202             for (ActionTitle title : values()) {
    203                 if (title != HOME && action != null && action.equals(title.action)) {
    204                     return title;
    205                 }
    206             }
    207             return DEFAULT;
    208         }
    209     }
    210 
    211     private Intent makeMyIntent() {
    212         Intent intent = new Intent(getIntent());
    213         intent.setComponent(null);
    214         // The resolver activity is set to be hidden from recent tasks.
    215         // we don't want this attribute to be propagated to the next activity
    216         // being launched.  Note that if the original Intent also had this
    217         // flag set, we are now losing it.  That should be a very rare case
    218         // and we can live with this.
    219         intent.setFlags(intent.getFlags()&~Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
    220         return intent;
    221     }
    222 
    223     @Override
    224     protected void onCreate(Bundle savedInstanceState) {
    225         // Use a specialized prompt when we're handling the 'Home' app startActivity()
    226         final Intent intent = makeMyIntent();
    227         final Set<String> categories = intent.getCategories();
    228         if (Intent.ACTION_MAIN.equals(intent.getAction())
    229                 && categories != null
    230                 && categories.size() == 1
    231                 && categories.contains(Intent.CATEGORY_HOME)) {
    232             // Note: this field is not set to true in the compatibility version.
    233             mResolvingHome = true;
    234         }
    235 
    236         setSafeForwardingMode(true);
    237 
    238         onCreate(savedInstanceState, intent, null, 0, null, null, true);
    239     }
    240 
    241     /**
    242      * Compatibility version for other bundled services that use this overload without
    243      * a default title resource
    244      */
    245     protected void onCreate(Bundle savedInstanceState, Intent intent,
    246             CharSequence title, Intent[] initialIntents,
    247             List<ResolveInfo> rList, boolean supportsAlwaysUseOption) {
    248         onCreate(savedInstanceState, intent, title, 0, initialIntents, rList,
    249                 supportsAlwaysUseOption);
    250     }
    251 
    252     protected void onCreate(Bundle savedInstanceState, Intent intent,
    253             CharSequence title, int defaultTitleRes, Intent[] initialIntents,
    254             List<ResolveInfo> rList, boolean supportsAlwaysUseOption) {
    255         setTheme(R.style.Theme_DeviceDefault_Resolver);
    256         super.onCreate(savedInstanceState);
    257 
    258         // Determine whether we should show that intent is forwarded
    259         // from managed profile to owner or other way around.
    260         setProfileSwitchMessageId(intent.getContentUserHint());
    261 
    262         try {
    263             mLaunchedFromUid = ActivityManager.getService().getLaunchedFromUid(
    264                     getActivityToken());
    265         } catch (RemoteException e) {
    266             mLaunchedFromUid = -1;
    267         }
    268 
    269         if (mLaunchedFromUid < 0 || UserHandle.isIsolated(mLaunchedFromUid)) {
    270             // Gulp!
    271             finish();
    272             return;
    273         }
    274 
    275         mPm = getPackageManager();
    276 
    277         mPackageMonitor.register(this, getMainLooper(), false);
    278         mRegistered = true;
    279         mReferrerPackage = getReferrerPackageName();
    280         mSupportsAlwaysUseOption = supportsAlwaysUseOption;
    281 
    282         final ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
    283         mIconDpi = am.getLauncherLargeIconDensity();
    284 
    285         // Add our initial intent as the first item, regardless of what else has already been added.
    286         mIntents.add(0, new Intent(intent));
    287         mTitle = title;
    288         mDefaultTitleResId = defaultTitleRes;
    289 
    290         if (configureContentView(mIntents, initialIntents, rList)) {
    291             return;
    292         }
    293 
    294         final ResolverDrawerLayout rdl = findViewById(R.id.contentPanel);
    295         if (rdl != null) {
    296             rdl.setOnDismissedListener(new ResolverDrawerLayout.OnDismissedListener() {
    297                 @Override
    298                 public void onDismissed() {
    299                     finish();
    300                 }
    301             });
    302             if (isVoiceInteraction()) {
    303                 rdl.setCollapsed(false);
    304             }
    305             mResolverDrawerLayout = rdl;
    306         }
    307 
    308         mProfileView = findViewById(R.id.profile_button);
    309         if (mProfileView != null) {
    310             mProfileView.setOnClickListener(new View.OnClickListener() {
    311                 @Override
    312                 public void onClick(View v) {
    313                     final DisplayResolveInfo dri = mAdapter.getOtherProfile();
    314                     if (dri == null) {
    315                         return;
    316                     }
    317 
    318                     // Do not show the profile switch message anymore.
    319                     mProfileSwitchMessageId = -1;
    320 
    321                     onTargetSelected(dri, false);
    322                     finish();
    323                 }
    324             });
    325             bindProfileView();
    326         }
    327 
    328         if (isVoiceInteraction()) {
    329             onSetupVoiceInteraction();
    330         }
    331         final Set<String> categories = intent.getCategories();
    332         MetricsLogger.action(this, mAdapter.hasFilteredItem()
    333                 ? MetricsProto.MetricsEvent.ACTION_SHOW_APP_DISAMBIG_APP_FEATURED
    334                 : MetricsProto.MetricsEvent.ACTION_SHOW_APP_DISAMBIG_NONE_FEATURED,
    335                 intent.getAction() + ":" + intent.getType() + ":"
    336                         + (categories != null ? Arrays.toString(categories.toArray()) : ""));
    337         mIconFactory = IconDrawableFactory.newInstance(this, true);
    338     }
    339 
    340     @Override
    341     public void onConfigurationChanged(Configuration newConfig) {
    342         super.onConfigurationChanged(newConfig);
    343         mAdapter.handlePackagesChanged();
    344     }
    345 
    346     /**
    347      * Perform any initialization needed for voice interaction.
    348      */
    349     public void onSetupVoiceInteraction() {
    350         // Do it right now. Subclasses may delay this and send it later.
    351         sendVoiceChoicesIfNeeded();
    352     }
    353 
    354     public void sendVoiceChoicesIfNeeded() {
    355         if (!isVoiceInteraction()) {
    356             // Clearly not needed.
    357             return;
    358         }
    359 
    360 
    361         final Option[] options = new Option[mAdapter.getCount()];
    362         for (int i = 0, N = options.length; i < N; i++) {
    363             options[i] = optionForChooserTarget(mAdapter.getItem(i), i);
    364         }
    365 
    366         mPickOptionRequest = new PickTargetOptionRequest(
    367                 new Prompt(getTitle()), options, null);
    368         getVoiceInteractor().submitRequest(mPickOptionRequest);
    369     }
    370 
    371     Option optionForChooserTarget(TargetInfo target, int index) {
    372         return new Option(target.getDisplayLabel(), index);
    373     }
    374 
    375     protected final void setAdditionalTargets(Intent[] intents) {
    376         if (intents != null) {
    377             for (Intent intent : intents) {
    378                 mIntents.add(intent);
    379             }
    380         }
    381     }
    382 
    383     public Intent getTargetIntent() {
    384         return mIntents.isEmpty() ? null : mIntents.get(0);
    385     }
    386 
    387     protected String getReferrerPackageName() {
    388         final Uri referrer = getReferrer();
    389         if (referrer != null && "android-app".equals(referrer.getScheme())) {
    390             return referrer.getHost();
    391         }
    392         return null;
    393     }
    394 
    395     public int getLayoutResource() {
    396         return R.layout.resolver_list;
    397     }
    398 
    399     void bindProfileView() {
    400         final DisplayResolveInfo dri = mAdapter.getOtherProfile();
    401         if (dri != null) {
    402             mProfileView.setVisibility(View.VISIBLE);
    403             View text = mProfileView.findViewById(R.id.profile_button);
    404             if (!(text instanceof TextView)) {
    405                 text = mProfileView.findViewById(R.id.text1);
    406             }
    407             ((TextView) text).setText(dri.getDisplayLabel());
    408         } else {
    409             mProfileView.setVisibility(View.GONE);
    410         }
    411     }
    412 
    413     private void setProfileSwitchMessageId(int contentUserHint) {
    414         if (contentUserHint != UserHandle.USER_CURRENT &&
    415                 contentUserHint != UserHandle.myUserId()) {
    416             UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
    417             UserInfo originUserInfo = userManager.getUserInfo(contentUserHint);
    418             boolean originIsManaged = originUserInfo != null ? originUserInfo.isManagedProfile()
    419                     : false;
    420             boolean targetIsManaged = userManager.isManagedProfile();
    421             if (originIsManaged && !targetIsManaged) {
    422                 mProfileSwitchMessageId = com.android.internal.R.string.forward_intent_to_owner;
    423             } else if (!originIsManaged && targetIsManaged) {
    424                 mProfileSwitchMessageId = com.android.internal.R.string.forward_intent_to_work;
    425             }
    426         }
    427     }
    428 
    429     /**
    430      * Turn on launch mode that is safe to use when forwarding intents received from
    431      * applications and running in system processes.  This mode uses Activity.startActivityAsCaller
    432      * instead of the normal Activity.startActivity for launching the activity selected
    433      * by the user.
    434      *
    435      * <p>This mode is set to true by default if the activity is initialized through
    436      * {@link #onCreate(android.os.Bundle)}.  If a subclass calls one of the other onCreate
    437      * methods, it is set to false by default.  You must set it before calling one of the
    438      * more detailed onCreate methods, so that it will be set correctly in the case where
    439      * there is only one intent to resolve and it is thus started immediately.</p>
    440      */
    441     public void setSafeForwardingMode(boolean safeForwarding) {
    442         mSafeForwardingMode = safeForwarding;
    443     }
    444 
    445     protected CharSequence getTitleForAction(String action, int defaultTitleRes) {
    446         final ActionTitle title = mResolvingHome ? ActionTitle.HOME : ActionTitle.forAction(action);
    447         // While there may already be a filtered item, we can only use it in the title if the list
    448         // is already sorted and all information relevant to it is already in the list.
    449         final boolean named = mAdapter.getFilteredPosition() >= 0;
    450         if (title == ActionTitle.DEFAULT && defaultTitleRes != 0) {
    451             return getString(defaultTitleRes);
    452         } else {
    453             return named
    454                     ? getString(title.namedTitleRes, mAdapter.getFilteredItem().getDisplayLabel())
    455                     : getString(title.titleRes);
    456         }
    457     }
    458 
    459     void dismiss() {
    460         if (!isFinishing()) {
    461             finish();
    462         }
    463     }
    464 
    465     Drawable getIcon(Resources res, int resId) {
    466         Drawable result;
    467         try {
    468             result = res.getDrawableForDensity(resId, mIconDpi);
    469         } catch (Resources.NotFoundException e) {
    470             result = null;
    471         }
    472 
    473         return result;
    474     }
    475 
    476     Drawable loadIconForResolveInfo(ResolveInfo ri) {
    477         Drawable dr;
    478         try {
    479             if (ri.resolvePackageName != null && ri.icon != 0) {
    480                 dr = getIcon(mPm.getResourcesForApplication(ri.resolvePackageName), ri.icon);
    481                 if (dr != null) {
    482                     return mIconFactory.getShadowedIcon(dr);
    483                 }
    484             }
    485             final int iconRes = ri.getIconResource();
    486             if (iconRes != 0) {
    487                 dr = getIcon(mPm.getResourcesForApplication(ri.activityInfo.packageName), iconRes);
    488                 if (dr != null) {
    489                     return mIconFactory.getShadowedIcon(dr);
    490                 }
    491             }
    492         } catch (NameNotFoundException e) {
    493             Log.e(TAG, "Couldn't find resources for package", e);
    494         }
    495         return mIconFactory.getBadgedIcon(ri.activityInfo.applicationInfo);
    496     }
    497 
    498     @Override
    499     protected void onRestart() {
    500         super.onRestart();
    501         if (!mRegistered) {
    502             mPackageMonitor.register(this, getMainLooper(), false);
    503             mRegistered = true;
    504         }
    505         mAdapter.handlePackagesChanged();
    506         if (mProfileView != null) {
    507             bindProfileView();
    508         }
    509     }
    510 
    511     @Override
    512     protected void onStop() {
    513         super.onStop();
    514         if (mRegistered) {
    515             mPackageMonitor.unregister();
    516             mRegistered = false;
    517         }
    518         final Intent intent = getIntent();
    519         if ((intent.getFlags() & FLAG_ACTIVITY_NEW_TASK) != 0 && !isVoiceInteraction()
    520                 && !mResolvingHome && !mRetainInOnStop) {
    521             // This resolver is in the unusual situation where it has been
    522             // launched at the top of a new task.  We don't let it be added
    523             // to the recent tasks shown to the user, and we need to make sure
    524             // that each time we are launched we get the correct launching
    525             // uid (not re-using the same resolver from an old launching uid),
    526             // so we will now finish ourself since being no longer visible,
    527             // the user probably can't get back to us.
    528             if (!isChangingConfigurations()) {
    529                 finish();
    530             }
    531         }
    532     }
    533 
    534     @Override
    535     protected void onDestroy() {
    536         super.onDestroy();
    537         if (!isChangingConfigurations() && mPickOptionRequest != null) {
    538             mPickOptionRequest.cancel();
    539         }
    540         if (mPostListReadyRunnable != null) {
    541             getMainThreadHandler().removeCallbacks(mPostListReadyRunnable);
    542             mPostListReadyRunnable = null;
    543         }
    544         if (mAdapter != null && mAdapter.mResolverListController != null) {
    545             mAdapter.mResolverListController.destroy();
    546         }
    547     }
    548 
    549     @Override
    550     protected void onRestoreInstanceState(Bundle savedInstanceState) {
    551         super.onRestoreInstanceState(savedInstanceState);
    552         resetAlwaysOrOnceButtonBar();
    553     }
    554 
    555     private boolean hasManagedProfile() {
    556         UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
    557         if (userManager == null) {
    558             return false;
    559         }
    560 
    561         try {
    562             List<UserInfo> profiles = userManager.getProfiles(getUserId());
    563             for (UserInfo userInfo : profiles) {
    564                 if (userInfo != null && userInfo.isManagedProfile()) {
    565                     return true;
    566                 }
    567             }
    568         } catch (SecurityException e) {
    569             return false;
    570         }
    571         return false;
    572     }
    573 
    574     private boolean supportsManagedProfiles(ResolveInfo resolveInfo) {
    575         try {
    576             ApplicationInfo appInfo = getPackageManager().getApplicationInfo(
    577                     resolveInfo.activityInfo.packageName, 0 /* default flags */);
    578             return appInfo.targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP;
    579         } catch (NameNotFoundException e) {
    580             return false;
    581         }
    582     }
    583 
    584     private void setAlwaysButtonEnabled(boolean hasValidSelection, int checkedPos,
    585             boolean filtered) {
    586         boolean enabled = false;
    587         if (hasValidSelection) {
    588             ResolveInfo ri = mAdapter.resolveInfoForPosition(checkedPos, filtered);
    589             if (ri == null) {
    590                 Log.e(TAG, "Invalid position supplied to setAlwaysButtonEnabled");
    591                 return;
    592             } else if (ri.targetUserId != UserHandle.USER_CURRENT) {
    593                 Log.e(TAG, "Attempted to set selection to resolve info for another user");
    594                 return;
    595             } else {
    596                 enabled = true;
    597             }
    598         }
    599         mAlwaysButton.setEnabled(enabled);
    600     }
    601 
    602     public void onButtonClick(View v) {
    603         final int id = v.getId();
    604         startSelected(mAdapter.hasFilteredItem() ?
    605                         mAdapter.getFilteredPosition():
    606                         mAdapterView.getCheckedItemPosition(),
    607                 id == R.id.button_always,
    608                 !mAdapter.hasFilteredItem());
    609     }
    610 
    611     public void startSelected(int which, boolean always, boolean hasIndexBeenFiltered) {
    612         if (isFinishing()) {
    613             return;
    614         }
    615         ResolveInfo ri = mAdapter.resolveInfoForPosition(which, hasIndexBeenFiltered);
    616         if (mResolvingHome && hasManagedProfile() && !supportsManagedProfiles(ri)) {
    617             Toast.makeText(this, String.format(getResources().getString(
    618                     com.android.internal.R.string.activity_resolver_work_profiles_support),
    619                     ri.activityInfo.loadLabel(getPackageManager()).toString()),
    620                     Toast.LENGTH_LONG).show();
    621             return;
    622         }
    623 
    624         TargetInfo target = mAdapter.targetInfoForPosition(which, hasIndexBeenFiltered);
    625         if (target == null) {
    626             return;
    627         }
    628         if (onTargetSelected(target, always)) {
    629             if (always && mSupportsAlwaysUseOption) {
    630                 MetricsLogger.action(
    631                         this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_ALWAYS);
    632             } else if (mSupportsAlwaysUseOption) {
    633                 MetricsLogger.action(
    634                         this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_JUST_ONCE);
    635             } else {
    636                 MetricsLogger.action(
    637                         this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_TAP);
    638             }
    639             MetricsLogger.action(this, mAdapter.hasFilteredItem()
    640                             ? MetricsProto.MetricsEvent.ACTION_HIDE_APP_DISAMBIG_APP_FEATURED
    641                             : MetricsProto.MetricsEvent.ACTION_HIDE_APP_DISAMBIG_NONE_FEATURED);
    642             finish();
    643         }
    644     }
    645 
    646     /**
    647      * Replace me in subclasses!
    648      */
    649     public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) {
    650         return defIntent;
    651     }
    652 
    653     protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) {
    654         final ResolveInfo ri = target.getResolveInfo();
    655         final Intent intent = target != null ? target.getResolvedIntent() : null;
    656 
    657         if (intent != null && (mSupportsAlwaysUseOption || mAdapter.hasFilteredItem())
    658                 && mAdapter.mUnfilteredResolveList != null) {
    659             // Build a reasonable intent filter, based on what matched.
    660             IntentFilter filter = new IntentFilter();
    661             Intent filterIntent;
    662 
    663             if (intent.getSelector() != null) {
    664                 filterIntent = intent.getSelector();
    665             } else {
    666                 filterIntent = intent;
    667             }
    668 
    669             String action = filterIntent.getAction();
    670             if (action != null) {
    671                 filter.addAction(action);
    672             }
    673             Set<String> categories = filterIntent.getCategories();
    674             if (categories != null) {
    675                 for (String cat : categories) {
    676                     filter.addCategory(cat);
    677                 }
    678             }
    679             filter.addCategory(Intent.CATEGORY_DEFAULT);
    680 
    681             int cat = ri.match & IntentFilter.MATCH_CATEGORY_MASK;
    682             Uri data = filterIntent.getData();
    683             if (cat == IntentFilter.MATCH_CATEGORY_TYPE) {
    684                 String mimeType = filterIntent.resolveType(this);
    685                 if (mimeType != null) {
    686                     try {
    687                         filter.addDataType(mimeType);
    688                     } catch (IntentFilter.MalformedMimeTypeException e) {
    689                         Log.w("ResolverActivity", e);
    690                         filter = null;
    691                     }
    692                 }
    693             }
    694             if (data != null && data.getScheme() != null) {
    695                 // We need the data specification if there was no type,
    696                 // OR if the scheme is not one of our magical "file:"
    697                 // or "content:" schemes (see IntentFilter for the reason).
    698                 if (cat != IntentFilter.MATCH_CATEGORY_TYPE
    699                         || (!"file".equals(data.getScheme())
    700                                 && !"content".equals(data.getScheme()))) {
    701                     filter.addDataScheme(data.getScheme());
    702 
    703                     // Look through the resolved filter to determine which part
    704                     // of it matched the original Intent.
    705                     Iterator<PatternMatcher> pIt = ri.filter.schemeSpecificPartsIterator();
    706                     if (pIt != null) {
    707                         String ssp = data.getSchemeSpecificPart();
    708                         while (ssp != null && pIt.hasNext()) {
    709                             PatternMatcher p = pIt.next();
    710                             if (p.match(ssp)) {
    711                                 filter.addDataSchemeSpecificPart(p.getPath(), p.getType());
    712                                 break;
    713                             }
    714                         }
    715                     }
    716                     Iterator<IntentFilter.AuthorityEntry> aIt = ri.filter.authoritiesIterator();
    717                     if (aIt != null) {
    718                         while (aIt.hasNext()) {
    719                             IntentFilter.AuthorityEntry a = aIt.next();
    720                             if (a.match(data) >= 0) {
    721                                 int port = a.getPort();
    722                                 filter.addDataAuthority(a.getHost(),
    723                                         port >= 0 ? Integer.toString(port) : null);
    724                                 break;
    725                             }
    726                         }
    727                     }
    728                     pIt = ri.filter.pathsIterator();
    729                     if (pIt != null) {
    730                         String path = data.getPath();
    731                         while (path != null && pIt.hasNext()) {
    732                             PatternMatcher p = pIt.next();
    733                             if (p.match(path)) {
    734                                 filter.addDataPath(p.getPath(), p.getType());
    735                                 break;
    736                             }
    737                         }
    738                     }
    739                 }
    740             }
    741 
    742             if (filter != null) {
    743                 final int N = mAdapter.mUnfilteredResolveList.size();
    744                 ComponentName[] set;
    745                 // If we don't add back in the component for forwarding the intent to a managed
    746                 // profile, the preferred activity may not be updated correctly (as the set of
    747                 // components we tell it we knew about will have changed).
    748                 final boolean needToAddBackProfileForwardingComponent
    749                         = mAdapter.mOtherProfile != null;
    750                 if (!needToAddBackProfileForwardingComponent) {
    751                     set = new ComponentName[N];
    752                 } else {
    753                     set = new ComponentName[N + 1];
    754                 }
    755 
    756                 int bestMatch = 0;
    757                 for (int i=0; i<N; i++) {
    758                     ResolveInfo r = mAdapter.mUnfilteredResolveList.get(i).getResolveInfoAt(0);
    759                     set[i] = new ComponentName(r.activityInfo.packageName,
    760                             r.activityInfo.name);
    761                     if (r.match > bestMatch) bestMatch = r.match;
    762                 }
    763 
    764                 if (needToAddBackProfileForwardingComponent) {
    765                     set[N] = mAdapter.mOtherProfile.getResolvedComponentName();
    766                     final int otherProfileMatch = mAdapter.mOtherProfile.getResolveInfo().match;
    767                     if (otherProfileMatch > bestMatch) bestMatch = otherProfileMatch;
    768                 }
    769 
    770                 if (alwaysCheck) {
    771                     final int userId = getUserId();
    772                     final PackageManager pm = getPackageManager();
    773 
    774                     // Set the preferred Activity
    775                     pm.addPreferredActivity(filter, bestMatch, set, intent.getComponent());
    776 
    777                     if (ri.handleAllWebDataURI) {
    778                         // Set default Browser if needed
    779                         final String packageName = pm.getDefaultBrowserPackageNameAsUser(userId);
    780                         if (TextUtils.isEmpty(packageName)) {
    781                             pm.setDefaultBrowserPackageNameAsUser(ri.activityInfo.packageName, userId);
    782                         }
    783                     } else {
    784                         // Update Domain Verification status
    785                         ComponentName cn = intent.getComponent();
    786                         String packageName = cn.getPackageName();
    787                         String dataScheme = (data != null) ? data.getScheme() : null;
    788 
    789                         boolean isHttpOrHttps = (dataScheme != null) &&
    790                                 (dataScheme.equals(IntentFilter.SCHEME_HTTP) ||
    791                                         dataScheme.equals(IntentFilter.SCHEME_HTTPS));
    792 
    793                         boolean isViewAction = (action != null) && action.equals(Intent.ACTION_VIEW);
    794                         boolean hasCategoryBrowsable = (categories != null) &&
    795                                 categories.contains(Intent.CATEGORY_BROWSABLE);
    796 
    797                         if (isHttpOrHttps && isViewAction && hasCategoryBrowsable) {
    798                             pm.updateIntentVerificationStatusAsUser(packageName,
    799                                     PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS,
    800                                     userId);
    801                         }
    802                     }
    803                 } else {
    804                     try {
    805                         mAdapter.mResolverListController.setLastChosen(intent, filter, bestMatch);
    806                     } catch (RemoteException re) {
    807                         Log.d(TAG, "Error calling setLastChosenActivity\n" + re);
    808                     }
    809                 }
    810             }
    811         }
    812 
    813         if (target != null) {
    814             safelyStartActivity(target);
    815         }
    816         return true;
    817     }
    818 
    819     public void safelyStartActivity(TargetInfo cti) {
    820         // We're dispatching intents that might be coming from legacy apps, so
    821         // don't kill ourselves.
    822         StrictMode.disableDeathOnFileUriExposure();
    823         try {
    824             safelyStartActivityInternal(cti);
    825         } finally {
    826             StrictMode.enableDeathOnFileUriExposure();
    827         }
    828     }
    829 
    830     private void safelyStartActivityInternal(TargetInfo cti) {
    831         // If needed, show that intent is forwarded
    832         // from managed profile to owner or other way around.
    833         if (mProfileSwitchMessageId != -1) {
    834             Toast.makeText(this, getString(mProfileSwitchMessageId), Toast.LENGTH_LONG).show();
    835         }
    836         if (!mSafeForwardingMode) {
    837             if (cti.start(this, null)) {
    838                 onActivityStarted(cti);
    839             }
    840             return;
    841         }
    842         try {
    843             if (cti.startAsCaller(this, null, UserHandle.USER_NULL)) {
    844                 onActivityStarted(cti);
    845             }
    846         } catch (RuntimeException e) {
    847             String launchedFromPackage;
    848             try {
    849                 launchedFromPackage = ActivityManager.getService().getLaunchedFromPackage(
    850                         getActivityToken());
    851             } catch (RemoteException e2) {
    852                 launchedFromPackage = "??";
    853             }
    854             Slog.wtf(TAG, "Unable to launch as uid " + mLaunchedFromUid
    855                     + " package " + launchedFromPackage + ", while running in "
    856                     + ActivityThread.currentProcessName(), e);
    857         }
    858     }
    859 
    860     public void onActivityStarted(TargetInfo cti) {
    861         // Do nothing
    862     }
    863 
    864     public boolean shouldGetActivityMetadata() {
    865         return false;
    866     }
    867 
    868     public boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
    869         return true;
    870     }
    871 
    872     public void showTargetDetails(ResolveInfo ri) {
    873         Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
    874                 .setData(Uri.fromParts("package", ri.activityInfo.packageName, null))
    875                 .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
    876         startActivity(in);
    877     }
    878 
    879     public ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
    880             Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
    881             boolean filterLastUsed) {
    882         return new ResolveListAdapter(context, payloadIntents, initialIntents, rList,
    883                 launchedFromUid, filterLastUsed, createListController());
    884     }
    885 
    886     @VisibleForTesting
    887     protected ResolverListController createListController() {
    888         return new ResolverListController(
    889                 this,
    890                 mPm,
    891                 getTargetIntent(),
    892                 getReferrerPackageName(),
    893                 mLaunchedFromUid);
    894     }
    895 
    896     /**
    897      * Returns true if the activity is finishing and creation should halt
    898      */
    899     public boolean configureContentView(List<Intent> payloadIntents, Intent[] initialIntents,
    900             List<ResolveInfo> rList) {
    901         // The last argument of createAdapter is whether to do special handling
    902         // of the last used choice to highlight it in the list.  We need to always
    903         // turn this off when running under voice interaction, since it results in
    904         // a more complicated UI that the current voice interaction flow is not able
    905         // to handle.
    906         mAdapter = createAdapter(this, payloadIntents, initialIntents, rList,
    907                 mLaunchedFromUid, mSupportsAlwaysUseOption && !isVoiceInteraction());
    908         boolean rebuildCompleted = mAdapter.rebuildList();
    909 
    910         if (useLayoutWithDefault()) {
    911             mLayoutId = R.layout.resolver_list_with_default;
    912         } else {
    913             mLayoutId = getLayoutResource();
    914         }
    915         setContentView(mLayoutId);
    916 
    917         int count = mAdapter.getUnfilteredCount();
    918 
    919         // We only rebuild asynchronously when we have multiple elements to sort. In the case where
    920         // we're already done, we can check if we should auto-launch immediately.
    921         if (rebuildCompleted) {
    922             if (count == 1 && mAdapter.getOtherProfile() == null) {
    923                 // Only one target, so we're a candidate to auto-launch!
    924                 final TargetInfo target = mAdapter.targetInfoForPosition(0, false);
    925                 if (shouldAutoLaunchSingleChoice(target)) {
    926                     safelyStartActivity(target);
    927                     mPackageMonitor.unregister();
    928                     mRegistered = false;
    929                     finish();
    930                     return true;
    931                 }
    932             }
    933         }
    934 
    935 
    936         mAdapterView = findViewById(R.id.resolver_list);
    937 
    938         if (count == 0 && mAdapter.mPlaceholderCount == 0) {
    939             final TextView emptyView = findViewById(R.id.empty);
    940             emptyView.setVisibility(View.VISIBLE);
    941             mAdapterView.setVisibility(View.GONE);
    942         } else {
    943             mAdapterView.setVisibility(View.VISIBLE);
    944             onPrepareAdapterView(mAdapterView, mAdapter);
    945         }
    946         return false;
    947     }
    948 
    949     public void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter) {
    950         final boolean useHeader = adapter.hasFilteredItem();
    951         final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null;
    952 
    953         adapterView.setAdapter(mAdapter);
    954 
    955         final ItemClickListener listener = new ItemClickListener();
    956         adapterView.setOnItemClickListener(listener);
    957         adapterView.setOnItemLongClickListener(listener);
    958 
    959         if (mSupportsAlwaysUseOption) {
    960             listView.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
    961         }
    962 
    963         // In case this method is called again (due to activity recreation), avoid adding a new
    964         // header if one is already present.
    965         if (useHeader && listView != null && listView.getHeaderViewsCount() == 0) {
    966             listView.addHeaderView(LayoutInflater.from(this).inflate(
    967                     R.layout.resolver_different_item_header, listView, false));
    968         }
    969     }
    970 
    971     public void setTitleAndIcon() {
    972         if (mAdapter.getCount() == 0 && mAdapter.mPlaceholderCount == 0) {
    973             final TextView titleView = findViewById(R.id.title);
    974             if (titleView != null) {
    975                 titleView.setVisibility(View.GONE);
    976             }
    977         }
    978 
    979         CharSequence title = mTitle != null
    980                 ? mTitle
    981                 : getTitleForAction(getTargetIntent().getAction(), mDefaultTitleResId);
    982 
    983         if (!TextUtils.isEmpty(title)) {
    984             final TextView titleView = findViewById(R.id.title);
    985             if (titleView != null) {
    986                 titleView.setText(title);
    987             }
    988             setTitle(title);
    989 
    990             // Try to initialize the title icon if we have a view for it and a title to match
    991             final ImageView titleIcon = findViewById(R.id.title_icon);
    992             if (titleIcon != null) {
    993                 ApplicationInfo ai = null;
    994                 try {
    995                     if (!TextUtils.isEmpty(mReferrerPackage)) {
    996                         ai = mPm.getApplicationInfo(mReferrerPackage, 0);
    997                     }
    998                 } catch (NameNotFoundException e) {
    999                     Log.e(TAG, "Could not find referrer package " + mReferrerPackage);
   1000                 }
   1001 
   1002                 if (ai != null) {
   1003                     titleIcon.setImageDrawable(ai.loadIcon(mPm));
   1004                 }
   1005             }
   1006         }
   1007 
   1008         final ImageView iconView = findViewById(R.id.icon);
   1009         final DisplayResolveInfo iconInfo = mAdapter.getFilteredItem();
   1010         if (iconView != null && iconInfo != null) {
   1011             new LoadIconIntoViewTask(iconInfo, iconView).execute();
   1012         }
   1013     }
   1014 
   1015     public void resetAlwaysOrOnceButtonBar() {
   1016         if (mSupportsAlwaysUseOption) {
   1017             final ViewGroup buttonLayout = findViewById(R.id.button_bar);
   1018             if (buttonLayout != null) {
   1019                 buttonLayout.setVisibility(View.VISIBLE);
   1020                 mAlwaysButton = (Button) buttonLayout.findViewById(R.id.button_always);
   1021                 mOnceButton = (Button) buttonLayout.findViewById(R.id.button_once);
   1022             } else {
   1023                 Log.e(TAG, "Layout unexpectedly does not have a button bar");
   1024             }
   1025         }
   1026 
   1027         if (useLayoutWithDefault()
   1028                 && mAdapter.getFilteredPosition() != ListView.INVALID_POSITION) {
   1029             setAlwaysButtonEnabled(true, mAdapter.getFilteredPosition(), false);
   1030             mOnceButton.setEnabled(true);
   1031             return;
   1032         }
   1033 
   1034         // When the items load in, if an item was already selected, enable the buttons
   1035         if (mAdapterView != null
   1036                 && mAdapterView.getCheckedItemPosition() != ListView.INVALID_POSITION) {
   1037             setAlwaysButtonEnabled(true, mAdapterView.getCheckedItemPosition(), true);
   1038             mOnceButton.setEnabled(true);
   1039         }
   1040     }
   1041 
   1042     private boolean useLayoutWithDefault() {
   1043         return mSupportsAlwaysUseOption && mAdapter.hasFilteredItem();
   1044     }
   1045 
   1046     /**
   1047      * If {@code retainInOnStop} is set to true, we will not finish ourselves when onStop gets
   1048      * called and we are launched in a new task.
   1049      */
   1050     protected void setRetainInOnStop(boolean retainInOnStop) {
   1051         mRetainInOnStop = retainInOnStop;
   1052     }
   1053 
   1054     /**
   1055      * Check a simple match for the component of two ResolveInfos.
   1056      */
   1057     static boolean resolveInfoMatch(ResolveInfo lhs, ResolveInfo rhs) {
   1058         return lhs == null ? rhs == null
   1059                 : lhs.activityInfo == null ? rhs.activityInfo == null
   1060                 : Objects.equals(lhs.activityInfo.name, rhs.activityInfo.name)
   1061                 && Objects.equals(lhs.activityInfo.packageName, rhs.activityInfo.packageName);
   1062     }
   1063 
   1064     public final class DisplayResolveInfo implements TargetInfo {
   1065         private final ResolveInfo mResolveInfo;
   1066         private final CharSequence mDisplayLabel;
   1067         private Drawable mDisplayIcon;
   1068         private Drawable mBadge;
   1069         private final CharSequence mExtendedInfo;
   1070         private final Intent mResolvedIntent;
   1071         private final List<Intent> mSourceIntents = new ArrayList<>();
   1072         private boolean mPinned;
   1073 
   1074         public DisplayResolveInfo(Intent originalIntent, ResolveInfo pri, CharSequence pLabel,
   1075                 CharSequence pInfo, Intent pOrigIntent) {
   1076             mSourceIntents.add(originalIntent);
   1077             mResolveInfo = pri;
   1078             mDisplayLabel = pLabel;
   1079             mExtendedInfo = pInfo;
   1080 
   1081             final Intent intent = new Intent(pOrigIntent != null ? pOrigIntent :
   1082                     getReplacementIntent(pri.activityInfo, getTargetIntent()));
   1083             intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT
   1084                     | Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
   1085             final ActivityInfo ai = mResolveInfo.activityInfo;
   1086             intent.setComponent(new ComponentName(ai.applicationInfo.packageName, ai.name));
   1087 
   1088             mResolvedIntent = intent;
   1089         }
   1090 
   1091         private DisplayResolveInfo(DisplayResolveInfo other, Intent fillInIntent, int flags) {
   1092             mSourceIntents.addAll(other.getAllSourceIntents());
   1093             mResolveInfo = other.mResolveInfo;
   1094             mDisplayLabel = other.mDisplayLabel;
   1095             mDisplayIcon = other.mDisplayIcon;
   1096             mExtendedInfo = other.mExtendedInfo;
   1097             mResolvedIntent = new Intent(other.mResolvedIntent);
   1098             mResolvedIntent.fillIn(fillInIntent, flags);
   1099             mPinned = other.mPinned;
   1100         }
   1101 
   1102         public ResolveInfo getResolveInfo() {
   1103             return mResolveInfo;
   1104         }
   1105 
   1106         public CharSequence getDisplayLabel() {
   1107             return mDisplayLabel;
   1108         }
   1109 
   1110         public Drawable getDisplayIcon() {
   1111             return mDisplayIcon;
   1112         }
   1113 
   1114         public Drawable getBadgeIcon() {
   1115             // We only expose a badge if we have extended info.
   1116             // The badge is a higher-priority disambiguation signal
   1117             // but we don't need one if we wouldn't show extended info at all.
   1118             if (TextUtils.isEmpty(getExtendedInfo())) {
   1119                 return null;
   1120             }
   1121 
   1122             if (mBadge == null && mResolveInfo != null && mResolveInfo.activityInfo != null
   1123                     && mResolveInfo.activityInfo.applicationInfo != null) {
   1124                 if (mResolveInfo.activityInfo.icon == 0 || mResolveInfo.activityInfo.icon
   1125                         == mResolveInfo.activityInfo.applicationInfo.icon) {
   1126                     // Badging an icon with exactly the same icon is silly.
   1127                     // If the activityInfo icon resid is 0 it will fall back
   1128                     // to the application's icon, making it a match.
   1129                     return null;
   1130                 }
   1131                 mBadge = mResolveInfo.activityInfo.applicationInfo.loadIcon(mPm);
   1132             }
   1133             return mBadge;
   1134         }
   1135 
   1136         @Override
   1137         public CharSequence getBadgeContentDescription() {
   1138             return null;
   1139         }
   1140 
   1141         @Override
   1142         public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
   1143             return new DisplayResolveInfo(this, fillInIntent, flags);
   1144         }
   1145 
   1146         @Override
   1147         public List<Intent> getAllSourceIntents() {
   1148             return mSourceIntents;
   1149         }
   1150 
   1151         public void addAlternateSourceIntent(Intent alt) {
   1152             mSourceIntents.add(alt);
   1153         }
   1154 
   1155         public void setDisplayIcon(Drawable icon) {
   1156             mDisplayIcon = icon;
   1157         }
   1158 
   1159         public boolean hasDisplayIcon() {
   1160             return mDisplayIcon != null;
   1161         }
   1162 
   1163         public CharSequence getExtendedInfo() {
   1164             return mExtendedInfo;
   1165         }
   1166 
   1167         public Intent getResolvedIntent() {
   1168             return mResolvedIntent;
   1169         }
   1170 
   1171         @Override
   1172         public ComponentName getResolvedComponentName() {
   1173             return new ComponentName(mResolveInfo.activityInfo.packageName,
   1174                     mResolveInfo.activityInfo.name);
   1175         }
   1176 
   1177         @Override
   1178         public boolean start(Activity activity, Bundle options) {
   1179             activity.startActivity(mResolvedIntent, options);
   1180             return true;
   1181         }
   1182 
   1183         @Override
   1184         public boolean startAsCaller(Activity activity, Bundle options, int userId) {
   1185             activity.startActivityAsCaller(mResolvedIntent, options, false, userId);
   1186             return true;
   1187         }
   1188 
   1189         @Override
   1190         public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
   1191             activity.startActivityAsUser(mResolvedIntent, options, user);
   1192             return false;
   1193         }
   1194 
   1195         @Override
   1196         public boolean isPinned() {
   1197             return mPinned;
   1198         }
   1199 
   1200         public void setPinned(boolean pinned) {
   1201             mPinned = pinned;
   1202         }
   1203     }
   1204 
   1205     /**
   1206      * A single target as represented in the chooser.
   1207      */
   1208     public interface TargetInfo {
   1209         /**
   1210          * Get the resolved intent that represents this target. Note that this may not be the
   1211          * intent that will be launched by calling one of the <code>start</code> methods provided;
   1212          * this is the intent that will be credited with the launch.
   1213          *
   1214          * @return the resolved intent for this target
   1215          */
   1216         Intent getResolvedIntent();
   1217 
   1218         /**
   1219          * Get the resolved component name that represents this target. Note that this may not
   1220          * be the component that will be directly launched by calling one of the <code>start</code>
   1221          * methods provided; this is the component that will be credited with the launch.
   1222          *
   1223          * @return the resolved ComponentName for this target
   1224          */
   1225         ComponentName getResolvedComponentName();
   1226 
   1227         /**
   1228          * Start the activity referenced by this target.
   1229          *
   1230          * @param activity calling Activity performing the launch
   1231          * @param options ActivityOptions bundle
   1232          * @return true if the start completed successfully
   1233          */
   1234         boolean start(Activity activity, Bundle options);
   1235 
   1236         /**
   1237          * Start the activity referenced by this target as if the ResolverActivity's caller
   1238          * was performing the start operation.
   1239          *
   1240          * @param activity calling Activity (actually) performing the launch
   1241          * @param options ActivityOptions bundle
   1242          * @param userId userId to start as or {@link UserHandle#USER_NULL} for activity's caller
   1243          * @return true if the start completed successfully
   1244          */
   1245         boolean startAsCaller(Activity activity, Bundle options, int userId);
   1246 
   1247         /**
   1248          * Start the activity referenced by this target as a given user.
   1249          *
   1250          * @param activity calling activity performing the launch
   1251          * @param options ActivityOptions bundle
   1252          * @param user handle for the user to start the activity as
   1253          * @return true if the start completed successfully
   1254          */
   1255         boolean startAsUser(Activity activity, Bundle options, UserHandle user);
   1256 
   1257         /**
   1258          * Return the ResolveInfo about how and why this target matched the original query
   1259          * for available targets.
   1260          *
   1261          * @return ResolveInfo representing this target's match
   1262          */
   1263         ResolveInfo getResolveInfo();
   1264 
   1265         /**
   1266          * Return the human-readable text label for this target.
   1267          *
   1268          * @return user-visible target label
   1269          */
   1270         CharSequence getDisplayLabel();
   1271 
   1272         /**
   1273          * Return any extended info for this target. This may be used to disambiguate
   1274          * otherwise identical targets.
   1275          *
   1276          * @return human-readable disambig string or null if none present
   1277          */
   1278         CharSequence getExtendedInfo();
   1279 
   1280         /**
   1281          * @return The drawable that should be used to represent this target
   1282          */
   1283         Drawable getDisplayIcon();
   1284 
   1285         /**
   1286          * @return The (small) icon to badge the target with
   1287          */
   1288         Drawable getBadgeIcon();
   1289 
   1290         /**
   1291          * @return The content description for the badge icon
   1292          */
   1293         CharSequence getBadgeContentDescription();
   1294 
   1295         /**
   1296          * Clone this target with the given fill-in information.
   1297          */
   1298         TargetInfo cloneFilledIn(Intent fillInIntent, int flags);
   1299 
   1300         /**
   1301          * @return the list of supported source intents deduped against this single target
   1302          */
   1303         List<Intent> getAllSourceIntents();
   1304 
   1305         /**
   1306          * @return true if this target should be pinned to the front by the request of the user
   1307          */
   1308         boolean isPinned();
   1309     }
   1310 
   1311     public class ResolveListAdapter extends BaseAdapter {
   1312         private final List<Intent> mIntents;
   1313         private final Intent[] mInitialIntents;
   1314         private final List<ResolveInfo> mBaseResolveList;
   1315         protected ResolveInfo mLastChosen;
   1316         private DisplayResolveInfo mOtherProfile;
   1317         private boolean mHasExtendedInfo;
   1318         private ResolverListController mResolverListController;
   1319         private int mPlaceholderCount;
   1320 
   1321         protected final LayoutInflater mInflater;
   1322 
   1323         List<DisplayResolveInfo> mDisplayList;
   1324         List<ResolvedComponentInfo> mUnfilteredResolveList;
   1325 
   1326         private int mLastChosenPosition = -1;
   1327         private boolean mFilterLastUsed;
   1328 
   1329         public ResolveListAdapter(Context context, List<Intent> payloadIntents,
   1330                 Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
   1331                 boolean filterLastUsed,
   1332                 ResolverListController resolverListController) {
   1333             mIntents = payloadIntents;
   1334             mInitialIntents = initialIntents;
   1335             mBaseResolveList = rList;
   1336             mLaunchedFromUid = launchedFromUid;
   1337             mInflater = LayoutInflater.from(context);
   1338             mDisplayList = new ArrayList<>();
   1339             mFilterLastUsed = filterLastUsed;
   1340             mResolverListController = resolverListController;
   1341         }
   1342 
   1343         public void handlePackagesChanged() {
   1344             rebuildList();
   1345             if (getCount() == 0) {
   1346                 // We no longer have any items...  just finish the activity.
   1347                 finish();
   1348             }
   1349         }
   1350 
   1351         public void setPlaceholderCount(int count) {
   1352             mPlaceholderCount = count;
   1353         }
   1354 
   1355         public int getPlaceholderCount() { return mPlaceholderCount; }
   1356 
   1357         @Nullable
   1358         public DisplayResolveInfo getFilteredItem() {
   1359             if (mFilterLastUsed && mLastChosenPosition >= 0) {
   1360                 // Not using getItem since it offsets to dodge this position for the list
   1361                 return mDisplayList.get(mLastChosenPosition);
   1362             }
   1363             return null;
   1364         }
   1365 
   1366         public DisplayResolveInfo getOtherProfile() {
   1367             return mOtherProfile;
   1368         }
   1369 
   1370         public int getFilteredPosition() {
   1371             if (mFilterLastUsed && mLastChosenPosition >= 0) {
   1372                 return mLastChosenPosition;
   1373             }
   1374             return AbsListView.INVALID_POSITION;
   1375         }
   1376 
   1377         public boolean hasFilteredItem() {
   1378             return mFilterLastUsed && mLastChosen != null;
   1379         }
   1380 
   1381         public float getScore(DisplayResolveInfo target) {
   1382             return mResolverListController.getScore(target);
   1383         }
   1384 
   1385         public void updateModel(ComponentName componentName) {
   1386             mResolverListController.updateModel(componentName);
   1387         }
   1388 
   1389         public void updateChooserCounts(String packageName, int userId, String action) {
   1390             mResolverListController.updateChooserCounts(packageName, userId, action);
   1391         }
   1392 
   1393         /**
   1394          * Rebuild the list of resolvers. In some cases some parts will need some asynchronous work
   1395          * to complete.
   1396          *
   1397          * @return Whether or not the list building is completed.
   1398          */
   1399         protected boolean rebuildList() {
   1400             List<ResolvedComponentInfo> currentResolveList = null;
   1401             // Clear the value of mOtherProfile from previous call.
   1402             mOtherProfile = null;
   1403             mLastChosen = null;
   1404             mLastChosenPosition = -1;
   1405             mDisplayList.clear();
   1406             if (mBaseResolveList != null) {
   1407                 currentResolveList = mUnfilteredResolveList = new ArrayList<>();
   1408                 mResolverListController.addResolveListDedupe(currentResolveList,
   1409                         getTargetIntent(),
   1410                         mBaseResolveList);
   1411             } else {
   1412                 currentResolveList = mUnfilteredResolveList =
   1413                         mResolverListController.getResolversForIntent(shouldGetResolvedFilter(),
   1414                                 shouldGetActivityMetadata(),
   1415                                 mIntents);
   1416                 if (currentResolveList == null) {
   1417                     processSortedList(currentResolveList);
   1418                     return true;
   1419                 }
   1420                 List<ResolvedComponentInfo> originalList =
   1421                         mResolverListController.filterIneligibleActivities(currentResolveList,
   1422                                 true);
   1423                 if (originalList != null) {
   1424                     mUnfilteredResolveList = originalList;
   1425                 }
   1426             }
   1427 
   1428             // So far we only support a single other profile at a time.
   1429             // The first one we see gets special treatment.
   1430             for (ResolvedComponentInfo info : currentResolveList) {
   1431                 if (info.getResolveInfoAt(0).targetUserId != UserHandle.USER_CURRENT) {
   1432                     mOtherProfile = new DisplayResolveInfo(info.getIntentAt(0),
   1433                             info.getResolveInfoAt(0),
   1434                             info.getResolveInfoAt(0).loadLabel(mPm),
   1435                             info.getResolveInfoAt(0).loadLabel(mPm),
   1436                             getReplacementIntent(info.getResolveInfoAt(0).activityInfo,
   1437                                     info.getIntentAt(0)));
   1438                     currentResolveList.remove(info);
   1439                     break;
   1440                 }
   1441             }
   1442 
   1443             if (mOtherProfile == null) {
   1444                 try {
   1445                     mLastChosen = mResolverListController.getLastChosen();
   1446                 } catch (RemoteException re) {
   1447                     Log.d(TAG, "Error calling getLastChosenActivity\n" + re);
   1448                 }
   1449             }
   1450 
   1451             int N;
   1452             if ((currentResolveList != null) && ((N = currentResolveList.size()) > 0)) {
   1453                 // We only care about fixing the unfilteredList if the current resolve list and
   1454                 // current resolve list are currently the same.
   1455                 List<ResolvedComponentInfo> originalList =
   1456                         mResolverListController.filterLowPriority(currentResolveList,
   1457                                 mUnfilteredResolveList == currentResolveList);
   1458                 if (originalList != null) {
   1459                     mUnfilteredResolveList = originalList;
   1460                 }
   1461 
   1462                 if (currentResolveList.size() > 1) {
   1463                     int placeholderCount = currentResolveList.size();
   1464                     if (useLayoutWithDefault()) {
   1465                         --placeholderCount;
   1466                     }
   1467                     setPlaceholderCount(placeholderCount);
   1468                     AsyncTask<List<ResolvedComponentInfo>,
   1469                             Void,
   1470                             List<ResolvedComponentInfo>> sortingTask =
   1471                             new AsyncTask<List<ResolvedComponentInfo>,
   1472                                     Void,
   1473                                     List<ResolvedComponentInfo>>() {
   1474                         @Override
   1475                         protected List<ResolvedComponentInfo> doInBackground(
   1476                                 List<ResolvedComponentInfo>... params) {
   1477                             mResolverListController.sort(params[0]);
   1478                             return params[0];
   1479                         }
   1480 
   1481                         @Override
   1482                         protected void onPostExecute(List<ResolvedComponentInfo> sortedComponents) {
   1483                             processSortedList(sortedComponents);
   1484                             if (mProfileView != null) {
   1485                                 bindProfileView();
   1486                             }
   1487                             notifyDataSetChanged();
   1488                         }
   1489                     };
   1490                     sortingTask.execute(currentResolveList);
   1491                     postListReadyRunnable();
   1492                     return false;
   1493                 } else {
   1494                     processSortedList(currentResolveList);
   1495                     return true;
   1496                 }
   1497             } else {
   1498                 processSortedList(currentResolveList);
   1499                 return true;
   1500             }
   1501         }
   1502 
   1503         private void processSortedList(List<ResolvedComponentInfo> sortedComponents) {
   1504             int N;
   1505             if (sortedComponents != null && (N = sortedComponents.size()) != 0) {
   1506                 // First put the initial items at the top.
   1507                 if (mInitialIntents != null) {
   1508                     for (int i = 0; i < mInitialIntents.length; i++) {
   1509                         Intent ii = mInitialIntents[i];
   1510                         if (ii == null) {
   1511                             continue;
   1512                         }
   1513                         ActivityInfo ai = ii.resolveActivityInfo(
   1514                                 getPackageManager(), 0);
   1515                         if (ai == null) {
   1516                             Log.w(TAG, "No activity found for " + ii);
   1517                             continue;
   1518                         }
   1519                         ResolveInfo ri = new ResolveInfo();
   1520                         ri.activityInfo = ai;
   1521                         UserManager userManager =
   1522                                 (UserManager) getSystemService(Context.USER_SERVICE);
   1523                         if (ii instanceof LabeledIntent) {
   1524                             LabeledIntent li = (LabeledIntent) ii;
   1525                             ri.resolvePackageName = li.getSourcePackage();
   1526                             ri.labelRes = li.getLabelResource();
   1527                             ri.nonLocalizedLabel = li.getNonLocalizedLabel();
   1528                             ri.icon = li.getIconResource();
   1529                             ri.iconResourceId = ri.icon;
   1530                         }
   1531                         if (userManager.isManagedProfile()) {
   1532                             ri.noResourceId = true;
   1533                             ri.icon = 0;
   1534                         }
   1535                         addResolveInfo(new DisplayResolveInfo(ii, ri,
   1536                                 ri.loadLabel(getPackageManager()), null, ii));
   1537                     }
   1538                 }
   1539 
   1540                 // Check for applications with same name and use application name or
   1541                 // package name if necessary
   1542                 ResolvedComponentInfo rci0 = sortedComponents.get(0);
   1543                 ResolveInfo r0 = rci0.getResolveInfoAt(0);
   1544                 int start = 0;
   1545                 CharSequence r0Label = r0.loadLabel(mPm);
   1546                 mHasExtendedInfo = false;
   1547                 for (int i = 1; i < N; i++) {
   1548                     if (r0Label == null) {
   1549                         r0Label = r0.activityInfo.packageName;
   1550                     }
   1551                     ResolvedComponentInfo rci = sortedComponents.get(i);
   1552                     ResolveInfo ri = rci.getResolveInfoAt(0);
   1553                     CharSequence riLabel = ri.loadLabel(mPm);
   1554                     if (riLabel == null) {
   1555                         riLabel = ri.activityInfo.packageName;
   1556                     }
   1557                     if (riLabel.equals(r0Label)) {
   1558                         continue;
   1559                     }
   1560                     processGroup(sortedComponents, start, (i - 1), rci0, r0Label);
   1561                     rci0 = rci;
   1562                     r0 = ri;
   1563                     r0Label = riLabel;
   1564                     start = i;
   1565                 }
   1566                 // Process last group
   1567                 processGroup(sortedComponents, start, (N - 1), rci0, r0Label);
   1568             }
   1569 
   1570             postListReadyRunnable();
   1571         }
   1572 
   1573         /**
   1574          * Some necessary methods for creating the list are initiated in onCreate and will also
   1575          * determine the layout known. We therefore can't update the UI inline and post to the
   1576          * handler thread to update after the current task is finished.
   1577          */
   1578         private void postListReadyRunnable() {
   1579             if (mPostListReadyRunnable == null) {
   1580                 mPostListReadyRunnable = new Runnable() {
   1581                     @Override
   1582                     public void run() {
   1583                         setTitleAndIcon();
   1584                         resetAlwaysOrOnceButtonBar();
   1585                         onListRebuilt();
   1586                         mPostListReadyRunnable = null;
   1587                     }
   1588                 };
   1589                 getMainThreadHandler().post(mPostListReadyRunnable);
   1590             }
   1591         }
   1592 
   1593         public void onListRebuilt() {
   1594             int count = getUnfilteredCount();
   1595             if (count == 1 && getOtherProfile() == null) {
   1596                 // Only one target, so we're a candidate to auto-launch!
   1597                 final TargetInfo target = targetInfoForPosition(0, false);
   1598                 if (shouldAutoLaunchSingleChoice(target)) {
   1599                     safelyStartActivity(target);
   1600                     finish();
   1601                 }
   1602             }
   1603         }
   1604 
   1605         public boolean shouldGetResolvedFilter() {
   1606             return mFilterLastUsed;
   1607         }
   1608 
   1609         private void processGroup(List<ResolvedComponentInfo> rList, int start, int end,
   1610                 ResolvedComponentInfo ro, CharSequence roLabel) {
   1611             // Process labels from start to i
   1612             int num = end - start+1;
   1613             if (num == 1) {
   1614                 // No duplicate labels. Use label for entry at start
   1615                 addResolveInfoWithAlternates(ro, null, roLabel);
   1616             } else {
   1617                 mHasExtendedInfo = true;
   1618                 boolean usePkg = false;
   1619                 final ApplicationInfo ai = ro.getResolveInfoAt(0).activityInfo.applicationInfo;
   1620                 final CharSequence startApp = ai.loadLabel(mPm);
   1621                 if (startApp == null) {
   1622                     usePkg = true;
   1623                 }
   1624                 if (!usePkg) {
   1625                     // Use HashSet to track duplicates
   1626                     HashSet<CharSequence> duplicates =
   1627                         new HashSet<CharSequence>();
   1628                     duplicates.add(startApp);
   1629                     for (int j = start+1; j <= end ; j++) {
   1630                         ResolveInfo jRi = rList.get(j).getResolveInfoAt(0);
   1631                         CharSequence jApp = jRi.activityInfo.applicationInfo.loadLabel(mPm);
   1632                         if ( (jApp == null) || (duplicates.contains(jApp))) {
   1633                             usePkg = true;
   1634                             break;
   1635                         } else {
   1636                             duplicates.add(jApp);
   1637                         }
   1638                     }
   1639                     // Clear HashSet for later use
   1640                     duplicates.clear();
   1641                 }
   1642                 for (int k = start; k <= end; k++) {
   1643                     final ResolvedComponentInfo rci = rList.get(k);
   1644                     final ResolveInfo add = rci.getResolveInfoAt(0);
   1645                     final CharSequence extraInfo;
   1646                     if (usePkg) {
   1647                         // Use package name for all entries from start to end-1
   1648                         extraInfo = add.activityInfo.packageName;
   1649                     } else {
   1650                         // Use application name for all entries from start to end-1
   1651                         extraInfo = add.activityInfo.applicationInfo.loadLabel(mPm);
   1652                     }
   1653                     addResolveInfoWithAlternates(rci, extraInfo, roLabel);
   1654                 }
   1655             }
   1656         }
   1657 
   1658         private void addResolveInfoWithAlternates(ResolvedComponentInfo rci,
   1659                 CharSequence extraInfo, CharSequence roLabel) {
   1660             final int count = rci.getCount();
   1661             final Intent intent = rci.getIntentAt(0);
   1662             final ResolveInfo add = rci.getResolveInfoAt(0);
   1663             final Intent replaceIntent = getReplacementIntent(add.activityInfo, intent);
   1664             final DisplayResolveInfo dri = new DisplayResolveInfo(intent, add, roLabel,
   1665                     extraInfo, replaceIntent);
   1666             dri.setPinned(rci.isPinned());
   1667             addResolveInfo(dri);
   1668             if (replaceIntent == intent) {
   1669                 // Only add alternates if we didn't get a specific replacement from
   1670                 // the caller. If we have one it trumps potential alternates.
   1671                 for (int i = 1, N = count; i < N; i++) {
   1672                     final Intent altIntent = rci.getIntentAt(i);
   1673                     dri.addAlternateSourceIntent(altIntent);
   1674                 }
   1675             }
   1676             updateLastChosenPosition(add);
   1677         }
   1678 
   1679         private void updateLastChosenPosition(ResolveInfo info) {
   1680             // If another profile is present, ignore the last chosen entry.
   1681             if (mOtherProfile != null) {
   1682                 mLastChosenPosition = -1;
   1683                 return;
   1684             }
   1685             if (mLastChosen != null
   1686                     && mLastChosen.activityInfo.packageName.equals(info.activityInfo.packageName)
   1687                     && mLastChosen.activityInfo.name.equals(info.activityInfo.name)) {
   1688                 mLastChosenPosition = mDisplayList.size() - 1;
   1689             }
   1690         }
   1691 
   1692         // We assume that at this point we've already filtered out the only intent for a different
   1693         // targetUserId which we're going to use.
   1694         private void addResolveInfo(DisplayResolveInfo dri) {
   1695             if (dri != null && dri.mResolveInfo != null
   1696                     && dri.mResolveInfo.targetUserId == UserHandle.USER_CURRENT) {
   1697                 // Checks if this info is already listed in display.
   1698                 for (DisplayResolveInfo existingInfo : mDisplayList) {
   1699                     if (resolveInfoMatch(dri.mResolveInfo, existingInfo.mResolveInfo)) {
   1700                         return;
   1701                     }
   1702                 }
   1703                 mDisplayList.add(dri);
   1704             }
   1705         }
   1706 
   1707         @Nullable
   1708         public ResolveInfo resolveInfoForPosition(int position, boolean filtered) {
   1709             TargetInfo target = targetInfoForPosition(position, filtered);
   1710             if (target != null) {
   1711                 return target.getResolveInfo();
   1712              }
   1713              return null;
   1714         }
   1715 
   1716         @Nullable
   1717         public TargetInfo targetInfoForPosition(int position, boolean filtered) {
   1718             if (filtered) {
   1719                 return getItem(position);
   1720             }
   1721             if (mDisplayList.size() > position) {
   1722                 return mDisplayList.get(position);
   1723             }
   1724             return null;
   1725         }
   1726 
   1727         public int getCount() {
   1728             int totalSize = mDisplayList == null || mDisplayList.isEmpty() ? mPlaceholderCount :
   1729                     mDisplayList.size();
   1730             if (mFilterLastUsed && mLastChosenPosition >= 0) {
   1731                 totalSize--;
   1732             }
   1733             return totalSize;
   1734         }
   1735 
   1736         public int getUnfilteredCount() {
   1737             return mDisplayList.size();
   1738         }
   1739 
   1740         public int getDisplayInfoCount() {
   1741             return mDisplayList.size();
   1742         }
   1743 
   1744         public DisplayResolveInfo getDisplayInfoAt(int index) {
   1745             return mDisplayList.get(index);
   1746         }
   1747 
   1748         @Nullable
   1749         public TargetInfo getItem(int position) {
   1750             if (mFilterLastUsed && mLastChosenPosition >= 0 && position >= mLastChosenPosition) {
   1751                 position++;
   1752             }
   1753             if (mDisplayList.size() > position) {
   1754                 return mDisplayList.get(position);
   1755             } else {
   1756                 return null;
   1757             }
   1758         }
   1759 
   1760         public long getItemId(int position) {
   1761             return position;
   1762         }
   1763 
   1764         public boolean hasExtendedInfo() {
   1765             return mHasExtendedInfo;
   1766         }
   1767 
   1768         public boolean hasResolvedTarget(ResolveInfo info) {
   1769             for (int i = 0, N = mDisplayList.size(); i < N; i++) {
   1770                 if (resolveInfoMatch(info, mDisplayList.get(i).getResolveInfo())) {
   1771                     return true;
   1772                 }
   1773             }
   1774             return false;
   1775         }
   1776 
   1777         public int getDisplayResolveInfoCount() {
   1778             return mDisplayList.size();
   1779         }
   1780 
   1781         public DisplayResolveInfo getDisplayResolveInfo(int index) {
   1782             // Used to query services. We only query services for primary targets, not alternates.
   1783             return mDisplayList.get(index);
   1784         }
   1785 
   1786         public final View getView(int position, View convertView, ViewGroup parent) {
   1787             View view = convertView;
   1788             if (view == null) {
   1789                 view = createView(parent);
   1790             }
   1791             onBindView(view, getItem(position));
   1792             return view;
   1793         }
   1794 
   1795         public final View createView(ViewGroup parent) {
   1796             final View view = onCreateView(parent);
   1797             final ViewHolder holder = new ViewHolder(view);
   1798             view.setTag(holder);
   1799             return view;
   1800         }
   1801 
   1802         public View onCreateView(ViewGroup parent) {
   1803             return mInflater.inflate(
   1804                     com.android.internal.R.layout.resolve_list_item, parent, false);
   1805         }
   1806 
   1807         public boolean showsExtendedInfo(TargetInfo info) {
   1808             return !TextUtils.isEmpty(info.getExtendedInfo());
   1809         }
   1810 
   1811         public boolean isComponentPinned(ComponentName name) {
   1812             return false;
   1813         }
   1814 
   1815         public final void bindView(int position, View view) {
   1816             onBindView(view, getItem(position));
   1817         }
   1818 
   1819         private void onBindView(View view, TargetInfo info) {
   1820             final ViewHolder holder = (ViewHolder) view.getTag();
   1821             if (info == null) {
   1822                 holder.icon.setImageDrawable(
   1823                         getDrawable(R.drawable.resolver_icon_placeholder));
   1824                 return;
   1825             }
   1826             final CharSequence label = info.getDisplayLabel();
   1827             if (!TextUtils.equals(holder.text.getText(), label)) {
   1828                 holder.text.setText(info.getDisplayLabel());
   1829             }
   1830             if (showsExtendedInfo(info)) {
   1831                 holder.text2.setVisibility(View.VISIBLE);
   1832                 holder.text2.setText(info.getExtendedInfo());
   1833             } else {
   1834                 holder.text2.setVisibility(View.GONE);
   1835             }
   1836             if (info instanceof DisplayResolveInfo
   1837                     && !((DisplayResolveInfo) info).hasDisplayIcon()) {
   1838                 new LoadAdapterIconTask((DisplayResolveInfo) info).execute();
   1839             }
   1840             holder.icon.setImageDrawable(info.getDisplayIcon());
   1841             if (holder.badge != null) {
   1842                 final Drawable badge = info.getBadgeIcon();
   1843                 if (badge != null) {
   1844                     holder.badge.setImageDrawable(badge);
   1845                     holder.badge.setContentDescription(info.getBadgeContentDescription());
   1846                     holder.badge.setVisibility(View.VISIBLE);
   1847                 } else {
   1848                     holder.badge.setVisibility(View.GONE);
   1849                 }
   1850             }
   1851         }
   1852     }
   1853 
   1854     @VisibleForTesting
   1855     public static final class ResolvedComponentInfo {
   1856         public final ComponentName name;
   1857         private boolean mPinned;
   1858         private final List<Intent> mIntents = new ArrayList<>();
   1859         private final List<ResolveInfo> mResolveInfos = new ArrayList<>();
   1860 
   1861         public ResolvedComponentInfo(ComponentName name, Intent intent, ResolveInfo info) {
   1862             this.name = name;
   1863             add(intent, info);
   1864         }
   1865 
   1866         public void add(Intent intent, ResolveInfo info) {
   1867             mIntents.add(intent);
   1868             mResolveInfos.add(info);
   1869         }
   1870 
   1871         public int getCount() {
   1872             return mIntents.size();
   1873         }
   1874 
   1875         public Intent getIntentAt(int index) {
   1876             return index >= 0 ? mIntents.get(index) : null;
   1877         }
   1878 
   1879         public ResolveInfo getResolveInfoAt(int index) {
   1880             return index >= 0 ? mResolveInfos.get(index) : null;
   1881         }
   1882 
   1883         public int findIntent(Intent intent) {
   1884             for (int i = 0, N = mIntents.size(); i < N; i++) {
   1885                 if (intent.equals(mIntents.get(i))) {
   1886                     return i;
   1887                 }
   1888             }
   1889             return -1;
   1890         }
   1891 
   1892         public int findResolveInfo(ResolveInfo info) {
   1893             for (int i = 0, N = mResolveInfos.size(); i < N; i++) {
   1894                 if (info.equals(mResolveInfos.get(i))) {
   1895                     return i;
   1896                 }
   1897             }
   1898             return -1;
   1899         }
   1900 
   1901         public boolean isPinned() {
   1902             return mPinned;
   1903         }
   1904 
   1905         public void setPinned(boolean pinned) {
   1906             mPinned = pinned;
   1907         }
   1908     }
   1909 
   1910     static class ViewHolder {
   1911         public TextView text;
   1912         public TextView text2;
   1913         public ImageView icon;
   1914         public ImageView badge;
   1915 
   1916         public ViewHolder(View view) {
   1917             text = (TextView) view.findViewById(com.android.internal.R.id.text1);
   1918             text2 = (TextView) view.findViewById(com.android.internal.R.id.text2);
   1919             icon = (ImageView) view.findViewById(R.id.icon);
   1920             badge = (ImageView) view.findViewById(R.id.target_badge);
   1921         }
   1922     }
   1923 
   1924     class ItemClickListener implements AdapterView.OnItemClickListener,
   1925             AdapterView.OnItemLongClickListener {
   1926         @Override
   1927         public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
   1928             final ListView listView = parent instanceof ListView ? (ListView) parent : null;
   1929             if (listView != null) {
   1930                 position -= listView.getHeaderViewsCount();
   1931             }
   1932             if (position < 0) {
   1933                 // Header views don't count.
   1934                 return;
   1935             }
   1936             // If we're still loading, we can't yet enable the buttons.
   1937             if (mAdapter.resolveInfoForPosition(position, true) == null) {
   1938                 return;
   1939             }
   1940 
   1941             final int checkedPos = mAdapterView.getCheckedItemPosition();
   1942             final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION;
   1943             if (!useLayoutWithDefault()
   1944                     && (!hasValidSelection || mLastSelected != checkedPos)
   1945                     && mAlwaysButton != null) {
   1946                 setAlwaysButtonEnabled(hasValidSelection, checkedPos, true);
   1947                 mOnceButton.setEnabled(hasValidSelection);
   1948                 if (hasValidSelection) {
   1949                     mAdapterView.smoothScrollToPosition(checkedPos);
   1950                 }
   1951                 mLastSelected = checkedPos;
   1952             } else {
   1953                 startSelected(position, false, true);
   1954             }
   1955         }
   1956 
   1957         @Override
   1958         public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
   1959             final ListView listView = parent instanceof ListView ? (ListView) parent : null;
   1960             if (listView != null) {
   1961                 position -= listView.getHeaderViewsCount();
   1962             }
   1963             if (position < 0) {
   1964                 // Header views don't count.
   1965                 return false;
   1966             }
   1967             ResolveInfo ri = mAdapter.resolveInfoForPosition(position, true);
   1968             showTargetDetails(ri);
   1969             return true;
   1970         }
   1971 
   1972     }
   1973 
   1974     abstract class LoadIconTask extends AsyncTask<Void, Void, Drawable> {
   1975         protected final DisplayResolveInfo mDisplayResolveInfo;
   1976         private final ResolveInfo mResolveInfo;
   1977 
   1978         public LoadIconTask(DisplayResolveInfo dri) {
   1979             mDisplayResolveInfo = dri;
   1980             mResolveInfo = dri.getResolveInfo();
   1981         }
   1982 
   1983         @Override
   1984         protected Drawable doInBackground(Void... params) {
   1985             return loadIconForResolveInfo(mResolveInfo);
   1986         }
   1987 
   1988         @Override
   1989         protected void onPostExecute(Drawable d) {
   1990             mDisplayResolveInfo.setDisplayIcon(d);
   1991         }
   1992     }
   1993 
   1994     class LoadAdapterIconTask extends LoadIconTask {
   1995         public LoadAdapterIconTask(DisplayResolveInfo dri) {
   1996             super(dri);
   1997         }
   1998 
   1999         @Override
   2000         protected void onPostExecute(Drawable d) {
   2001             super.onPostExecute(d);
   2002             if (mProfileView != null && mAdapter.getOtherProfile() == mDisplayResolveInfo) {
   2003                 bindProfileView();
   2004             }
   2005             mAdapter.notifyDataSetChanged();
   2006         }
   2007     }
   2008 
   2009     class LoadIconIntoViewTask extends LoadIconTask {
   2010         private final ImageView mTargetView;
   2011 
   2012         public LoadIconIntoViewTask(DisplayResolveInfo dri, ImageView target) {
   2013             super(dri);
   2014             mTargetView = target;
   2015         }
   2016 
   2017         @Override
   2018         protected void onPostExecute(Drawable d) {
   2019             super.onPostExecute(d);
   2020             mTargetView.setImageDrawable(d);
   2021         }
   2022     }
   2023 
   2024     static final boolean isSpecificUriMatch(int match) {
   2025         match = match&IntentFilter.MATCH_CATEGORY_MASK;
   2026         return match >= IntentFilter.MATCH_CATEGORY_HOST
   2027                 && match <= IntentFilter.MATCH_CATEGORY_PATH;
   2028     }
   2029 
   2030     static class PickTargetOptionRequest extends PickOptionRequest {
   2031         public PickTargetOptionRequest(@Nullable Prompt prompt, Option[] options,
   2032                 @Nullable Bundle extras) {
   2033             super(prompt, options, extras);
   2034         }
   2035 
   2036         @Override
   2037         public void onCancel() {
   2038             super.onCancel();
   2039             final ResolverActivity ra = (ResolverActivity) getActivity();
   2040             if (ra != null) {
   2041                 ra.mPickOptionRequest = null;
   2042                 ra.finish();
   2043             }
   2044         }
   2045 
   2046         @Override
   2047         public void onPickOptionResult(boolean finished, Option[] selections, Bundle result) {
   2048             super.onPickOptionResult(finished, selections, result);
   2049             if (selections.length != 1) {
   2050                 // TODO In a better world we would filter the UI presented here and let the
   2051                 // user refine. Maybe later.
   2052                 return;
   2053             }
   2054 
   2055             final ResolverActivity ra = (ResolverActivity) getActivity();
   2056             if (ra != null) {
   2057                 final TargetInfo ti = ra.mAdapter.getItem(selections[0].getIndex());
   2058                 if (ra.onTargetSelected(ti, false)) {
   2059                     ra.mPickOptionRequest = null;
   2060                     ra.finish();
   2061                 }
   2062             }
   2063         }
   2064     }
   2065 }
   2066