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