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