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