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