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