Home | History | Annotate | Download | only in app
      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 
     17 package com.android.internal.app;
     18 
     19 import android.os.AsyncTask;
     20 import com.android.internal.R;
     21 import com.android.internal.content.PackageMonitor;
     22 
     23 import android.app.ActivityManager;
     24 import android.app.ActivityManagerNative;
     25 import android.app.AppGlobals;
     26 import android.content.ComponentName;
     27 import android.content.Context;
     28 import android.content.Intent;
     29 import android.content.IntentFilter;
     30 import android.content.pm.ActivityInfo;
     31 import android.content.pm.LabeledIntent;
     32 import android.content.pm.PackageManager;
     33 import android.content.pm.PackageManager.NameNotFoundException;
     34 import android.content.pm.ResolveInfo;
     35 import android.content.res.Resources;
     36 import android.graphics.drawable.Drawable;
     37 import android.net.Uri;
     38 import android.os.Bundle;
     39 import android.os.PatternMatcher;
     40 import android.os.RemoteException;
     41 import android.os.UserHandle;
     42 import android.util.Log;
     43 import android.view.LayoutInflater;
     44 import android.view.View;
     45 import android.view.ViewGroup;
     46 import android.widget.AdapterView;
     47 import android.widget.BaseAdapter;
     48 import android.widget.Button;
     49 import android.widget.ImageView;
     50 import android.widget.ListView;
     51 import android.widget.TextView;
     52 
     53 import java.util.ArrayList;
     54 import java.util.Collections;
     55 import java.util.HashSet;
     56 import java.util.Iterator;
     57 import java.util.List;
     58 import 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  */
     65 public 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