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