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