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      */
getLabelRes(String action)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 
ActionTitle(String action, int titleRes, int namedTitleRes, @StringRes int labelRes)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 
forAction(String action)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 
makeMyIntent()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
onCreate(Bundle savedInstanceState)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      */
onCreate(Bundle savedInstanceState, Intent intent, CharSequence title, Intent[] initialIntents, List<ResolveInfo> rList, boolean supportsAlwaysUseOption)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 
onCreate(Bundle savedInstanceState, Intent intent, CharSequence title, int defaultTitleRes, Intent[] initialIntents, List<ResolveInfo> rList, boolean supportsAlwaysUseOption)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
onConfigurationChanged(Configuration newConfig)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      */
onSetupVoiceInteraction()349     public void onSetupVoiceInteraction() {
350         // Do it right now. Subclasses may delay this and send it later.
351         sendVoiceChoicesIfNeeded();
352     }
353 
sendVoiceChoicesIfNeeded()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 
optionForChooserTarget(TargetInfo target, int index)371     Option optionForChooserTarget(TargetInfo target, int index) {
372         return new Option(target.getDisplayLabel(), index);
373     }
374 
setAdditionalTargets(Intent[] intents)375     protected final void setAdditionalTargets(Intent[] intents) {
376         if (intents != null) {
377             for (Intent intent : intents) {
378                 mIntents.add(intent);
379             }
380         }
381     }
382 
getTargetIntent()383     public Intent getTargetIntent() {
384         return mIntents.isEmpty() ? null : mIntents.get(0);
385     }
386 
getReferrerPackageName()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 
getLayoutResource()395     public int getLayoutResource() {
396         return R.layout.resolver_list;
397     }
398 
bindProfileView()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 
setProfileSwitchMessageId(int contentUserHint)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      */
setSafeForwardingMode(boolean safeForwarding)441     public void setSafeForwardingMode(boolean safeForwarding) {
442         mSafeForwardingMode = safeForwarding;
443     }
444 
getTitleForAction(String action, int defaultTitleRes)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 
dismiss()459     void dismiss() {
460         if (!isFinishing()) {
461             finish();
462         }
463     }
464 
getIcon(Resources res, int resId)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 
loadIconForResolveInfo(ResolveInfo ri)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
onRestart()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
onStop()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
onDestroy()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
onRestoreInstanceState(Bundle savedInstanceState)550     protected void onRestoreInstanceState(Bundle savedInstanceState) {
551         super.onRestoreInstanceState(savedInstanceState);
552         resetAlwaysOrOnceButtonBar();
553     }
554 
hasManagedProfile()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 
supportsManagedProfiles(ResolveInfo resolveInfo)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 
setAlwaysButtonEnabled(boolean hasValidSelection, int checkedPos, boolean filtered)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 
onButtonClick(View v)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 
startSelected(int which, boolean always, boolean hasIndexBeenFiltered)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      */
getReplacementIntent(ActivityInfo aInfo, Intent defIntent)649     public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) {
650         return defIntent;
651     }
652 
onTargetSelected(TargetInfo target, boolean alwaysCheck)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 
safelyStartActivity(TargetInfo cti)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 
safelyStartActivityInternal(TargetInfo cti)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 
onActivityStarted(TargetInfo cti)860     public void onActivityStarted(TargetInfo cti) {
861         // Do nothing
862     }
863 
shouldGetActivityMetadata()864     public boolean shouldGetActivityMetadata() {
865         return false;
866     }
867 
shouldAutoLaunchSingleChoice(TargetInfo target)868     public boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
869         return true;
870     }
871 
showTargetDetails(ResolveInfo ri)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 
createAdapter(Context context, List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid, boolean filterLastUsed)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
createListController()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      */
configureContentView(List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList)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 
onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter)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 
setTitleAndIcon()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 
resetAlwaysOrOnceButtonBar()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 
useLayoutWithDefault()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      */
setRetainInOnStop(boolean retainInOnStop)1050     protected void setRetainInOnStop(boolean retainInOnStop) {
1051         mRetainInOnStop = retainInOnStop;
1052     }
1053 
1054     /**
1055      * Check a simple match for the component of two ResolveInfos.
1056      */
resolveInfoMatch(ResolveInfo lhs, ResolveInfo rhs)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 
DisplayResolveInfo(Intent originalIntent, ResolveInfo pri, CharSequence pLabel, CharSequence pInfo, Intent pOrigIntent)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 
DisplayResolveInfo(DisplayResolveInfo other, Intent fillInIntent, int flags)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 
getResolveInfo()1102         public ResolveInfo getResolveInfo() {
1103             return mResolveInfo;
1104         }
1105 
getDisplayLabel()1106         public CharSequence getDisplayLabel() {
1107             return mDisplayLabel;
1108         }
1109 
getDisplayIcon()1110         public Drawable getDisplayIcon() {
1111             return mDisplayIcon;
1112         }
1113 
getBadgeIcon()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
getBadgeContentDescription()1137         public CharSequence getBadgeContentDescription() {
1138             return null;
1139         }
1140 
1141         @Override
cloneFilledIn(Intent fillInIntent, int flags)1142         public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
1143             return new DisplayResolveInfo(this, fillInIntent, flags);
1144         }
1145 
1146         @Override
getAllSourceIntents()1147         public List<Intent> getAllSourceIntents() {
1148             return mSourceIntents;
1149         }
1150 
addAlternateSourceIntent(Intent alt)1151         public void addAlternateSourceIntent(Intent alt) {
1152             mSourceIntents.add(alt);
1153         }
1154 
setDisplayIcon(Drawable icon)1155         public void setDisplayIcon(Drawable icon) {
1156             mDisplayIcon = icon;
1157         }
1158 
hasDisplayIcon()1159         public boolean hasDisplayIcon() {
1160             return mDisplayIcon != null;
1161         }
1162 
getExtendedInfo()1163         public CharSequence getExtendedInfo() {
1164             return mExtendedInfo;
1165         }
1166 
getResolvedIntent()1167         public Intent getResolvedIntent() {
1168             return mResolvedIntent;
1169         }
1170 
1171         @Override
getResolvedComponentName()1172         public ComponentName getResolvedComponentName() {
1173             return new ComponentName(mResolveInfo.activityInfo.packageName,
1174                     mResolveInfo.activityInfo.name);
1175         }
1176 
1177         @Override
start(Activity activity, Bundle options)1178         public boolean start(Activity activity, Bundle options) {
1179             activity.startActivity(mResolvedIntent, options);
1180             return true;
1181         }
1182 
1183         @Override
startAsCaller(Activity activity, Bundle options, int userId)1184         public boolean startAsCaller(Activity activity, Bundle options, int userId) {
1185             activity.startActivityAsCaller(mResolvedIntent, options, false, userId);
1186             return true;
1187         }
1188 
1189         @Override
startAsUser(Activity activity, Bundle options, UserHandle user)1190         public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
1191             activity.startActivityAsUser(mResolvedIntent, options, user);
1192             return false;
1193         }
1194 
1195         @Override
isPinned()1196         public boolean isPinned() {
1197             return mPinned;
1198         }
1199 
setPinned(boolean pinned)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          */
getResolvedIntent()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          */
getResolvedComponentName()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          */
start(Activity activity, Bundle options)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          */
startAsCaller(Activity activity, Bundle options, int userId)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          */
startAsUser(Activity activity, Bundle options, UserHandle user)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          */
getResolveInfo()1263         ResolveInfo getResolveInfo();
1264 
1265         /**
1266          * Return the human-readable text label for this target.
1267          *
1268          * @return user-visible target label
1269          */
getDisplayLabel()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          */
getExtendedInfo()1278         CharSequence getExtendedInfo();
1279 
1280         /**
1281          * @return The drawable that should be used to represent this target
1282          */
getDisplayIcon()1283         Drawable getDisplayIcon();
1284 
1285         /**
1286          * @return The (small) icon to badge the target with
1287          */
getBadgeIcon()1288         Drawable getBadgeIcon();
1289 
1290         /**
1291          * @return The content description for the badge icon
1292          */
getBadgeContentDescription()1293         CharSequence getBadgeContentDescription();
1294 
1295         /**
1296          * Clone this target with the given fill-in information.
1297          */
cloneFilledIn(Intent fillInIntent, int flags)1298         TargetInfo cloneFilledIn(Intent fillInIntent, int flags);
1299 
1300         /**
1301          * @return the list of supported source intents deduped against this single target
1302          */
getAllSourceIntents()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          */
isPinned()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 
ResolveListAdapter(Context context, List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid, boolean filterLastUsed, ResolverListController resolverListController)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 
handlePackagesChanged()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 
setPlaceholderCount(int count)1351         public void setPlaceholderCount(int count) {
1352             mPlaceholderCount = count;
1353         }
1354 
getPlaceholderCount()1355         public int getPlaceholderCount() { return mPlaceholderCount; }
1356 
1357         @Nullable
getFilteredItem()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 
getOtherProfile()1366         public DisplayResolveInfo getOtherProfile() {
1367             return mOtherProfile;
1368         }
1369 
getFilteredPosition()1370         public int getFilteredPosition() {
1371             if (mFilterLastUsed && mLastChosenPosition >= 0) {
1372                 return mLastChosenPosition;
1373             }
1374             return AbsListView.INVALID_POSITION;
1375         }
1376 
hasFilteredItem()1377         public boolean hasFilteredItem() {
1378             return mFilterLastUsed && mLastChosen != null;
1379         }
1380 
getScore(DisplayResolveInfo target)1381         public float getScore(DisplayResolveInfo target) {
1382             return mResolverListController.getScore(target);
1383         }
1384 
updateModel(ComponentName componentName)1385         public void updateModel(ComponentName componentName) {
1386             mResolverListController.updateModel(componentName);
1387         }
1388 
updateChooserCounts(String packageName, int userId, String action)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          */
rebuildList()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 
processSortedList(List<ResolvedComponentInfo> sortedComponents)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          */
postListReadyRunnable()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 
onListRebuilt()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 
shouldGetResolvedFilter()1605         public boolean shouldGetResolvedFilter() {
1606             return mFilterLastUsed;
1607         }
1608 
processGroup(List<ResolvedComponentInfo> rList, int start, int end, ResolvedComponentInfo ro, CharSequence roLabel)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 
addResolveInfoWithAlternates(ResolvedComponentInfo rci, CharSequence extraInfo, CharSequence roLabel)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 
updateLastChosenPosition(ResolveInfo info)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.
addResolveInfo(DisplayResolveInfo dri)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
resolveInfoForPosition(int position, boolean filtered)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
targetInfoForPosition(int position, boolean filtered)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 
getCount()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 
getUnfilteredCount()1736         public int getUnfilteredCount() {
1737             return mDisplayList.size();
1738         }
1739 
getDisplayInfoCount()1740         public int getDisplayInfoCount() {
1741             return mDisplayList.size();
1742         }
1743 
getDisplayInfoAt(int index)1744         public DisplayResolveInfo getDisplayInfoAt(int index) {
1745             return mDisplayList.get(index);
1746         }
1747 
1748         @Nullable
getItem(int position)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 
getItemId(int position)1760         public long getItemId(int position) {
1761             return position;
1762         }
1763 
hasExtendedInfo()1764         public boolean hasExtendedInfo() {
1765             return mHasExtendedInfo;
1766         }
1767 
hasResolvedTarget(ResolveInfo info)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 
getDisplayResolveInfoCount()1777         public int getDisplayResolveInfoCount() {
1778             return mDisplayList.size();
1779         }
1780 
getDisplayResolveInfo(int index)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 
getView(int position, View convertView, ViewGroup parent)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 
createView(ViewGroup parent)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 
onCreateView(ViewGroup parent)1802         public View onCreateView(ViewGroup parent) {
1803             return mInflater.inflate(
1804                     com.android.internal.R.layout.resolve_list_item, parent, false);
1805         }
1806 
showsExtendedInfo(TargetInfo info)1807         public boolean showsExtendedInfo(TargetInfo info) {
1808             return !TextUtils.isEmpty(info.getExtendedInfo());
1809         }
1810 
isComponentPinned(ComponentName name)1811         public boolean isComponentPinned(ComponentName name) {
1812             return false;
1813         }
1814 
bindView(int position, View view)1815         public final void bindView(int position, View view) {
1816             onBindView(view, getItem(position));
1817         }
1818 
onBindView(View view, TargetInfo info)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 
ResolvedComponentInfo(ComponentName name, Intent intent, ResolveInfo info)1861         public ResolvedComponentInfo(ComponentName name, Intent intent, ResolveInfo info) {
1862             this.name = name;
1863             add(intent, info);
1864         }
1865 
add(Intent intent, ResolveInfo info)1866         public void add(Intent intent, ResolveInfo info) {
1867             mIntents.add(intent);
1868             mResolveInfos.add(info);
1869         }
1870 
getCount()1871         public int getCount() {
1872             return mIntents.size();
1873         }
1874 
getIntentAt(int index)1875         public Intent getIntentAt(int index) {
1876             return index >= 0 ? mIntents.get(index) : null;
1877         }
1878 
getResolveInfoAt(int index)1879         public ResolveInfo getResolveInfoAt(int index) {
1880             return index >= 0 ? mResolveInfos.get(index) : null;
1881         }
1882 
findIntent(Intent intent)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 
findResolveInfo(ResolveInfo info)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 
isPinned()1901         public boolean isPinned() {
1902             return mPinned;
1903         }
1904 
setPinned(boolean pinned)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 
ViewHolder(View view)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
onItemClick(AdapterView<?> parent, View view, int position, long id)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
onItemLongClick(AdapterView<?> parent, View view, int position, long id)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 
LoadIconTask(DisplayResolveInfo dri)1978         public LoadIconTask(DisplayResolveInfo dri) {
1979             mDisplayResolveInfo = dri;
1980             mResolveInfo = dri.getResolveInfo();
1981         }
1982 
1983         @Override
doInBackground(Void... params)1984         protected Drawable doInBackground(Void... params) {
1985             return loadIconForResolveInfo(mResolveInfo);
1986         }
1987 
1988         @Override
onPostExecute(Drawable d)1989         protected void onPostExecute(Drawable d) {
1990             mDisplayResolveInfo.setDisplayIcon(d);
1991         }
1992     }
1993 
1994     class LoadAdapterIconTask extends LoadIconTask {
LoadAdapterIconTask(DisplayResolveInfo dri)1995         public LoadAdapterIconTask(DisplayResolveInfo dri) {
1996             super(dri);
1997         }
1998 
1999         @Override
onPostExecute(Drawable d)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 
LoadIconIntoViewTask(DisplayResolveInfo dri, ImageView target)2012         public LoadIconIntoViewTask(DisplayResolveInfo dri, ImageView target) {
2013             super(dri);
2014             mTargetView = target;
2015         }
2016 
2017         @Override
onPostExecute(Drawable d)2018         protected void onPostExecute(Drawable d) {
2019             super.onPostExecute(d);
2020             mTargetView.setImageDrawable(d);
2021         }
2022     }
2023 
isSpecificUriMatch(int match)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 {
PickTargetOptionRequest(@ullable Prompt prompt, Option[] options, @Nullable Bundle extras)2031         public PickTargetOptionRequest(@Nullable Prompt prompt, Option[] options,
2032                 @Nullable Bundle extras) {
2033             super(prompt, options, extras);
2034         }
2035 
2036         @Override
onCancel()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
onPickOptionResult(boolean finished, Option[] selections, Bundle result)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