ResolverActivity.java revision 2ed547e55f820a9c705872d802b051d8ae9c906b
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 78import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; 79import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; 80 81/** 82 * This activity is displayed when the system attempts to start an Intent for 83 * which there is more than one matching activity, allowing the user to decide 84 * which to go to. It is not normally used directly by application developers. 85 */ 86public class ResolverActivity extends Activity implements AdapterView.OnItemClickListener { 87 private static final String TAG = "ResolverActivity"; 88 private static final boolean DEBUG = false; 89 90 private int mLaunchedFromUid; 91 private ResolveListAdapter mAdapter; 92 private PackageManager mPm; 93 private boolean mSafeForwardingMode; 94 private boolean mAlwaysUseOption; 95 private AbsListView mAdapterView; 96 private ListView mListView; 97 private GridView mGridView; 98 private Button mAlwaysButton; 99 private Button mOnceButton; 100 private View mProfileView; 101 private int mIconDpi; 102 private int mLastSelected = AbsListView.INVALID_POSITION; 103 private boolean mResolvingHome = false; 104 private int mProfileSwitchMessageId = -1; 105 private final ArrayList<Intent> mIntents = new ArrayList<>(); 106 107 private UsageStatsManager mUsm; 108 private Map<String, UsageStats> mStats; 109 private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 14; 110 111 private boolean mRegistered; 112 private final PackageMonitor mPackageMonitor = new PackageMonitor() { 113 @Override public void onSomePackagesChanged() { 114 mAdapter.handlePackagesChanged(); 115 if (mProfileView != null) { 116 bindProfileView(); 117 } 118 } 119 }; 120 121 private enum ActionTitle { 122 VIEW(Intent.ACTION_VIEW, 123 com.android.internal.R.string.whichViewApplication, 124 com.android.internal.R.string.whichViewApplicationNamed), 125 EDIT(Intent.ACTION_EDIT, 126 com.android.internal.R.string.whichEditApplication, 127 com.android.internal.R.string.whichEditApplicationNamed), 128 SEND(Intent.ACTION_SEND, 129 com.android.internal.R.string.whichSendApplication, 130 com.android.internal.R.string.whichSendApplicationNamed), 131 SENDTO(Intent.ACTION_SENDTO, 132 com.android.internal.R.string.whichSendApplication, 133 com.android.internal.R.string.whichSendApplicationNamed), 134 SEND_MULTIPLE(Intent.ACTION_SEND_MULTIPLE, 135 com.android.internal.R.string.whichSendApplication, 136 com.android.internal.R.string.whichSendApplicationNamed), 137 DEFAULT(null, 138 com.android.internal.R.string.whichApplication, 139 com.android.internal.R.string.whichApplicationNamed), 140 HOME(Intent.ACTION_MAIN, 141 com.android.internal.R.string.whichHomeApplication, 142 com.android.internal.R.string.whichHomeApplicationNamed); 143 144 public final String action; 145 public final int titleRes; 146 public final int namedTitleRes; 147 148 ActionTitle(String action, int titleRes, int namedTitleRes) { 149 this.action = action; 150 this.titleRes = titleRes; 151 this.namedTitleRes = namedTitleRes; 152 } 153 154 public static ActionTitle forAction(String action) { 155 for (ActionTitle title : values()) { 156 if (title != HOME && action != null && action.equals(title.action)) { 157 return title; 158 } 159 } 160 return DEFAULT; 161 } 162 } 163 164 private Intent makeMyIntent() { 165 Intent intent = new Intent(getIntent()); 166 intent.setComponent(null); 167 // The resolver activity is set to be hidden from recent tasks. 168 // we don't want this attribute to be propagated to the next activity 169 // being launched. Note that if the original Intent also had this 170 // flag set, we are now losing it. That should be a very rare case 171 // and we can live with this. 172 intent.setFlags(intent.getFlags()&~Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 173 return intent; 174 } 175 176 @Override 177 protected void onCreate(Bundle savedInstanceState) { 178 // Use a specialized prompt when we're handling the 'Home' app startActivity() 179 final Intent intent = makeMyIntent(); 180 final Set<String> categories = intent.getCategories(); 181 if (Intent.ACTION_MAIN.equals(intent.getAction()) 182 && categories != null 183 && categories.size() == 1 184 && categories.contains(Intent.CATEGORY_HOME)) { 185 // Note: this field is not set to true in the compatibility version. 186 mResolvingHome = true; 187 } 188 189 setSafeForwardingMode(true); 190 191 onCreate(savedInstanceState, intent, null, 0, null, null, true); 192 } 193 194 /** 195 * Compatibility version for other bundled services that use this overload without 196 * a default title resource 197 */ 198 protected void onCreate(Bundle savedInstanceState, Intent intent, 199 CharSequence title, Intent[] initialIntents, 200 List<ResolveInfo> rList, boolean alwaysUseOption) { 201 onCreate(savedInstanceState, intent, title, 0, initialIntents, rList, alwaysUseOption); 202 } 203 204 protected void onCreate(Bundle savedInstanceState, Intent intent, 205 CharSequence title, int defaultTitleRes, Intent[] initialIntents, 206 List<ResolveInfo> rList, boolean alwaysUseOption) { 207 setTheme(R.style.Theme_DeviceDefault_Resolver); 208 super.onCreate(savedInstanceState); 209 210 // Determine whether we should show that intent is forwarded 211 // from managed profile to owner or other way around. 212 setProfileSwitchMessageId(intent.getContentUserHint()); 213 214 try { 215 mLaunchedFromUid = ActivityManagerNative.getDefault().getLaunchedFromUid( 216 getActivityToken()); 217 } catch (RemoteException e) { 218 mLaunchedFromUid = -1; 219 } 220 mPm = getPackageManager(); 221 mUsm = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE); 222 223 final long sinceTime = System.currentTimeMillis() - USAGE_STATS_PERIOD; 224 mStats = mUsm.queryAndAggregateUsageStats(sinceTime, System.currentTimeMillis()); 225 226 mPackageMonitor.register(this, getMainLooper(), false); 227 mRegistered = true; 228 229 final ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE); 230 mIconDpi = am.getLauncherLargeIconDensity(); 231 232 mIntents.add(0, new Intent(intent)); 233 mAdapter = createAdapter(this, initialIntents, rList, mLaunchedFromUid, alwaysUseOption); 234 235 final int layoutId; 236 final boolean useHeader; 237 if (mAdapter.hasFilteredItem()) { 238 layoutId = R.layout.resolver_list_with_default; 239 alwaysUseOption = false; 240 useHeader = true; 241 } else { 242 useHeader = false; 243 layoutId = getLayoutResource(); 244 } 245 mAlwaysUseOption = alwaysUseOption; 246 247 if (mLaunchedFromUid < 0 || UserHandle.isIsolated(mLaunchedFromUid)) { 248 // Gulp! 249 finish(); 250 return; 251 } 252 253 int count = mAdapter.mDisplayList.size(); 254 if (count > 1 || (count == 1 && mAdapter.getOtherProfile() != null)) { 255 setContentView(layoutId); 256 mAdapterView = (AbsListView) findViewById(R.id.resolver_list); 257 mAdapterView.setAdapter(mAdapter); 258 mAdapterView.setOnItemClickListener(this); 259 mAdapterView.setOnItemLongClickListener(new ItemLongClickListener()); 260 261 // Initialize the different types of collection views we may have. Depending 262 // on which ones are initialized later we'll configure different properties. 263 if (mAdapterView instanceof ListView) { 264 mListView = (ListView) mAdapterView; 265 } 266 if (mAdapterView instanceof GridView) { 267 mGridView = (GridView) mAdapterView; 268 } 269 270 if (alwaysUseOption) { 271 mAdapterView.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE); 272 } 273 274 if (useHeader && mListView != null) { 275 mListView.addHeaderView(LayoutInflater.from(this).inflate( 276 R.layout.resolver_different_item_header, mListView, false)); 277 } 278 } else if (count == 1) { 279 safelyStartActivity(mAdapter.targetInfoForPosition(0, false)); 280 mPackageMonitor.unregister(); 281 mRegistered = false; 282 finish(); 283 return; 284 } else { 285 setContentView(R.layout.resolver_list); 286 287 final TextView empty = (TextView) findViewById(R.id.empty); 288 empty.setVisibility(View.VISIBLE); 289 290 mAdapterView = (AbsListView) findViewById(R.id.resolver_list); 291 mAdapterView.setVisibility(View.GONE); 292 } 293 // Prevent the Resolver window from becoming the top fullscreen window and thus from taking 294 // control of the system bars. 295 getWindow().clearFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR); 296 297 final ResolverDrawerLayout rdl = (ResolverDrawerLayout) findViewById(R.id.contentPanel); 298 if (rdl != null) { 299 rdl.setOnDismissedListener(new ResolverDrawerLayout.OnDismissedListener() { 300 @Override 301 public void onDismissed() { 302 finish(); 303 } 304 }); 305 } 306 307 if (title == null) { 308 title = getTitleForAction(intent.getAction(), defaultTitleRes); 309 } 310 if (!TextUtils.isEmpty(title)) { 311 final TextView titleView = (TextView) findViewById(R.id.title); 312 if (titleView != null) { 313 titleView.setText(title); 314 } 315 setTitle(title); 316 317 // Try to initialize the title icon if we have a view for it and a title to match 318 final ImageView titleIcon = (ImageView) findViewById(R.id.title_icon); 319 if (titleIcon != null) { 320 final String referrerPackage = getReferrerPackageName(); 321 ApplicationInfo ai = null; 322 try { 323 if (!TextUtils.isEmpty(referrerPackage)) { 324 ai = mPm.getApplicationInfo(referrerPackage, 0); 325 } 326 } catch (NameNotFoundException e) { 327 Log.e(TAG, "Could not find referrer package " + referrerPackage); 328 } 329 330 if (ai != null) { 331 titleIcon.setImageDrawable(ai.loadIcon(mPm)); 332 } 333 } 334 } 335 336 final ImageView iconView = (ImageView) findViewById(R.id.icon); 337 final DisplayResolveInfo iconInfo = mAdapter.getFilteredItem(); 338 if (iconView != null && iconInfo != null) { 339 new LoadIconIntoViewTask(iconInfo, iconView).execute(); 340 } 341 342 if (alwaysUseOption || mAdapter.hasFilteredItem()) { 343 final ViewGroup buttonLayout = (ViewGroup) findViewById(R.id.button_bar); 344 if (buttonLayout != null) { 345 buttonLayout.setVisibility(View.VISIBLE); 346 mAlwaysButton = (Button) buttonLayout.findViewById(R.id.button_always); 347 mOnceButton = (Button) buttonLayout.findViewById(R.id.button_once); 348 } else { 349 mAlwaysUseOption = false; 350 } 351 } 352 353 if (mAdapter.hasFilteredItem()) { 354 setAlwaysButtonEnabled(true, mAdapter.getFilteredPosition(), false); 355 mOnceButton.setEnabled(true); 356 } 357 358 mProfileView = findViewById(R.id.profile_button); 359 if (mProfileView != null) { 360 mProfileView.setOnClickListener(new View.OnClickListener() { 361 @Override 362 public void onClick(View v) { 363 final DisplayResolveInfo dri = mAdapter.getOtherProfile(); 364 if (dri == null) { 365 return; 366 } 367 368 // Do not show the profile switch message anymore. 369 mProfileSwitchMessageId = -1; 370 371 onTargetSelected(dri, false); 372 finish(); 373 } 374 }); 375 bindProfileView(); 376 } 377 } 378 379 protected final void setAdditionalTargets(Intent[] intents) { 380 if (intents != null) { 381 for (Intent intent : intents) { 382 mIntents.add(intent); 383 } 384 } 385 } 386 387 public Intent getTargetIntent() { 388 return mIntents.isEmpty() ? null : mIntents.get(0); 389 } 390 391 private String getReferrerPackageName() { 392 final Uri referrer = getReferrer(); 393 if (referrer != null && "android-app".equals(referrer.getScheme())) { 394 return referrer.getHost(); 395 } 396 return null; 397 } 398 399 int getLayoutResource() { 400 return R.layout.resolver_list; 401 } 402 403 void bindProfileView() { 404 final DisplayResolveInfo dri = mAdapter.getOtherProfile(); 405 if (dri != null) { 406 mProfileView.setVisibility(View.VISIBLE); 407 final ImageView icon = (ImageView) mProfileView.findViewById(R.id.icon); 408 final TextView text = (TextView) mProfileView.findViewById(R.id.text1); 409 if (!dri.hasDisplayIcon()) { 410 new LoadIconIntoViewTask(dri, icon).execute(); 411 } 412 icon.setImageDrawable(dri.getDisplayIcon()); 413 text.setText(dri.getDisplayLabel()); 414 } else { 415 mProfileView.setVisibility(View.GONE); 416 } 417 } 418 419 private void setProfileSwitchMessageId(int contentUserHint) { 420 if (contentUserHint != UserHandle.USER_CURRENT && 421 contentUserHint != UserHandle.myUserId()) { 422 UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE); 423 UserInfo originUserInfo = userManager.getUserInfo(contentUserHint); 424 boolean originIsManaged = originUserInfo != null ? originUserInfo.isManagedProfile() 425 : false; 426 boolean targetIsManaged = userManager.isManagedProfile(); 427 if (originIsManaged && !targetIsManaged) { 428 mProfileSwitchMessageId = com.android.internal.R.string.forward_intent_to_owner; 429 } else if (!originIsManaged && targetIsManaged) { 430 mProfileSwitchMessageId = com.android.internal.R.string.forward_intent_to_work; 431 } 432 } 433 } 434 435 /** 436 * Turn on launch mode that is safe to use when forwarding intents received from 437 * applications and running in system processes. This mode uses Activity.startActivityAsCaller 438 * instead of the normal Activity.startActivity for launching the activity selected 439 * by the user. 440 * 441 * <p>This mode is set to true by default if the activity is initialized through 442 * {@link #onCreate(android.os.Bundle)}. If a subclass calls one of the other onCreate 443 * methods, it is set to false by default. You must set it before calling one of the 444 * more detailed onCreate methods, so that it will be set correctly in the case where 445 * there is only one intent to resolve and it is thus started immediately.</p> 446 */ 447 public void setSafeForwardingMode(boolean safeForwarding) { 448 mSafeForwardingMode = safeForwarding; 449 } 450 451 protected CharSequence getTitleForAction(String action, int defaultTitleRes) { 452 final ActionTitle title = mResolvingHome ? ActionTitle.HOME : ActionTitle.forAction(action); 453 final boolean named = mAdapter.hasFilteredItem(); 454 if (title == ActionTitle.DEFAULT && defaultTitleRes != 0) { 455 return getString(defaultTitleRes); 456 } else { 457 return named 458 ? getString(title.namedTitleRes, mAdapter.getFilteredItem().getDisplayLabel()) 459 : getString(title.titleRes); 460 } 461 } 462 463 void dismiss() { 464 if (!isFinishing()) { 465 finish(); 466 } 467 } 468 469 Drawable getIcon(Resources res, int resId) { 470 Drawable result; 471 try { 472 result = res.getDrawableForDensity(resId, mIconDpi); 473 } catch (Resources.NotFoundException e) { 474 result = null; 475 } 476 477 return result; 478 } 479 480 Drawable loadIconForResolveInfo(ResolveInfo ri) { 481 Drawable dr; 482 try { 483 if (ri.resolvePackageName != null && ri.icon != 0) { 484 dr = getIcon(mPm.getResourcesForApplication(ri.resolvePackageName), ri.icon); 485 if (dr != null) { 486 return dr; 487 } 488 } 489 final int iconRes = ri.getIconResource(); 490 if (iconRes != 0) { 491 dr = getIcon(mPm.getResourcesForApplication(ri.activityInfo.packageName), iconRes); 492 if (dr != null) { 493 return dr; 494 } 495 } 496 } catch (NameNotFoundException e) { 497 Log.e(TAG, "Couldn't find resources for package", e); 498 } 499 return ri.loadIcon(mPm); 500 } 501 502 @Override 503 protected void onRestart() { 504 super.onRestart(); 505 if (!mRegistered) { 506 mPackageMonitor.register(this, getMainLooper(), false); 507 mRegistered = true; 508 } 509 mAdapter.handlePackagesChanged(); 510 if (mProfileView != null) { 511 bindProfileView(); 512 } 513 } 514 515 @Override 516 protected void onStop() { 517 super.onStop(); 518 if (mRegistered) { 519 mPackageMonitor.unregister(); 520 mRegistered = false; 521 } 522 if ((getIntent().getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) { 523 // This resolver is in the unusual situation where it has been 524 // launched at the top of a new task. We don't let it be added 525 // to the recent tasks shown to the user, and we need to make sure 526 // that each time we are launched we get the correct launching 527 // uid (not re-using the same resolver from an old launching uid), 528 // so we will now finish ourself since being no longer visible, 529 // the user probably can't get back to us. 530 if (!isChangingConfigurations()) { 531 finish(); 532 } 533 } 534 } 535 536 @Override 537 protected void onRestoreInstanceState(Bundle savedInstanceState) { 538 super.onRestoreInstanceState(savedInstanceState); 539 if (mAlwaysUseOption) { 540 final int checkedPos = mAdapterView.getCheckedItemPosition(); 541 final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION; 542 mLastSelected = checkedPos; 543 setAlwaysButtonEnabled(hasValidSelection, checkedPos, true); 544 mOnceButton.setEnabled(hasValidSelection); 545 if (hasValidSelection) { 546 mAdapterView.setSelection(checkedPos); 547 } 548 } 549 } 550 551 @Override 552 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 553 if (mListView != null) { 554 position -= mListView.getHeaderViewsCount(); 555 } 556 if (position < 0) { 557 // Header views don't count. 558 return; 559 } 560 final int checkedPos = mAdapterView.getCheckedItemPosition(); 561 final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION; 562 if (mAlwaysUseOption && (!hasValidSelection || mLastSelected != checkedPos)) { 563 setAlwaysButtonEnabled(hasValidSelection, checkedPos, true); 564 mOnceButton.setEnabled(hasValidSelection); 565 if (hasValidSelection) { 566 mAdapterView.smoothScrollToPosition(checkedPos); 567 } 568 mLastSelected = checkedPos; 569 } else { 570 startSelected(position, false, true); 571 } 572 } 573 574 private boolean hasManagedProfile() { 575 UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE); 576 if (userManager == null) { 577 return false; 578 } 579 580 try { 581 List<UserInfo> profiles = userManager.getProfiles(getUserId()); 582 for (UserInfo userInfo : profiles) { 583 if (userInfo != null && userInfo.isManagedProfile()) { 584 return true; 585 } 586 } 587 } catch (SecurityException e) { 588 return false; 589 } 590 return false; 591 } 592 593 private boolean supportsManagedProfiles(ResolveInfo resolveInfo) { 594 try { 595 ApplicationInfo appInfo = getPackageManager().getApplicationInfo( 596 resolveInfo.activityInfo.packageName, 0 /* default flags */); 597 return versionNumberAtLeastL(appInfo.targetSdkVersion); 598 } catch (NameNotFoundException e) { 599 return false; 600 } 601 } 602 603 private boolean versionNumberAtLeastL(int versionNumber) { 604 return versionNumber >= Build.VERSION_CODES.LOLLIPOP; 605 } 606 607 private void setAlwaysButtonEnabled(boolean hasValidSelection, int checkedPos, 608 boolean filtered) { 609 boolean enabled = false; 610 if (hasValidSelection) { 611 ResolveInfo ri = mAdapter.resolveInfoForPosition(checkedPos, filtered); 612 if (ri.targetUserId == UserHandle.USER_CURRENT) { 613 enabled = true; 614 } 615 } 616 mAlwaysButton.setEnabled(enabled); 617 } 618 619 public void onButtonClick(View v) { 620 final int id = v.getId(); 621 startSelected(mAlwaysUseOption ? 622 mAdapterView.getCheckedItemPosition() : mAdapter.getFilteredPosition(), 623 id == R.id.button_always, 624 mAlwaysUseOption); 625 } 626 627 void startSelected(int which, boolean always, boolean filtered) { 628 if (isFinishing()) { 629 return; 630 } 631 ResolveInfo ri = mAdapter.resolveInfoForPosition(which, filtered); 632 if (mResolvingHome && hasManagedProfile() && !supportsManagedProfiles(ri)) { 633 Toast.makeText(this, String.format(getResources().getString( 634 com.android.internal.R.string.activity_resolver_work_profiles_support), 635 ri.activityInfo.loadLabel(getPackageManager()).toString()), 636 Toast.LENGTH_LONG).show(); 637 return; 638 } 639 640 TargetInfo target = mAdapter.targetInfoForPosition(which, filtered); 641 if (onTargetSelected(target, always)) { 642 finish(); 643 } 644 } 645 646 /** 647 * Replace me in subclasses! 648 */ 649 public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) { 650 return defIntent; 651 } 652 653 protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) { 654 final ResolveInfo ri = target.getResolveInfo(); 655 final Intent intent = target != null ? target.getResolvedIntent() : null; 656 657 if (intent != null && (mAlwaysUseOption || mAdapter.hasFilteredItem()) 658 && mAdapter.mOrigResolveList != null) { 659 // Build a reasonable intent filter, based on what matched. 660 IntentFilter filter = new IntentFilter(); 661 String action = intent.getAction(); 662 663 if (action != null) { 664 filter.addAction(action); 665 } 666 Set<String> categories = intent.getCategories(); 667 if (categories != null) { 668 for (String cat : categories) { 669 filter.addCategory(cat); 670 } 671 } 672 filter.addCategory(Intent.CATEGORY_DEFAULT); 673 674 int cat = ri.match & IntentFilter.MATCH_CATEGORY_MASK; 675 Uri data = intent.getData(); 676 if (cat == IntentFilter.MATCH_CATEGORY_TYPE) { 677 String mimeType = intent.resolveType(this); 678 if (mimeType != null) { 679 try { 680 filter.addDataType(mimeType); 681 } catch (IntentFilter.MalformedMimeTypeException e) { 682 Log.w("ResolverActivity", e); 683 filter = null; 684 } 685 } 686 } 687 if (data != null && data.getScheme() != null) { 688 // We need the data specification if there was no type, 689 // OR if the scheme is not one of our magical "file:" 690 // or "content:" schemes (see IntentFilter for the reason). 691 if (cat != IntentFilter.MATCH_CATEGORY_TYPE 692 || (!"file".equals(data.getScheme()) 693 && !"content".equals(data.getScheme()))) { 694 filter.addDataScheme(data.getScheme()); 695 696 // Look through the resolved filter to determine which part 697 // of it matched the original Intent. 698 Iterator<PatternMatcher> pIt = ri.filter.schemeSpecificPartsIterator(); 699 if (pIt != null) { 700 String ssp = data.getSchemeSpecificPart(); 701 while (ssp != null && pIt.hasNext()) { 702 PatternMatcher p = pIt.next(); 703 if (p.match(ssp)) { 704 filter.addDataSchemeSpecificPart(p.getPath(), p.getType()); 705 break; 706 } 707 } 708 } 709 Iterator<IntentFilter.AuthorityEntry> aIt = ri.filter.authoritiesIterator(); 710 if (aIt != null) { 711 while (aIt.hasNext()) { 712 IntentFilter.AuthorityEntry a = aIt.next(); 713 if (a.match(data) >= 0) { 714 int port = a.getPort(); 715 filter.addDataAuthority(a.getHost(), 716 port >= 0 ? Integer.toString(port) : null); 717 break; 718 } 719 } 720 } 721 pIt = ri.filter.pathsIterator(); 722 if (pIt != null) { 723 String path = data.getPath(); 724 while (path != null && pIt.hasNext()) { 725 PatternMatcher p = pIt.next(); 726 if (p.match(path)) { 727 filter.addDataPath(p.getPath(), p.getType()); 728 break; 729 } 730 } 731 } 732 } 733 } 734 735 if (filter != null) { 736 final int N = mAdapter.mOrigResolveList.size(); 737 ComponentName[] set = new ComponentName[N]; 738 int bestMatch = 0; 739 for (int i=0; i<N; i++) { 740 ResolveInfo r = mAdapter.mOrigResolveList.get(i).getResolveInfoAt(0); 741 set[i] = new ComponentName(r.activityInfo.packageName, 742 r.activityInfo.name); 743 if (r.match > bestMatch) bestMatch = r.match; 744 } 745 if (alwaysCheck) { 746 PackageManager pm = getPackageManager(); 747 748 // Set the preferred Activity 749 pm.addPreferredActivity(filter, bestMatch, set, intent.getComponent()); 750 751 // Update Domain Verification status 752 int userId = getUserId(); 753 ComponentName cn = intent.getComponent(); 754 String packageName = cn.getPackageName(); 755 String dataScheme = (data != null) ? data.getScheme() : null; 756 757 boolean isHttpOrHttps = (dataScheme != null) && 758 (dataScheme.equals(IntentFilter.SCHEME_HTTP) || 759 dataScheme.equals(IntentFilter.SCHEME_HTTPS)); 760 761 boolean isViewAction = (action != null) && action.equals(Intent.ACTION_VIEW); 762 boolean hasCategoryBrowsable = (categories != null) && 763 categories.contains(Intent.CATEGORY_BROWSABLE); 764 765 if (isHttpOrHttps && isViewAction && hasCategoryBrowsable) { 766 pm.updateIntentVerificationStatus(packageName, 767 PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS, 768 userId); 769 } 770 } else { 771 try { 772 AppGlobals.getPackageManager().setLastChosenActivity(intent, 773 intent.resolveTypeIfNeeded(getContentResolver()), 774 PackageManager.MATCH_DEFAULT_ONLY, 775 filter, bestMatch, intent.getComponent()); 776 } catch (RemoteException re) { 777 Log.d(TAG, "Error calling setLastChosenActivity\n" + re); 778 } 779 } 780 } 781 } 782 783 if (target != null) { 784 safelyStartActivity(target); 785 } 786 return true; 787 } 788 789 void safelyStartActivity(TargetInfo cti) { 790 // If needed, show that intent is forwarded 791 // from managed profile to owner or other way around. 792 if (mProfileSwitchMessageId != -1) { 793 Toast.makeText(this, getString(mProfileSwitchMessageId), Toast.LENGTH_LONG).show(); 794 } 795 if (!mSafeForwardingMode) { 796 if (cti.start(this, null)) { 797 onActivityStarted(cti); 798 } 799 return; 800 } 801 try { 802 if (cti.startAsCaller(this, null, UserHandle.USER_NULL)) { 803 onActivityStarted(cti); 804 } 805 } catch (RuntimeException e) { 806 String launchedFromPackage; 807 try { 808 launchedFromPackage = ActivityManagerNative.getDefault().getLaunchedFromPackage( 809 getActivityToken()); 810 } catch (RemoteException e2) { 811 launchedFromPackage = "??"; 812 } 813 Slog.wtf(TAG, "Unable to launch as uid " + mLaunchedFromUid 814 + " package " + launchedFromPackage + ", while running in " 815 + ActivityThread.currentProcessName(), e); 816 } 817 } 818 819 void onActivityStarted(TargetInfo cti) { 820 // Do nothing 821 } 822 823 boolean shouldGetActivityMetadata() { 824 return false; 825 } 826 827 void showAppDetails(ResolveInfo ri) { 828 Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) 829 .setData(Uri.fromParts("package", ri.activityInfo.packageName, null)) 830 .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); 831 startActivity(in); 832 } 833 834 ResolveListAdapter createAdapter(Context context, Intent[] initialIntents, 835 List<ResolveInfo> rList, int launchedFromUid, boolean filterLastUsed) { 836 return new ResolveListAdapter(context, initialIntents, rList, launchedFromUid, 837 filterLastUsed); 838 } 839 840 ResolveListAdapter getAdapter() { 841 return mAdapter; 842 } 843 844 final class DisplayResolveInfo implements TargetInfo { 845 private final ResolveInfo mResolveInfo; 846 private final CharSequence mDisplayLabel; 847 private Drawable mDisplayIcon; 848 private final CharSequence mExtendedInfo; 849 private final Intent mResolvedIntent; 850 private final List<Intent> mSourceIntents = new ArrayList<>(); 851 852 DisplayResolveInfo(Intent originalIntent, ResolveInfo pri, CharSequence pLabel, 853 CharSequence pInfo, Intent pOrigIntent) { 854 mSourceIntents.add(originalIntent); 855 mResolveInfo = pri; 856 mDisplayLabel = pLabel; 857 mExtendedInfo = pInfo; 858 859 final Intent intent = new Intent(pOrigIntent != null ? pOrigIntent : 860 getReplacementIntent(pri.activityInfo, getTargetIntent())); 861 intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT 862 | Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP); 863 final ActivityInfo ai = mResolveInfo.activityInfo; 864 intent.setComponent(new ComponentName(ai.applicationInfo.packageName, ai.name)); 865 866 mResolvedIntent = intent; 867 } 868 869 private DisplayResolveInfo(DisplayResolveInfo other, Intent fillInIntent, int flags) { 870 mSourceIntents.addAll(other.getAllSourceIntents()); 871 mResolveInfo = other.mResolveInfo; 872 mDisplayLabel = other.mDisplayLabel; 873 mDisplayIcon = other.mDisplayIcon; 874 mExtendedInfo = other.mExtendedInfo; 875 mResolvedIntent = new Intent(other.mResolvedIntent); 876 mResolvedIntent.fillIn(fillInIntent, flags); 877 } 878 879 public ResolveInfo getResolveInfo() { 880 return mResolveInfo; 881 } 882 883 public CharSequence getDisplayLabel() { 884 return mDisplayLabel; 885 } 886 887 public Drawable getDisplayIcon() { 888 return mDisplayIcon; 889 } 890 891 @Override 892 public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) { 893 return new DisplayResolveInfo(this, fillInIntent, flags); 894 } 895 896 @Override 897 public List<Intent> getAllSourceIntents() { 898 return mSourceIntents; 899 } 900 901 public void addAlternateSourceIntent(Intent alt) { 902 mSourceIntents.add(alt); 903 } 904 905 public void setDisplayIcon(Drawable icon) { 906 mDisplayIcon = icon; 907 } 908 909 public boolean hasDisplayIcon() { 910 return mDisplayIcon != null; 911 } 912 913 public CharSequence getExtendedInfo() { 914 return mExtendedInfo; 915 } 916 917 public Intent getResolvedIntent() { 918 return mResolvedIntent; 919 } 920 921 @Override 922 public ComponentName getResolvedComponentName() { 923 return new ComponentName(mResolveInfo.activityInfo.packageName, 924 mResolveInfo.activityInfo.name); 925 } 926 927 @Override 928 public boolean start(Activity activity, Bundle options) { 929 activity.startActivity(mResolvedIntent, options); 930 return true; 931 } 932 933 @Override 934 public boolean startAsCaller(Activity activity, Bundle options, int userId) { 935 activity.startActivityAsCaller(mResolvedIntent, options, userId); 936 return true; 937 } 938 939 @Override 940 public boolean startAsUser(Activity activity, Bundle options, UserHandle user) { 941 activity.startActivityAsUser(mResolvedIntent, options, user); 942 return false; 943 } 944 } 945 946 /** 947 * A single target as represented in the chooser. 948 */ 949 public interface TargetInfo { 950 /** 951 * Get the resolved intent that represents this target. Note that this may not be the 952 * intent that will be launched by calling one of the <code>start</code> methods provided; 953 * this is the intent that will be credited with the launch. 954 * 955 * @return the resolved intent for this target 956 */ 957 public Intent getResolvedIntent(); 958 959 /** 960 * Get the resolved component name that represents this target. Note that this may not 961 * be the component that will be directly launched by calling one of the <code>start</code> 962 * methods provided; this is the component that will be credited with the launch. 963 * 964 * @return the resolved ComponentName for this target 965 */ 966 public ComponentName getResolvedComponentName(); 967 968 /** 969 * Start the activity referenced by this target. 970 * 971 * @param activity calling Activity performing the launch 972 * @param options ActivityOptions bundle 973 * @return true if the start completed successfully 974 */ 975 public boolean start(Activity activity, Bundle options); 976 977 /** 978 * Start the activity referenced by this target as if the ResolverActivity's caller 979 * was performing the start operation. 980 * 981 * @param activity calling Activity (actually) performing the launch 982 * @param options ActivityOptions bundle 983 * @param userId userId to start as or {@link UserHandle#USER_NULL} for activity's caller 984 * @return true if the start completed successfully 985 */ 986 public boolean startAsCaller(Activity activity, Bundle options, int userId); 987 988 /** 989 * Start the activity referenced by this target as a given user. 990 * 991 * @param activity calling activity performing the launch 992 * @param options ActivityOptions bundle 993 * @param user handle for the user to start the activity as 994 * @return true if the start completed successfully 995 */ 996 public boolean startAsUser(Activity activity, Bundle options, UserHandle user); 997 998 /** 999 * Return the ResolveInfo about how and why this target matched the original query 1000 * for available targets. 1001 * 1002 * @return ResolveInfo representing this target's match 1003 */ 1004 public ResolveInfo getResolveInfo(); 1005 1006 /** 1007 * Return the human-readable text label for this target. 1008 * 1009 * @return user-visible target label 1010 */ 1011 public CharSequence getDisplayLabel(); 1012 1013 /** 1014 * Return any extended info for this target. This may be used to disambiguate 1015 * otherwise identical targets. 1016 * 1017 * @return human-readable disambig string or null if none present 1018 */ 1019 public CharSequence getExtendedInfo(); 1020 1021 /** 1022 * @return The drawable that should be used to represent this target 1023 */ 1024 public Drawable getDisplayIcon(); 1025 1026 /** 1027 * Clone this target with the given fill-in information. 1028 */ 1029 public TargetInfo cloneFilledIn(Intent fillInIntent, int flags); 1030 1031 /** 1032 * @return the list of supported source intents deduped against this single target 1033 */ 1034 public List<Intent> getAllSourceIntents(); 1035 } 1036 1037 class ResolveListAdapter extends BaseAdapter { 1038 private final Intent[] mInitialIntents; 1039 private final List<ResolveInfo> mBaseResolveList; 1040 private ResolveInfo mLastChosen; 1041 private DisplayResolveInfo mOtherProfile; 1042 private final int mLaunchedFromUid; 1043 private boolean mHasExtendedInfo; 1044 1045 protected final LayoutInflater mInflater; 1046 1047 List<DisplayResolveInfo> mDisplayList; 1048 List<ResolvedComponentInfo> mOrigResolveList; 1049 1050 private int mLastChosenPosition = -1; 1051 private boolean mFilterLastUsed; 1052 1053 public ResolveListAdapter(Context context, Intent[] initialIntents, 1054 List<ResolveInfo> rList, int launchedFromUid, boolean filterLastUsed) { 1055 mInitialIntents = initialIntents; 1056 mBaseResolveList = rList; 1057 mLaunchedFromUid = launchedFromUid; 1058 mInflater = LayoutInflater.from(context); 1059 mDisplayList = new ArrayList<>(); 1060 mFilterLastUsed = filterLastUsed; 1061 rebuildList(); 1062 } 1063 1064 public void handlePackagesChanged() { 1065 rebuildList(); 1066 notifyDataSetChanged(); 1067 if (getCount() == 0) { 1068 // We no longer have any items... just finish the activity. 1069 finish(); 1070 } 1071 } 1072 1073 public DisplayResolveInfo getFilteredItem() { 1074 if (mFilterLastUsed && mLastChosenPosition >= 0) { 1075 // Not using getItem since it offsets to dodge this position for the list 1076 return mDisplayList.get(mLastChosenPosition); 1077 } 1078 return null; 1079 } 1080 1081 public DisplayResolveInfo getOtherProfile() { 1082 return mOtherProfile; 1083 } 1084 1085 public int getFilteredPosition() { 1086 if (mFilterLastUsed && mLastChosenPosition >= 0) { 1087 return mLastChosenPosition; 1088 } 1089 return AbsListView.INVALID_POSITION; 1090 } 1091 1092 public boolean hasFilteredItem() { 1093 return mFilterLastUsed && mLastChosenPosition >= 0; 1094 } 1095 1096 private void rebuildList() { 1097 List<ResolvedComponentInfo> currentResolveList = null; 1098 1099 try { 1100 final Intent primaryIntent = getTargetIntent(); 1101 mLastChosen = AppGlobals.getPackageManager().getLastChosenActivity( 1102 primaryIntent, primaryIntent.resolveTypeIfNeeded(getContentResolver()), 1103 PackageManager.MATCH_DEFAULT_ONLY); 1104 } catch (RemoteException re) { 1105 Log.d(TAG, "Error calling setLastChosenActivity\n" + re); 1106 } 1107 1108 // Clear the value of mOtherProfile from previous call. 1109 mOtherProfile = null; 1110 mDisplayList.clear(); 1111 if (mBaseResolveList != null) { 1112 currentResolveList = mOrigResolveList = new ArrayList<>(); 1113 addResolveListDedupe(currentResolveList, getTargetIntent(), mBaseResolveList); 1114 } else { 1115 final boolean shouldGetResolvedFilter = shouldGetResolvedFilter(); 1116 final boolean shouldGetActivityMetadata = shouldGetActivityMetadata(); 1117 for (int i = 0, N = mIntents.size(); i < N; i++) { 1118 final Intent intent = mIntents.get(i); 1119 final List<ResolveInfo> infos = mPm.queryIntentActivities(intent, 1120 PackageManager.MATCH_DEFAULT_ONLY 1121 | (shouldGetResolvedFilter ? PackageManager.GET_RESOLVED_FILTER : 0) 1122 | (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0)); 1123 if (infos != null) { 1124 if (currentResolveList == null) { 1125 currentResolveList = mOrigResolveList = new ArrayList<>(); 1126 } 1127 addResolveListDedupe(currentResolveList, intent, infos); 1128 } 1129 } 1130 1131 // Filter out any activities that the launched uid does not 1132 // have permission for. We don't do this when we have an explicit 1133 // list of resolved activities, because that only happens when 1134 // we are being subclassed, so we can safely launch whatever 1135 // they gave us. 1136 if (currentResolveList != null) { 1137 for (int i=currentResolveList.size()-1; i >= 0; i--) { 1138 ActivityInfo ai = currentResolveList.get(i) 1139 .getResolveInfoAt(0).activityInfo; 1140 int granted = ActivityManager.checkComponentPermission( 1141 ai.permission, mLaunchedFromUid, 1142 ai.applicationInfo.uid, ai.exported); 1143 if (granted != PackageManager.PERMISSION_GRANTED) { 1144 // Access not allowed! 1145 if (mOrigResolveList == currentResolveList) { 1146 mOrigResolveList = new ArrayList<>(mOrigResolveList); 1147 } 1148 currentResolveList.remove(i); 1149 } 1150 } 1151 } 1152 } 1153 int N; 1154 if ((currentResolveList != null) && ((N = currentResolveList.size()) > 0)) { 1155 // Only display the first matches that are either of equal 1156 // priority or have asked to be default options. 1157 ResolvedComponentInfo rci0 = currentResolveList.get(0); 1158 ResolveInfo r0 = rci0.getResolveInfoAt(0); 1159 for (int i=1; i<N; i++) { 1160 ResolveInfo ri = currentResolveList.get(i).getResolveInfoAt(0); 1161 if (DEBUG) Log.v( 1162 TAG, 1163 r0.activityInfo.name + "=" + 1164 r0.priority + "/" + r0.isDefault + " vs " + 1165 ri.activityInfo.name + "=" + 1166 ri.priority + "/" + ri.isDefault); 1167 if (r0.priority != ri.priority || 1168 r0.isDefault != ri.isDefault) { 1169 while (i < N) { 1170 if (mOrigResolveList == currentResolveList) { 1171 mOrigResolveList = new ArrayList<>(mOrigResolveList); 1172 } 1173 currentResolveList.remove(i); 1174 N--; 1175 } 1176 } 1177 } 1178 if (N > 1) { 1179 Collections.sort(currentResolveList, 1180 new ResolverComparator(ResolverActivity.this, getTargetIntent())); 1181 } 1182 // First put the initial items at the top. 1183 if (mInitialIntents != null) { 1184 for (int i=0; i<mInitialIntents.length; i++) { 1185 Intent ii = mInitialIntents[i]; 1186 if (ii == null) { 1187 continue; 1188 } 1189 ActivityInfo ai = ii.resolveActivityInfo( 1190 getPackageManager(), 0); 1191 if (ai == null) { 1192 Log.w(TAG, "No activity found for " + ii); 1193 continue; 1194 } 1195 ResolveInfo ri = new ResolveInfo(); 1196 ri.activityInfo = ai; 1197 UserManager userManager = 1198 (UserManager) getSystemService(Context.USER_SERVICE); 1199 if (userManager.isManagedProfile()) { 1200 ri.noResourceId = true; 1201 } 1202 if (ii instanceof LabeledIntent) { 1203 LabeledIntent li = (LabeledIntent)ii; 1204 ri.resolvePackageName = li.getSourcePackage(); 1205 ri.labelRes = li.getLabelResource(); 1206 ri.nonLocalizedLabel = li.getNonLocalizedLabel(); 1207 ri.icon = li.getIconResource(); 1208 } 1209 addResolveInfo(new DisplayResolveInfo(ii, ri, 1210 ri.loadLabel(getPackageManager()), null, ii)); 1211 } 1212 } 1213 1214 // Check for applications with same name and use application name or 1215 // package name if necessary 1216 rci0 = currentResolveList.get(0); 1217 r0 = rci0.getResolveInfoAt(0); 1218 int start = 0; 1219 CharSequence r0Label = r0.loadLabel(mPm); 1220 mHasExtendedInfo = false; 1221 for (int i = 1; i < N; i++) { 1222 if (r0Label == null) { 1223 r0Label = r0.activityInfo.packageName; 1224 } 1225 ResolvedComponentInfo rci = currentResolveList.get(i); 1226 ResolveInfo ri = rci.getResolveInfoAt(0); 1227 CharSequence riLabel = ri.loadLabel(mPm); 1228 if (riLabel == null) { 1229 riLabel = ri.activityInfo.packageName; 1230 } 1231 if (riLabel.equals(r0Label)) { 1232 continue; 1233 } 1234 processGroup(currentResolveList, start, (i-1), rci0, r0Label); 1235 rci0 = rci; 1236 r0 = ri; 1237 r0Label = riLabel; 1238 start = i; 1239 } 1240 // Process last group 1241 processGroup(currentResolveList, start, (N-1), rci0, r0Label); 1242 } 1243 1244 // Layout doesn't handle both profile button and last chosen 1245 // so disable last chosen if profile button is present. 1246 if (mOtherProfile != null && mLastChosenPosition >= 0) { 1247 mLastChosenPosition = -1; 1248 mFilterLastUsed = false; 1249 } 1250 1251 onListRebuilt(); 1252 } 1253 1254 private void addResolveListDedupe(List<ResolvedComponentInfo> into, Intent intent, 1255 List<ResolveInfo> from) { 1256 final int fromCount = from.size(); 1257 final int intoCount = into.size(); 1258 for (int i = 0; i < fromCount; i++) { 1259 final ResolveInfo newInfo = from.get(i); 1260 boolean found = false; 1261 // Only loop to the end of into as it was before we started; no dupes in from. 1262 for (int j = 0; j < intoCount; j++) { 1263 final ResolvedComponentInfo rci = into.get(i); 1264 if (isSameResolvedComponent(newInfo, rci)) { 1265 found = true; 1266 rci.add(intent, newInfo); 1267 break; 1268 } 1269 } 1270 if (!found) { 1271 into.add(new ResolvedComponentInfo(new ComponentName( 1272 newInfo.activityInfo.packageName, newInfo.activityInfo.name), 1273 intent, newInfo)); 1274 } 1275 } 1276 } 1277 1278 private boolean isSameResolvedComponent(ResolveInfo a, ResolvedComponentInfo b) { 1279 final ActivityInfo ai = a.activityInfo; 1280 return ai.packageName.equals(b.name.getPackageName()) 1281 && ai.name.equals(b.name.getClassName()); 1282 } 1283 1284 public void onListRebuilt() { 1285 // This space for rent 1286 } 1287 1288 public boolean shouldGetResolvedFilter() { 1289 return mFilterLastUsed; 1290 } 1291 1292 private void processGroup(List<ResolvedComponentInfo> rList, int start, int end, 1293 ResolvedComponentInfo ro, CharSequence roLabel) { 1294 // Process labels from start to i 1295 int num = end - start+1; 1296 if (num == 1) { 1297 // No duplicate labels. Use label for entry at start 1298 addResolveInfoWithAlternates(ro, null, roLabel); 1299 } else { 1300 mHasExtendedInfo = true; 1301 boolean usePkg = false; 1302 CharSequence startApp = ro.getResolveInfoAt(0).activityInfo.applicationInfo 1303 .loadLabel(mPm); 1304 if (startApp == null) { 1305 usePkg = true; 1306 } 1307 if (!usePkg) { 1308 // Use HashSet to track duplicates 1309 HashSet<CharSequence> duplicates = 1310 new HashSet<CharSequence>(); 1311 duplicates.add(startApp); 1312 for (int j = start+1; j <= end ; j++) { 1313 ResolveInfo jRi = rList.get(j).getResolveInfoAt(0); 1314 CharSequence jApp = jRi.activityInfo.applicationInfo.loadLabel(mPm); 1315 if ( (jApp == null) || (duplicates.contains(jApp))) { 1316 usePkg = true; 1317 break; 1318 } else { 1319 duplicates.add(jApp); 1320 } 1321 } 1322 // Clear HashSet for later use 1323 duplicates.clear(); 1324 } 1325 for (int k = start; k <= end; k++) { 1326 final ResolvedComponentInfo rci = rList.get(k); 1327 final ResolveInfo add = rci.getResolveInfoAt(0); 1328 final CharSequence extraInfo; 1329 if (usePkg) { 1330 // Use package name for all entries from start to end-1 1331 extraInfo = add.activityInfo.packageName; 1332 } else { 1333 // Use application name for all entries from start to end-1 1334 extraInfo = add.activityInfo.applicationInfo.loadLabel(mPm); 1335 } 1336 addResolveInfoWithAlternates(rci, extraInfo, roLabel); 1337 } 1338 } 1339 } 1340 1341 private void addResolveInfoWithAlternates(ResolvedComponentInfo rci, 1342 CharSequence extraInfo, CharSequence roLabel) { 1343 final int count = rci.getCount(); 1344 final Intent intent = rci.getIntentAt(0); 1345 final ResolveInfo add = rci.getResolveInfoAt(0); 1346 final Intent replaceIntent = getReplacementIntent(add.activityInfo, intent); 1347 final DisplayResolveInfo dri = new DisplayResolveInfo(intent, add, roLabel, 1348 extraInfo, replaceIntent); 1349 addResolveInfo(dri); 1350 if (replaceIntent == intent) { 1351 // Only add alternates if we didn't get a specific replacement from 1352 // the caller. If we have one it trumps potential alternates. 1353 for (int i = 1, N = count; i < N; i++) { 1354 final Intent altIntent = rci.getIntentAt(i); 1355 dri.addAlternateSourceIntent(altIntent); 1356 } 1357 } 1358 updateLastChosenPosition(add); 1359 } 1360 1361 private void updateLastChosenPosition(ResolveInfo info) { 1362 if (mLastChosen != null 1363 && mLastChosen.activityInfo.packageName.equals(info.activityInfo.packageName) 1364 && mLastChosen.activityInfo.name.equals(info.activityInfo.name)) { 1365 mLastChosenPosition = mDisplayList.size() - 1; 1366 } 1367 } 1368 1369 private void addResolveInfo(DisplayResolveInfo dri) { 1370 if (dri.mResolveInfo.targetUserId != UserHandle.USER_CURRENT && mOtherProfile == null) { 1371 // So far we only support a single other profile at a time. 1372 // The first one we see gets special treatment. 1373 mOtherProfile = dri; 1374 } else { 1375 mDisplayList.add(dri); 1376 } 1377 } 1378 1379 public ResolveInfo resolveInfoForPosition(int position, boolean filtered) { 1380 return (filtered ? getItem(position) : mDisplayList.get(position)) 1381 .getResolveInfo(); 1382 } 1383 1384 public TargetInfo targetInfoForPosition(int position, boolean filtered) { 1385 return filtered ? getItem(position) : mDisplayList.get(position); 1386 } 1387 1388 public int getCount() { 1389 int result = mDisplayList.size(); 1390 if (mFilterLastUsed && mLastChosenPosition >= 0) { 1391 result--; 1392 } 1393 return result; 1394 } 1395 1396 public TargetInfo getItem(int position) { 1397 if (mFilterLastUsed && mLastChosenPosition >= 0 && position >= mLastChosenPosition) { 1398 position++; 1399 } 1400 return mDisplayList.get(position); 1401 } 1402 1403 public long getItemId(int position) { 1404 return position; 1405 } 1406 1407 public boolean hasExtendedInfo() { 1408 return mHasExtendedInfo; 1409 } 1410 1411 public boolean hasResolvedTarget(ResolveInfo info) { 1412 for (int i = 0, N = mDisplayList.size(); i < N; i++) { 1413 if (info.equals(mDisplayList.get(i).getResolveInfo())) { 1414 return true; 1415 } 1416 } 1417 return false; 1418 } 1419 1420 protected int getDisplayResolveInfoCount() { 1421 return mDisplayList.size(); 1422 } 1423 1424 protected DisplayResolveInfo getDisplayResolveInfo(int index) { 1425 // Used to query services. We only query services for primary targets, not alternates. 1426 return mDisplayList.get(index); 1427 } 1428 1429 public final View getView(int position, View convertView, ViewGroup parent) { 1430 View view = convertView; 1431 if (view == null) { 1432 view = createView(parent); 1433 1434 final ViewHolder holder = new ViewHolder(view); 1435 view.setTag(holder); 1436 } 1437 bindView(view, getItem(position)); 1438 return view; 1439 } 1440 1441 public View createView(ViewGroup parent) { 1442 return mInflater.inflate( 1443 com.android.internal.R.layout.resolve_list_item, parent, false); 1444 } 1445 1446 public boolean showsExtendedInfo(TargetInfo info) { 1447 return !TextUtils.isEmpty(info.getExtendedInfo()); 1448 } 1449 1450 private final void bindView(View view, TargetInfo info) { 1451 final ViewHolder holder = (ViewHolder) view.getTag(); 1452 holder.text.setText(info.getDisplayLabel()); 1453 if (showsExtendedInfo(info)) { 1454 holder.text2.setVisibility(View.VISIBLE); 1455 holder.text2.setText(info.getExtendedInfo()); 1456 } else { 1457 holder.text2.setVisibility(View.GONE); 1458 } 1459 if (info instanceof DisplayResolveInfo 1460 && !((DisplayResolveInfo) info).hasDisplayIcon()) { 1461 new LoadAdapterIconTask((DisplayResolveInfo) info).execute(); 1462 } 1463 holder.icon.setImageDrawable(info.getDisplayIcon()); 1464 } 1465 } 1466 1467 static final class ResolvedComponentInfo { 1468 public final ComponentName name; 1469 private final List<Intent> mIntents = new ArrayList<>(); 1470 private final List<ResolveInfo> mResolveInfos = new ArrayList<>(); 1471 1472 public ResolvedComponentInfo(ComponentName name, Intent intent, ResolveInfo info) { 1473 this.name = name; 1474 add(intent, info); 1475 } 1476 1477 public void add(Intent intent, ResolveInfo info) { 1478 mIntents.add(intent); 1479 mResolveInfos.add(info); 1480 } 1481 1482 public int getCount() { 1483 return mIntents.size(); 1484 } 1485 1486 public Intent getIntentAt(int index) { 1487 return index >= 0 ? mIntents.get(index) : null; 1488 } 1489 1490 public ResolveInfo getResolveInfoAt(int index) { 1491 return index >= 0 ? mResolveInfos.get(index) : null; 1492 } 1493 1494 public int findIntent(Intent intent) { 1495 for (int i = 0, N = mIntents.size(); i < N; i++) { 1496 if (intent.equals(mIntents.get(i))) { 1497 return i; 1498 } 1499 } 1500 return -1; 1501 } 1502 1503 public int findResolveInfo(ResolveInfo info) { 1504 for (int i = 0, N = mResolveInfos.size(); i < N; i++) { 1505 if (info.equals(mResolveInfos.get(i))) { 1506 return i; 1507 } 1508 } 1509 return -1; 1510 } 1511 } 1512 1513 static class ViewHolder { 1514 public TextView text; 1515 public TextView text2; 1516 public ImageView icon; 1517 1518 public ViewHolder(View view) { 1519 text = (TextView) view.findViewById(com.android.internal.R.id.text1); 1520 text2 = (TextView) view.findViewById(com.android.internal.R.id.text2); 1521 icon = (ImageView) view.findViewById(R.id.icon); 1522 } 1523 } 1524 1525 class ItemLongClickListener implements AdapterView.OnItemLongClickListener { 1526 1527 @Override 1528 public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { 1529 if (mListView != null) { 1530 position -= mListView.getHeaderViewsCount(); 1531 } 1532 if (position < 0) { 1533 // Header views don't count. 1534 return false; 1535 } 1536 ResolveInfo ri = mAdapter.resolveInfoForPosition(position, true); 1537 showAppDetails(ri); 1538 return true; 1539 } 1540 1541 } 1542 1543 abstract class LoadIconTask extends AsyncTask<Void, Void, Drawable> { 1544 protected final DisplayResolveInfo mDisplayResolveInfo; 1545 private final ResolveInfo mResolveInfo; 1546 1547 public LoadIconTask(DisplayResolveInfo dri) { 1548 mDisplayResolveInfo = dri; 1549 mResolveInfo = dri.getResolveInfo(); 1550 } 1551 1552 @Override 1553 protected Drawable doInBackground(Void... params) { 1554 return loadIconForResolveInfo(mResolveInfo); 1555 } 1556 1557 @Override 1558 protected void onPostExecute(Drawable d) { 1559 mDisplayResolveInfo.setDisplayIcon(d); 1560 } 1561 } 1562 1563 class LoadAdapterIconTask extends LoadIconTask { 1564 public LoadAdapterIconTask(DisplayResolveInfo dri) { 1565 super(dri); 1566 } 1567 1568 @Override 1569 protected void onPostExecute(Drawable d) { 1570 super.onPostExecute(d); 1571 if (mProfileView != null && mAdapter.getOtherProfile() == mDisplayResolveInfo) { 1572 bindProfileView(); 1573 } 1574 mAdapter.notifyDataSetChanged(); 1575 } 1576 } 1577 1578 class LoadIconIntoViewTask extends LoadIconTask { 1579 private final ImageView mTargetView; 1580 1581 public LoadIconIntoViewTask(DisplayResolveInfo dri, ImageView target) { 1582 super(dri); 1583 mTargetView = target; 1584 } 1585 1586 @Override 1587 protected void onPostExecute(Drawable d) { 1588 super.onPostExecute(d); 1589 mTargetView.setImageDrawable(d); 1590 } 1591 } 1592 1593 static final boolean isSpecificUriMatch(int match) { 1594 match = match&IntentFilter.MATCH_CATEGORY_MASK; 1595 return match >= IntentFilter.MATCH_CATEGORY_HOST 1596 && match <= IntentFilter.MATCH_CATEGORY_PATH; 1597 } 1598 1599 class ResolverComparator implements Comparator<ResolvedComponentInfo> { 1600 private final Collator mCollator; 1601 private final boolean mHttp; 1602 1603 public ResolverComparator(Context context, Intent intent) { 1604 mCollator = Collator.getInstance(context.getResources().getConfiguration().locale); 1605 String scheme = intent.getScheme(); 1606 mHttp = "http".equals(scheme) || "https".equals(scheme); 1607 } 1608 1609 @Override 1610 public int compare(ResolvedComponentInfo lhsp, ResolvedComponentInfo rhsp) { 1611 final ResolveInfo lhs = lhsp.getResolveInfoAt(0); 1612 final ResolveInfo rhs = rhsp.getResolveInfoAt(0); 1613 1614 // We want to put the one targeted to another user at the end of the dialog. 1615 if (lhs.targetUserId != UserHandle.USER_CURRENT) { 1616 return 1; 1617 } 1618 1619 if (mHttp) { 1620 // Special case: we want filters that match URI paths/schemes to be 1621 // ordered before others. This is for the case when opening URIs, 1622 // to make native apps go above browsers. 1623 final boolean lhsSpecific = isSpecificUriMatch(lhs.match); 1624 final boolean rhsSpecific = isSpecificUriMatch(rhs.match); 1625 if (lhsSpecific != rhsSpecific) { 1626 return lhsSpecific ? -1 : 1; 1627 } 1628 } 1629 1630 if (mStats != null) { 1631 final long timeDiff = 1632 getPackageTimeSpent(rhs.activityInfo.packageName) - 1633 getPackageTimeSpent(lhs.activityInfo.packageName); 1634 1635 if (timeDiff != 0) { 1636 return timeDiff > 0 ? 1 : -1; 1637 } 1638 } 1639 1640 CharSequence sa = lhs.loadLabel(mPm); 1641 if (sa == null) sa = lhs.activityInfo.name; 1642 CharSequence sb = rhs.loadLabel(mPm); 1643 if (sb == null) sb = rhs.activityInfo.name; 1644 1645 return mCollator.compare(sa.toString(), sb.toString()); 1646 } 1647 1648 private long getPackageTimeSpent(String packageName) { 1649 if (mStats != null) { 1650 final UsageStats stats = mStats.get(packageName); 1651 if (stats != null) { 1652 return stats.getTotalTimeInForeground(); 1653 } 1654 } 1655 return 0; 1656 } 1657 } 1658} 1659