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