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