ResolverActivity.java revision 1a815921694ca2b3ad4dbb85bf82d85e831bc84e
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 com.android.internal.R; 29import com.android.internal.content.PackageMonitor; 30 31import android.app.ActivityManager; 32import android.app.ActivityManagerNative; 33import android.app.AppGlobals; 34import android.content.ComponentName; 35import android.content.Context; 36import android.content.Intent; 37import android.content.IntentFilter; 38import android.content.pm.ActivityInfo; 39import android.content.pm.ApplicationInfo; 40import android.content.pm.LabeledIntent; 41import android.content.pm.PackageManager; 42import android.content.pm.PackageManager.NameNotFoundException; 43import android.content.pm.ResolveInfo; 44import android.content.pm.UserInfo; 45import android.content.res.Resources; 46import android.graphics.drawable.Drawable; 47import android.net.Uri; 48import android.os.Build; 49import android.os.Bundle; 50import android.os.PatternMatcher; 51import android.os.RemoteException; 52import android.os.UserHandle; 53import android.os.UserManager; 54import android.util.Log; 55import android.view.LayoutInflater; 56import android.view.View; 57import android.view.ViewGroup; 58import android.widget.AdapterView; 59import android.widget.BaseAdapter; 60import android.widget.Button; 61import android.widget.ImageView; 62import android.widget.ListView; 63import android.widget.TextView; 64import android.widget.Toast; 65import com.android.internal.widget.ResolverDrawerLayout; 66 67import java.text.Collator; 68import java.util.ArrayList; 69import java.util.Collections; 70import java.util.Comparator; 71import java.util.HashSet; 72import java.util.Iterator; 73import java.util.List; 74import java.util.Map; 75import 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 */ 82public 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); 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 UserManager userManager = 823 (UserManager) getSystemService(Context.USER_SERVICE); 824 if (userManager.isManagedProfile()) { 825 ri.noResourceId = true; 826 } 827 if (ii instanceof LabeledIntent) { 828 LabeledIntent li = (LabeledIntent)ii; 829 ri.resolvePackageName = li.getSourcePackage(); 830 ri.labelRes = li.getLabelResource(); 831 ri.nonLocalizedLabel = li.getNonLocalizedLabel(); 832 ri.icon = li.getIconResource(); 833 } 834 mList.add(new DisplayResolveInfo(ri, 835 ri.loadLabel(getPackageManager()), null, ii)); 836 } 837 } 838 839 // Check for applications with same name and use application name or 840 // package name if necessary 841 r0 = currentResolveList.get(0); 842 int start = 0; 843 CharSequence r0Label = r0.loadLabel(mPm); 844 mShowExtended = false; 845 for (int i = 1; i < N; i++) { 846 if (r0Label == null) { 847 r0Label = r0.activityInfo.packageName; 848 } 849 ResolveInfo ri = currentResolveList.get(i); 850 CharSequence riLabel = ri.loadLabel(mPm); 851 if (riLabel == null) { 852 riLabel = ri.activityInfo.packageName; 853 } 854 if (riLabel.equals(r0Label)) { 855 continue; 856 } 857 processGroup(currentResolveList, start, (i-1), r0, r0Label); 858 r0 = ri; 859 r0Label = riLabel; 860 start = i; 861 } 862 // Process last group 863 processGroup(currentResolveList, start, (N-1), r0, r0Label); 864 } 865 } 866 867 private void processGroup(List<ResolveInfo> rList, int start, int end, ResolveInfo ro, 868 CharSequence roLabel) { 869 // Process labels from start to i 870 int num = end - start+1; 871 if (num == 1) { 872 if (mLastChosen != null 873 && mLastChosen.activityInfo.packageName.equals( 874 ro.activityInfo.packageName) 875 && mLastChosen.activityInfo.name.equals(ro.activityInfo.name)) { 876 mLastChosenPosition = mList.size(); 877 } 878 // No duplicate labels. Use label for entry at start 879 mList.add(new DisplayResolveInfo(ro, roLabel, null, null)); 880 } else { 881 mShowExtended = true; 882 boolean usePkg = false; 883 CharSequence startApp = ro.activityInfo.applicationInfo.loadLabel(mPm); 884 if (startApp == null) { 885 usePkg = true; 886 } 887 if (!usePkg) { 888 // Use HashSet to track duplicates 889 HashSet<CharSequence> duplicates = 890 new HashSet<CharSequence>(); 891 duplicates.add(startApp); 892 for (int j = start+1; j <= end ; j++) { 893 ResolveInfo jRi = rList.get(j); 894 CharSequence jApp = jRi.activityInfo.applicationInfo.loadLabel(mPm); 895 if ( (jApp == null) || (duplicates.contains(jApp))) { 896 usePkg = true; 897 break; 898 } else { 899 duplicates.add(jApp); 900 } 901 } 902 // Clear HashSet for later use 903 duplicates.clear(); 904 } 905 for (int k = start; k <= end; k++) { 906 ResolveInfo add = rList.get(k); 907 if (mLastChosen != null 908 && mLastChosen.activityInfo.packageName.equals( 909 add.activityInfo.packageName) 910 && mLastChosen.activityInfo.name.equals(add.activityInfo.name)) { 911 mLastChosenPosition = mList.size(); 912 } 913 if (usePkg) { 914 // Use application name for all entries from start to end-1 915 mList.add(new DisplayResolveInfo(add, roLabel, 916 add.activityInfo.packageName, null)); 917 } else { 918 // Use package name for all entries from start to end-1 919 mList.add(new DisplayResolveInfo(add, roLabel, 920 add.activityInfo.applicationInfo.loadLabel(mPm), null)); 921 } 922 } 923 } 924 } 925 926 public ResolveInfo resolveInfoForPosition(int position, boolean filtered) { 927 return (filtered ? getItem(position) : mList.get(position)).ri; 928 } 929 930 public Intent intentForPosition(int position, boolean filtered) { 931 DisplayResolveInfo dri = filtered ? getItem(position) : mList.get(position); 932 933 Intent intent = new Intent(dri.origIntent != null ? dri.origIntent : 934 getReplacementIntent(dri.ri.activityInfo.packageName, mIntent)); 935 intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT 936 |Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP); 937 ActivityInfo ai = dri.ri.activityInfo; 938 intent.setComponent(new ComponentName( 939 ai.applicationInfo.packageName, ai.name)); 940 return intent; 941 } 942 943 public int getCount() { 944 int result = mList.size(); 945 if (mFilterLastUsed && mLastChosenPosition >= 0) { 946 result--; 947 } 948 return result; 949 } 950 951 public DisplayResolveInfo getItem(int position) { 952 if (mFilterLastUsed && mLastChosenPosition >= 0 && position >= mLastChosenPosition) { 953 position++; 954 } 955 return mList.get(position); 956 } 957 958 public long getItemId(int position) { 959 return position; 960 } 961 962 public View getView(int position, View convertView, ViewGroup parent) { 963 View view = convertView; 964 if (view == null) { 965 view = mInflater.inflate( 966 com.android.internal.R.layout.resolve_list_item, parent, false); 967 968 final ViewHolder holder = new ViewHolder(view); 969 view.setTag(holder); 970 } 971 bindView(view, getItem(position)); 972 return view; 973 } 974 975 private final void bindView(View view, DisplayResolveInfo info) { 976 final ViewHolder holder = (ViewHolder) view.getTag(); 977 holder.text.setText(info.displayLabel); 978 if (mShowExtended) { 979 holder.text2.setVisibility(View.VISIBLE); 980 holder.text2.setText(info.extendedInfo); 981 } else { 982 holder.text2.setVisibility(View.GONE); 983 } 984 if (info.displayIcon == null) { 985 new LoadIconTask().execute(info); 986 } 987 holder.icon.setImageDrawable(info.displayIcon); 988 } 989 } 990 991 static class ViewHolder { 992 public TextView text; 993 public TextView text2; 994 public ImageView icon; 995 996 public ViewHolder(View view) { 997 text = (TextView) view.findViewById(com.android.internal.R.id.text1); 998 text2 = (TextView) view.findViewById(com.android.internal.R.id.text2); 999 icon = (ImageView) view.findViewById(R.id.icon); 1000 } 1001 } 1002 1003 class ItemLongClickListener implements AdapterView.OnItemLongClickListener { 1004 1005 @Override 1006 public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { 1007 position -= mListView.getHeaderViewsCount(); 1008 if (position < 0) { 1009 // Header views don't count. 1010 return false; 1011 } 1012 ResolveInfo ri = mAdapter.resolveInfoForPosition(position, true); 1013 showAppDetails(ri); 1014 return true; 1015 } 1016 1017 } 1018 1019 class LoadIconTask extends AsyncTask<DisplayResolveInfo, Void, DisplayResolveInfo> { 1020 @Override 1021 protected DisplayResolveInfo doInBackground(DisplayResolveInfo... params) { 1022 final DisplayResolveInfo info = params[0]; 1023 if (info.displayIcon == null) { 1024 info.displayIcon = loadIconForResolveInfo(info.ri); 1025 } 1026 return info; 1027 } 1028 1029 @Override 1030 protected void onPostExecute(DisplayResolveInfo info) { 1031 mAdapter.notifyDataSetChanged(); 1032 } 1033 } 1034 1035 class LoadIconIntoViewTask extends AsyncTask<DisplayResolveInfo, Void, DisplayResolveInfo> { 1036 final ImageView mTargetView; 1037 1038 public LoadIconIntoViewTask(ImageView target) { 1039 mTargetView = target; 1040 } 1041 1042 @Override 1043 protected DisplayResolveInfo doInBackground(DisplayResolveInfo... params) { 1044 final DisplayResolveInfo info = params[0]; 1045 if (info.displayIcon == null) { 1046 info.displayIcon = loadIconForResolveInfo(info.ri); 1047 } 1048 return info; 1049 } 1050 1051 @Override 1052 protected void onPostExecute(DisplayResolveInfo info) { 1053 mTargetView.setImageDrawable(info.displayIcon); 1054 } 1055 } 1056 1057 class ResolverComparator implements Comparator<ResolveInfo> { 1058 private final Collator mCollator; 1059 1060 public ResolverComparator(Context context) { 1061 mCollator = Collator.getInstance(context.getResources().getConfiguration().locale); 1062 } 1063 1064 @Override 1065 public int compare(ResolveInfo lhs, ResolveInfo rhs) { 1066 // We want to put the one targeted to another user at the end of the dialog. 1067 if (lhs.targetUserId != UserHandle.USER_CURRENT) { 1068 return 1; 1069 } 1070 1071 if (mStats != null) { 1072 final long timeDiff = 1073 getPackageTimeSpent(rhs.activityInfo.packageName) - 1074 getPackageTimeSpent(lhs.activityInfo.packageName); 1075 1076 if (timeDiff != 0) { 1077 return timeDiff > 0 ? 1 : -1; 1078 } 1079 } 1080 1081 CharSequence sa = lhs.loadLabel(mPm); 1082 if (sa == null) sa = lhs.activityInfo.name; 1083 CharSequence sb = rhs.loadLabel(mPm); 1084 if (sb == null) sb = rhs.activityInfo.name; 1085 1086 return mCollator.compare(sa.toString(), sb.toString()); 1087 } 1088 1089 private long getPackageTimeSpent(String packageName) { 1090 if (mStats != null) { 1091 final UsageStats stats = mStats.get(packageName); 1092 if (stats != null) { 1093 return stats.getTotalTimeInForeground(); 1094 } 1095 1096 } 1097 return 0; 1098 } 1099 } 1100} 1101 1102