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