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.os.AsyncTask;
20import com.android.internal.R;
21import com.android.internal.content.PackageMonitor;
22
23import android.app.ActivityManager;
24import android.app.ActivityManagerNative;
25import android.app.AppGlobals;
26import android.content.ComponentName;
27import android.content.Context;
28import android.content.Intent;
29import android.content.IntentFilter;
30import android.content.pm.ActivityInfo;
31import android.content.pm.LabeledIntent;
32import android.content.pm.PackageManager;
33import android.content.pm.PackageManager.NameNotFoundException;
34import android.content.pm.ResolveInfo;
35import android.content.res.Resources;
36import android.graphics.drawable.Drawable;
37import android.net.Uri;
38import android.os.Bundle;
39import android.os.PatternMatcher;
40import android.os.RemoteException;
41import android.os.UserHandle;
42import android.util.Log;
43import android.view.LayoutInflater;
44import android.view.View;
45import android.view.ViewGroup;
46import android.widget.AdapterView;
47import android.widget.BaseAdapter;
48import android.widget.Button;
49import android.widget.ImageView;
50import android.widget.ListView;
51import android.widget.TextView;
52
53import java.util.ArrayList;
54import java.util.Collections;
55import java.util.HashSet;
56import java.util.Iterator;
57import java.util.List;
58import java.util.Set;
59
60/**
61 * This activity is displayed when the system attempts to start an Intent for
62 * which there is more than one matching activity, allowing the user to decide
63 * which to go to.  It is not normally used directly by application developers.
64 */
65public class ResolverActivity extends AlertActivity implements AdapterView.OnItemClickListener {
66    private static final String TAG = "ResolverActivity";
67    private static final boolean DEBUG = false;
68
69    private int mLaunchedFromUid;
70    private ResolveListAdapter mAdapter;
71    private PackageManager mPm;
72    private boolean mAlwaysUseOption;
73    private boolean mShowExtended;
74    private ListView mListView;
75    private Button mAlwaysButton;
76    private Button mOnceButton;
77    private int mIconDpi;
78    private int mIconSize;
79    private int mMaxColumns;
80    private int mLastSelected = ListView.INVALID_POSITION;
81
82    private boolean mRegistered;
83    private final PackageMonitor mPackageMonitor = new PackageMonitor() {
84        @Override public void onSomePackagesChanged() {
85            mAdapter.handlePackagesChanged();
86        }
87    };
88
89    private Intent makeMyIntent() {
90        Intent intent = new Intent(getIntent());
91        intent.setComponent(null);
92        // The resolver activity is set to be hidden from recent tasks.
93        // we don't want this attribute to be propagated to the next activity
94        // being launched.  Note that if the original Intent also had this
95        // flag set, we are now losing it.  That should be a very rare case
96        // and we can live with this.
97        intent.setFlags(intent.getFlags()&~Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
98        return intent;
99    }
100
101    @Override
102    protected void onCreate(Bundle savedInstanceState) {
103        // Use a specialized prompt when we're handling the 'Home' app startActivity()
104        final int titleResource;
105        final Intent intent = makeMyIntent();
106        final Set<String> categories = intent.getCategories();
107        if (Intent.ACTION_MAIN.equals(intent.getAction())
108                && categories != null
109                && categories.size() == 1
110                && categories.contains(Intent.CATEGORY_HOME)) {
111            titleResource = com.android.internal.R.string.whichHomeApplication;
112        } else {
113            titleResource = com.android.internal.R.string.whichApplication;
114        }
115
116        onCreate(savedInstanceState, intent, getResources().getText(titleResource),
117                null, null, true);
118    }
119
120    protected void onCreate(Bundle savedInstanceState, Intent intent,
121            CharSequence title, Intent[] initialIntents, List<ResolveInfo> rList,
122            boolean alwaysUseOption) {
123        setTheme(R.style.Theme_DeviceDefault_Light_Dialog_Alert);
124        super.onCreate(savedInstanceState);
125        try {
126            mLaunchedFromUid = ActivityManagerNative.getDefault().getLaunchedFromUid(
127                    getActivityToken());
128        } catch (RemoteException e) {
129            mLaunchedFromUid = -1;
130        }
131        mPm = getPackageManager();
132        mAlwaysUseOption = alwaysUseOption;
133        mMaxColumns = getResources().getInteger(R.integer.config_maxResolverActivityColumns);
134
135        AlertController.AlertParams ap = mAlertParams;
136
137        ap.mTitle = title;
138
139        mPackageMonitor.register(this, getMainLooper(), false);
140        mRegistered = true;
141
142        final ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
143        mIconDpi = am.getLauncherLargeIconDensity();
144        mIconSize = am.getLauncherLargeIconSize();
145
146        mAdapter = new ResolveListAdapter(this, intent, initialIntents, rList,
147                mLaunchedFromUid);
148        int count = mAdapter.getCount();
149        if (mLaunchedFromUid < 0 || UserHandle.isIsolated(mLaunchedFromUid)) {
150            // Gulp!
151            finish();
152            return;
153        } else if (count > 1) {
154            ap.mView = getLayoutInflater().inflate(R.layout.resolver_list, null);
155            mListView = (ListView) ap.mView.findViewById(R.id.resolver_list);
156            mListView.setAdapter(mAdapter);
157            mListView.setOnItemClickListener(this);
158            mListView.setOnItemLongClickListener(new ItemLongClickListener());
159
160            if (alwaysUseOption) {
161                mListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
162            }
163        } else if (count == 1) {
164            startActivity(mAdapter.intentForPosition(0));
165            mPackageMonitor.unregister();
166            mRegistered = false;
167            finish();
168            return;
169        } else {
170            ap.mMessage = getResources().getText(R.string.noApplications);
171        }
172
173        setupAlert();
174
175        if (alwaysUseOption) {
176            final ViewGroup buttonLayout = (ViewGroup) findViewById(R.id.button_bar);
177            if (buttonLayout != null) {
178                buttonLayout.setVisibility(View.VISIBLE);
179                mAlwaysButton = (Button) buttonLayout.findViewById(R.id.button_always);
180                mOnceButton = (Button) buttonLayout.findViewById(R.id.button_once);
181            } else {
182                mAlwaysUseOption = false;
183            }
184            // Set the initial highlight if there was a preferred or last used choice
185            final int initialHighlight = mAdapter.getInitialHighlight();
186            if (initialHighlight >= 0) {
187                mListView.setItemChecked(initialHighlight, true);
188                onItemClick(null, null, initialHighlight, 0); // Other entries are not used
189            }
190        }
191    }
192
193    Drawable getIcon(Resources res, int resId) {
194        Drawable result;
195        try {
196            result = res.getDrawableForDensity(resId, mIconDpi);
197        } catch (Resources.NotFoundException e) {
198            result = null;
199        }
200
201        return result;
202    }
203
204    Drawable loadIconForResolveInfo(ResolveInfo ri) {
205        Drawable dr;
206        try {
207            if (ri.resolvePackageName != null && ri.icon != 0) {
208                dr = getIcon(mPm.getResourcesForApplication(ri.resolvePackageName), ri.icon);
209                if (dr != null) {
210                    return dr;
211                }
212            }
213            final int iconRes = ri.getIconResource();
214            if (iconRes != 0) {
215                dr = getIcon(mPm.getResourcesForApplication(ri.activityInfo.packageName), iconRes);
216                if (dr != null) {
217                    return dr;
218                }
219            }
220        } catch (NameNotFoundException e) {
221            Log.e(TAG, "Couldn't find resources for package", e);
222        }
223        return ri.loadIcon(mPm);
224    }
225
226    @Override
227    protected void onRestart() {
228        super.onRestart();
229        if (!mRegistered) {
230            mPackageMonitor.register(this, getMainLooper(), false);
231            mRegistered = true;
232        }
233        mAdapter.handlePackagesChanged();
234    }
235
236    @Override
237    protected void onStop() {
238        super.onStop();
239        if (mRegistered) {
240            mPackageMonitor.unregister();
241            mRegistered = false;
242        }
243        if ((getIntent().getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
244            // This resolver is in the unusual situation where it has been
245            // launched at the top of a new task.  We don't let it be added
246            // to the recent tasks shown to the user, and we need to make sure
247            // that each time we are launched we get the correct launching
248            // uid (not re-using the same resolver from an old launching uid),
249            // so we will now finish ourself since being no longer visible,
250            // the user probably can't get back to us.
251            if (!isChangingConfigurations()) {
252                finish();
253            }
254        }
255    }
256
257    @Override
258    protected void onRestoreInstanceState(Bundle savedInstanceState) {
259        super.onRestoreInstanceState(savedInstanceState);
260        if (mAlwaysUseOption) {
261            final int checkedPos = mListView.getCheckedItemPosition();
262            final boolean enabled = checkedPos != ListView.INVALID_POSITION;
263            mLastSelected = checkedPos;
264            mAlwaysButton.setEnabled(enabled);
265            mOnceButton.setEnabled(enabled);
266            if (enabled) {
267                mListView.setSelection(checkedPos);
268            }
269        }
270    }
271
272    @Override
273    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
274        final int checkedPos = mListView.getCheckedItemPosition();
275        final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION;
276        if (mAlwaysUseOption && (!hasValidSelection || mLastSelected != checkedPos)) {
277            mAlwaysButton.setEnabled(hasValidSelection);
278            mOnceButton.setEnabled(hasValidSelection);
279            if (hasValidSelection) {
280                mListView.smoothScrollToPosition(checkedPos);
281            }
282            mLastSelected = checkedPos;
283        } else {
284            startSelected(position, false);
285        }
286    }
287
288    public void onButtonClick(View v) {
289        final int id = v.getId();
290        startSelected(mListView.getCheckedItemPosition(), id == R.id.button_always);
291        dismiss();
292    }
293
294    void startSelected(int which, boolean always) {
295        if (isFinishing()) {
296            return;
297        }
298        ResolveInfo ri = mAdapter.resolveInfoForPosition(which);
299        Intent intent = mAdapter.intentForPosition(which);
300        onIntentSelected(ri, intent, always);
301        finish();
302    }
303
304    protected void onIntentSelected(ResolveInfo ri, Intent intent, boolean alwaysCheck) {
305        if (mAlwaysUseOption && mAdapter.mOrigResolveList != null) {
306            // Build a reasonable intent filter, based on what matched.
307            IntentFilter filter = new IntentFilter();
308
309            if (intent.getAction() != null) {
310                filter.addAction(intent.getAction());
311            }
312            Set<String> categories = intent.getCategories();
313            if (categories != null) {
314                for (String cat : categories) {
315                    filter.addCategory(cat);
316                }
317            }
318            filter.addCategory(Intent.CATEGORY_DEFAULT);
319
320            int cat = ri.match&IntentFilter.MATCH_CATEGORY_MASK;
321            Uri data = intent.getData();
322            if (cat == IntentFilter.MATCH_CATEGORY_TYPE) {
323                String mimeType = intent.resolveType(this);
324                if (mimeType != null) {
325                    try {
326                        filter.addDataType(mimeType);
327                    } catch (IntentFilter.MalformedMimeTypeException e) {
328                        Log.w("ResolverActivity", e);
329                        filter = null;
330                    }
331                }
332            }
333            if (data != null && data.getScheme() != null) {
334                // We need the data specification if there was no type,
335                // OR if the scheme is not one of our magical "file:"
336                // or "content:" schemes (see IntentFilter for the reason).
337                if (cat != IntentFilter.MATCH_CATEGORY_TYPE
338                        || (!"file".equals(data.getScheme())
339                                && !"content".equals(data.getScheme()))) {
340                    filter.addDataScheme(data.getScheme());
341
342                    // Look through the resolved filter to determine which part
343                    // of it matched the original Intent.
344                    Iterator<PatternMatcher> pIt = ri.filter.schemeSpecificPartsIterator();
345                    if (pIt != null) {
346                        String ssp = data.getSchemeSpecificPart();
347                        while (ssp != null && pIt.hasNext()) {
348                            PatternMatcher p = pIt.next();
349                            if (p.match(ssp)) {
350                                filter.addDataSchemeSpecificPart(p.getPath(), p.getType());
351                                break;
352                            }
353                        }
354                    }
355                    Iterator<IntentFilter.AuthorityEntry> aIt = ri.filter.authoritiesIterator();
356                    if (aIt != null) {
357                        while (aIt.hasNext()) {
358                            IntentFilter.AuthorityEntry a = aIt.next();
359                            if (a.match(data) >= 0) {
360                                int port = a.getPort();
361                                filter.addDataAuthority(a.getHost(),
362                                        port >= 0 ? Integer.toString(port) : null);
363                                break;
364                            }
365                        }
366                    }
367                    pIt = ri.filter.pathsIterator();
368                    if (pIt != null) {
369                        String path = data.getPath();
370                        while (path != null && pIt.hasNext()) {
371                            PatternMatcher p = pIt.next();
372                            if (p.match(path)) {
373                                filter.addDataPath(p.getPath(), p.getType());
374                                break;
375                            }
376                        }
377                    }
378                }
379            }
380
381            if (filter != null) {
382                final int N = mAdapter.mOrigResolveList.size();
383                ComponentName[] set = new ComponentName[N];
384                int bestMatch = 0;
385                for (int i=0; i<N; i++) {
386                    ResolveInfo r = mAdapter.mOrigResolveList.get(i);
387                    set[i] = new ComponentName(r.activityInfo.packageName,
388                            r.activityInfo.name);
389                    if (r.match > bestMatch) bestMatch = r.match;
390                }
391                if (alwaysCheck) {
392                    getPackageManager().addPreferredActivity(filter, bestMatch, set,
393                            intent.getComponent());
394                } else {
395                    try {
396                        AppGlobals.getPackageManager().setLastChosenActivity(intent,
397                                intent.resolveTypeIfNeeded(getContentResolver()),
398                                PackageManager.MATCH_DEFAULT_ONLY,
399                                filter, bestMatch, intent.getComponent());
400                    } catch (RemoteException re) {
401                        Log.d(TAG, "Error calling setLastChosenActivity\n" + re);
402                    }
403                }
404            }
405        }
406
407        if (intent != null) {
408            startActivity(intent);
409        }
410    }
411
412    void showAppDetails(ResolveInfo ri) {
413        Intent in = new Intent().setAction("android.settings.APPLICATION_DETAILS_SETTINGS")
414                .setData(Uri.fromParts("package", ri.activityInfo.packageName, null))
415                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
416        startActivity(in);
417    }
418
419    private final class DisplayResolveInfo {
420        ResolveInfo ri;
421        CharSequence displayLabel;
422        Drawable displayIcon;
423        CharSequence extendedInfo;
424        Intent origIntent;
425
426        DisplayResolveInfo(ResolveInfo pri, CharSequence pLabel,
427                CharSequence pInfo, Intent pOrigIntent) {
428            ri = pri;
429            displayLabel = pLabel;
430            extendedInfo = pInfo;
431            origIntent = pOrigIntent;
432        }
433    }
434
435    private final class ResolveListAdapter extends BaseAdapter {
436        private final Intent[] mInitialIntents;
437        private final List<ResolveInfo> mBaseResolveList;
438        private ResolveInfo mLastChosen;
439        private final Intent mIntent;
440        private final int mLaunchedFromUid;
441        private final LayoutInflater mInflater;
442
443        List<DisplayResolveInfo> mList;
444        List<ResolveInfo> mOrigResolveList;
445
446        private int mInitialHighlight = -1;
447
448        public ResolveListAdapter(Context context, Intent intent,
449                Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid) {
450            mIntent = new Intent(intent);
451            mInitialIntents = initialIntents;
452            mBaseResolveList = rList;
453            mLaunchedFromUid = launchedFromUid;
454            mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
455            mList = new ArrayList<DisplayResolveInfo>();
456            rebuildList();
457        }
458
459        public void handlePackagesChanged() {
460            final int oldItemCount = getCount();
461            rebuildList();
462            notifyDataSetChanged();
463            final int newItemCount = getCount();
464            if (newItemCount == 0) {
465                // We no longer have any items...  just finish the activity.
466                finish();
467            }
468        }
469
470        public int getInitialHighlight() {
471            return mInitialHighlight;
472        }
473
474        private void rebuildList() {
475            List<ResolveInfo> currentResolveList;
476
477            try {
478                mLastChosen = AppGlobals.getPackageManager().getLastChosenActivity(
479                        mIntent, mIntent.resolveTypeIfNeeded(getContentResolver()),
480                        PackageManager.MATCH_DEFAULT_ONLY);
481            } catch (RemoteException re) {
482                Log.d(TAG, "Error calling setLastChosenActivity\n" + re);
483            }
484
485            mList.clear();
486            if (mBaseResolveList != null) {
487                currentResolveList = mBaseResolveList;
488                mOrigResolveList = null;
489            } else {
490                currentResolveList = mOrigResolveList = mPm.queryIntentActivities(
491                        mIntent, PackageManager.MATCH_DEFAULT_ONLY
492                        | (mAlwaysUseOption ? PackageManager.GET_RESOLVED_FILTER : 0));
493                // Filter out any activities that the launched uid does not
494                // have permission for.  We don't do this when we have an explicit
495                // list of resolved activities, because that only happens when
496                // we are being subclassed, so we can safely launch whatever
497                // they gave us.
498                if (currentResolveList != null) {
499                    for (int i=currentResolveList.size()-1; i >= 0; i--) {
500                        ActivityInfo ai = currentResolveList.get(i).activityInfo;
501                        int granted = ActivityManager.checkComponentPermission(
502                                ai.permission, mLaunchedFromUid,
503                                ai.applicationInfo.uid, ai.exported);
504                        if (granted != PackageManager.PERMISSION_GRANTED) {
505                            // Access not allowed!
506                            if (mOrigResolveList == currentResolveList) {
507                                mOrigResolveList = new ArrayList<ResolveInfo>(mOrigResolveList);
508                            }
509                            currentResolveList.remove(i);
510                        }
511                    }
512                }
513            }
514            int N;
515            if ((currentResolveList != null) && ((N = currentResolveList.size()) > 0)) {
516                // Only display the first matches that are either of equal
517                // priority or have asked to be default options.
518                ResolveInfo r0 = currentResolveList.get(0);
519                for (int i=1; i<N; i++) {
520                    ResolveInfo ri = currentResolveList.get(i);
521                    if (DEBUG) Log.v(
522                        "ResolveListActivity",
523                        r0.activityInfo.name + "=" +
524                        r0.priority + "/" + r0.isDefault + " vs " +
525                        ri.activityInfo.name + "=" +
526                        ri.priority + "/" + ri.isDefault);
527                    if (r0.priority != ri.priority ||
528                        r0.isDefault != ri.isDefault) {
529                        while (i < N) {
530                            if (mOrigResolveList == currentResolveList) {
531                                mOrigResolveList = new ArrayList<ResolveInfo>(mOrigResolveList);
532                            }
533                            currentResolveList.remove(i);
534                            N--;
535                        }
536                    }
537                }
538                if (N > 1) {
539                    ResolveInfo.DisplayNameComparator rComparator =
540                            new ResolveInfo.DisplayNameComparator(mPm);
541                    Collections.sort(currentResolveList, rComparator);
542                }
543                // First put the initial items at the top.
544                if (mInitialIntents != null) {
545                    for (int i=0; i<mInitialIntents.length; i++) {
546                        Intent ii = mInitialIntents[i];
547                        if (ii == null) {
548                            continue;
549                        }
550                        ActivityInfo ai = ii.resolveActivityInfo(
551                                getPackageManager(), 0);
552                        if (ai == null) {
553                            Log.w("ResolverActivity", "No activity found for "
554                                    + ii);
555                            continue;
556                        }
557                        ResolveInfo ri = new ResolveInfo();
558                        ri.activityInfo = ai;
559                        if (ii instanceof LabeledIntent) {
560                            LabeledIntent li = (LabeledIntent)ii;
561                            ri.resolvePackageName = li.getSourcePackage();
562                            ri.labelRes = li.getLabelResource();
563                            ri.nonLocalizedLabel = li.getNonLocalizedLabel();
564                            ri.icon = li.getIconResource();
565                        }
566                        mList.add(new DisplayResolveInfo(ri,
567                                ri.loadLabel(getPackageManager()), null, ii));
568                    }
569                }
570
571                // Check for applications with same name and use application name or
572                // package name if necessary
573                r0 = currentResolveList.get(0);
574                int start = 0;
575                CharSequence r0Label =  r0.loadLabel(mPm);
576                mShowExtended = false;
577                for (int i = 1; i < N; i++) {
578                    if (r0Label == null) {
579                        r0Label = r0.activityInfo.packageName;
580                    }
581                    ResolveInfo ri = currentResolveList.get(i);
582                    CharSequence riLabel = ri.loadLabel(mPm);
583                    if (riLabel == null) {
584                        riLabel = ri.activityInfo.packageName;
585                    }
586                    if (riLabel.equals(r0Label)) {
587                        continue;
588                    }
589                    processGroup(currentResolveList, start, (i-1), r0, r0Label);
590                    r0 = ri;
591                    r0Label = riLabel;
592                    start = i;
593                }
594                // Process last group
595                processGroup(currentResolveList, start, (N-1), r0, r0Label);
596            }
597        }
598
599        private void processGroup(List<ResolveInfo> rList, int start, int end, ResolveInfo ro,
600                CharSequence roLabel) {
601            // Process labels from start to i
602            int num = end - start+1;
603            if (num == 1) {
604                if (mLastChosen != null
605                        && mLastChosen.activityInfo.packageName.equals(
606                                ro.activityInfo.packageName)
607                        && mLastChosen.activityInfo.name.equals(ro.activityInfo.name)) {
608                    mInitialHighlight = mList.size();
609                }
610                // No duplicate labels. Use label for entry at start
611                mList.add(new DisplayResolveInfo(ro, roLabel, null, null));
612            } else {
613                mShowExtended = true;
614                boolean usePkg = false;
615                CharSequence startApp = ro.activityInfo.applicationInfo.loadLabel(mPm);
616                if (startApp == null) {
617                    usePkg = true;
618                }
619                if (!usePkg) {
620                    // Use HashSet to track duplicates
621                    HashSet<CharSequence> duplicates =
622                        new HashSet<CharSequence>();
623                    duplicates.add(startApp);
624                    for (int j = start+1; j <= end ; j++) {
625                        ResolveInfo jRi = rList.get(j);
626                        CharSequence jApp = jRi.activityInfo.applicationInfo.loadLabel(mPm);
627                        if ( (jApp == null) || (duplicates.contains(jApp))) {
628                            usePkg = true;
629                            break;
630                        } else {
631                            duplicates.add(jApp);
632                        }
633                    }
634                    // Clear HashSet for later use
635                    duplicates.clear();
636                }
637                for (int k = start; k <= end; k++) {
638                    ResolveInfo add = rList.get(k);
639                    if (mLastChosen != null
640                            && mLastChosen.activityInfo.packageName.equals(
641                                    add.activityInfo.packageName)
642                            && mLastChosen.activityInfo.name.equals(add.activityInfo.name)) {
643                        mInitialHighlight = mList.size();
644                    }
645                    if (usePkg) {
646                        // Use application name for all entries from start to end-1
647                        mList.add(new DisplayResolveInfo(add, roLabel,
648                                add.activityInfo.packageName, null));
649                    } else {
650                        // Use package name for all entries from start to end-1
651                        mList.add(new DisplayResolveInfo(add, roLabel,
652                                add.activityInfo.applicationInfo.loadLabel(mPm), null));
653                    }
654                }
655            }
656        }
657
658        public ResolveInfo resolveInfoForPosition(int position) {
659            return mList.get(position).ri;
660        }
661
662        public Intent intentForPosition(int position) {
663            DisplayResolveInfo dri = mList.get(position);
664
665            Intent intent = new Intent(dri.origIntent != null
666                    ? dri.origIntent : mIntent);
667            intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT
668                    |Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
669            ActivityInfo ai = dri.ri.activityInfo;
670            intent.setComponent(new ComponentName(
671                    ai.applicationInfo.packageName, ai.name));
672            return intent;
673        }
674
675        public int getCount() {
676            return mList.size();
677        }
678
679        public Object getItem(int position) {
680            return mList.get(position);
681        }
682
683        public long getItemId(int position) {
684            return position;
685        }
686
687        public View getView(int position, View convertView, ViewGroup parent) {
688            View view;
689            if (convertView == null) {
690                view = mInflater.inflate(
691                        com.android.internal.R.layout.resolve_list_item, parent, false);
692
693                final ViewHolder holder = new ViewHolder(view);
694                view.setTag(holder);
695
696                // Fix the icon size even if we have different sized resources
697                ViewGroup.LayoutParams lp = holder.icon.getLayoutParams();
698                lp.width = lp.height = mIconSize;
699            } else {
700                view = convertView;
701            }
702            bindView(view, mList.get(position));
703            return view;
704        }
705
706        private final void bindView(View view, DisplayResolveInfo info) {
707            final ViewHolder holder = (ViewHolder) view.getTag();
708            holder.text.setText(info.displayLabel);
709            if (mShowExtended) {
710                holder.text2.setVisibility(View.VISIBLE);
711                holder.text2.setText(info.extendedInfo);
712            } else {
713                holder.text2.setVisibility(View.GONE);
714            }
715            if (info.displayIcon == null) {
716                new LoadIconTask().execute(info);
717            }
718            holder.icon.setImageDrawable(info.displayIcon);
719        }
720    }
721
722    static class ViewHolder {
723        public TextView text;
724        public TextView text2;
725        public ImageView icon;
726
727        public ViewHolder(View view) {
728            text = (TextView) view.findViewById(com.android.internal.R.id.text1);
729            text2 = (TextView) view.findViewById(com.android.internal.R.id.text2);
730            icon = (ImageView) view.findViewById(R.id.icon);
731        }
732    }
733
734    class ItemLongClickListener implements AdapterView.OnItemLongClickListener {
735
736        @Override
737        public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
738            ResolveInfo ri = mAdapter.resolveInfoForPosition(position);
739            showAppDetails(ri);
740            return true;
741        }
742
743    }
744
745    class LoadIconTask extends AsyncTask<DisplayResolveInfo, Void, DisplayResolveInfo> {
746        @Override
747        protected DisplayResolveInfo doInBackground(DisplayResolveInfo... params) {
748            final DisplayResolveInfo info = params[0];
749            if (info.displayIcon == null) {
750                info.displayIcon = loadIconForResolveInfo(info.ri);
751            }
752            return info;
753        }
754
755        @Override
756        protected void onPostExecute(DisplayResolveInfo info) {
757            mAdapter.notifyDataSetChanged();
758        }
759    }
760}
761
762