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