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