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