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