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 17 package com.android.internal.app; 18 19 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; 20 21 import android.annotation.Nullable; 22 import android.annotation.StringRes; 23 import android.annotation.UiThread; 24 import android.annotation.UnsupportedAppUsage; 25 import android.app.Activity; 26 import android.app.ActivityManager; 27 import android.app.ActivityTaskManager; 28 import android.app.ActivityThread; 29 import android.app.VoiceInteractor.PickOptionRequest; 30 import android.app.VoiceInteractor.PickOptionRequest.Option; 31 import android.app.VoiceInteractor.Prompt; 32 import android.content.ComponentName; 33 import android.content.Context; 34 import android.content.Intent; 35 import android.content.IntentFilter; 36 import android.content.pm.ActivityInfo; 37 import android.content.pm.ApplicationInfo; 38 import android.content.pm.LabeledIntent; 39 import android.content.pm.PackageManager; 40 import android.content.pm.PackageManager.NameNotFoundException; 41 import android.content.pm.ResolveInfo; 42 import android.content.pm.UserInfo; 43 import android.content.res.Configuration; 44 import android.content.res.Resources; 45 import android.graphics.Bitmap; 46 import android.graphics.ColorMatrix; 47 import android.graphics.ColorMatrixColorFilter; 48 import android.graphics.Insets; 49 import android.graphics.drawable.BitmapDrawable; 50 import android.graphics.drawable.Drawable; 51 import android.net.Uri; 52 import android.os.AsyncTask; 53 import android.os.Build; 54 import android.os.Bundle; 55 import android.os.IBinder; 56 import android.os.PatternMatcher; 57 import android.os.Process; 58 import android.os.RemoteException; 59 import android.os.StrictMode; 60 import android.os.UserHandle; 61 import android.os.UserManager; 62 import android.provider.MediaStore; 63 import android.provider.Settings; 64 import android.text.TextUtils; 65 import android.util.Log; 66 import android.util.Slog; 67 import android.view.LayoutInflater; 68 import android.view.View; 69 import android.view.ViewGroup; 70 import android.view.ViewGroup.LayoutParams; 71 import android.view.WindowInsets; 72 import android.widget.AbsListView; 73 import android.widget.AdapterView; 74 import android.widget.BaseAdapter; 75 import android.widget.Button; 76 import android.widget.ImageView; 77 import android.widget.ListView; 78 import android.widget.Space; 79 import android.widget.TextView; 80 import android.widget.Toast; 81 82 import com.android.internal.R; 83 import com.android.internal.annotations.VisibleForTesting; 84 import com.android.internal.content.PackageMonitor; 85 import com.android.internal.logging.MetricsLogger; 86 import com.android.internal.logging.nano.MetricsProto; 87 import com.android.internal.widget.ResolverDrawerLayout; 88 89 import java.util.ArrayList; 90 import java.util.Arrays; 91 import java.util.Iterator; 92 import java.util.List; 93 import java.util.Objects; 94 import java.util.Set; 95 96 /** 97 * This activity is displayed when the system attempts to start an Intent for 98 * which there is more than one matching activity, allowing the user to decide 99 * which to go to. It is not normally used directly by application developers. 100 */ 101 @UiThread 102 public class ResolverActivity extends Activity { 103 104 // Temporary flag for new chooser delegate behavior. 105 boolean mEnableChooserDelegate = true; 106 107 @UnsupportedAppUsage 108 protected ResolveListAdapter mAdapter; 109 private boolean mSafeForwardingMode; 110 protected AbsListView mAdapterView; 111 private Button mAlwaysButton; 112 private Button mOnceButton; 113 protected View mProfileView; 114 private int mIconDpi; 115 private int mLastSelected = AbsListView.INVALID_POSITION; 116 private boolean mResolvingHome = false; 117 private int mProfileSwitchMessageId = -1; 118 private int mLayoutId; 119 private final ArrayList<Intent> mIntents = new ArrayList<>(); 120 private PickTargetOptionRequest mPickOptionRequest; 121 private String mReferrerPackage; 122 private CharSequence mTitle; 123 private int mDefaultTitleResId; 124 private boolean mUseLayoutForBrowsables; 125 126 // Whether or not this activity supports choosing a default handler for the intent. 127 private boolean mSupportsAlwaysUseOption; 128 protected ResolverDrawerLayout mResolverDrawerLayout; 129 @UnsupportedAppUsage 130 protected PackageManager mPm; 131 protected int mLaunchedFromUid; 132 133 private static final String TAG = "ResolverActivity"; 134 private static final boolean DEBUG = false; 135 private Runnable mPostListReadyRunnable; 136 137 private boolean mRegistered; 138 139 private ColorMatrixColorFilter mSuspendedMatrixColorFilter; 140 141 protected Insets mSystemWindowInsets = null; 142 private Space mFooterSpacer = null; 143 144 /** See {@link #setRetainInOnStop}. */ 145 private boolean mRetainInOnStop; 146 147 private static final String EXTRA_SHOW_FRAGMENT_ARGS = ":settings:show_fragment_args"; 148 private static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key"; 149 private static final String OPEN_LINKS_COMPONENT_KEY = "app_link_state"; 150 151 private final PackageMonitor mPackageMonitor = createPackageMonitor(); 152 153 /** 154 * Get the string resource to be used as a label for the link to the resolver activity for an 155 * action. 156 * 157 * @param action The action to resolve 158 * 159 * @return The string resource to be used as a label 160 */ getLabelRes(String action)161 public static @StringRes int getLabelRes(String action) { 162 return ActionTitle.forAction(action).labelRes; 163 } 164 165 private enum ActionTitle { 166 VIEW(Intent.ACTION_VIEW, 167 com.android.internal.R.string.whichViewApplication, 168 com.android.internal.R.string.whichViewApplicationNamed, 169 com.android.internal.R.string.whichViewApplicationLabel), 170 EDIT(Intent.ACTION_EDIT, 171 com.android.internal.R.string.whichEditApplication, 172 com.android.internal.R.string.whichEditApplicationNamed, 173 com.android.internal.R.string.whichEditApplicationLabel), 174 SEND(Intent.ACTION_SEND, 175 com.android.internal.R.string.whichSendApplication, 176 com.android.internal.R.string.whichSendApplicationNamed, 177 com.android.internal.R.string.whichSendApplicationLabel), 178 SENDTO(Intent.ACTION_SENDTO, 179 com.android.internal.R.string.whichSendToApplication, 180 com.android.internal.R.string.whichSendToApplicationNamed, 181 com.android.internal.R.string.whichSendToApplicationLabel), 182 SEND_MULTIPLE(Intent.ACTION_SEND_MULTIPLE, 183 com.android.internal.R.string.whichSendApplication, 184 com.android.internal.R.string.whichSendApplicationNamed, 185 com.android.internal.R.string.whichSendApplicationLabel), 186 CAPTURE_IMAGE(MediaStore.ACTION_IMAGE_CAPTURE, 187 com.android.internal.R.string.whichImageCaptureApplication, 188 com.android.internal.R.string.whichImageCaptureApplicationNamed, 189 com.android.internal.R.string.whichImageCaptureApplicationLabel), 190 DEFAULT(null, 191 com.android.internal.R.string.whichApplication, 192 com.android.internal.R.string.whichApplicationNamed, 193 com.android.internal.R.string.whichApplicationLabel), 194 HOME(Intent.ACTION_MAIN, 195 com.android.internal.R.string.whichHomeApplication, 196 com.android.internal.R.string.whichHomeApplicationNamed, 197 com.android.internal.R.string.whichHomeApplicationLabel); 198 199 // titles for layout that deals with http(s) intents 200 public static final int BROWSABLE_TITLE_RES = 201 com.android.internal.R.string.whichOpenLinksWith; 202 public static final int BROWSABLE_HOST_TITLE_RES = 203 com.android.internal.R.string.whichOpenHostLinksWith; 204 public static final int BROWSABLE_HOST_APP_TITLE_RES = 205 com.android.internal.R.string.whichOpenHostLinksWithApp; 206 public static final int BROWSABLE_APP_TITLE_RES = 207 com.android.internal.R.string.whichOpenLinksWithApp; 208 209 public final String action; 210 public final int titleRes; 211 public final int namedTitleRes; 212 public final @StringRes int labelRes; 213 ActionTitle(String action, int titleRes, int namedTitleRes, @StringRes int labelRes)214 ActionTitle(String action, int titleRes, int namedTitleRes, @StringRes int labelRes) { 215 this.action = action; 216 this.titleRes = titleRes; 217 this.namedTitleRes = namedTitleRes; 218 this.labelRes = labelRes; 219 } 220 forAction(String action)221 public static ActionTitle forAction(String action) { 222 for (ActionTitle title : values()) { 223 if (title != HOME && action != null && action.equals(title.action)) { 224 return title; 225 } 226 } 227 return DEFAULT; 228 } 229 } 230 createPackageMonitor()231 protected PackageMonitor createPackageMonitor() { 232 return new PackageMonitor() { 233 @Override 234 public void onSomePackagesChanged() { 235 mAdapter.handlePackagesChanged(); 236 bindProfileView(); 237 } 238 239 @Override 240 public boolean onPackageChanged(String packageName, int uid, String[] components) { 241 // We care about all package changes, not just the whole package itself which is 242 // default behavior. 243 return true; 244 } 245 }; 246 } 247 248 private Intent makeMyIntent() { 249 Intent intent = new Intent(getIntent()); 250 intent.setComponent(null); 251 // The resolver activity is set to be hidden from recent tasks. 252 // we don't want this attribute to be propagated to the next activity 253 // being launched. Note that if the original Intent also had this 254 // flag set, we are now losing it. That should be a very rare case 255 // and we can live with this. 256 intent.setFlags(intent.getFlags()&~Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 257 return intent; 258 } 259 260 @Override 261 protected void onCreate(Bundle savedInstanceState) { 262 // Use a specialized prompt when we're handling the 'Home' app startActivity() 263 final Intent intent = makeMyIntent(); 264 final Set<String> categories = intent.getCategories(); 265 if (Intent.ACTION_MAIN.equals(intent.getAction()) 266 && categories != null 267 && categories.size() == 1 268 && categories.contains(Intent.CATEGORY_HOME)) { 269 // Note: this field is not set to true in the compatibility version. 270 mResolvingHome = true; 271 } 272 273 setSafeForwardingMode(true); 274 275 onCreate(savedInstanceState, intent, null, 0, null, null, true); 276 } 277 278 /** 279 * Compatibility version for other bundled services that use this overload without 280 * a default title resource 281 */ 282 @UnsupportedAppUsage 283 protected void onCreate(Bundle savedInstanceState, Intent intent, 284 CharSequence title, Intent[] initialIntents, 285 List<ResolveInfo> rList, boolean supportsAlwaysUseOption) { 286 onCreate(savedInstanceState, intent, title, 0, initialIntents, rList, 287 supportsAlwaysUseOption); 288 } 289 290 protected void onCreate(Bundle savedInstanceState, Intent intent, 291 CharSequence title, int defaultTitleRes, Intent[] initialIntents, 292 List<ResolveInfo> rList, boolean supportsAlwaysUseOption) { 293 setTheme(R.style.Theme_DeviceDefault_Resolver); 294 super.onCreate(savedInstanceState); 295 296 // Determine whether we should show that intent is forwarded 297 // from managed profile to owner or other way around. 298 setProfileSwitchMessageId(intent.getContentUserHint()); 299 300 try { 301 mLaunchedFromUid = ActivityTaskManager.getService().getLaunchedFromUid( 302 getActivityToken()); 303 } catch (RemoteException e) { 304 mLaunchedFromUid = -1; 305 } 306 307 if (mLaunchedFromUid < 0 || UserHandle.isIsolated(mLaunchedFromUid)) { 308 // Gulp! 309 finish(); 310 return; 311 } 312 313 mPm = getPackageManager(); 314 315 mPackageMonitor.register(this, getMainLooper(), false); 316 mRegistered = true; 317 mReferrerPackage = getReferrerPackageName(); 318 319 final ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE); 320 mIconDpi = am.getLauncherLargeIconDensity(); 321 322 // Add our initial intent as the first item, regardless of what else has already been added. 323 mIntents.add(0, new Intent(intent)); 324 mTitle = title; 325 mDefaultTitleResId = defaultTitleRes; 326 327 mUseLayoutForBrowsables = getTargetIntent() == null 328 ? false 329 : isHttpSchemeAndViewAction(getTargetIntent()); 330 331 mSupportsAlwaysUseOption = supportsAlwaysUseOption; 332 333 if (configureContentView(mIntents, initialIntents, rList)) { 334 return; 335 } 336 337 final ResolverDrawerLayout rdl = findViewById(R.id.contentPanel); 338 if (rdl != null) { 339 rdl.setOnDismissedListener(new ResolverDrawerLayout.OnDismissedListener() { 340 @Override 341 public void onDismissed() { 342 finish(); 343 } 344 }); 345 if (isVoiceInteraction()) { 346 rdl.setCollapsed(false); 347 } 348 349 rdl.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 350 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); 351 rdl.setOnApplyWindowInsetsListener(this::onApplyWindowInsets); 352 353 mResolverDrawerLayout = rdl; 354 } 355 356 mProfileView = findViewById(R.id.profile_button); 357 if (mProfileView != null) { 358 mProfileView.setOnClickListener(this::onProfileClick); 359 bindProfileView(); 360 } 361 362 initSuspendedColorMatrix(); 363 364 if (isVoiceInteraction()) { 365 onSetupVoiceInteraction(); 366 } 367 final Set<String> categories = intent.getCategories(); 368 MetricsLogger.action(this, mAdapter.hasFilteredItem() 369 ? MetricsProto.MetricsEvent.ACTION_SHOW_APP_DISAMBIG_APP_FEATURED 370 : MetricsProto.MetricsEvent.ACTION_SHOW_APP_DISAMBIG_NONE_FEATURED, 371 intent.getAction() + ":" + intent.getType() + ":" 372 + (categories != null ? Arrays.toString(categories.toArray()) : "")); 373 } 374 375 protected void onProfileClick(View v) { 376 final DisplayResolveInfo dri = mAdapter.getOtherProfile(); 377 if (dri == null) { 378 return; 379 } 380 381 // Do not show the profile switch message anymore. 382 mProfileSwitchMessageId = -1; 383 384 onTargetSelected(dri, false); 385 finish(); 386 } 387 388 protected WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { 389 mSystemWindowInsets = insets.getSystemWindowInsets(); 390 391 mResolverDrawerLayout.setPadding(mSystemWindowInsets.left, mSystemWindowInsets.top, 392 mSystemWindowInsets.right, 0); 393 394 View emptyView = findViewById(R.id.empty); 395 if (emptyView != null) { 396 emptyView.setPadding(0, 0, 0, mSystemWindowInsets.bottom 397 + getResources().getDimensionPixelSize( 398 R.dimen.chooser_edge_margin_normal) * 2); 399 } 400 401 if (mFooterSpacer == null) { 402 mFooterSpacer = new Space(getApplicationContext()); 403 } else { 404 ((ListView) mAdapterView).removeFooterView(mFooterSpacer); 405 } 406 mFooterSpacer.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT, 407 mSystemWindowInsets.bottom)); 408 ((ListView) mAdapterView).addFooterView(mFooterSpacer); 409 410 resetButtonBar(); 411 412 return insets.consumeSystemWindowInsets(); 413 } 414 415 @Override 416 public void onConfigurationChanged(Configuration newConfig) { 417 super.onConfigurationChanged(newConfig); 418 mAdapter.handlePackagesChanged(); 419 420 if (mSystemWindowInsets != null) { 421 mResolverDrawerLayout.setPadding(mSystemWindowInsets.left, mSystemWindowInsets.top, 422 mSystemWindowInsets.right, 0); 423 } 424 } 425 426 private void initSuspendedColorMatrix() { 427 int grayValue = 127; 428 float scale = 0.5f; // half bright 429 430 ColorMatrix tempBrightnessMatrix = new ColorMatrix(); 431 float[] mat = tempBrightnessMatrix.getArray(); 432 mat[0] = scale; 433 mat[6] = scale; 434 mat[12] = scale; 435 mat[4] = grayValue; 436 mat[9] = grayValue; 437 mat[14] = grayValue; 438 439 ColorMatrix matrix = new ColorMatrix(); 440 matrix.setSaturation(0.0f); 441 matrix.preConcat(tempBrightnessMatrix); 442 mSuspendedMatrixColorFilter = new ColorMatrixColorFilter(matrix); 443 } 444 445 /** 446 * Perform any initialization needed for voice interaction. 447 */ 448 public void onSetupVoiceInteraction() { 449 // Do it right now. Subclasses may delay this and send it later. 450 sendVoiceChoicesIfNeeded(); 451 } 452 453 public void sendVoiceChoicesIfNeeded() { 454 if (!isVoiceInteraction()) { 455 // Clearly not needed. 456 return; 457 } 458 459 460 final Option[] options = new Option[mAdapter.getCount()]; 461 for (int i = 0, N = options.length; i < N; i++) { 462 options[i] = optionForChooserTarget(mAdapter.getItem(i), i); 463 } 464 465 mPickOptionRequest = new PickTargetOptionRequest( 466 new Prompt(getTitle()), options, null); 467 getVoiceInteractor().submitRequest(mPickOptionRequest); 468 } 469 470 Option optionForChooserTarget(TargetInfo target, int index) { 471 return new Option(target.getDisplayLabel(), index); 472 } 473 474 protected final void setAdditionalTargets(Intent[] intents) { 475 if (intents != null) { 476 for (Intent intent : intents) { 477 mIntents.add(intent); 478 } 479 } 480 } 481 482 public Intent getTargetIntent() { 483 return mIntents.isEmpty() ? null : mIntents.get(0); 484 } 485 486 protected String getReferrerPackageName() { 487 final Uri referrer = getReferrer(); 488 if (referrer != null && "android-app".equals(referrer.getScheme())) { 489 return referrer.getHost(); 490 } 491 return null; 492 } 493 494 public int getLayoutResource() { 495 return R.layout.resolver_list; 496 } 497 498 protected void bindProfileView() { 499 if (mProfileView == null) { 500 return; 501 } 502 503 final DisplayResolveInfo dri = mAdapter.getOtherProfile(); 504 if (dri != null) { 505 mProfileView.setVisibility(View.VISIBLE); 506 View text = mProfileView.findViewById(R.id.profile_button); 507 if (!(text instanceof TextView)) { 508 text = mProfileView.findViewById(R.id.text1); 509 } 510 ((TextView) text).setText(dri.getDisplayLabel()); 511 } else { 512 mProfileView.setVisibility(View.GONE); 513 } 514 } 515 516 private void setProfileSwitchMessageId(int contentUserHint) { 517 if (contentUserHint != UserHandle.USER_CURRENT && 518 contentUserHint != UserHandle.myUserId()) { 519 UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE); 520 UserInfo originUserInfo = userManager.getUserInfo(contentUserHint); 521 boolean originIsManaged = originUserInfo != null ? originUserInfo.isManagedProfile() 522 : false; 523 boolean targetIsManaged = userManager.isManagedProfile(); 524 if (originIsManaged && !targetIsManaged) { 525 mProfileSwitchMessageId = com.android.internal.R.string.forward_intent_to_owner; 526 } else if (!originIsManaged && targetIsManaged) { 527 mProfileSwitchMessageId = com.android.internal.R.string.forward_intent_to_work; 528 } 529 } 530 } 531 532 /** 533 * Turn on launch mode that is safe to use when forwarding intents received from 534 * applications and running in system processes. This mode uses Activity.startActivityAsCaller 535 * instead of the normal Activity.startActivity for launching the activity selected 536 * by the user. 537 * 538 * <p>This mode is set to true by default if the activity is initialized through 539 * {@link #onCreate(android.os.Bundle)}. If a subclass calls one of the other onCreate 540 * methods, it is set to false by default. You must set it before calling one of the 541 * more detailed onCreate methods, so that it will be set correctly in the case where 542 * there is only one intent to resolve and it is thus started immediately.</p> 543 */ 544 public void setSafeForwardingMode(boolean safeForwarding) { 545 mSafeForwardingMode = safeForwarding; 546 } 547 548 protected CharSequence getTitleForAction(Intent intent, int defaultTitleRes) { 549 final ActionTitle title = mResolvingHome 550 ? ActionTitle.HOME 551 : ActionTitle.forAction(intent.getAction()); 552 553 // While there may already be a filtered item, we can only use it in the title if the list 554 // is already sorted and all information relevant to it is already in the list. 555 final boolean named = mAdapter.getFilteredPosition() >= 0; 556 if (title == ActionTitle.DEFAULT && defaultTitleRes != 0) { 557 return getString(defaultTitleRes); 558 } else if (isHttpSchemeAndViewAction(intent)) { 559 // If the Intent's scheme is http(s) then we need to warn the user that 560 // they're giving access for the activity to open URLs from this specific host 561 String dialogTitle = null; 562 if (named && !mUseLayoutForBrowsables) { 563 dialogTitle = getString(ActionTitle.BROWSABLE_APP_TITLE_RES, 564 mAdapter.getFilteredItem().getDisplayLabel()); 565 } else if (named && mUseLayoutForBrowsables) { 566 dialogTitle = getString(ActionTitle.BROWSABLE_HOST_APP_TITLE_RES, 567 intent.getData().getHost(), 568 mAdapter.getFilteredItem().getDisplayLabel()); 569 } else if (mAdapter.areAllTargetsBrowsers()) { 570 dialogTitle = getString(ActionTitle.BROWSABLE_TITLE_RES); 571 } else { 572 dialogTitle = getString(ActionTitle.BROWSABLE_HOST_TITLE_RES, 573 intent.getData().getHost()); 574 } 575 return dialogTitle; 576 } else { 577 return named 578 ? getString(title.namedTitleRes, mAdapter.getFilteredItem().getDisplayLabel()) 579 : getString(title.titleRes); 580 } 581 } 582 583 void dismiss() { 584 if (!isFinishing()) { 585 finish(); 586 } 587 } 588 589 590 /** 591 * Loads the icon and label for the provided ApplicationInfo. Defaults to using the application 592 * icon and label over any IntentFilter or Activity icon to increase user understanding, with an 593 * exception for applications that hold the right permission. Always attempts to use available 594 * resources over PackageManager loading mechanisms so badging can be done by iconloader. Uses 595 * Strings to strip creative formatting. 596 */ 597 private abstract static class TargetPresentationGetter { 598 @Nullable abstract Drawable getIconSubstituteInternal(); 599 @Nullable abstract String getAppSubLabelInternal(); 600 601 private Context mCtx; 602 private final int mIconDpi; 603 private final boolean mHasSubstitutePermission; 604 private final ApplicationInfo mAi; 605 606 protected PackageManager mPm; 607 608 TargetPresentationGetter(Context ctx, int iconDpi, ApplicationInfo ai) { 609 mCtx = ctx; 610 mPm = ctx.getPackageManager(); 611 mAi = ai; 612 mIconDpi = iconDpi; 613 mHasSubstitutePermission = PackageManager.PERMISSION_GRANTED == mPm.checkPermission( 614 android.Manifest.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON, 615 mAi.packageName); 616 } 617 618 public Drawable getIcon(UserHandle userHandle) { 619 return new BitmapDrawable(mCtx.getResources(), getIconBitmap(userHandle)); 620 } 621 622 public Bitmap getIconBitmap(UserHandle userHandle) { 623 Drawable dr = null; 624 if (mHasSubstitutePermission) { 625 dr = getIconSubstituteInternal(); 626 } 627 628 if (dr == null) { 629 try { 630 if (mAi.icon != 0) { 631 dr = loadIconFromResource(mPm.getResourcesForApplication(mAi), mAi.icon); 632 } 633 } catch (NameNotFoundException ignore) { 634 } 635 } 636 637 // Fall back to ApplicationInfo#loadIcon if nothing has been loaded 638 if (dr == null) { 639 dr = mAi.loadIcon(mPm); 640 } 641 642 SimpleIconFactory sif = SimpleIconFactory.obtain(mCtx); 643 Bitmap icon = sif.createUserBadgedIconBitmap(dr, userHandle); 644 sif.recycle(); 645 646 return icon; 647 } 648 649 public String getLabel() { 650 String label = null; 651 // Apps with the substitute permission will always show the sublabel as their label 652 if (mHasSubstitutePermission) { 653 label = getAppSubLabelInternal(); 654 } 655 656 if (label == null) { 657 label = (String) mAi.loadLabel(mPm); 658 } 659 660 return label; 661 } 662 663 public String getSubLabel() { 664 // Apps with the substitute permission will never have a sublabel 665 if (mHasSubstitutePermission) return null; 666 return getAppSubLabelInternal(); 667 } 668 669 protected String loadLabelFromResource(Resources res, int resId) { 670 return res.getString(resId); 671 } 672 673 @Nullable 674 protected Drawable loadIconFromResource(Resources res, int resId) { 675 return res.getDrawableForDensity(resId, mIconDpi); 676 } 677 678 } 679 680 /** 681 * Loads the icon and label for the provided ResolveInfo. 682 */ 683 @VisibleForTesting 684 public static class ResolveInfoPresentationGetter extends ActivityInfoPresentationGetter { 685 private final ResolveInfo mRi; 686 public ResolveInfoPresentationGetter(Context ctx, int iconDpi, ResolveInfo ri) { 687 super(ctx, iconDpi, ri.activityInfo); 688 mRi = ri; 689 } 690 691 @Override 692 Drawable getIconSubstituteInternal() { 693 Drawable dr = null; 694 try { 695 // Do not use ResolveInfo#getIconResource() as it defaults to the app 696 if (mRi.resolvePackageName != null && mRi.icon != 0) { 697 dr = loadIconFromResource( 698 mPm.getResourcesForApplication(mRi.resolvePackageName), mRi.icon); 699 } 700 } catch (NameNotFoundException e) { 701 Log.e(TAG, "SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON permission granted but " 702 + "couldn't find resources for package", e); 703 } 704 705 // Fall back to ActivityInfo if no icon is found via ResolveInfo 706 if (dr == null) dr = super.getIconSubstituteInternal(); 707 708 return dr; 709 } 710 711 @Override 712 String getAppSubLabelInternal() { 713 // Will default to app name if no intent filter or activity label set, make sure to 714 // check if subLabel matches label before final display 715 return (String) mRi.loadLabel(mPm); 716 } 717 } 718 719 ResolveInfoPresentationGetter makePresentationGetter(ResolveInfo ri) { 720 return new ResolveInfoPresentationGetter(this, mIconDpi, ri); 721 } 722 723 /** 724 * Loads the icon and label for the provided ActivityInfo. 725 */ 726 @VisibleForTesting 727 public static class ActivityInfoPresentationGetter extends TargetPresentationGetter { 728 private final ActivityInfo mActivityInfo; 729 public ActivityInfoPresentationGetter(Context ctx, int iconDpi, 730 ActivityInfo activityInfo) { 731 super(ctx, iconDpi, activityInfo.applicationInfo); 732 mActivityInfo = activityInfo; 733 } 734 735 @Override 736 Drawable getIconSubstituteInternal() { 737 Drawable dr = null; 738 try { 739 // Do not use ActivityInfo#getIconResource() as it defaults to the app 740 if (mActivityInfo.icon != 0) { 741 dr = loadIconFromResource( 742 mPm.getResourcesForApplication(mActivityInfo.applicationInfo), 743 mActivityInfo.icon); 744 } 745 } catch (NameNotFoundException e) { 746 Log.e(TAG, "SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON permission granted but " 747 + "couldn't find resources for package", e); 748 } 749 750 return dr; 751 } 752 753 @Override 754 String getAppSubLabelInternal() { 755 // Will default to app name if no activity label set, make sure to check if subLabel 756 // matches label before final display 757 return (String) mActivityInfo.loadLabel(mPm); 758 } 759 } 760 761 protected ActivityInfoPresentationGetter makePresentationGetter(ActivityInfo ai) { 762 return new ActivityInfoPresentationGetter(this, mIconDpi, ai); 763 } 764 765 Drawable loadIconForResolveInfo(ResolveInfo ri) { 766 // Load icons based on the current process. If in work profile icons should be badged. 767 return makePresentationGetter(ri).getIcon(Process.myUserHandle()); 768 } 769 770 @Override 771 protected void onRestart() { 772 super.onRestart(); 773 if (!mRegistered) { 774 mPackageMonitor.register(this, getMainLooper(), false); 775 mRegistered = true; 776 } 777 mAdapter.handlePackagesChanged(); 778 bindProfileView(); 779 } 780 781 @Override 782 protected void onStop() { 783 super.onStop(); 784 if (mRegistered) { 785 mPackageMonitor.unregister(); 786 mRegistered = false; 787 } 788 final Intent intent = getIntent(); 789 if ((intent.getFlags() & FLAG_ACTIVITY_NEW_TASK) != 0 && !isVoiceInteraction() 790 && !mResolvingHome && !mRetainInOnStop) { 791 // This resolver is in the unusual situation where it has been 792 // launched at the top of a new task. We don't let it be added 793 // to the recent tasks shown to the user, and we need to make sure 794 // that each time we are launched we get the correct launching 795 // uid (not re-using the same resolver from an old launching uid), 796 // so we will now finish ourself since being no longer visible, 797 // the user probably can't get back to us. 798 if (!isChangingConfigurations()) { 799 finish(); 800 } 801 } 802 } 803 804 @Override 805 protected void onDestroy() { 806 super.onDestroy(); 807 if (!isChangingConfigurations() && mPickOptionRequest != null) { 808 mPickOptionRequest.cancel(); 809 } 810 if (mPostListReadyRunnable != null) { 811 getMainThreadHandler().removeCallbacks(mPostListReadyRunnable); 812 mPostListReadyRunnable = null; 813 } 814 if (mAdapter != null && mAdapter.mResolverListController != null) { 815 mAdapter.mResolverListController.destroy(); 816 } 817 } 818 819 @Override 820 protected void onRestoreInstanceState(Bundle savedInstanceState) { 821 super.onRestoreInstanceState(savedInstanceState); 822 resetButtonBar(); 823 } 824 825 private boolean isHttpSchemeAndViewAction(Intent intent) { 826 return (IntentFilter.SCHEME_HTTP.equals(intent.getScheme()) 827 || IntentFilter.SCHEME_HTTPS.equals(intent.getScheme())) 828 && Intent.ACTION_VIEW.equals(intent.getAction()); 829 } 830 831 private boolean hasManagedProfile() { 832 UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE); 833 if (userManager == null) { 834 return false; 835 } 836 837 try { 838 List<UserInfo> profiles = userManager.getProfiles(getUserId()); 839 for (UserInfo userInfo : profiles) { 840 if (userInfo != null && userInfo.isManagedProfile()) { 841 return true; 842 } 843 } 844 } catch (SecurityException e) { 845 return false; 846 } 847 return false; 848 } 849 850 private boolean supportsManagedProfiles(ResolveInfo resolveInfo) { 851 try { 852 ApplicationInfo appInfo = getPackageManager().getApplicationInfo( 853 resolveInfo.activityInfo.packageName, 0 /* default flags */); 854 return appInfo.targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP; 855 } catch (NameNotFoundException e) { 856 return false; 857 } 858 } 859 860 private void setAlwaysButtonEnabled(boolean hasValidSelection, int checkedPos, 861 boolean filtered) { 862 boolean enabled = false; 863 if (hasValidSelection) { 864 ResolveInfo ri = mAdapter.resolveInfoForPosition(checkedPos, filtered); 865 if (ri == null) { 866 Log.e(TAG, "Invalid position supplied to setAlwaysButtonEnabled"); 867 return; 868 } else if (ri.targetUserId != UserHandle.USER_CURRENT) { 869 Log.e(TAG, "Attempted to set selection to resolve info for another user"); 870 return; 871 } else { 872 enabled = true; 873 } 874 if (mUseLayoutForBrowsables && !ri.handleAllWebDataURI) { 875 mAlwaysButton.setText(getResources() 876 .getString(R.string.activity_resolver_set_always)); 877 } else { 878 mAlwaysButton.setText(getResources() 879 .getString(R.string.activity_resolver_use_always)); 880 } 881 } 882 mAlwaysButton.setEnabled(enabled); 883 } 884 885 public void onButtonClick(View v) { 886 final int id = v.getId(); 887 int which = mAdapter.hasFilteredItem() 888 ? mAdapter.getFilteredPosition() 889 : mAdapterView.getCheckedItemPosition(); 890 boolean hasIndexBeenFiltered = !mAdapter.hasFilteredItem(); 891 ResolveInfo ri = mAdapter.resolveInfoForPosition(which, hasIndexBeenFiltered); 892 if (mUseLayoutForBrowsables 893 && !ri.handleAllWebDataURI && id == R.id.button_always) { 894 showSettingsForSelected(ri); 895 } else { 896 startSelected(which, id == R.id.button_always, hasIndexBeenFiltered); 897 } 898 } 899 900 private void showSettingsForSelected(ResolveInfo ri) { 901 Intent intent = new Intent(); 902 903 final String packageName = ri.activityInfo.packageName; 904 Bundle showFragmentArgs = new Bundle(); 905 showFragmentArgs.putString(EXTRA_FRAGMENT_ARG_KEY, OPEN_LINKS_COMPONENT_KEY); 906 showFragmentArgs.putString("package", packageName); 907 908 // For regular apps, we open the Open by Default page 909 intent.setAction(Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS) 910 .setData(Uri.fromParts("package", packageName, null)) 911 .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT) 912 .putExtra(EXTRA_FRAGMENT_ARG_KEY, OPEN_LINKS_COMPONENT_KEY) 913 .putExtra(EXTRA_SHOW_FRAGMENT_ARGS, showFragmentArgs); 914 915 startActivity(intent); 916 } 917 918 public void startSelected(int which, boolean always, boolean hasIndexBeenFiltered) { 919 if (isFinishing()) { 920 return; 921 } 922 ResolveInfo ri = mAdapter.resolveInfoForPosition(which, hasIndexBeenFiltered); 923 if (mResolvingHome && hasManagedProfile() && !supportsManagedProfiles(ri)) { 924 Toast.makeText(this, String.format(getResources().getString( 925 com.android.internal.R.string.activity_resolver_work_profiles_support), 926 ri.activityInfo.loadLabel(getPackageManager()).toString()), 927 Toast.LENGTH_LONG).show(); 928 return; 929 } 930 931 TargetInfo target = mAdapter.targetInfoForPosition(which, hasIndexBeenFiltered); 932 if (target == null) { 933 return; 934 } 935 if (onTargetSelected(target, always)) { 936 if (always && mSupportsAlwaysUseOption) { 937 MetricsLogger.action( 938 this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_ALWAYS); 939 } else if (mSupportsAlwaysUseOption) { 940 MetricsLogger.action( 941 this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_JUST_ONCE); 942 } else { 943 MetricsLogger.action( 944 this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_TAP); 945 } 946 MetricsLogger.action(this, mAdapter.hasFilteredItem() 947 ? MetricsProto.MetricsEvent.ACTION_HIDE_APP_DISAMBIG_APP_FEATURED 948 : MetricsProto.MetricsEvent.ACTION_HIDE_APP_DISAMBIG_NONE_FEATURED); 949 finish(); 950 } 951 } 952 953 /** 954 * Replace me in subclasses! 955 */ 956 public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) { 957 return defIntent; 958 } 959 960 protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) { 961 final ResolveInfo ri = target.getResolveInfo(); 962 final Intent intent = target != null ? target.getResolvedIntent() : null; 963 964 if (intent != null && (mSupportsAlwaysUseOption || mAdapter.hasFilteredItem()) 965 && mAdapter.mUnfilteredResolveList != null) { 966 // Build a reasonable intent filter, based on what matched. 967 IntentFilter filter = new IntentFilter(); 968 Intent filterIntent; 969 970 if (intent.getSelector() != null) { 971 filterIntent = intent.getSelector(); 972 } else { 973 filterIntent = intent; 974 } 975 976 String action = filterIntent.getAction(); 977 if (action != null) { 978 filter.addAction(action); 979 } 980 Set<String> categories = filterIntent.getCategories(); 981 if (categories != null) { 982 for (String cat : categories) { 983 filter.addCategory(cat); 984 } 985 } 986 filter.addCategory(Intent.CATEGORY_DEFAULT); 987 988 int cat = ri.match & IntentFilter.MATCH_CATEGORY_MASK; 989 Uri data = filterIntent.getData(); 990 if (cat == IntentFilter.MATCH_CATEGORY_TYPE) { 991 String mimeType = filterIntent.resolveType(this); 992 if (mimeType != null) { 993 try { 994 filter.addDataType(mimeType); 995 } catch (IntentFilter.MalformedMimeTypeException e) { 996 Log.w("ResolverActivity", e); 997 filter = null; 998 } 999 } 1000 } 1001 if (data != null && data.getScheme() != null) { 1002 // We need the data specification if there was no type, 1003 // OR if the scheme is not one of our magical "file:" 1004 // or "content:" schemes (see IntentFilter for the reason). 1005 if (cat != IntentFilter.MATCH_CATEGORY_TYPE 1006 || (!"file".equals(data.getScheme()) 1007 && !"content".equals(data.getScheme()))) { 1008 filter.addDataScheme(data.getScheme()); 1009 1010 // Look through the resolved filter to determine which part 1011 // of it matched the original Intent. 1012 Iterator<PatternMatcher> pIt = ri.filter.schemeSpecificPartsIterator(); 1013 if (pIt != null) { 1014 String ssp = data.getSchemeSpecificPart(); 1015 while (ssp != null && pIt.hasNext()) { 1016 PatternMatcher p = pIt.next(); 1017 if (p.match(ssp)) { 1018 filter.addDataSchemeSpecificPart(p.getPath(), p.getType()); 1019 break; 1020 } 1021 } 1022 } 1023 Iterator<IntentFilter.AuthorityEntry> aIt = ri.filter.authoritiesIterator(); 1024 if (aIt != null) { 1025 while (aIt.hasNext()) { 1026 IntentFilter.AuthorityEntry a = aIt.next(); 1027 if (a.match(data) >= 0) { 1028 int port = a.getPort(); 1029 filter.addDataAuthority(a.getHost(), 1030 port >= 0 ? Integer.toString(port) : null); 1031 break; 1032 } 1033 } 1034 } 1035 pIt = ri.filter.pathsIterator(); 1036 if (pIt != null) { 1037 String path = data.getPath(); 1038 while (path != null && pIt.hasNext()) { 1039 PatternMatcher p = pIt.next(); 1040 if (p.match(path)) { 1041 filter.addDataPath(p.getPath(), p.getType()); 1042 break; 1043 } 1044 } 1045 } 1046 } 1047 } 1048 1049 if (filter != null) { 1050 final int N = mAdapter.mUnfilteredResolveList.size(); 1051 ComponentName[] set; 1052 // If we don't add back in the component for forwarding the intent to a managed 1053 // profile, the preferred activity may not be updated correctly (as the set of 1054 // components we tell it we knew about will have changed). 1055 final boolean needToAddBackProfileForwardingComponent 1056 = mAdapter.mOtherProfile != null; 1057 if (!needToAddBackProfileForwardingComponent) { 1058 set = new ComponentName[N]; 1059 } else { 1060 set = new ComponentName[N + 1]; 1061 } 1062 1063 int bestMatch = 0; 1064 for (int i=0; i<N; i++) { 1065 ResolveInfo r = mAdapter.mUnfilteredResolveList.get(i).getResolveInfoAt(0); 1066 set[i] = new ComponentName(r.activityInfo.packageName, 1067 r.activityInfo.name); 1068 if (r.match > bestMatch) bestMatch = r.match; 1069 } 1070 1071 if (needToAddBackProfileForwardingComponent) { 1072 set[N] = mAdapter.mOtherProfile.getResolvedComponentName(); 1073 final int otherProfileMatch = mAdapter.mOtherProfile.getResolveInfo().match; 1074 if (otherProfileMatch > bestMatch) bestMatch = otherProfileMatch; 1075 } 1076 1077 if (alwaysCheck) { 1078 final int userId = getUserId(); 1079 final PackageManager pm = getPackageManager(); 1080 1081 // Set the preferred Activity 1082 pm.addPreferredActivity(filter, bestMatch, set, intent.getComponent()); 1083 1084 if (ri.handleAllWebDataURI) { 1085 // Set default Browser if needed 1086 final String packageName = pm.getDefaultBrowserPackageNameAsUser(userId); 1087 if (TextUtils.isEmpty(packageName)) { 1088 pm.setDefaultBrowserPackageNameAsUser(ri.activityInfo.packageName, userId); 1089 } 1090 } else { 1091 // Update Domain Verification status 1092 ComponentName cn = intent.getComponent(); 1093 String packageName = cn.getPackageName(); 1094 String dataScheme = (data != null) ? data.getScheme() : null; 1095 1096 boolean isHttpOrHttps = (dataScheme != null) && 1097 (dataScheme.equals(IntentFilter.SCHEME_HTTP) || 1098 dataScheme.equals(IntentFilter.SCHEME_HTTPS)); 1099 1100 boolean isViewAction = (action != null) && action.equals(Intent.ACTION_VIEW); 1101 boolean hasCategoryBrowsable = (categories != null) && 1102 categories.contains(Intent.CATEGORY_BROWSABLE); 1103 1104 if (isHttpOrHttps && isViewAction && hasCategoryBrowsable) { 1105 pm.updateIntentVerificationStatusAsUser(packageName, 1106 PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS, 1107 userId); 1108 } 1109 } 1110 } else { 1111 try { 1112 mAdapter.mResolverListController.setLastChosen(intent, filter, bestMatch); 1113 } catch (RemoteException re) { 1114 Log.d(TAG, "Error calling setLastChosenActivity\n" + re); 1115 } 1116 } 1117 } 1118 } 1119 1120 if (target != null) { 1121 safelyStartActivity(target); 1122 1123 // Rely on the ActivityManager to pop up a dialog regarding app suspension 1124 // and return false 1125 if (target.isSuspended()) { 1126 return false; 1127 } 1128 } 1129 1130 return true; 1131 } 1132 1133 public void safelyStartActivity(TargetInfo cti) { 1134 // We're dispatching intents that might be coming from legacy apps, so 1135 // don't kill ourselves. 1136 StrictMode.disableDeathOnFileUriExposure(); 1137 try { 1138 safelyStartActivityInternal(cti); 1139 } finally { 1140 StrictMode.enableDeathOnFileUriExposure(); 1141 } 1142 } 1143 1144 private void safelyStartActivityInternal(TargetInfo cti) { 1145 // If needed, show that intent is forwarded 1146 // from managed profile to owner or other way around. 1147 if (mProfileSwitchMessageId != -1) { 1148 Toast.makeText(this, getString(mProfileSwitchMessageId), Toast.LENGTH_LONG).show(); 1149 } 1150 if (!mSafeForwardingMode) { 1151 if (cti.start(this, null)) { 1152 onActivityStarted(cti); 1153 } 1154 return; 1155 } 1156 try { 1157 if (cti.startAsCaller(this, null, UserHandle.USER_NULL)) { 1158 onActivityStarted(cti); 1159 } 1160 } catch (RuntimeException e) { 1161 String launchedFromPackage; 1162 try { 1163 launchedFromPackage = ActivityTaskManager.getService().getLaunchedFromPackage( 1164 getActivityToken()); 1165 } catch (RemoteException e2) { 1166 launchedFromPackage = "??"; 1167 } 1168 Slog.wtf(TAG, "Unable to launch as uid " + mLaunchedFromUid 1169 + " package " + launchedFromPackage + ", while running in " 1170 + ActivityThread.currentProcessName(), e); 1171 } 1172 } 1173 1174 1175 boolean startAsCallerImpl(Intent intent, Bundle options, boolean ignoreTargetSecurity, 1176 int userId) { 1177 // Pass intent to delegate chooser activity with permission token. 1178 // TODO: This should move to a trampoline Activity in the system when the ChooserActivity 1179 // moves into systemui 1180 try { 1181 // TODO: Once this is a small springboard activity, it can move off the UI process 1182 // and we can move the request method to ActivityManagerInternal. 1183 IBinder permissionToken = ActivityTaskManager.getService() 1184 .requestStartActivityPermissionToken(getActivityToken()); 1185 final Intent chooserIntent = new Intent(); 1186 final ComponentName delegateActivity = ComponentName.unflattenFromString( 1187 Resources.getSystem().getString(R.string.config_chooserActivity)); 1188 chooserIntent.setClassName(delegateActivity.getPackageName(), 1189 delegateActivity.getClassName()); 1190 chooserIntent.putExtra(ActivityTaskManager.EXTRA_PERMISSION_TOKEN, permissionToken); 1191 1192 // TODO: These extras will change as chooser activity moves into systemui 1193 chooserIntent.putExtra(Intent.EXTRA_INTENT, intent); 1194 chooserIntent.putExtra(ActivityTaskManager.EXTRA_OPTIONS, options); 1195 chooserIntent.putExtra(ActivityTaskManager.EXTRA_IGNORE_TARGET_SECURITY, 1196 ignoreTargetSecurity); 1197 chooserIntent.putExtra(Intent.EXTRA_USER_ID, userId); 1198 chooserIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT 1199 | Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP); 1200 startActivity(chooserIntent); 1201 } catch (RemoteException e) { 1202 Log.e(TAG, e.toString()); 1203 } 1204 return true; 1205 } 1206 1207 public void onActivityStarted(TargetInfo cti) { 1208 // Do nothing 1209 } 1210 1211 public boolean shouldGetActivityMetadata() { 1212 return false; 1213 } 1214 1215 public boolean shouldAutoLaunchSingleChoice(TargetInfo target) { 1216 return !target.isSuspended(); 1217 } 1218 1219 public void showTargetDetails(ResolveInfo ri) { 1220 Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) 1221 .setData(Uri.fromParts("package", ri.activityInfo.packageName, null)) 1222 .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); 1223 startActivity(in); 1224 } 1225 1226 public ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents, 1227 Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid, 1228 boolean filterLastUsed) { 1229 return new ResolveListAdapter(context, payloadIntents, initialIntents, rList, 1230 launchedFromUid, filterLastUsed, createListController()); 1231 } 1232 1233 @VisibleForTesting 1234 protected ResolverListController createListController() { 1235 return new ResolverListController( 1236 this, 1237 mPm, 1238 getTargetIntent(), 1239 getReferrerPackageName(), 1240 mLaunchedFromUid); 1241 } 1242 1243 /** 1244 * Returns true if the activity is finishing and creation should halt 1245 */ 1246 public boolean configureContentView(List<Intent> payloadIntents, Intent[] initialIntents, 1247 List<ResolveInfo> rList) { 1248 // The last argument of createAdapter is whether to do special handling 1249 // of the last used choice to highlight it in the list. We need to always 1250 // turn this off when running under voice interaction, since it results in 1251 // a more complicated UI that the current voice interaction flow is not able 1252 // to handle. 1253 mAdapter = createAdapter(this, payloadIntents, initialIntents, rList, 1254 mLaunchedFromUid, mSupportsAlwaysUseOption && !isVoiceInteraction()); 1255 boolean rebuildCompleted = mAdapter.rebuildList(); 1256 1257 if (useLayoutWithDefault()) { 1258 mLayoutId = R.layout.resolver_list_with_default; 1259 } else { 1260 mLayoutId = getLayoutResource(); 1261 } 1262 setContentView(mLayoutId); 1263 1264 int count = mAdapter.getUnfilteredCount(); 1265 1266 // We only rebuild asynchronously when we have multiple elements to sort. In the case where 1267 // we're already done, we can check if we should auto-launch immediately. 1268 if (rebuildCompleted) { 1269 if (count == 1 && mAdapter.getOtherProfile() == null) { 1270 // Only one target, so we're a candidate to auto-launch! 1271 final TargetInfo target = mAdapter.targetInfoForPosition(0, false); 1272 if (shouldAutoLaunchSingleChoice(target)) { 1273 safelyStartActivity(target); 1274 mPackageMonitor.unregister(); 1275 mRegistered = false; 1276 finish(); 1277 return true; 1278 } 1279 } 1280 } 1281 1282 1283 mAdapterView = findViewById(R.id.resolver_list); 1284 1285 if (count == 0 && mAdapter.mPlaceholderCount == 0) { 1286 final TextView emptyView = findViewById(R.id.empty); 1287 emptyView.setVisibility(View.VISIBLE); 1288 mAdapterView.setVisibility(View.GONE); 1289 } else { 1290 mAdapterView.setVisibility(View.VISIBLE); 1291 onPrepareAdapterView(mAdapterView, mAdapter); 1292 } 1293 return false; 1294 } 1295 1296 public void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter) { 1297 final boolean useHeader = adapter.hasFilteredItem(); 1298 final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null; 1299 1300 adapterView.setAdapter(mAdapter); 1301 1302 final ItemClickListener listener = new ItemClickListener(); 1303 adapterView.setOnItemClickListener(listener); 1304 adapterView.setOnItemLongClickListener(listener); 1305 1306 if (mSupportsAlwaysUseOption || mUseLayoutForBrowsables) { 1307 listView.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE); 1308 } 1309 1310 // In case this method is called again (due to activity recreation), avoid adding a new 1311 // header if one is already present. 1312 if (useHeader && listView != null && listView.getHeaderViewsCount() == 0) { 1313 listView.addHeaderView(LayoutInflater.from(this).inflate( 1314 R.layout.resolver_different_item_header, listView, false)); 1315 } 1316 } 1317 1318 /** 1319 * Configure the area above the app selection list (title, content preview, etc). 1320 */ 1321 public void setHeader() { 1322 if (mAdapter.getCount() == 0 && mAdapter.mPlaceholderCount == 0) { 1323 final TextView titleView = findViewById(R.id.title); 1324 if (titleView != null) { 1325 titleView.setVisibility(View.GONE); 1326 } 1327 } 1328 1329 CharSequence title = mTitle != null 1330 ? mTitle 1331 : getTitleForAction(getTargetIntent(), mDefaultTitleResId); 1332 1333 if (!TextUtils.isEmpty(title)) { 1334 final TextView titleView = findViewById(R.id.title); 1335 if (titleView != null) { 1336 titleView.setText(title); 1337 } 1338 setTitle(title); 1339 } 1340 1341 final ImageView iconView = findViewById(R.id.icon); 1342 final DisplayResolveInfo iconInfo = mAdapter.getFilteredItem(); 1343 if (iconView != null && iconInfo != null) { 1344 new LoadIconTask(iconInfo, iconView).execute(); 1345 } 1346 } 1347 1348 private void resetButtonBar() { 1349 if (!mSupportsAlwaysUseOption && !mUseLayoutForBrowsables) { 1350 return; 1351 } 1352 final ViewGroup buttonLayout = findViewById(R.id.button_bar); 1353 if (buttonLayout != null) { 1354 buttonLayout.setVisibility(View.VISIBLE); 1355 int inset = mSystemWindowInsets != null ? mSystemWindowInsets.bottom : 0; 1356 buttonLayout.setPadding(buttonLayout.getPaddingLeft(), buttonLayout.getPaddingTop(), 1357 buttonLayout.getPaddingRight(), getResources().getDimensionPixelSize( 1358 R.dimen.resolver_button_bar_spacing) + inset); 1359 1360 mOnceButton = (Button) buttonLayout.findViewById(R.id.button_once); 1361 mAlwaysButton = (Button) buttonLayout.findViewById(R.id.button_always); 1362 1363 resetAlwaysOrOnceButtonBar(); 1364 } else { 1365 Log.e(TAG, "Layout unexpectedly does not have a button bar"); 1366 } 1367 } 1368 1369 private void resetAlwaysOrOnceButtonBar() { 1370 if (useLayoutWithDefault() 1371 && mAdapter.getFilteredPosition() != ListView.INVALID_POSITION) { 1372 setAlwaysButtonEnabled(true, mAdapter.getFilteredPosition(), false); 1373 mOnceButton.setEnabled(true); 1374 return; 1375 } 1376 1377 // When the items load in, if an item was already selected, enable the buttons 1378 if (mAdapterView != null 1379 && mAdapterView.getCheckedItemPosition() != ListView.INVALID_POSITION) { 1380 setAlwaysButtonEnabled(true, mAdapterView.getCheckedItemPosition(), true); 1381 mOnceButton.setEnabled(true); 1382 } 1383 } 1384 1385 private boolean useLayoutWithDefault() { 1386 return mSupportsAlwaysUseOption && mAdapter.hasFilteredItem(); 1387 } 1388 1389 /** 1390 * If {@code retainInOnStop} is set to true, we will not finish ourselves when onStop gets 1391 * called and we are launched in a new task. 1392 */ 1393 protected void setRetainInOnStop(boolean retainInOnStop) { 1394 mRetainInOnStop = retainInOnStop; 1395 } 1396 1397 /** 1398 * Check a simple match for the component of two ResolveInfos. 1399 */ 1400 static boolean resolveInfoMatch(ResolveInfo lhs, ResolveInfo rhs) { 1401 return lhs == null ? rhs == null 1402 : lhs.activityInfo == null ? rhs.activityInfo == null 1403 : Objects.equals(lhs.activityInfo.name, rhs.activityInfo.name) 1404 && Objects.equals(lhs.activityInfo.packageName, rhs.activityInfo.packageName); 1405 } 1406 1407 public final class DisplayResolveInfo implements TargetInfo { 1408 private final ResolveInfo mResolveInfo; 1409 private final CharSequence mDisplayLabel; 1410 private Drawable mDisplayIcon; 1411 private Drawable mBadge; 1412 private final CharSequence mExtendedInfo; 1413 private final Intent mResolvedIntent; 1414 private final List<Intent> mSourceIntents = new ArrayList<>(); 1415 private boolean mIsSuspended; 1416 1417 public DisplayResolveInfo(Intent originalIntent, ResolveInfo pri, CharSequence pLabel, 1418 CharSequence pInfo, Intent pOrigIntent) { 1419 mSourceIntents.add(originalIntent); 1420 mResolveInfo = pri; 1421 mDisplayLabel = pLabel; 1422 mExtendedInfo = pInfo; 1423 1424 final Intent intent = new Intent(pOrigIntent != null ? pOrigIntent : 1425 getReplacementIntent(pri.activityInfo, getTargetIntent())); 1426 intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT 1427 | Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP); 1428 final ActivityInfo ai = mResolveInfo.activityInfo; 1429 intent.setComponent(new ComponentName(ai.applicationInfo.packageName, ai.name)); 1430 1431 mIsSuspended = (ai.applicationInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0; 1432 1433 mResolvedIntent = intent; 1434 } 1435 1436 private DisplayResolveInfo(DisplayResolveInfo other, Intent fillInIntent, int flags) { 1437 mSourceIntents.addAll(other.getAllSourceIntents()); 1438 mResolveInfo = other.mResolveInfo; 1439 mDisplayLabel = other.mDisplayLabel; 1440 mDisplayIcon = other.mDisplayIcon; 1441 mExtendedInfo = other.mExtendedInfo; 1442 mResolvedIntent = new Intent(other.mResolvedIntent); 1443 mResolvedIntent.fillIn(fillInIntent, flags); 1444 } 1445 1446 public ResolveInfo getResolveInfo() { 1447 return mResolveInfo; 1448 } 1449 1450 public CharSequence getDisplayLabel() { 1451 return mDisplayLabel; 1452 } 1453 1454 public Drawable getDisplayIcon() { 1455 return mDisplayIcon; 1456 } 1457 1458 @Override 1459 public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) { 1460 return new DisplayResolveInfo(this, fillInIntent, flags); 1461 } 1462 1463 @Override 1464 public List<Intent> getAllSourceIntents() { 1465 return mSourceIntents; 1466 } 1467 1468 public void addAlternateSourceIntent(Intent alt) { 1469 mSourceIntents.add(alt); 1470 } 1471 1472 public void setDisplayIcon(Drawable icon) { 1473 mDisplayIcon = icon; 1474 } 1475 1476 public boolean hasDisplayIcon() { 1477 return mDisplayIcon != null; 1478 } 1479 1480 public CharSequence getExtendedInfo() { 1481 return mExtendedInfo; 1482 } 1483 1484 public Intent getResolvedIntent() { 1485 return mResolvedIntent; 1486 } 1487 1488 @Override 1489 public ComponentName getResolvedComponentName() { 1490 return new ComponentName(mResolveInfo.activityInfo.packageName, 1491 mResolveInfo.activityInfo.name); 1492 } 1493 1494 @Override 1495 public boolean start(Activity activity, Bundle options) { 1496 activity.startActivity(mResolvedIntent, options); 1497 return true; 1498 } 1499 1500 @Override 1501 public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) { 1502 if (mEnableChooserDelegate) { 1503 return activity.startAsCallerImpl(mResolvedIntent, options, false, userId); 1504 } else { 1505 activity.startActivityAsCaller(mResolvedIntent, options, null, false, userId); 1506 return true; 1507 } 1508 } 1509 1510 @Override 1511 public boolean startAsUser(Activity activity, Bundle options, UserHandle user) { 1512 activity.startActivityAsUser(mResolvedIntent, options, user); 1513 return false; 1514 } 1515 1516 public boolean isSuspended() { 1517 return mIsSuspended; 1518 } 1519 } 1520 1521 List<DisplayResolveInfo> getDisplayList() { 1522 return mAdapter.mDisplayList; 1523 } 1524 1525 /** 1526 * A single target as represented in the chooser. 1527 */ 1528 public interface TargetInfo { 1529 /** 1530 * Get the resolved intent that represents this target. Note that this may not be the 1531 * intent that will be launched by calling one of the <code>start</code> methods provided; 1532 * this is the intent that will be credited with the launch. 1533 * 1534 * @return the resolved intent for this target 1535 */ 1536 Intent getResolvedIntent(); 1537 1538 /** 1539 * Get the resolved component name that represents this target. Note that this may not 1540 * be the component that will be directly launched by calling one of the <code>start</code> 1541 * methods provided; this is the component that will be credited with the launch. 1542 * 1543 * @return the resolved ComponentName for this target 1544 */ 1545 ComponentName getResolvedComponentName(); 1546 1547 /** 1548 * Start the activity referenced by this target. 1549 * 1550 * @param activity calling Activity performing the launch 1551 * @param options ActivityOptions bundle 1552 * @return true if the start completed successfully 1553 */ 1554 boolean start(Activity activity, Bundle options); 1555 1556 /** 1557 * Start the activity referenced by this target as if the ResolverActivity's caller 1558 * was performing the start operation. 1559 * 1560 * @param activity calling Activity (actually) performing the launch 1561 * @param options ActivityOptions bundle 1562 * @param userId userId to start as or {@link UserHandle#USER_NULL} for activity's caller 1563 * @return true if the start completed successfully 1564 */ 1565 boolean startAsCaller(ResolverActivity activity, Bundle options, int userId); 1566 1567 /** 1568 * Start the activity referenced by this target as a given user. 1569 * 1570 * @param activity calling activity performing the launch 1571 * @param options ActivityOptions bundle 1572 * @param user handle for the user to start the activity as 1573 * @return true if the start completed successfully 1574 */ 1575 boolean startAsUser(Activity activity, Bundle options, UserHandle user); 1576 1577 /** 1578 * Return the ResolveInfo about how and why this target matched the original query 1579 * for available targets. 1580 * 1581 * @return ResolveInfo representing this target's match 1582 */ 1583 ResolveInfo getResolveInfo(); 1584 1585 /** 1586 * Return the human-readable text label for this target. 1587 * 1588 * @return user-visible target label 1589 */ 1590 CharSequence getDisplayLabel(); 1591 1592 /** 1593 * Return any extended info for this target. This may be used to disambiguate 1594 * otherwise identical targets. 1595 * 1596 * @return human-readable disambig string or null if none present 1597 */ 1598 CharSequence getExtendedInfo(); 1599 1600 /** 1601 * @return The drawable that should be used to represent this target including badge 1602 */ 1603 Drawable getDisplayIcon(); 1604 1605 /** 1606 * Clone this target with the given fill-in information. 1607 */ 1608 TargetInfo cloneFilledIn(Intent fillInIntent, int flags); 1609 1610 /** 1611 * @return the list of supported source intents deduped against this single target 1612 */ 1613 List<Intent> getAllSourceIntents(); 1614 1615 /** 1616 * @return true if this target can be selected by the user 1617 */ 1618 boolean isSuspended(); 1619 } 1620 1621 public class ResolveListAdapter extends BaseAdapter { 1622 private final List<Intent> mIntents; 1623 private final Intent[] mInitialIntents; 1624 private final List<ResolveInfo> mBaseResolveList; 1625 protected ResolveInfo mLastChosen; 1626 private DisplayResolveInfo mOtherProfile; 1627 private ResolverListController mResolverListController; 1628 private int mPlaceholderCount; 1629 private boolean mAllTargetsAreBrowsers = false; 1630 1631 protected final LayoutInflater mInflater; 1632 1633 // This one is the list that the Adapter will actually present. 1634 List<DisplayResolveInfo> mDisplayList; 1635 List<ResolvedComponentInfo> mUnfilteredResolveList; 1636 1637 private int mLastChosenPosition = -1; 1638 private boolean mFilterLastUsed; 1639 1640 public ResolveListAdapter(Context context, List<Intent> payloadIntents, 1641 Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid, 1642 boolean filterLastUsed, 1643 ResolverListController resolverListController) { 1644 mIntents = payloadIntents; 1645 mInitialIntents = initialIntents; 1646 mBaseResolveList = rList; 1647 mLaunchedFromUid = launchedFromUid; 1648 mInflater = LayoutInflater.from(context); 1649 mDisplayList = new ArrayList<>(); 1650 mFilterLastUsed = filterLastUsed; 1651 mResolverListController = resolverListController; 1652 } 1653 1654 public void handlePackagesChanged() { 1655 rebuildList(); 1656 if (getCount() == 0) { 1657 // We no longer have any items... just finish the activity. 1658 finish(); 1659 } 1660 } 1661 1662 public void setPlaceholderCount(int count) { 1663 mPlaceholderCount = count; 1664 } 1665 1666 public int getPlaceholderCount() { return mPlaceholderCount; } 1667 1668 @Nullable 1669 public DisplayResolveInfo getFilteredItem() { 1670 if (mFilterLastUsed && mLastChosenPosition >= 0) { 1671 // Not using getItem since it offsets to dodge this position for the list 1672 return mDisplayList.get(mLastChosenPosition); 1673 } 1674 return null; 1675 } 1676 1677 public DisplayResolveInfo getOtherProfile() { 1678 return mOtherProfile; 1679 } 1680 1681 public int getFilteredPosition() { 1682 if (mFilterLastUsed && mLastChosenPosition >= 0) { 1683 return mLastChosenPosition; 1684 } 1685 return AbsListView.INVALID_POSITION; 1686 } 1687 1688 public boolean hasFilteredItem() { 1689 return mFilterLastUsed && mLastChosen != null; 1690 } 1691 1692 public float getScore(DisplayResolveInfo target) { 1693 return mResolverListController.getScore(target); 1694 } 1695 1696 public void updateModel(ComponentName componentName) { 1697 mResolverListController.updateModel(componentName); 1698 } 1699 1700 public void updateChooserCounts(String packageName, int userId, String action) { 1701 mResolverListController.updateChooserCounts(packageName, userId, action); 1702 } 1703 1704 /** 1705 * @return true if all items in the display list are defined as browsers by 1706 * ResolveInfo.handleAllWebDataURI 1707 */ 1708 public boolean areAllTargetsBrowsers() { 1709 return mAllTargetsAreBrowsers; 1710 } 1711 1712 /** 1713 * Rebuild the list of resolvers. In some cases some parts will need some asynchronous work 1714 * to complete. 1715 * 1716 * @return Whether or not the list building is completed. 1717 */ 1718 protected boolean rebuildList() { 1719 List<ResolvedComponentInfo> currentResolveList = null; 1720 // Clear the value of mOtherProfile from previous call. 1721 mOtherProfile = null; 1722 mLastChosen = null; 1723 mLastChosenPosition = -1; 1724 mAllTargetsAreBrowsers = false; 1725 mDisplayList.clear(); 1726 if (mBaseResolveList != null) { 1727 currentResolveList = mUnfilteredResolveList = new ArrayList<>(); 1728 mResolverListController.addResolveListDedupe(currentResolveList, 1729 getTargetIntent(), 1730 mBaseResolveList); 1731 } else { 1732 currentResolveList = mUnfilteredResolveList = 1733 mResolverListController.getResolversForIntent(shouldGetResolvedFilter(), 1734 shouldGetActivityMetadata(), 1735 mIntents); 1736 if (currentResolveList == null) { 1737 processSortedList(currentResolveList); 1738 return true; 1739 } 1740 List<ResolvedComponentInfo> originalList = 1741 mResolverListController.filterIneligibleActivities(currentResolveList, 1742 true); 1743 if (originalList != null) { 1744 mUnfilteredResolveList = originalList; 1745 } 1746 } 1747 1748 // So far we only support a single other profile at a time. 1749 // The first one we see gets special treatment. 1750 for (ResolvedComponentInfo info : currentResolveList) { 1751 if (info.getResolveInfoAt(0).targetUserId != UserHandle.USER_CURRENT) { 1752 mOtherProfile = new DisplayResolveInfo(info.getIntentAt(0), 1753 info.getResolveInfoAt(0), 1754 info.getResolveInfoAt(0).loadLabel(mPm), 1755 info.getResolveInfoAt(0).loadLabel(mPm), 1756 getReplacementIntent(info.getResolveInfoAt(0).activityInfo, 1757 info.getIntentAt(0))); 1758 currentResolveList.remove(info); 1759 break; 1760 } 1761 } 1762 1763 if (mOtherProfile == null) { 1764 try { 1765 mLastChosen = mResolverListController.getLastChosen(); 1766 } catch (RemoteException re) { 1767 Log.d(TAG, "Error calling getLastChosenActivity\n" + re); 1768 } 1769 } 1770 1771 int N; 1772 if ((currentResolveList != null) && ((N = currentResolveList.size()) > 0)) { 1773 // We only care about fixing the unfilteredList if the current resolve list and 1774 // current resolve list are currently the same. 1775 List<ResolvedComponentInfo> originalList = 1776 mResolverListController.filterLowPriority(currentResolveList, 1777 mUnfilteredResolveList == currentResolveList); 1778 if (originalList != null) { 1779 mUnfilteredResolveList = originalList; 1780 } 1781 1782 if (currentResolveList.size() > 1) { 1783 int placeholderCount = currentResolveList.size(); 1784 if (useLayoutWithDefault()) { 1785 --placeholderCount; 1786 } 1787 setPlaceholderCount(placeholderCount); 1788 AsyncTask<List<ResolvedComponentInfo>, 1789 Void, 1790 List<ResolvedComponentInfo>> sortingTask = 1791 new AsyncTask<List<ResolvedComponentInfo>, 1792 Void, 1793 List<ResolvedComponentInfo>>() { 1794 @Override 1795 protected List<ResolvedComponentInfo> doInBackground( 1796 List<ResolvedComponentInfo>... params) { 1797 mResolverListController.sort(params[0]); 1798 return params[0]; 1799 } 1800 1801 @Override 1802 protected void onPostExecute(List<ResolvedComponentInfo> sortedComponents) { 1803 processSortedList(sortedComponents); 1804 bindProfileView(); 1805 notifyDataSetChanged(); 1806 } 1807 }; 1808 sortingTask.execute(currentResolveList); 1809 postListReadyRunnable(); 1810 return false; 1811 } else { 1812 processSortedList(currentResolveList); 1813 return true; 1814 } 1815 } else { 1816 processSortedList(currentResolveList); 1817 return true; 1818 } 1819 } 1820 1821 1822 private void processSortedList(List<ResolvedComponentInfo> sortedComponents) { 1823 int N; 1824 if (sortedComponents != null && (N = sortedComponents.size()) != 0) { 1825 mAllTargetsAreBrowsers = mUseLayoutForBrowsables; 1826 1827 // First put the initial items at the top. 1828 if (mInitialIntents != null) { 1829 for (int i = 0; i < mInitialIntents.length; i++) { 1830 Intent ii = mInitialIntents[i]; 1831 if (ii == null) { 1832 continue; 1833 } 1834 ActivityInfo ai = ii.resolveActivityInfo( 1835 getPackageManager(), 0); 1836 if (ai == null) { 1837 Log.w(TAG, "No activity found for " + ii); 1838 continue; 1839 } 1840 ResolveInfo ri = new ResolveInfo(); 1841 ri.activityInfo = ai; 1842 UserManager userManager = 1843 (UserManager) getSystemService(Context.USER_SERVICE); 1844 if (ii instanceof LabeledIntent) { 1845 LabeledIntent li = (LabeledIntent) ii; 1846 ri.resolvePackageName = li.getSourcePackage(); 1847 ri.labelRes = li.getLabelResource(); 1848 ri.nonLocalizedLabel = li.getNonLocalizedLabel(); 1849 ri.icon = li.getIconResource(); 1850 ri.iconResourceId = ri.icon; 1851 } 1852 if (userManager.isManagedProfile()) { 1853 ri.noResourceId = true; 1854 ri.icon = 0; 1855 } 1856 mAllTargetsAreBrowsers &= ri.handleAllWebDataURI; 1857 1858 addResolveInfo(new DisplayResolveInfo(ii, ri, 1859 ri.loadLabel(getPackageManager()), null, ii)); 1860 } 1861 } 1862 1863 1864 for (ResolvedComponentInfo rci : sortedComponents) { 1865 final ResolveInfo ri = rci.getResolveInfoAt(0); 1866 if (ri != null) { 1867 mAllTargetsAreBrowsers &= ri.handleAllWebDataURI; 1868 1869 ResolveInfoPresentationGetter pg = makePresentationGetter(ri); 1870 addResolveInfoWithAlternates(rci, pg.getSubLabel(), pg.getLabel()); 1871 } 1872 } 1873 } 1874 1875 1876 postListReadyRunnable(); 1877 } 1878 1879 1880 1881 /** 1882 * Some necessary methods for creating the list are initiated in onCreate and will also 1883 * determine the layout known. We therefore can't update the UI inline and post to the 1884 * handler thread to update after the current task is finished. 1885 */ 1886 private void postListReadyRunnable() { 1887 if (mPostListReadyRunnable == null) { 1888 mPostListReadyRunnable = new Runnable() { 1889 @Override 1890 public void run() { 1891 setHeader(); 1892 resetButtonBar(); 1893 onListRebuilt(); 1894 mPostListReadyRunnable = null; 1895 } 1896 }; 1897 getMainThreadHandler().post(mPostListReadyRunnable); 1898 } 1899 } 1900 1901 public void onListRebuilt() { 1902 int count = getUnfilteredCount(); 1903 if (count == 1 && getOtherProfile() == null) { 1904 // Only one target, so we're a candidate to auto-launch! 1905 final TargetInfo target = targetInfoForPosition(0, false); 1906 if (shouldAutoLaunchSingleChoice(target)) { 1907 safelyStartActivity(target); 1908 finish(); 1909 } 1910 } 1911 } 1912 1913 public boolean shouldGetResolvedFilter() { 1914 return mFilterLastUsed; 1915 } 1916 1917 private void addResolveInfoWithAlternates(ResolvedComponentInfo rci, 1918 CharSequence extraInfo, CharSequence roLabel) { 1919 final int count = rci.getCount(); 1920 final Intent intent = rci.getIntentAt(0); 1921 final ResolveInfo add = rci.getResolveInfoAt(0); 1922 final Intent replaceIntent = getReplacementIntent(add.activityInfo, intent); 1923 final DisplayResolveInfo dri = new DisplayResolveInfo(intent, add, roLabel, 1924 extraInfo, replaceIntent); 1925 addResolveInfo(dri); 1926 if (replaceIntent == intent) { 1927 // Only add alternates if we didn't get a specific replacement from 1928 // the caller. If we have one it trumps potential alternates. 1929 for (int i = 1, N = count; i < N; i++) { 1930 final Intent altIntent = rci.getIntentAt(i); 1931 dri.addAlternateSourceIntent(altIntent); 1932 } 1933 } 1934 updateLastChosenPosition(add); 1935 } 1936 1937 private void updateLastChosenPosition(ResolveInfo info) { 1938 // If another profile is present, ignore the last chosen entry. 1939 if (mOtherProfile != null) { 1940 mLastChosenPosition = -1; 1941 return; 1942 } 1943 if (mLastChosen != null 1944 && mLastChosen.activityInfo.packageName.equals(info.activityInfo.packageName) 1945 && mLastChosen.activityInfo.name.equals(info.activityInfo.name)) { 1946 mLastChosenPosition = mDisplayList.size() - 1; 1947 } 1948 } 1949 1950 // We assume that at this point we've already filtered out the only intent for a different 1951 // targetUserId which we're going to use. 1952 private void addResolveInfo(DisplayResolveInfo dri) { 1953 if (dri != null && dri.mResolveInfo != null 1954 && dri.mResolveInfo.targetUserId == UserHandle.USER_CURRENT) { 1955 // Checks if this info is already listed in display. 1956 for (DisplayResolveInfo existingInfo : mDisplayList) { 1957 if (resolveInfoMatch(dri.mResolveInfo, existingInfo.mResolveInfo)) { 1958 return; 1959 } 1960 } 1961 mDisplayList.add(dri); 1962 } 1963 } 1964 1965 @Nullable 1966 public ResolveInfo resolveInfoForPosition(int position, boolean filtered) { 1967 TargetInfo target = targetInfoForPosition(position, filtered); 1968 if (target != null) { 1969 return target.getResolveInfo(); 1970 } 1971 return null; 1972 } 1973 1974 @Nullable 1975 public TargetInfo targetInfoForPosition(int position, boolean filtered) { 1976 if (filtered) { 1977 return getItem(position); 1978 } 1979 if (mDisplayList.size() > position) { 1980 return mDisplayList.get(position); 1981 } 1982 return null; 1983 } 1984 1985 public int getCount() { 1986 int totalSize = mDisplayList == null || mDisplayList.isEmpty() ? mPlaceholderCount : 1987 mDisplayList.size(); 1988 if (mFilterLastUsed && mLastChosenPosition >= 0) { 1989 totalSize--; 1990 } 1991 return totalSize; 1992 } 1993 1994 public int getUnfilteredCount() { 1995 return mDisplayList.size(); 1996 } 1997 1998 @Nullable 1999 public TargetInfo getItem(int position) { 2000 if (mFilterLastUsed && mLastChosenPosition >= 0 && position >= mLastChosenPosition) { 2001 position++; 2002 } 2003 if (mDisplayList.size() > position) { 2004 return mDisplayList.get(position); 2005 } else { 2006 return null; 2007 } 2008 } 2009 2010 public long getItemId(int position) { 2011 return position; 2012 } 2013 2014 public int getDisplayResolveInfoCount() { 2015 return mDisplayList.size(); 2016 } 2017 2018 public DisplayResolveInfo getDisplayResolveInfo(int index) { 2019 // Used to query services. We only query services for primary targets, not alternates. 2020 return mDisplayList.get(index); 2021 } 2022 2023 public final View getView(int position, View convertView, ViewGroup parent) { 2024 View view = convertView; 2025 if (view == null) { 2026 view = createView(parent); 2027 } 2028 onBindView(view, getItem(position)); 2029 return view; 2030 } 2031 2032 public final View createView(ViewGroup parent) { 2033 final View view = onCreateView(parent); 2034 final ViewHolder holder = new ViewHolder(view); 2035 view.setTag(holder); 2036 return view; 2037 } 2038 2039 public View onCreateView(ViewGroup parent) { 2040 return mInflater.inflate( 2041 com.android.internal.R.layout.resolve_list_item, parent, false); 2042 } 2043 2044 public final void bindView(int position, View view) { 2045 onBindView(view, getItem(position)); 2046 } 2047 2048 protected void onBindView(View view, TargetInfo info) { 2049 final ViewHolder holder = (ViewHolder) view.getTag(); 2050 if (info == null) { 2051 holder.icon.setImageDrawable( 2052 getDrawable(R.drawable.resolver_icon_placeholder)); 2053 return; 2054 } 2055 2056 final CharSequence label = info.getDisplayLabel(); 2057 if (!TextUtils.equals(holder.text.getText(), label)) { 2058 holder.text.setText(info.getDisplayLabel()); 2059 } 2060 2061 // Always show a subLabel for visual consistency across list items. Show an empty 2062 // subLabel if the subLabel is the same as the label 2063 CharSequence subLabel = info.getExtendedInfo(); 2064 if (TextUtils.equals(label, subLabel)) subLabel = null; 2065 2066 if (!TextUtils.equals(holder.text2.getText(), subLabel)) { 2067 holder.text2.setText(subLabel); 2068 } 2069 2070 if (info.isSuspended()) { 2071 holder.icon.setColorFilter(mSuspendedMatrixColorFilter); 2072 } else { 2073 holder.icon.setColorFilter(null); 2074 } 2075 2076 if (info instanceof DisplayResolveInfo 2077 && !((DisplayResolveInfo) info).hasDisplayIcon()) { 2078 new LoadIconTask((DisplayResolveInfo) info, holder.icon).execute(); 2079 } else { 2080 holder.icon.setImageDrawable(info.getDisplayIcon()); 2081 } 2082 } 2083 } 2084 2085 2086 @VisibleForTesting 2087 public static final class ResolvedComponentInfo { 2088 public final ComponentName name; 2089 private final List<Intent> mIntents = new ArrayList<>(); 2090 private final List<ResolveInfo> mResolveInfos = new ArrayList<>(); 2091 2092 public ResolvedComponentInfo(ComponentName name, Intent intent, ResolveInfo info) { 2093 this.name = name; 2094 add(intent, info); 2095 } 2096 2097 public void add(Intent intent, ResolveInfo info) { 2098 mIntents.add(intent); 2099 mResolveInfos.add(info); 2100 } 2101 2102 public int getCount() { 2103 return mIntents.size(); 2104 } 2105 2106 public Intent getIntentAt(int index) { 2107 return index >= 0 ? mIntents.get(index) : null; 2108 } 2109 2110 public ResolveInfo getResolveInfoAt(int index) { 2111 return index >= 0 ? mResolveInfos.get(index) : null; 2112 } 2113 2114 public int findIntent(Intent intent) { 2115 for (int i = 0, N = mIntents.size(); i < N; i++) { 2116 if (intent.equals(mIntents.get(i))) { 2117 return i; 2118 } 2119 } 2120 return -1; 2121 } 2122 2123 public int findResolveInfo(ResolveInfo info) { 2124 for (int i = 0, N = mResolveInfos.size(); i < N; i++) { 2125 if (info.equals(mResolveInfos.get(i))) { 2126 return i; 2127 } 2128 } 2129 return -1; 2130 } 2131 } 2132 2133 static class ViewHolder { 2134 public View itemView; 2135 public Drawable defaultItemViewBackground; 2136 2137 public TextView text; 2138 public TextView text2; 2139 public ImageView icon; 2140 2141 public ViewHolder(View view) { 2142 itemView = view; 2143 defaultItemViewBackground = view.getBackground(); 2144 text = (TextView) view.findViewById(com.android.internal.R.id.text1); 2145 text2 = (TextView) view.findViewById(com.android.internal.R.id.text2); 2146 icon = (ImageView) view.findViewById(R.id.icon); 2147 } 2148 } 2149 2150 class ItemClickListener implements AdapterView.OnItemClickListener, 2151 AdapterView.OnItemLongClickListener { 2152 @Override 2153 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 2154 final ListView listView = parent instanceof ListView ? (ListView) parent : null; 2155 if (listView != null) { 2156 position -= listView.getHeaderViewsCount(); 2157 } 2158 if (position < 0) { 2159 // Header views don't count. 2160 return; 2161 } 2162 // If we're still loading, we can't yet enable the buttons. 2163 if (mAdapter.resolveInfoForPosition(position, true) == null) { 2164 return; 2165 } 2166 2167 final int checkedPos = mAdapterView.getCheckedItemPosition(); 2168 final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION; 2169 if (!useLayoutWithDefault() 2170 && (!hasValidSelection || mLastSelected != checkedPos) 2171 && mAlwaysButton != null) { 2172 setAlwaysButtonEnabled(hasValidSelection, checkedPos, true); 2173 mOnceButton.setEnabled(hasValidSelection); 2174 if (hasValidSelection) { 2175 mAdapterView.smoothScrollToPosition(checkedPos); 2176 } 2177 mLastSelected = checkedPos; 2178 } else { 2179 startSelected(position, false, true); 2180 } 2181 } 2182 2183 @Override 2184 public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { 2185 final ListView listView = parent instanceof ListView ? (ListView) parent : null; 2186 if (listView != null) { 2187 position -= listView.getHeaderViewsCount(); 2188 } 2189 if (position < 0) { 2190 // Header views don't count. 2191 return false; 2192 } 2193 ResolveInfo ri = mAdapter.resolveInfoForPosition(position, true); 2194 showTargetDetails(ri); 2195 return true; 2196 } 2197 2198 } 2199 2200 class LoadIconTask extends AsyncTask<Void, Void, Drawable> { 2201 protected final DisplayResolveInfo mDisplayResolveInfo; 2202 private final ResolveInfo mResolveInfo; 2203 private final ImageView mTargetView; 2204 2205 LoadIconTask(DisplayResolveInfo dri, ImageView target) { 2206 mDisplayResolveInfo = dri; 2207 mResolveInfo = dri.getResolveInfo(); 2208 mTargetView = target; 2209 } 2210 2211 @Override 2212 protected Drawable doInBackground(Void... params) { 2213 return loadIconForResolveInfo(mResolveInfo); 2214 } 2215 2216 @Override 2217 protected void onPostExecute(Drawable d) { 2218 if (mAdapter.getOtherProfile() == mDisplayResolveInfo) { 2219 bindProfileView(); 2220 } else { 2221 mDisplayResolveInfo.setDisplayIcon(d); 2222 mTargetView.setImageDrawable(d); 2223 } 2224 } 2225 } 2226 2227 static final boolean isSpecificUriMatch(int match) { 2228 match = match&IntentFilter.MATCH_CATEGORY_MASK; 2229 return match >= IntentFilter.MATCH_CATEGORY_HOST 2230 && match <= IntentFilter.MATCH_CATEGORY_PATH; 2231 } 2232 2233 static class PickTargetOptionRequest extends PickOptionRequest { 2234 public PickTargetOptionRequest(@Nullable Prompt prompt, Option[] options, 2235 @Nullable Bundle extras) { 2236 super(prompt, options, extras); 2237 } 2238 2239 @Override 2240 public void onCancel() { 2241 super.onCancel(); 2242 final ResolverActivity ra = (ResolverActivity) getActivity(); 2243 if (ra != null) { 2244 ra.mPickOptionRequest = null; 2245 ra.finish(); 2246 } 2247 } 2248 2249 @Override 2250 public void onPickOptionResult(boolean finished, Option[] selections, Bundle result) { 2251 super.onPickOptionResult(finished, selections, result); 2252 if (selections.length != 1) { 2253 // TODO In a better world we would filter the UI presented here and let the 2254 // user refine. Maybe later. 2255 return; 2256 } 2257 2258 final ResolverActivity ra = (ResolverActivity) getActivity(); 2259 if (ra != null) { 2260 final TargetInfo ti = ra.mAdapter.getItem(selections[0].getIndex()); 2261 if (ra.onTargetSelected(ti, false)) { 2262 ra.mPickOptionRequest = null; 2263 ra.finish(); 2264 } 2265 } 2266 } 2267 } 2268 } 2269