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