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