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