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