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