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