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