ResolverActivity.java revision 09a65601b24b77b7e5b3d2d4c7c30eded1ba1040
1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.app;
18
19import android.app.Activity;
20import android.app.usage.PackageUsageStats;
21import android.app.usage.UsageStats;
22import android.app.usage.UsageStatsManager;
23import android.os.AsyncTask;
24import android.widget.AbsListView;
25import android.widget.GridView;
26import com.android.internal.R;
27import com.android.internal.content.PackageMonitor;
28
29import android.app.ActivityManager;
30import android.app.ActivityManagerNative;
31import android.app.AppGlobals;
32import android.content.ComponentName;
33import android.content.Context;
34import android.content.Intent;
35import android.content.IntentFilter;
36import android.content.pm.ActivityInfo;
37import android.content.pm.LabeledIntent;
38import android.content.pm.PackageManager;
39import android.content.pm.PackageManager.NameNotFoundException;
40import android.content.pm.ResolveInfo;
41import android.content.res.Resources;
42import android.graphics.drawable.Drawable;
43import android.net.Uri;
44import android.os.Bundle;
45import android.os.PatternMatcher;
46import android.os.RemoteException;
47import android.os.UserHandle;
48import android.util.Log;
49import android.view.LayoutInflater;
50import android.view.View;
51import android.view.ViewGroup;
52import android.widget.AdapterView;
53import android.widget.BaseAdapter;
54import android.widget.Button;
55import android.widget.ImageView;
56import android.widget.ListView;
57import android.widget.TextView;
58import com.android.internal.widget.ResolverDrawerLayout;
59
60import java.text.Collator;
61import java.util.ArrayList;
62import java.util.Collections;
63import java.util.Comparator;
64import java.util.HashSet;
65import java.util.Iterator;
66import java.util.List;
67import java.util.Set;
68
69/**
70 * This activity is displayed when the system attempts to start an Intent for
71 * which there is more than one matching activity, allowing the user to decide
72 * which to go to.  It is not normally used directly by application developers.
73 */
74public class ResolverActivity extends Activity implements AdapterView.OnItemClickListener {
75    private static final String TAG = "ResolverActivity";
76    private static final boolean DEBUG = false;
77
78    private int mLaunchedFromUid;
79    private ResolveListAdapter mAdapter;
80    private PackageManager mPm;
81    private boolean mAlwaysUseOption;
82    private boolean mShowExtended;
83    private GridView mGridView;
84    private Button mAlwaysButton;
85    private Button mOnceButton;
86    private int mIconDpi;
87    private int mIconSize;
88    private int mMaxColumns;
89    private int mLastSelected = ListView.INVALID_POSITION;
90
91    private UsageStatsManager mUsm;
92    private UsageStats mStats;
93    private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 14;
94
95    private boolean mRegistered;
96    private final PackageMonitor mPackageMonitor = new PackageMonitor() {
97        @Override public void onSomePackagesChanged() {
98            mAdapter.handlePackagesChanged();
99        }
100    };
101
102    private enum ActionTitle {
103        VIEW(Intent.ACTION_VIEW,
104                com.android.internal.R.string.whichViewApplication,
105                com.android.internal.R.string.whichViewApplicationNamed),
106        EDIT(Intent.ACTION_EDIT,
107                com.android.internal.R.string.whichEditApplication,
108                com.android.internal.R.string.whichEditApplicationNamed),
109        SEND(Intent.ACTION_SEND,
110                com.android.internal.R.string.whichSendApplication,
111                com.android.internal.R.string.whichSendApplicationNamed),
112        SENDTO(Intent.ACTION_SENDTO,
113                com.android.internal.R.string.whichSendApplication,
114                com.android.internal.R.string.whichSendApplicationNamed),
115        SEND_MULTIPLE(Intent.ACTION_SEND_MULTIPLE,
116                com.android.internal.R.string.whichSendApplication,
117                com.android.internal.R.string.whichSendApplicationNamed),
118        DEFAULT(null,
119                com.android.internal.R.string.whichApplication,
120                com.android.internal.R.string.whichApplicationNamed);
121
122        public final String action;
123        public final int titleRes;
124        public final int namedTitleRes;
125
126        ActionTitle(String action, int titleRes, int namedTitleRes) {
127            this.action = action;
128            this.titleRes = titleRes;
129            this.namedTitleRes = namedTitleRes;
130        }
131
132        public static ActionTitle forAction(String action) {
133            for (ActionTitle title : values()) {
134                if (action != null && action.equals(title.action)) {
135                    return title;
136                }
137            }
138            return DEFAULT;
139        }
140    }
141
142    private Intent makeMyIntent() {
143        Intent intent = new Intent(getIntent());
144        intent.setComponent(null);
145        // The resolver activity is set to be hidden from recent tasks.
146        // we don't want this attribute to be propagated to the next activity
147        // being launched.  Note that if the original Intent also had this
148        // flag set, we are now losing it.  That should be a very rare case
149        // and we can live with this.
150        intent.setFlags(intent.getFlags()&~Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
151        return intent;
152    }
153
154    @Override
155    protected void onCreate(Bundle savedInstanceState) {
156        // Use a specialized prompt when we're handling the 'Home' app startActivity()
157        final int titleResource;
158        final Intent intent = makeMyIntent();
159        final Set<String> categories = intent.getCategories();
160        if (Intent.ACTION_MAIN.equals(intent.getAction())
161                && categories != null
162                && categories.size() == 1
163                && categories.contains(Intent.CATEGORY_HOME)) {
164            titleResource = com.android.internal.R.string.whichHomeApplication;
165        } else {
166            titleResource = 0;
167        }
168
169        onCreate(savedInstanceState, intent,
170                titleResource != 0 ? getResources().getText(titleResource) : null, titleResource,
171                null, null, true);
172    }
173
174    /**
175     * Compatibility version for other bundled services that use this ocerload without
176     * a default title resource
177     */
178    protected void onCreate(Bundle savedInstanceState, Intent intent,
179            CharSequence title, Intent[] initialIntents,
180            List<ResolveInfo> rList, boolean alwaysUseOption) {
181        onCreate(savedInstanceState, intent, title, 0, initialIntents, rList, alwaysUseOption);
182    }
183
184    protected void onCreate(Bundle savedInstanceState, Intent intent,
185            CharSequence title, int defaultTitleRes, Intent[] initialIntents,
186            List<ResolveInfo> rList, boolean alwaysUseOption) {
187        setTheme(R.style.Theme_DeviceDefault_Resolver);
188        super.onCreate(savedInstanceState);
189        try {
190            mLaunchedFromUid = ActivityManagerNative.getDefault().getLaunchedFromUid(
191                    getActivityToken());
192        } catch (RemoteException e) {
193            mLaunchedFromUid = -1;
194        }
195        mPm = getPackageManager();
196        mUsm = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE);
197
198        final long sinceTime = System.currentTimeMillis() - USAGE_STATS_PERIOD;
199        mStats = mUsm.getRecentStatsSince(sinceTime);
200        Log.d(TAG, "sinceTime=" + sinceTime);
201
202        mMaxColumns = getResources().getInteger(R.integer.config_maxResolverActivityColumns);
203
204        mPackageMonitor.register(this, getMainLooper(), false);
205        mRegistered = true;
206
207        final ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
208        mIconDpi = am.getLauncherLargeIconDensity();
209        mIconSize = am.getLauncherLargeIconSize();
210
211        mAdapter = new ResolveListAdapter(this, intent, initialIntents, rList,
212                mLaunchedFromUid, alwaysUseOption);
213
214        final int layoutId;
215        if (mAdapter.hasFilteredItem()) {
216            layoutId = R.layout.resolver_list_with_default;
217            alwaysUseOption = false;
218        } else {
219            layoutId = R.layout.resolver_list;
220        }
221        mAlwaysUseOption = alwaysUseOption;
222
223        int count = mAdapter.getCount();
224        if (mLaunchedFromUid < 0 || UserHandle.isIsolated(mLaunchedFromUid)) {
225            // Gulp!
226            finish();
227            return;
228        } else if (count > 1) {
229            setContentView(layoutId);
230            mGridView = (GridView) findViewById(R.id.resolver_list);
231            mGridView.setAdapter(mAdapter);
232            mGridView.setOnItemClickListener(this);
233            mGridView.setOnItemLongClickListener(new ItemLongClickListener());
234
235            if (alwaysUseOption) {
236                mGridView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
237            }
238
239            resizeGrid();
240        } else if (count == 1) {
241            startActivity(mAdapter.intentForPosition(0, false));
242            mPackageMonitor.unregister();
243            mRegistered = false;
244            finish();
245            return;
246        } else {
247            setContentView(R.layout.resolver_list);
248
249            final TextView empty = (TextView) findViewById(R.id.empty);
250            empty.setVisibility(View.VISIBLE);
251
252            mGridView = (GridView) findViewById(R.id.resolver_list);
253            mGridView.setVisibility(View.GONE);
254        }
255
256        final ResolverDrawerLayout rdl = (ResolverDrawerLayout) findViewById(R.id.contentPanel);
257        if (rdl != null) {
258            rdl.setOnClickOutsideListener(new View.OnClickListener() {
259                @Override
260                public void onClick(View v) {
261                    finish();
262                }
263            });
264        }
265
266        final TextView titleView = (TextView) findViewById(R.id.title);
267        if (titleView != null) {
268            if (title == null) {
269                title = getTitleForAction(intent.getAction(), defaultTitleRes);
270            }
271            titleView.setText(title);
272        }
273
274        final ImageView iconView = (ImageView) findViewById(R.id.icon);
275        final DisplayResolveInfo iconInfo = mAdapter.getFilteredItem();
276        if (iconView != null && iconInfo != null) {
277            new LoadIconIntoViewTask(iconView).execute(iconInfo);
278        }
279
280        if (alwaysUseOption || mAdapter.hasFilteredItem()) {
281            final ViewGroup buttonLayout = (ViewGroup) findViewById(R.id.button_bar);
282            if (buttonLayout != null) {
283                buttonLayout.setVisibility(View.VISIBLE);
284                mAlwaysButton = (Button) buttonLayout.findViewById(R.id.button_always);
285                mOnceButton = (Button) buttonLayout.findViewById(R.id.button_once);
286            } else {
287                mAlwaysUseOption = false;
288            }
289        }
290
291        if (mAdapter.hasFilteredItem()) {
292            setAlwaysButtonEnabled(true, mAdapter.getFilteredPosition(), false);
293            mOnceButton.setEnabled(true);
294        }
295    }
296
297    protected CharSequence getTitleForAction(String action, int defaultTitleRes) {
298        final ActionTitle title = ActionTitle.forAction(action);
299        final boolean named = mAdapter.hasFilteredItem();
300        if (title == ActionTitle.DEFAULT && defaultTitleRes != 0) {
301            return getString(defaultTitleRes);
302        } else {
303            return named ? getString(title.namedTitleRes, mAdapter.getFilteredItem().displayLabel) :
304                    getString(title.titleRes);
305        }
306    }
307
308    void resizeGrid() {
309        final int itemCount = mAdapter.getCount();
310        mGridView.setNumColumns(Math.min(itemCount, mMaxColumns));
311    }
312
313    void dismiss() {
314        if (!isFinishing()) {
315            finish();
316        }
317    }
318
319    Drawable getIcon(Resources res, int resId) {
320        Drawable result;
321        try {
322            result = res.getDrawableForDensity(resId, mIconDpi);
323        } catch (Resources.NotFoundException e) {
324            result = null;
325        }
326
327        return result;
328    }
329
330    Drawable loadIconForResolveInfo(ResolveInfo ri) {
331        Drawable dr;
332        try {
333            if (ri.resolvePackageName != null && ri.icon != 0) {
334                dr = getIcon(mPm.getResourcesForApplication(ri.resolvePackageName), ri.icon);
335                if (dr != null) {
336                    return dr;
337                }
338            }
339            final int iconRes = ri.getIconResource();
340            if (iconRes != 0) {
341                dr = getIcon(mPm.getResourcesForApplication(ri.activityInfo.packageName), iconRes);
342                if (dr != null) {
343                    return dr;
344                }
345            }
346        } catch (NameNotFoundException e) {
347            Log.e(TAG, "Couldn't find resources for package", e);
348        }
349        return ri.loadIcon(mPm);
350    }
351
352    @Override
353    protected void onRestart() {
354        super.onRestart();
355        if (!mRegistered) {
356            mPackageMonitor.register(this, getMainLooper(), false);
357            mRegistered = true;
358        }
359        mAdapter.handlePackagesChanged();
360    }
361
362    @Override
363    protected void onStop() {
364        super.onStop();
365        if (mRegistered) {
366            mPackageMonitor.unregister();
367            mRegistered = false;
368        }
369        if ((getIntent().getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
370            // This resolver is in the unusual situation where it has been
371            // launched at the top of a new task.  We don't let it be added
372            // to the recent tasks shown to the user, and we need to make sure
373            // that each time we are launched we get the correct launching
374            // uid (not re-using the same resolver from an old launching uid),
375            // so we will now finish ourself since being no longer visible,
376            // the user probably can't get back to us.
377            if (!isChangingConfigurations()) {
378                finish();
379            }
380        }
381    }
382
383    @Override
384    protected void onRestoreInstanceState(Bundle savedInstanceState) {
385        super.onRestoreInstanceState(savedInstanceState);
386        if (mAlwaysUseOption) {
387            final int checkedPos = mGridView.getCheckedItemPosition();
388            final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION;
389            mLastSelected = checkedPos;
390            setAlwaysButtonEnabled(hasValidSelection, checkedPos, true);
391            mOnceButton.setEnabled(hasValidSelection);
392            if (hasValidSelection) {
393                mGridView.setSelection(checkedPos);
394            }
395        }
396    }
397
398    @Override
399    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
400        final int checkedPos = mGridView.getCheckedItemPosition();
401        final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION;
402        if (mAlwaysUseOption && (!hasValidSelection || mLastSelected != checkedPos)) {
403            setAlwaysButtonEnabled(hasValidSelection, checkedPos, true);
404            mOnceButton.setEnabled(hasValidSelection);
405            if (hasValidSelection) {
406                mGridView.smoothScrollToPosition(checkedPos);
407            }
408            mLastSelected = checkedPos;
409        } else {
410            startSelected(position, false, true);
411        }
412    }
413
414    private void setAlwaysButtonEnabled(boolean hasValidSelection, int checkedPos,
415            boolean filtered) {
416        boolean enabled = false;
417        if (hasValidSelection) {
418            ResolveInfo ri = mAdapter.resolveInfoForPosition(checkedPos, filtered);
419            if (ri.targetUserId == UserHandle.USER_CURRENT) {
420                enabled = true;
421            }
422        }
423        mAlwaysButton.setEnabled(enabled);
424    }
425
426    public void onButtonClick(View v) {
427        final int id = v.getId();
428        startSelected(mAlwaysUseOption ?
429                mGridView.getCheckedItemPosition() : mAdapter.getFilteredPosition(),
430                id == R.id.button_always,
431                mAlwaysUseOption);
432        dismiss();
433    }
434
435    void startSelected(int which, boolean always, boolean filtered) {
436        if (isFinishing()) {
437            return;
438        }
439        ResolveInfo ri = mAdapter.resolveInfoForPosition(which, filtered);
440        Intent intent = mAdapter.intentForPosition(which, filtered);
441        onIntentSelected(ri, intent, always);
442        finish();
443    }
444
445    /**
446     * Replace me in subclasses!
447     */
448    public Intent getReplacementIntent(String packageName, Intent defIntent) {
449        return defIntent;
450    }
451
452    protected void onIntentSelected(ResolveInfo ri, Intent intent, boolean alwaysCheck) {
453        if ((mAlwaysUseOption || mAdapter.hasFilteredItem()) && mAdapter.mOrigResolveList != null) {
454            // Build a reasonable intent filter, based on what matched.
455            IntentFilter filter = new IntentFilter();
456
457            if (intent.getAction() != null) {
458                filter.addAction(intent.getAction());
459            }
460            Set<String> categories = intent.getCategories();
461            if (categories != null) {
462                for (String cat : categories) {
463                    filter.addCategory(cat);
464                }
465            }
466            filter.addCategory(Intent.CATEGORY_DEFAULT);
467
468            int cat = ri.match&IntentFilter.MATCH_CATEGORY_MASK;
469            Uri data = intent.getData();
470            if (cat == IntentFilter.MATCH_CATEGORY_TYPE) {
471                String mimeType = intent.resolveType(this);
472                if (mimeType != null) {
473                    try {
474                        filter.addDataType(mimeType);
475                    } catch (IntentFilter.MalformedMimeTypeException e) {
476                        Log.w("ResolverActivity", e);
477                        filter = null;
478                    }
479                }
480            }
481            if (data != null && data.getScheme() != null) {
482                // We need the data specification if there was no type,
483                // OR if the scheme is not one of our magical "file:"
484                // or "content:" schemes (see IntentFilter for the reason).
485                if (cat != IntentFilter.MATCH_CATEGORY_TYPE
486                        || (!"file".equals(data.getScheme())
487                                && !"content".equals(data.getScheme()))) {
488                    filter.addDataScheme(data.getScheme());
489
490                    // Look through the resolved filter to determine which part
491                    // of it matched the original Intent.
492                    Iterator<PatternMatcher> pIt = ri.filter.schemeSpecificPartsIterator();
493                    if (pIt != null) {
494                        String ssp = data.getSchemeSpecificPart();
495                        while (ssp != null && pIt.hasNext()) {
496                            PatternMatcher p = pIt.next();
497                            if (p.match(ssp)) {
498                                filter.addDataSchemeSpecificPart(p.getPath(), p.getType());
499                                break;
500                            }
501                        }
502                    }
503                    Iterator<IntentFilter.AuthorityEntry> aIt = ri.filter.authoritiesIterator();
504                    if (aIt != null) {
505                        while (aIt.hasNext()) {
506                            IntentFilter.AuthorityEntry a = aIt.next();
507                            if (a.match(data) >= 0) {
508                                int port = a.getPort();
509                                filter.addDataAuthority(a.getHost(),
510                                        port >= 0 ? Integer.toString(port) : null);
511                                break;
512                            }
513                        }
514                    }
515                    pIt = ri.filter.pathsIterator();
516                    if (pIt != null) {
517                        String path = data.getPath();
518                        while (path != null && pIt.hasNext()) {
519                            PatternMatcher p = pIt.next();
520                            if (p.match(path)) {
521                                filter.addDataPath(p.getPath(), p.getType());
522                                break;
523                            }
524                        }
525                    }
526                }
527            }
528
529            if (filter != null) {
530                final int N = mAdapter.mOrigResolveList.size();
531                ComponentName[] set = new ComponentName[N];
532                int bestMatch = 0;
533                for (int i=0; i<N; i++) {
534                    ResolveInfo r = mAdapter.mOrigResolveList.get(i);
535                    set[i] = new ComponentName(r.activityInfo.packageName,
536                            r.activityInfo.name);
537                    if (r.match > bestMatch) bestMatch = r.match;
538                }
539                if (alwaysCheck) {
540                    getPackageManager().addPreferredActivity(filter, bestMatch, set,
541                            intent.getComponent());
542                } else {
543                    try {
544                        AppGlobals.getPackageManager().setLastChosenActivity(intent,
545                                intent.resolveTypeIfNeeded(getContentResolver()),
546                                PackageManager.MATCH_DEFAULT_ONLY,
547                                filter, bestMatch, intent.getComponent());
548                    } catch (RemoteException re) {
549                        Log.d(TAG, "Error calling setLastChosenActivity\n" + re);
550                    }
551                }
552            }
553        }
554
555        if (intent != null) {
556            startActivity(intent);
557        }
558    }
559
560    void showAppDetails(ResolveInfo ri) {
561        Intent in = new Intent().setAction("android.settings.APPLICATION_DETAILS_SETTINGS")
562                .setData(Uri.fromParts("package", ri.activityInfo.packageName, null))
563                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
564        startActivity(in);
565    }
566
567    private final class DisplayResolveInfo {
568        ResolveInfo ri;
569        CharSequence displayLabel;
570        Drawable displayIcon;
571        CharSequence extendedInfo;
572        Intent origIntent;
573
574        DisplayResolveInfo(ResolveInfo pri, CharSequence pLabel,
575                CharSequence pInfo, Intent pOrigIntent) {
576            ri = pri;
577            displayLabel = pLabel;
578            extendedInfo = pInfo;
579            origIntent = pOrigIntent;
580        }
581    }
582
583    private final class ResolveListAdapter extends BaseAdapter {
584        private final Intent[] mInitialIntents;
585        private final List<ResolveInfo> mBaseResolveList;
586        private ResolveInfo mLastChosen;
587        private final Intent mIntent;
588        private final int mLaunchedFromUid;
589        private final LayoutInflater mInflater;
590
591        List<DisplayResolveInfo> mList;
592        List<ResolveInfo> mOrigResolveList;
593
594        private int mLastChosenPosition = -1;
595        private boolean mFilterLastUsed;
596
597        public ResolveListAdapter(Context context, Intent intent,
598                Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
599                boolean filterLastUsed) {
600            mIntent = new Intent(intent);
601            mInitialIntents = initialIntents;
602            mBaseResolveList = rList;
603            mLaunchedFromUid = launchedFromUid;
604            mInflater = LayoutInflater.from(context);
605            mList = new ArrayList<DisplayResolveInfo>();
606            mFilterLastUsed = filterLastUsed;
607            rebuildList();
608        }
609
610        public void handlePackagesChanged() {
611            final int oldItemCount = getCount();
612            rebuildList();
613            notifyDataSetChanged();
614            final int newItemCount = getCount();
615            if (newItemCount == 0) {
616                // We no longer have any items...  just finish the activity.
617                finish();
618            } else if (newItemCount != oldItemCount) {
619                resizeGrid();
620            }
621        }
622
623        public DisplayResolveInfo getFilteredItem() {
624            if (mFilterLastUsed && mLastChosenPosition >= 0) {
625                // Not using getItem since it offsets to dodge this position for the list
626                return mList.get(mLastChosenPosition);
627            }
628            return null;
629        }
630
631        public int getFilteredPosition() {
632            if (mFilterLastUsed && mLastChosenPosition >= 0) {
633                return mLastChosenPosition;
634            }
635            return AbsListView.INVALID_POSITION;
636        }
637
638        public boolean hasFilteredItem() {
639            return mFilterLastUsed && mLastChosenPosition >= 0;
640        }
641
642        private void rebuildList() {
643            List<ResolveInfo> currentResolveList;
644
645            try {
646                mLastChosen = AppGlobals.getPackageManager().getLastChosenActivity(
647                        mIntent, mIntent.resolveTypeIfNeeded(getContentResolver()),
648                        PackageManager.MATCH_DEFAULT_ONLY);
649            } catch (RemoteException re) {
650                Log.d(TAG, "Error calling setLastChosenActivity\n" + re);
651            }
652
653            mList.clear();
654            if (mBaseResolveList != null) {
655                currentResolveList = mOrigResolveList = mBaseResolveList;
656            } else {
657                currentResolveList = mOrigResolveList = mPm.queryIntentActivities(
658                        mIntent, PackageManager.MATCH_DEFAULT_ONLY
659                        | (mFilterLastUsed ? PackageManager.GET_RESOLVED_FILTER : 0));
660                // Filter out any activities that the launched uid does not
661                // have permission for.  We don't do this when we have an explicit
662                // list of resolved activities, because that only happens when
663                // we are being subclassed, so we can safely launch whatever
664                // they gave us.
665                if (currentResolveList != null) {
666                    for (int i=currentResolveList.size()-1; i >= 0; i--) {
667                        ActivityInfo ai = currentResolveList.get(i).activityInfo;
668                        int granted = ActivityManager.checkComponentPermission(
669                                ai.permission, mLaunchedFromUid,
670                                ai.applicationInfo.uid, ai.exported);
671                        if (granted != PackageManager.PERMISSION_GRANTED) {
672                            // Access not allowed!
673                            if (mOrigResolveList == currentResolveList) {
674                                mOrigResolveList = new ArrayList<ResolveInfo>(mOrigResolveList);
675                            }
676                            currentResolveList.remove(i);
677                        }
678                    }
679                }
680            }
681            int N;
682            if ((currentResolveList != null) && ((N = currentResolveList.size()) > 0)) {
683                // Only display the first matches that are either of equal
684                // priority or have asked to be default options.
685                ResolveInfo r0 = currentResolveList.get(0);
686                for (int i=1; i<N; i++) {
687                    ResolveInfo ri = currentResolveList.get(i);
688                    if (DEBUG) Log.v(
689                        TAG,
690                        r0.activityInfo.name + "=" +
691                        r0.priority + "/" + r0.isDefault + " vs " +
692                        ri.activityInfo.name + "=" +
693                        ri.priority + "/" + ri.isDefault);
694                    if (r0.priority != ri.priority ||
695                        r0.isDefault != ri.isDefault) {
696                        while (i < N) {
697                            if (mOrigResolveList == currentResolveList) {
698                                mOrigResolveList = new ArrayList<ResolveInfo>(mOrigResolveList);
699                            }
700                            currentResolveList.remove(i);
701                            N--;
702                        }
703                    }
704                }
705                if (N > 1) {
706                    Comparator<ResolveInfo> rComparator =
707                            new ResolverComparator(ResolverActivity.this);
708                    Collections.sort(currentResolveList, rComparator);
709                }
710                // First put the initial items at the top.
711                if (mInitialIntents != null) {
712                    for (int i=0; i<mInitialIntents.length; i++) {
713                        Intent ii = mInitialIntents[i];
714                        if (ii == null) {
715                            continue;
716                        }
717                        ActivityInfo ai = ii.resolveActivityInfo(
718                                getPackageManager(), 0);
719                        if (ai == null) {
720                            Log.w(TAG, "No activity found for " + ii);
721                            continue;
722                        }
723                        ResolveInfo ri = new ResolveInfo();
724                        ri.activityInfo = ai;
725                        if (ii instanceof LabeledIntent) {
726                            LabeledIntent li = (LabeledIntent)ii;
727                            ri.resolvePackageName = li.getSourcePackage();
728                            ri.labelRes = li.getLabelResource();
729                            ri.nonLocalizedLabel = li.getNonLocalizedLabel();
730                            ri.icon = li.getIconResource();
731                        }
732                        mList.add(new DisplayResolveInfo(ri,
733                                ri.loadLabel(getPackageManager()), null, ii));
734                    }
735                }
736
737                // Check for applications with same name and use application name or
738                // package name if necessary
739                r0 = currentResolveList.get(0);
740                int start = 0;
741                CharSequence r0Label =  r0.loadLabel(mPm);
742                mShowExtended = false;
743                for (int i = 1; i < N; i++) {
744                    if (r0Label == null) {
745                        r0Label = r0.activityInfo.packageName;
746                    }
747                    ResolveInfo ri = currentResolveList.get(i);
748                    CharSequence riLabel = ri.loadLabel(mPm);
749                    if (riLabel == null) {
750                        riLabel = ri.activityInfo.packageName;
751                    }
752                    if (riLabel.equals(r0Label)) {
753                        continue;
754                    }
755                    processGroup(currentResolveList, start, (i-1), r0, r0Label);
756                    r0 = ri;
757                    r0Label = riLabel;
758                    start = i;
759                }
760                // Process last group
761                processGroup(currentResolveList, start, (N-1), r0, r0Label);
762            }
763        }
764
765        private void processGroup(List<ResolveInfo> rList, int start, int end, ResolveInfo ro,
766                CharSequence roLabel) {
767            // Process labels from start to i
768            int num = end - start+1;
769            if (num == 1) {
770                if (mLastChosen != null
771                        && mLastChosen.activityInfo.packageName.equals(
772                                ro.activityInfo.packageName)
773                        && mLastChosen.activityInfo.name.equals(ro.activityInfo.name)) {
774                    mLastChosenPosition = mList.size();
775                }
776                // No duplicate labels. Use label for entry at start
777                mList.add(new DisplayResolveInfo(ro, roLabel, null, null));
778            } else {
779                mShowExtended = true;
780                boolean usePkg = false;
781                CharSequence startApp = ro.activityInfo.applicationInfo.loadLabel(mPm);
782                if (startApp == null) {
783                    usePkg = true;
784                }
785                if (!usePkg) {
786                    // Use HashSet to track duplicates
787                    HashSet<CharSequence> duplicates =
788                        new HashSet<CharSequence>();
789                    duplicates.add(startApp);
790                    for (int j = start+1; j <= end ; j++) {
791                        ResolveInfo jRi = rList.get(j);
792                        CharSequence jApp = jRi.activityInfo.applicationInfo.loadLabel(mPm);
793                        if ( (jApp == null) || (duplicates.contains(jApp))) {
794                            usePkg = true;
795                            break;
796                        } else {
797                            duplicates.add(jApp);
798                        }
799                    }
800                    // Clear HashSet for later use
801                    duplicates.clear();
802                }
803                for (int k = start; k <= end; k++) {
804                    ResolveInfo add = rList.get(k);
805                    if (mLastChosen != null
806                            && mLastChosen.activityInfo.packageName.equals(
807                                    add.activityInfo.packageName)
808                            && mLastChosen.activityInfo.name.equals(add.activityInfo.name)) {
809                        mLastChosenPosition = mList.size();
810                    }
811                    if (usePkg) {
812                        // Use application name for all entries from start to end-1
813                        mList.add(new DisplayResolveInfo(add, roLabel,
814                                add.activityInfo.packageName, null));
815                    } else {
816                        // Use package name for all entries from start to end-1
817                        mList.add(new DisplayResolveInfo(add, roLabel,
818                                add.activityInfo.applicationInfo.loadLabel(mPm), null));
819                    }
820                }
821            }
822        }
823
824        public ResolveInfo resolveInfoForPosition(int position, boolean filtered) {
825            return (filtered ? getItem(position) : mList.get(position)).ri;
826        }
827
828        public Intent intentForPosition(int position, boolean filtered) {
829            DisplayResolveInfo dri = filtered ? getItem(position) : mList.get(position);
830
831            Intent intent = new Intent(dri.origIntent != null ? dri.origIntent :
832                    getReplacementIntent(dri.ri.activityInfo.packageName, mIntent));
833            intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT
834                    |Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
835            ActivityInfo ai = dri.ri.activityInfo;
836            intent.setComponent(new ComponentName(
837                    ai.applicationInfo.packageName, ai.name));
838            return intent;
839        }
840
841        public int getCount() {
842            int result = mList.size();
843            if (mFilterLastUsed && mLastChosenPosition >= 0) {
844                result--;
845            }
846            return result;
847        }
848
849        public DisplayResolveInfo getItem(int position) {
850            if (mFilterLastUsed && mLastChosenPosition >= 0 && position >= mLastChosenPosition) {
851                position++;
852            }
853            return mList.get(position);
854        }
855
856        public long getItemId(int position) {
857            return position;
858        }
859
860        public View getView(int position, View convertView, ViewGroup parent) {
861            View view;
862            if (convertView == null) {
863                view = mInflater.inflate(
864                        com.android.internal.R.layout.resolve_list_item, parent, false);
865
866                final ViewHolder holder = new ViewHolder(view);
867                view.setTag(holder);
868
869                // Fix the icon size even if we have different sized resources
870                ViewGroup.LayoutParams lp = holder.icon.getLayoutParams();
871                lp.width = lp.height = mIconSize;
872            } else {
873                view = convertView;
874            }
875            bindView(view, getItem(position));
876            return view;
877        }
878
879        private final void bindView(View view, DisplayResolveInfo info) {
880            final ViewHolder holder = (ViewHolder) view.getTag();
881            holder.text.setText(info.displayLabel);
882            if (mShowExtended) {
883                holder.text2.setVisibility(View.VISIBLE);
884                holder.text2.setText(info.extendedInfo);
885            } else {
886                holder.text2.setVisibility(View.GONE);
887            }
888            if (info.displayIcon == null) {
889                new LoadIconTask().execute(info);
890            }
891            holder.icon.setImageDrawable(info.displayIcon);
892        }
893    }
894
895    static class ViewHolder {
896        public TextView text;
897        public TextView text2;
898        public ImageView icon;
899
900        public ViewHolder(View view) {
901            text = (TextView) view.findViewById(com.android.internal.R.id.text1);
902            text2 = (TextView) view.findViewById(com.android.internal.R.id.text2);
903            icon = (ImageView) view.findViewById(R.id.icon);
904        }
905    }
906
907    class ItemLongClickListener implements AdapterView.OnItemLongClickListener {
908
909        @Override
910        public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
911            ResolveInfo ri = mAdapter.resolveInfoForPosition(position, true);
912            showAppDetails(ri);
913            return true;
914        }
915
916    }
917
918    class LoadIconTask extends AsyncTask<DisplayResolveInfo, Void, DisplayResolveInfo> {
919        @Override
920        protected DisplayResolveInfo doInBackground(DisplayResolveInfo... params) {
921            final DisplayResolveInfo info = params[0];
922            if (info.displayIcon == null) {
923                info.displayIcon = loadIconForResolveInfo(info.ri);
924            }
925            return info;
926        }
927
928        @Override
929        protected void onPostExecute(DisplayResolveInfo info) {
930            mAdapter.notifyDataSetChanged();
931        }
932    }
933
934    class LoadIconIntoViewTask extends AsyncTask<DisplayResolveInfo, Void, DisplayResolveInfo> {
935        final ImageView mTargetView;
936
937        public LoadIconIntoViewTask(ImageView target) {
938            mTargetView = target;
939        }
940
941        @Override
942        protected DisplayResolveInfo doInBackground(DisplayResolveInfo... params) {
943            final DisplayResolveInfo info = params[0];
944            if (info.displayIcon == null) {
945                info.displayIcon = loadIconForResolveInfo(info.ri);
946            }
947            return info;
948        }
949
950        @Override
951        protected void onPostExecute(DisplayResolveInfo info) {
952            mTargetView.setImageDrawable(info.displayIcon);
953        }
954    }
955
956    class ResolverComparator implements Comparator<ResolveInfo> {
957        private final Collator mCollator;
958
959        public ResolverComparator(Context context) {
960            mCollator = Collator.getInstance(context.getResources().getConfiguration().locale);
961        }
962
963        @Override
964        public int compare(ResolveInfo lhs, ResolveInfo rhs) {
965            // We want to put the one targeted to another user at the end of the dialog.
966            if (lhs.targetUserId != UserHandle.USER_CURRENT) {
967                return 1;
968            }
969            if (lhs.targetUserId != UserHandle.USER_CURRENT) {
970                return -1;
971            }
972
973            if (mStats != null) {
974                final long timeDiff =
975                        getPackageTimeSpent(rhs.activityInfo.packageName) -
976                        getPackageTimeSpent(lhs.activityInfo.packageName);
977
978                if (timeDiff != 0) {
979                    return timeDiff > 0 ? 1 : -1;
980                }
981            }
982
983            CharSequence  sa = lhs.loadLabel(mPm);
984            if (sa == null) sa = lhs.activityInfo.name;
985            CharSequence  sb = rhs.loadLabel(mPm);
986            if (sb == null) sb = rhs.activityInfo.name;
987
988            return mCollator.compare(sa.toString(), sb.toString());
989        }
990
991        private long getPackageTimeSpent(String packageName) {
992            if (mStats != null) {
993                final PackageUsageStats stats = mStats.getPackage(packageName);
994                if (stats != null) {
995                    return stats.getTotalTimeSpent();
996                }
997
998            }
999            return 0;
1000        }
1001    }
1002}
1003
1004