ResolverActivity.java revision cc562a811da508b275254f275d6e0c1758a47d07
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 GridView mGridView; 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 133 public final String action; 134 public final int titleRes; 135 public final int namedTitleRes; 136 137 ActionTitle(String action, int titleRes, int namedTitleRes) { 138 this.action = action; 139 this.titleRes = titleRes; 140 this.namedTitleRes = namedTitleRes; 141 } 142 143 public static ActionTitle forAction(String action) { 144 for (ActionTitle title : values()) { 145 if (action != null && action.equals(title.action)) { 146 return title; 147 } 148 } 149 return DEFAULT; 150 } 151 } 152 153 private Intent makeMyIntent() { 154 Intent intent = new Intent(getIntent()); 155 intent.setComponent(null); 156 // The resolver activity is set to be hidden from recent tasks. 157 // we don't want this attribute to be propagated to the next activity 158 // being launched. Note that if the original Intent also had this 159 // flag set, we are now losing it. That should be a very rare case 160 // and we can live with this. 161 intent.setFlags(intent.getFlags()&~Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 162 return intent; 163 } 164 165 @Override 166 protected void onCreate(Bundle savedInstanceState) { 167 // Use a specialized prompt when we're handling the 'Home' app startActivity() 168 final int titleResource; 169 final Intent intent = makeMyIntent(); 170 final Set<String> categories = intent.getCategories(); 171 if (Intent.ACTION_MAIN.equals(intent.getAction()) 172 && categories != null 173 && categories.size() == 1 174 && categories.contains(Intent.CATEGORY_HOME)) { 175 titleResource = com.android.internal.R.string.whichHomeApplication; 176 177 // Note: this field is not set to true in the compatibility version. 178 mResolvingHome = true; 179 } else { 180 titleResource = 0; 181 } 182 183 setSafeForwardingMode(true); 184 185 onCreate(savedInstanceState, intent, 186 titleResource != 0 ? getResources().getText(titleResource) : null, titleResource, 187 null, null, true); 188 } 189 190 /** 191 * Compatibility version for other bundled services that use this ocerload without 192 * a default title resource 193 */ 194 protected void onCreate(Bundle savedInstanceState, Intent intent, 195 CharSequence title, Intent[] initialIntents, 196 List<ResolveInfo> rList, boolean alwaysUseOption) { 197 onCreate(savedInstanceState, intent, title, 0, initialIntents, rList, alwaysUseOption); 198 } 199 200 protected void onCreate(Bundle savedInstanceState, Intent intent, 201 CharSequence title, int defaultTitleRes, Intent[] initialIntents, 202 List<ResolveInfo> rList, boolean alwaysUseOption) { 203 setTheme(R.style.Theme_DeviceDefault_Resolver); 204 super.onCreate(savedInstanceState); 205 try { 206 mLaunchedFromUid = ActivityManagerNative.getDefault().getLaunchedFromUid( 207 getActivityToken()); 208 } catch (RemoteException e) { 209 mLaunchedFromUid = -1; 210 } 211 mPm = getPackageManager(); 212 mUsm = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE); 213 214 final long sinceTime = System.currentTimeMillis() - USAGE_STATS_PERIOD; 215 mStats = mUsm.queryAndAggregateUsageStats(sinceTime, System.currentTimeMillis()); 216 Log.d(TAG, "sinceTime=" + sinceTime); 217 218 mMaxColumns = getResources().getInteger(R.integer.config_maxResolverActivityColumns); 219 220 mPackageMonitor.register(this, getMainLooper(), false); 221 mRegistered = true; 222 223 final ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE); 224 mIconDpi = am.getLauncherLargeIconDensity(); 225 mIconSize = am.getLauncherLargeIconSize(); 226 227 mAdapter = new ResolveListAdapter(this, intent, initialIntents, rList, 228 mLaunchedFromUid, alwaysUseOption); 229 230 final int layoutId; 231 if (mAdapter.hasFilteredItem()) { 232 layoutId = R.layout.resolver_list_with_default; 233 alwaysUseOption = false; 234 } else { 235 layoutId = R.layout.resolver_list; 236 } 237 mAlwaysUseOption = alwaysUseOption; 238 239 int count = mAdapter.mList.size(); 240 if (mLaunchedFromUid < 0 || UserHandle.isIsolated(mLaunchedFromUid)) { 241 // Gulp! 242 finish(); 243 return; 244 } else if (count > 1) { 245 setContentView(layoutId); 246 mGridView = (GridView) findViewById(R.id.resolver_list); 247 mGridView.setAdapter(mAdapter); 248 mGridView.setOnItemClickListener(this); 249 mGridView.setOnItemLongClickListener(new ItemLongClickListener()); 250 251 if (alwaysUseOption) { 252 mGridView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 253 } 254 255 resizeGrid(); 256 } else if (count == 1) { 257 safelyStartActivity(mAdapter.intentForPosition(0, false)); 258 mPackageMonitor.unregister(); 259 mRegistered = false; 260 finish(); 261 return; 262 } else { 263 setContentView(R.layout.resolver_list); 264 265 final TextView empty = (TextView) findViewById(R.id.empty); 266 empty.setVisibility(View.VISIBLE); 267 268 mGridView = (GridView) findViewById(R.id.resolver_list); 269 mGridView.setVisibility(View.GONE); 270 } 271 272 final ResolverDrawerLayout rdl = (ResolverDrawerLayout) findViewById(R.id.contentPanel); 273 if (rdl != null) { 274 rdl.setOnClickOutsideListener(new View.OnClickListener() { 275 @Override 276 public void onClick(View v) { 277 finish(); 278 } 279 }); 280 } 281 282 if (title == null) { 283 title = getTitleForAction(intent.getAction(), defaultTitleRes); 284 } 285 if (!TextUtils.isEmpty(title)) { 286 final TextView titleView = (TextView) findViewById(R.id.title); 287 if (titleView != null) { 288 titleView.setText(title); 289 } 290 setTitle(title); 291 } 292 293 final ImageView iconView = (ImageView) findViewById(R.id.icon); 294 final DisplayResolveInfo iconInfo = mAdapter.getFilteredItem(); 295 if (iconView != null && iconInfo != null) { 296 new LoadIconIntoViewTask(iconView).execute(iconInfo); 297 } 298 299 if (alwaysUseOption || mAdapter.hasFilteredItem()) { 300 final ViewGroup buttonLayout = (ViewGroup) findViewById(R.id.button_bar); 301 if (buttonLayout != null) { 302 buttonLayout.setVisibility(View.VISIBLE); 303 mAlwaysButton = (Button) buttonLayout.findViewById(R.id.button_always); 304 mOnceButton = (Button) buttonLayout.findViewById(R.id.button_once); 305 } else { 306 mAlwaysUseOption = false; 307 } 308 } 309 310 if (mAdapter.hasFilteredItem()) { 311 setAlwaysButtonEnabled(true, mAdapter.getFilteredPosition(), false); 312 mOnceButton.setEnabled(true); 313 } 314 } 315 316 /** 317 * Turn on launch mode that is safe to use when forwarding intents received from 318 * applications and running in system processes. This mode uses Activity.startActivityAsCaller 319 * instead of the normal Activity.startActivity for launching the activity selected 320 * by the user. 321 * 322 * <p>This mode is set to true by default if the activity is initialized through 323 * {@link #onCreate(android.os.Bundle)}. If a subclass calls one of the other onCreate 324 * methods, it is set to false by default. You must set it before calling one of the 325 * more detailed onCreate methods, so that it will be set correctly in the case where 326 * there is only one intent to resolve and it is thus started immediately.</p> 327 */ 328 public void setSafeForwardingMode(boolean safeForwarding) { 329 mSafeForwardingMode = safeForwarding; 330 } 331 332 protected CharSequence getTitleForAction(String action, int defaultTitleRes) { 333 final ActionTitle title = ActionTitle.forAction(action); 334 final boolean named = mAdapter.hasFilteredItem(); 335 if (title == ActionTitle.DEFAULT && defaultTitleRes != 0) { 336 return getString(defaultTitleRes); 337 } else { 338 return named ? getString(title.namedTitleRes, mAdapter.getFilteredItem().displayLabel) : 339 getString(title.titleRes); 340 } 341 } 342 343 void resizeGrid() { 344 final int itemCount = mAdapter.getCount(); 345 mGridView.setNumColumns(Math.min(itemCount, mMaxColumns)); 346 } 347 348 void dismiss() { 349 if (!isFinishing()) { 350 finish(); 351 } 352 } 353 354 Drawable getIcon(Resources res, int resId) { 355 Drawable result; 356 try { 357 result = res.getDrawableForDensity(resId, mIconDpi); 358 } catch (Resources.NotFoundException e) { 359 result = null; 360 } 361 362 return result; 363 } 364 365 Drawable loadIconForResolveInfo(ResolveInfo ri) { 366 Drawable dr; 367 try { 368 if (ri.resolvePackageName != null && ri.icon != 0) { 369 dr = getIcon(mPm.getResourcesForApplication(ri.resolvePackageName), ri.icon); 370 if (dr != null) { 371 return dr; 372 } 373 } 374 final int iconRes = ri.getIconResource(); 375 if (iconRes != 0) { 376 dr = getIcon(mPm.getResourcesForApplication(ri.activityInfo.packageName), iconRes); 377 if (dr != null) { 378 return dr; 379 } 380 } 381 } catch (NameNotFoundException e) { 382 Log.e(TAG, "Couldn't find resources for package", e); 383 } 384 return ri.loadIcon(mPm); 385 } 386 387 @Override 388 protected void onRestart() { 389 super.onRestart(); 390 if (!mRegistered) { 391 mPackageMonitor.register(this, getMainLooper(), false); 392 mRegistered = true; 393 } 394 mAdapter.handlePackagesChanged(); 395 } 396 397 @Override 398 protected void onStop() { 399 super.onStop(); 400 if (mRegistered) { 401 mPackageMonitor.unregister(); 402 mRegistered = false; 403 } 404 if ((getIntent().getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) { 405 // This resolver is in the unusual situation where it has been 406 // launched at the top of a new task. We don't let it be added 407 // to the recent tasks shown to the user, and we need to make sure 408 // that each time we are launched we get the correct launching 409 // uid (not re-using the same resolver from an old launching uid), 410 // so we will now finish ourself since being no longer visible, 411 // the user probably can't get back to us. 412 if (!isChangingConfigurations()) { 413 finish(); 414 } 415 } 416 } 417 418 @Override 419 protected void onRestoreInstanceState(Bundle savedInstanceState) { 420 super.onRestoreInstanceState(savedInstanceState); 421 if (mAlwaysUseOption) { 422 final int checkedPos = mGridView.getCheckedItemPosition(); 423 final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION; 424 mLastSelected = checkedPos; 425 setAlwaysButtonEnabled(hasValidSelection, checkedPos, true); 426 mOnceButton.setEnabled(hasValidSelection); 427 if (hasValidSelection) { 428 mGridView.setSelection(checkedPos); 429 } 430 } 431 } 432 433 @Override 434 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 435 ResolveInfo resolveInfo = mAdapter.resolveInfoForPosition(position, true); 436 if (mResolvingHome && hasManagedProfile() 437 && !supportsManagedProfiles(resolveInfo)) { 438 Toast.makeText(this, String.format(getResources().getString( 439 com.android.internal.R.string.activity_resolver_work_profiles_support), 440 resolveInfo.activityInfo.loadLabel(getPackageManager()).toString()), 441 Toast.LENGTH_LONG).show(); 442 return; 443 } 444 final int checkedPos = mGridView.getCheckedItemPosition(); 445 final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION; 446 if (mAlwaysUseOption && (!hasValidSelection || mLastSelected != checkedPos)) { 447 setAlwaysButtonEnabled(hasValidSelection, checkedPos, true); 448 mOnceButton.setEnabled(hasValidSelection); 449 if (hasValidSelection) { 450 mGridView.smoothScrollToPosition(checkedPos); 451 } 452 mLastSelected = checkedPos; 453 } else { 454 startSelected(position, false, true); 455 } 456 } 457 458 private boolean hasManagedProfile() { 459 UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE); 460 if (userManager == null) { 461 return false; 462 } 463 464 try { 465 List<UserInfo> profiles = userManager.getProfiles(getUserId()); 466 for (UserInfo userInfo : profiles) { 467 if (userInfo != null && userInfo.isManagedProfile()) { 468 return true; 469 } 470 } 471 } catch (SecurityException e) { 472 return false; 473 } 474 return false; 475 } 476 477 private boolean supportsManagedProfiles(ResolveInfo resolveInfo) { 478 try { 479 ApplicationInfo appInfo = getPackageManager().getApplicationInfo( 480 resolveInfo.activityInfo.packageName, 0 /* default flags */); 481 return versionNumberAtLeastL(appInfo.targetSdkVersion); 482 } catch (NameNotFoundException e) { 483 return false; 484 } 485 } 486 487 private boolean versionNumberAtLeastL(int versionNumber) { 488 // TODO: remove "|| true" once the build code for L is fixed. 489 return versionNumber >= Build.VERSION_CODES.L || true; 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 mGridView.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); 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 } else if (newItemCount != oldItemCount) { 718 resizeGrid(); 719 } 720 } 721 722 public DisplayResolveInfo getFilteredItem() { 723 if (mFilterLastUsed && mLastChosenPosition >= 0) { 724 // Not using getItem since it offsets to dodge this position for the list 725 return mList.get(mLastChosenPosition); 726 } 727 return null; 728 } 729 730 public int getFilteredPosition() { 731 if (mFilterLastUsed && mLastChosenPosition >= 0) { 732 return mLastChosenPosition; 733 } 734 return AbsListView.INVALID_POSITION; 735 } 736 737 public boolean hasFilteredItem() { 738 return mFilterLastUsed && mLastChosenPosition >= 0; 739 } 740 741 private void rebuildList() { 742 List<ResolveInfo> currentResolveList; 743 744 try { 745 mLastChosen = AppGlobals.getPackageManager().getLastChosenActivity( 746 mIntent, mIntent.resolveTypeIfNeeded(getContentResolver()), 747 PackageManager.MATCH_DEFAULT_ONLY); 748 } catch (RemoteException re) { 749 Log.d(TAG, "Error calling setLastChosenActivity\n" + re); 750 } 751 752 mList.clear(); 753 if (mBaseResolveList != null) { 754 currentResolveList = mOrigResolveList = mBaseResolveList; 755 } else { 756 currentResolveList = mOrigResolveList = mPm.queryIntentActivities( 757 mIntent, PackageManager.MATCH_DEFAULT_ONLY 758 | (mFilterLastUsed ? PackageManager.GET_RESOLVED_FILTER : 0)); 759 // Filter out any activities that the launched uid does not 760 // have permission for. We don't do this when we have an explicit 761 // list of resolved activities, because that only happens when 762 // we are being subclassed, so we can safely launch whatever 763 // they gave us. 764 if (currentResolveList != null) { 765 for (int i=currentResolveList.size()-1; i >= 0; i--) { 766 ActivityInfo ai = currentResolveList.get(i).activityInfo; 767 int granted = ActivityManager.checkComponentPermission( 768 ai.permission, mLaunchedFromUid, 769 ai.applicationInfo.uid, ai.exported); 770 if (granted != PackageManager.PERMISSION_GRANTED) { 771 // Access not allowed! 772 if (mOrigResolveList == currentResolveList) { 773 mOrigResolveList = new ArrayList<ResolveInfo>(mOrigResolveList); 774 } 775 currentResolveList.remove(i); 776 } 777 } 778 } 779 } 780 int N; 781 if ((currentResolveList != null) && ((N = currentResolveList.size()) > 0)) { 782 // Only display the first matches that are either of equal 783 // priority or have asked to be default options. 784 ResolveInfo r0 = currentResolveList.get(0); 785 for (int i=1; i<N; i++) { 786 ResolveInfo ri = currentResolveList.get(i); 787 if (DEBUG) Log.v( 788 TAG, 789 r0.activityInfo.name + "=" + 790 r0.priority + "/" + r0.isDefault + " vs " + 791 ri.activityInfo.name + "=" + 792 ri.priority + "/" + ri.isDefault); 793 if (r0.priority != ri.priority || 794 r0.isDefault != ri.isDefault) { 795 while (i < N) { 796 if (mOrigResolveList == currentResolveList) { 797 mOrigResolveList = new ArrayList<ResolveInfo>(mOrigResolveList); 798 } 799 currentResolveList.remove(i); 800 N--; 801 } 802 } 803 } 804 if (N > 1) { 805 Comparator<ResolveInfo> rComparator = 806 new ResolverComparator(ResolverActivity.this); 807 Collections.sort(currentResolveList, rComparator); 808 } 809 // First put the initial items at the top. 810 if (mInitialIntents != null) { 811 for (int i=0; i<mInitialIntents.length; i++) { 812 Intent ii = mInitialIntents[i]; 813 if (ii == null) { 814 continue; 815 } 816 ActivityInfo ai = ii.resolveActivityInfo( 817 getPackageManager(), 0); 818 if (ai == null) { 819 Log.w(TAG, "No activity found for " + ii); 820 continue; 821 } 822 ResolveInfo ri = new ResolveInfo(); 823 ri.activityInfo = ai; 824 if (ii instanceof LabeledIntent) { 825 LabeledIntent li = (LabeledIntent)ii; 826 ri.resolvePackageName = li.getSourcePackage(); 827 ri.labelRes = li.getLabelResource(); 828 ri.nonLocalizedLabel = li.getNonLocalizedLabel(); 829 ri.icon = li.getIconResource(); 830 } 831 mList.add(new DisplayResolveInfo(ri, 832 ri.loadLabel(getPackageManager()), null, ii)); 833 } 834 } 835 836 // Check for applications with same name and use application name or 837 // package name if necessary 838 r0 = currentResolveList.get(0); 839 int start = 0; 840 CharSequence r0Label = r0.loadLabel(mPm); 841 mShowExtended = false; 842 for (int i = 1; i < N; i++) { 843 if (r0Label == null) { 844 r0Label = r0.activityInfo.packageName; 845 } 846 ResolveInfo ri = currentResolveList.get(i); 847 CharSequence riLabel = ri.loadLabel(mPm); 848 if (riLabel == null) { 849 riLabel = ri.activityInfo.packageName; 850 } 851 if (riLabel.equals(r0Label)) { 852 continue; 853 } 854 processGroup(currentResolveList, start, (i-1), r0, r0Label); 855 r0 = ri; 856 r0Label = riLabel; 857 start = i; 858 } 859 // Process last group 860 processGroup(currentResolveList, start, (N-1), r0, r0Label); 861 } 862 } 863 864 private void processGroup(List<ResolveInfo> rList, int start, int end, ResolveInfo ro, 865 CharSequence roLabel) { 866 // Process labels from start to i 867 int num = end - start+1; 868 if (num == 1) { 869 if (mLastChosen != null 870 && mLastChosen.activityInfo.packageName.equals( 871 ro.activityInfo.packageName) 872 && mLastChosen.activityInfo.name.equals(ro.activityInfo.name)) { 873 mLastChosenPosition = mList.size(); 874 } 875 // No duplicate labels. Use label for entry at start 876 mList.add(new DisplayResolveInfo(ro, roLabel, null, null)); 877 } else { 878 mShowExtended = true; 879 boolean usePkg = false; 880 CharSequence startApp = ro.activityInfo.applicationInfo.loadLabel(mPm); 881 if (startApp == null) { 882 usePkg = true; 883 } 884 if (!usePkg) { 885 // Use HashSet to track duplicates 886 HashSet<CharSequence> duplicates = 887 new HashSet<CharSequence>(); 888 duplicates.add(startApp); 889 for (int j = start+1; j <= end ; j++) { 890 ResolveInfo jRi = rList.get(j); 891 CharSequence jApp = jRi.activityInfo.applicationInfo.loadLabel(mPm); 892 if ( (jApp == null) || (duplicates.contains(jApp))) { 893 usePkg = true; 894 break; 895 } else { 896 duplicates.add(jApp); 897 } 898 } 899 // Clear HashSet for later use 900 duplicates.clear(); 901 } 902 for (int k = start; k <= end; k++) { 903 ResolveInfo add = rList.get(k); 904 if (mLastChosen != null 905 && mLastChosen.activityInfo.packageName.equals( 906 add.activityInfo.packageName) 907 && mLastChosen.activityInfo.name.equals(add.activityInfo.name)) { 908 mLastChosenPosition = mList.size(); 909 } 910 if (usePkg) { 911 // Use application name for all entries from start to end-1 912 mList.add(new DisplayResolveInfo(add, roLabel, 913 add.activityInfo.packageName, null)); 914 } else { 915 // Use package name for all entries from start to end-1 916 mList.add(new DisplayResolveInfo(add, roLabel, 917 add.activityInfo.applicationInfo.loadLabel(mPm), null)); 918 } 919 } 920 } 921 } 922 923 public ResolveInfo resolveInfoForPosition(int position, boolean filtered) { 924 return (filtered ? getItem(position) : mList.get(position)).ri; 925 } 926 927 public Intent intentForPosition(int position, boolean filtered) { 928 DisplayResolveInfo dri = filtered ? getItem(position) : mList.get(position); 929 930 Intent intent = new Intent(dri.origIntent != null ? dri.origIntent : 931 getReplacementIntent(dri.ri.activityInfo.packageName, mIntent)); 932 intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT 933 |Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP); 934 ActivityInfo ai = dri.ri.activityInfo; 935 intent.setComponent(new ComponentName( 936 ai.applicationInfo.packageName, ai.name)); 937 return intent; 938 } 939 940 public int getCount() { 941 int result = mList.size(); 942 if (mFilterLastUsed && mLastChosenPosition >= 0) { 943 result--; 944 } 945 return result; 946 } 947 948 public DisplayResolveInfo getItem(int position) { 949 if (mFilterLastUsed && mLastChosenPosition >= 0 && position >= mLastChosenPosition) { 950 position++; 951 } 952 return mList.get(position); 953 } 954 955 public long getItemId(int position) { 956 return position; 957 } 958 959 public View getView(int position, View convertView, ViewGroup parent) { 960 View view; 961 if (convertView == null) { 962 view = mInflater.inflate( 963 com.android.internal.R.layout.resolve_list_item, parent, false); 964 965 final ViewHolder holder = new ViewHolder(view); 966 view.setTag(holder); 967 968 // Fix the icon size even if we have different sized resources 969 ViewGroup.LayoutParams lp = holder.icon.getLayoutParams(); 970 lp.width = lp.height = mIconSize; 971 } else { 972 view = convertView; 973 } 974 bindView(view, getItem(position)); 975 return view; 976 } 977 978 private final void bindView(View view, DisplayResolveInfo info) { 979 final ViewHolder holder = (ViewHolder) view.getTag(); 980 holder.text.setText(info.displayLabel); 981 if (mShowExtended) { 982 holder.text2.setVisibility(View.VISIBLE); 983 holder.text2.setText(info.extendedInfo); 984 } else { 985 holder.text2.setVisibility(View.GONE); 986 } 987 if (info.displayIcon == null) { 988 new LoadIconTask().execute(info); 989 } 990 holder.icon.setImageDrawable(info.displayIcon); 991 } 992 } 993 994 static class ViewHolder { 995 public TextView text; 996 public TextView text2; 997 public ImageView icon; 998 999 public ViewHolder(View view) { 1000 text = (TextView) view.findViewById(com.android.internal.R.id.text1); 1001 text2 = (TextView) view.findViewById(com.android.internal.R.id.text2); 1002 icon = (ImageView) view.findViewById(R.id.icon); 1003 } 1004 } 1005 1006 class ItemLongClickListener implements AdapterView.OnItemLongClickListener { 1007 1008 @Override 1009 public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { 1010 ResolveInfo ri = mAdapter.resolveInfoForPosition(position, true); 1011 showAppDetails(ri); 1012 return true; 1013 } 1014 1015 } 1016 1017 class LoadIconTask extends AsyncTask<DisplayResolveInfo, Void, DisplayResolveInfo> { 1018 @Override 1019 protected DisplayResolveInfo doInBackground(DisplayResolveInfo... params) { 1020 final DisplayResolveInfo info = params[0]; 1021 if (info.displayIcon == null) { 1022 info.displayIcon = loadIconForResolveInfo(info.ri); 1023 } 1024 return info; 1025 } 1026 1027 @Override 1028 protected void onPostExecute(DisplayResolveInfo info) { 1029 mAdapter.notifyDataSetChanged(); 1030 } 1031 } 1032 1033 class LoadIconIntoViewTask extends AsyncTask<DisplayResolveInfo, Void, DisplayResolveInfo> { 1034 final ImageView mTargetView; 1035 1036 public LoadIconIntoViewTask(ImageView target) { 1037 mTargetView = target; 1038 } 1039 1040 @Override 1041 protected DisplayResolveInfo doInBackground(DisplayResolveInfo... params) { 1042 final DisplayResolveInfo info = params[0]; 1043 if (info.displayIcon == null) { 1044 info.displayIcon = loadIconForResolveInfo(info.ri); 1045 } 1046 return info; 1047 } 1048 1049 @Override 1050 protected void onPostExecute(DisplayResolveInfo info) { 1051 mTargetView.setImageDrawable(info.displayIcon); 1052 } 1053 } 1054 1055 class ResolverComparator implements Comparator<ResolveInfo> { 1056 private final Collator mCollator; 1057 1058 public ResolverComparator(Context context) { 1059 mCollator = Collator.getInstance(context.getResources().getConfiguration().locale); 1060 } 1061 1062 @Override 1063 public int compare(ResolveInfo lhs, ResolveInfo rhs) { 1064 // We want to put the one targeted to another user at the end of the dialog. 1065 if (lhs.targetUserId != UserHandle.USER_CURRENT) { 1066 return 1; 1067 } 1068 1069 if (mStats != null) { 1070 final long timeDiff = 1071 getPackageTimeSpent(rhs.activityInfo.packageName) - 1072 getPackageTimeSpent(lhs.activityInfo.packageName); 1073 1074 if (timeDiff != 0) { 1075 return timeDiff > 0 ? 1 : -1; 1076 } 1077 } 1078 1079 CharSequence sa = lhs.loadLabel(mPm); 1080 if (sa == null) sa = lhs.activityInfo.name; 1081 CharSequence sb = rhs.loadLabel(mPm); 1082 if (sb == null) sb = rhs.activityInfo.name; 1083 1084 return mCollator.compare(sa.toString(), sb.toString()); 1085 } 1086 1087 private long getPackageTimeSpent(String packageName) { 1088 if (mStats != null) { 1089 final UsageStats stats = mStats.get(packageName); 1090 if (stats != null) { 1091 return stats.getTotalTimeInForeground(); 1092 } 1093 1094 } 1095 return 0; 1096 } 1097 } 1098} 1099 1100