1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.internal.app; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.StringRes; 22 import android.app.Activity; 23 import android.app.ActivityThread; 24 import android.app.VoiceInteractor.PickOptionRequest; 25 import android.app.VoiceInteractor.PickOptionRequest.Option; 26 import android.app.VoiceInteractor.Prompt; 27 import android.content.pm.ComponentInfo; 28 import android.os.AsyncTask; 29 import android.provider.MediaStore; 30 import android.provider.Settings; 31 import android.text.TextUtils; 32 import android.util.Slog; 33 import android.widget.AbsListView; 34 import com.android.internal.R; 35 import com.android.internal.content.PackageMonitor; 36 37 import android.app.ActivityManager; 38 import android.app.ActivityManagerNative; 39 import android.app.AppGlobals; 40 import android.content.ComponentName; 41 import android.content.Context; 42 import android.content.Intent; 43 import android.content.IntentFilter; 44 import android.content.pm.ActivityInfo; 45 import android.content.pm.ApplicationInfo; 46 import android.content.pm.LabeledIntent; 47 import android.content.pm.PackageManager; 48 import android.content.pm.PackageManager.NameNotFoundException; 49 import android.content.pm.ResolveInfo; 50 import android.content.pm.UserInfo; 51 import android.content.res.Resources; 52 import android.graphics.drawable.Drawable; 53 import android.net.Uri; 54 import android.os.Build; 55 import android.os.Bundle; 56 import android.os.PatternMatcher; 57 import android.os.RemoteException; 58 import android.os.StrictMode; 59 import android.os.UserHandle; 60 import android.os.UserManager; 61 import android.util.Log; 62 import android.view.LayoutInflater; 63 import android.view.View; 64 import android.view.ViewGroup; 65 import android.widget.AdapterView; 66 import android.widget.BaseAdapter; 67 import android.widget.Button; 68 import android.widget.ImageView; 69 import android.widget.ListView; 70 import android.widget.TextView; 71 import android.widget.Toast; 72 import com.android.internal.widget.ResolverDrawerLayout; 73 74 import java.util.ArrayList; 75 import java.util.Collections; 76 import java.util.HashSet; 77 import java.util.Iterator; 78 import java.util.List; 79 import java.util.Objects; 80 import java.util.Set; 81 82 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; 83 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; 84 import 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 */ 91 public 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 */ getLabelRes(String action)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 ActionTitle(String action, int titleRes, int namedTitleRes, @StringRes int labelRes)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 forAction(String action)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 makeMyIntent()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 onCreate(Bundle savedInstanceState)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 */ onCreate(Bundle savedInstanceState, Intent intent, CharSequence title, Intent[] initialIntents, List<ResolveInfo> rList, boolean alwaysUseOption)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 onCreate(Bundle savedInstanceState, Intent intent, CharSequence title, int defaultTitleRes, Intent[] initialIntents, List<ResolveInfo> rList, boolean alwaysUseOption)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 setFilteredComponents(ComponentName[] components)364 public final void setFilteredComponents(ComponentName[] components) { 365 mFilteredComponents = components; 366 } 367 isComponentFiltered(ComponentInfo component)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 */ onSetupVoiceInteraction()385 public void onSetupVoiceInteraction() { 386 // Do it right now. Subclasses may delay this and send it later. 387 sendVoiceChoicesIfNeeded(); 388 } 389 sendVoiceChoicesIfNeeded()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 optionForChooserTarget(TargetInfo target, int index)407 Option optionForChooserTarget(TargetInfo target, int index) { 408 return new Option(target.getDisplayLabel(), index); 409 } 410 setAdditionalTargets(Intent[] intents)411 protected final void setAdditionalTargets(Intent[] intents) { 412 if (intents != null) { 413 for (Intent intent : intents) { 414 mIntents.add(intent); 415 } 416 } 417 } 418 getTargetIntent()419 public Intent getTargetIntent() { 420 return mIntents.isEmpty() ? null : mIntents.get(0); 421 } 422 getReferrerPackageName()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 getLayoutResource()431 public int getLayoutResource() { 432 return R.layout.resolver_list; 433 } 434 bindProfileView()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 setProfileSwitchMessageId(int contentUserHint)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 */ setSafeForwardingMode(boolean safeForwarding)474 public void setSafeForwardingMode(boolean safeForwarding) { 475 mSafeForwardingMode = safeForwarding; 476 } 477 getTitleForAction(String action, int defaultTitleRes)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 dismiss()490 void dismiss() { 491 if (!isFinishing()) { 492 finish(); 493 } 494 } 495 getIcon(Resources res, int resId)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 loadIconForResolveInfo(ResolveInfo ri)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 onRestart()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 onStop()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 onDestroy()566 protected void onDestroy() { 567 super.onDestroy(); 568 if (!isChangingConfigurations() && mPickOptionRequest != null) { 569 mPickOptionRequest.cancel(); 570 } 571 } 572 573 @Override onRestoreInstanceState(Bundle savedInstanceState)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 hasManagedProfile()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 supportsManagedProfiles(ResolveInfo resolveInfo)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 setAlwaysButtonEnabled(boolean hasValidSelection, int checkedPos, boolean filtered)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 onButtonClick(View v)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 startSelected(int which, boolean always, boolean filtered)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 */ getReplacementIntent(ActivityInfo aInfo, Intent defIntent)659 public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) { 660 return defIntent; 661 } 662 onTargetSelected(TargetInfo target, boolean alwaysCheck)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 Intent filterIntent; 672 673 if (intent.getSelector() != null) { 674 filterIntent = intent.getSelector(); 675 } else { 676 filterIntent = intent; 677 } 678 679 String action = filterIntent.getAction(); 680 if (action != null) { 681 filter.addAction(action); 682 } 683 Set<String> categories = filterIntent.getCategories(); 684 if (categories != null) { 685 for (String cat : categories) { 686 filter.addCategory(cat); 687 } 688 } 689 filter.addCategory(Intent.CATEGORY_DEFAULT); 690 691 int cat = ri.match & IntentFilter.MATCH_CATEGORY_MASK; 692 Uri data = filterIntent.getData(); 693 if (cat == IntentFilter.MATCH_CATEGORY_TYPE) { 694 String mimeType = filterIntent.resolveType(this); 695 if (mimeType != null) { 696 try { 697 filter.addDataType(mimeType); 698 } catch (IntentFilter.MalformedMimeTypeException e) { 699 Log.w("ResolverActivity", e); 700 filter = null; 701 } 702 } 703 } 704 if (data != null && data.getScheme() != null) { 705 // We need the data specification if there was no type, 706 // OR if the scheme is not one of our magical "file:" 707 // or "content:" schemes (see IntentFilter for the reason). 708 if (cat != IntentFilter.MATCH_CATEGORY_TYPE 709 || (!"file".equals(data.getScheme()) 710 && !"content".equals(data.getScheme()))) { 711 filter.addDataScheme(data.getScheme()); 712 713 // Look through the resolved filter to determine which part 714 // of it matched the original Intent. 715 Iterator<PatternMatcher> pIt = ri.filter.schemeSpecificPartsIterator(); 716 if (pIt != null) { 717 String ssp = data.getSchemeSpecificPart(); 718 while (ssp != null && pIt.hasNext()) { 719 PatternMatcher p = pIt.next(); 720 if (p.match(ssp)) { 721 filter.addDataSchemeSpecificPart(p.getPath(), p.getType()); 722 break; 723 } 724 } 725 } 726 Iterator<IntentFilter.AuthorityEntry> aIt = ri.filter.authoritiesIterator(); 727 if (aIt != null) { 728 while (aIt.hasNext()) { 729 IntentFilter.AuthorityEntry a = aIt.next(); 730 if (a.match(data) >= 0) { 731 int port = a.getPort(); 732 filter.addDataAuthority(a.getHost(), 733 port >= 0 ? Integer.toString(port) : null); 734 break; 735 } 736 } 737 } 738 pIt = ri.filter.pathsIterator(); 739 if (pIt != null) { 740 String path = data.getPath(); 741 while (path != null && pIt.hasNext()) { 742 PatternMatcher p = pIt.next(); 743 if (p.match(path)) { 744 filter.addDataPath(p.getPath(), p.getType()); 745 break; 746 } 747 } 748 } 749 } 750 } 751 752 if (filter != null) { 753 final int N = mAdapter.mOrigResolveList.size(); 754 ComponentName[] set = new ComponentName[N]; 755 int bestMatch = 0; 756 for (int i=0; i<N; i++) { 757 ResolveInfo r = mAdapter.mOrigResolveList.get(i).getResolveInfoAt(0); 758 set[i] = new ComponentName(r.activityInfo.packageName, 759 r.activityInfo.name); 760 if (r.match > bestMatch) bestMatch = r.match; 761 } 762 if (alwaysCheck) { 763 final int userId = getUserId(); 764 final PackageManager pm = getPackageManager(); 765 766 // Set the preferred Activity 767 pm.addPreferredActivity(filter, bestMatch, set, intent.getComponent()); 768 769 if (ri.handleAllWebDataURI) { 770 // Set default Browser if needed 771 final String packageName = pm.getDefaultBrowserPackageNameAsUser(userId); 772 if (TextUtils.isEmpty(packageName)) { 773 pm.setDefaultBrowserPackageNameAsUser(ri.activityInfo.packageName, userId); 774 } 775 } else { 776 // Update Domain Verification status 777 ComponentName cn = intent.getComponent(); 778 String packageName = cn.getPackageName(); 779 String dataScheme = (data != null) ? data.getScheme() : null; 780 781 boolean isHttpOrHttps = (dataScheme != null) && 782 (dataScheme.equals(IntentFilter.SCHEME_HTTP) || 783 dataScheme.equals(IntentFilter.SCHEME_HTTPS)); 784 785 boolean isViewAction = (action != null) && action.equals(Intent.ACTION_VIEW); 786 boolean hasCategoryBrowsable = (categories != null) && 787 categories.contains(Intent.CATEGORY_BROWSABLE); 788 789 if (isHttpOrHttps && isViewAction && hasCategoryBrowsable) { 790 pm.updateIntentVerificationStatusAsUser(packageName, 791 PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS, 792 userId); 793 } 794 } 795 } else { 796 try { 797 AppGlobals.getPackageManager().setLastChosenActivity(intent, 798 intent.resolveType(getContentResolver()), 799 PackageManager.MATCH_DEFAULT_ONLY, 800 filter, bestMatch, intent.getComponent()); 801 } catch (RemoteException re) { 802 Log.d(TAG, "Error calling setLastChosenActivity\n" + re); 803 } 804 } 805 } 806 } 807 808 if (target != null) { 809 safelyStartActivity(target); 810 } 811 return true; 812 } 813 safelyStartActivity(TargetInfo cti)814 public void safelyStartActivity(TargetInfo cti) { 815 // We're dispatching intents that might be coming from legacy apps, so 816 // don't kill ourselves. 817 StrictMode.disableDeathOnFileUriExposure(); 818 try { 819 safelyStartActivityInternal(cti); 820 } finally { 821 StrictMode.enableDeathOnFileUriExposure(); 822 } 823 } 824 safelyStartActivityInternal(TargetInfo cti)825 private void safelyStartActivityInternal(TargetInfo cti) { 826 // If needed, show that intent is forwarded 827 // from managed profile to owner or other way around. 828 if (mProfileSwitchMessageId != -1) { 829 Toast.makeText(this, getString(mProfileSwitchMessageId), Toast.LENGTH_LONG).show(); 830 } 831 if (!mSafeForwardingMode) { 832 if (cti.start(this, null)) { 833 onActivityStarted(cti); 834 } 835 return; 836 } 837 try { 838 if (cti.startAsCaller(this, null, UserHandle.USER_NULL)) { 839 onActivityStarted(cti); 840 } 841 } catch (RuntimeException e) { 842 String launchedFromPackage; 843 try { 844 launchedFromPackage = ActivityManagerNative.getDefault().getLaunchedFromPackage( 845 getActivityToken()); 846 } catch (RemoteException e2) { 847 launchedFromPackage = "??"; 848 } 849 Slog.wtf(TAG, "Unable to launch as uid " + mLaunchedFromUid 850 + " package " + launchedFromPackage + ", while running in " 851 + ActivityThread.currentProcessName(), e); 852 } 853 } 854 onActivityStarted(TargetInfo cti)855 public void onActivityStarted(TargetInfo cti) { 856 // Do nothing 857 } 858 shouldGetActivityMetadata()859 public boolean shouldGetActivityMetadata() { 860 return false; 861 } 862 shouldAutoLaunchSingleChoice(TargetInfo target)863 public boolean shouldAutoLaunchSingleChoice(TargetInfo target) { 864 return true; 865 } 866 showTargetDetails(ResolveInfo ri)867 public void showTargetDetails(ResolveInfo ri) { 868 Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) 869 .setData(Uri.fromParts("package", ri.activityInfo.packageName, null)) 870 .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); 871 startActivity(in); 872 } 873 createAdapter(Context context, List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid, boolean filterLastUsed)874 public ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents, 875 Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid, 876 boolean filterLastUsed) { 877 return new ResolveListAdapter(context, payloadIntents, initialIntents, rList, 878 launchedFromUid, filterLastUsed); 879 } 880 881 /** 882 * Returns true if the activity is finishing and creation should halt 883 */ configureContentView(List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList, boolean alwaysUseOption)884 public boolean configureContentView(List<Intent> payloadIntents, Intent[] initialIntents, 885 List<ResolveInfo> rList, boolean alwaysUseOption) { 886 // The last argument of createAdapter is whether to do special handling 887 // of the last used choice to highlight it in the list. We need to always 888 // turn this off when running under voice interaction, since it results in 889 // a more complicated UI that the current voice interaction flow is not able 890 // to handle. 891 mAdapter = createAdapter(this, payloadIntents, initialIntents, rList, 892 mLaunchedFromUid, alwaysUseOption && !isVoiceInteraction()); 893 894 final int layoutId; 895 if (mAdapter.hasFilteredItem()) { 896 layoutId = R.layout.resolver_list_with_default; 897 alwaysUseOption = false; 898 } else { 899 layoutId = getLayoutResource(); 900 } 901 mAlwaysUseOption = alwaysUseOption; 902 903 int count = mAdapter.getUnfilteredCount(); 904 if (count == 1 && mAdapter.getOtherProfile() == null) { 905 // Only one target, so we're a candidate to auto-launch! 906 final TargetInfo target = mAdapter.targetInfoForPosition(0, false); 907 if (shouldAutoLaunchSingleChoice(target)) { 908 safelyStartActivity(target); 909 mPackageMonitor.unregister(); 910 mRegistered = false; 911 finish(); 912 return true; 913 } 914 } 915 if (count > 0) { 916 setContentView(layoutId); 917 mAdapterView = (AbsListView) findViewById(R.id.resolver_list); 918 onPrepareAdapterView(mAdapterView, mAdapter, alwaysUseOption); 919 } else { 920 setContentView(R.layout.resolver_list); 921 922 final TextView empty = (TextView) findViewById(R.id.empty); 923 empty.setVisibility(View.VISIBLE); 924 925 mAdapterView = (AbsListView) findViewById(R.id.resolver_list); 926 mAdapterView.setVisibility(View.GONE); 927 } 928 return false; 929 } 930 onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter, boolean alwaysUseOption)931 public void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter, 932 boolean alwaysUseOption) { 933 final boolean useHeader = adapter.hasFilteredItem(); 934 final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null; 935 936 adapterView.setAdapter(mAdapter); 937 938 final ItemClickListener listener = new ItemClickListener(); 939 adapterView.setOnItemClickListener(listener); 940 adapterView.setOnItemLongClickListener(listener); 941 942 if (alwaysUseOption) { 943 listView.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE); 944 } 945 946 if (useHeader && listView != null) { 947 listView.addHeaderView(LayoutInflater.from(this).inflate( 948 R.layout.resolver_different_item_header, listView, false)); 949 } 950 } 951 952 /** 953 * Check a simple match for the component of two ResolveInfos. 954 */ resolveInfoMatch(ResolveInfo lhs, ResolveInfo rhs)955 static boolean resolveInfoMatch(ResolveInfo lhs, ResolveInfo rhs) { 956 return lhs == null ? rhs == null 957 : lhs.activityInfo == null ? rhs.activityInfo == null 958 : Objects.equals(lhs.activityInfo.name, rhs.activityInfo.name) 959 && Objects.equals(lhs.activityInfo.packageName, rhs.activityInfo.packageName); 960 } 961 962 public final class DisplayResolveInfo implements TargetInfo { 963 private final ResolveInfo mResolveInfo; 964 private final CharSequence mDisplayLabel; 965 private Drawable mDisplayIcon; 966 private Drawable mBadge; 967 private final CharSequence mExtendedInfo; 968 private final Intent mResolvedIntent; 969 private final List<Intent> mSourceIntents = new ArrayList<>(); 970 private boolean mPinned; 971 DisplayResolveInfo(Intent originalIntent, ResolveInfo pri, CharSequence pLabel, CharSequence pInfo, Intent pOrigIntent)972 public DisplayResolveInfo(Intent originalIntent, ResolveInfo pri, CharSequence pLabel, 973 CharSequence pInfo, Intent pOrigIntent) { 974 mSourceIntents.add(originalIntent); 975 mResolveInfo = pri; 976 mDisplayLabel = pLabel; 977 mExtendedInfo = pInfo; 978 979 final Intent intent = new Intent(pOrigIntent != null ? pOrigIntent : 980 getReplacementIntent(pri.activityInfo, getTargetIntent())); 981 intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT 982 | Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP); 983 final ActivityInfo ai = mResolveInfo.activityInfo; 984 intent.setComponent(new ComponentName(ai.applicationInfo.packageName, ai.name)); 985 986 mResolvedIntent = intent; 987 } 988 DisplayResolveInfo(DisplayResolveInfo other, Intent fillInIntent, int flags)989 private DisplayResolveInfo(DisplayResolveInfo other, Intent fillInIntent, int flags) { 990 mSourceIntents.addAll(other.getAllSourceIntents()); 991 mResolveInfo = other.mResolveInfo; 992 mDisplayLabel = other.mDisplayLabel; 993 mDisplayIcon = other.mDisplayIcon; 994 mExtendedInfo = other.mExtendedInfo; 995 mResolvedIntent = new Intent(other.mResolvedIntent); 996 mResolvedIntent.fillIn(fillInIntent, flags); 997 mPinned = other.mPinned; 998 } 999 getResolveInfo()1000 public ResolveInfo getResolveInfo() { 1001 return mResolveInfo; 1002 } 1003 getDisplayLabel()1004 public CharSequence getDisplayLabel() { 1005 return mDisplayLabel; 1006 } 1007 getDisplayIcon()1008 public Drawable getDisplayIcon() { 1009 return mDisplayIcon; 1010 } 1011 getBadgeIcon()1012 public Drawable getBadgeIcon() { 1013 // We only expose a badge if we have extended info. 1014 // The badge is a higher-priority disambiguation signal 1015 // but we don't need one if we wouldn't show extended info at all. 1016 if (TextUtils.isEmpty(getExtendedInfo())) { 1017 return null; 1018 } 1019 1020 if (mBadge == null && mResolveInfo != null && mResolveInfo.activityInfo != null 1021 && mResolveInfo.activityInfo.applicationInfo != null) { 1022 if (mResolveInfo.activityInfo.icon == 0 || mResolveInfo.activityInfo.icon 1023 == mResolveInfo.activityInfo.applicationInfo.icon) { 1024 // Badging an icon with exactly the same icon is silly. 1025 // If the activityInfo icon resid is 0 it will fall back 1026 // to the application's icon, making it a match. 1027 return null; 1028 } 1029 mBadge = mResolveInfo.activityInfo.applicationInfo.loadIcon(mPm); 1030 } 1031 return mBadge; 1032 } 1033 1034 @Override getBadgeContentDescription()1035 public CharSequence getBadgeContentDescription() { 1036 return null; 1037 } 1038 1039 @Override cloneFilledIn(Intent fillInIntent, int flags)1040 public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) { 1041 return new DisplayResolveInfo(this, fillInIntent, flags); 1042 } 1043 1044 @Override getAllSourceIntents()1045 public List<Intent> getAllSourceIntents() { 1046 return mSourceIntents; 1047 } 1048 addAlternateSourceIntent(Intent alt)1049 public void addAlternateSourceIntent(Intent alt) { 1050 mSourceIntents.add(alt); 1051 } 1052 setDisplayIcon(Drawable icon)1053 public void setDisplayIcon(Drawable icon) { 1054 mDisplayIcon = icon; 1055 } 1056 hasDisplayIcon()1057 public boolean hasDisplayIcon() { 1058 return mDisplayIcon != null; 1059 } 1060 getExtendedInfo()1061 public CharSequence getExtendedInfo() { 1062 return mExtendedInfo; 1063 } 1064 getResolvedIntent()1065 public Intent getResolvedIntent() { 1066 return mResolvedIntent; 1067 } 1068 1069 @Override getResolvedComponentName()1070 public ComponentName getResolvedComponentName() { 1071 return new ComponentName(mResolveInfo.activityInfo.packageName, 1072 mResolveInfo.activityInfo.name); 1073 } 1074 1075 @Override start(Activity activity, Bundle options)1076 public boolean start(Activity activity, Bundle options) { 1077 activity.startActivity(mResolvedIntent, options); 1078 return true; 1079 } 1080 1081 @Override startAsCaller(Activity activity, Bundle options, int userId)1082 public boolean startAsCaller(Activity activity, Bundle options, int userId) { 1083 activity.startActivityAsCaller(mResolvedIntent, options, false, userId); 1084 return true; 1085 } 1086 1087 @Override startAsUser(Activity activity, Bundle options, UserHandle user)1088 public boolean startAsUser(Activity activity, Bundle options, UserHandle user) { 1089 activity.startActivityAsUser(mResolvedIntent, options, user); 1090 return false; 1091 } 1092 1093 @Override isPinned()1094 public boolean isPinned() { 1095 return mPinned; 1096 } 1097 setPinned(boolean pinned)1098 public void setPinned(boolean pinned) { 1099 mPinned = pinned; 1100 } 1101 } 1102 1103 /** 1104 * A single target as represented in the chooser. 1105 */ 1106 public interface TargetInfo { 1107 /** 1108 * Get the resolved intent that represents this target. Note that this may not be the 1109 * intent that will be launched by calling one of the <code>start</code> methods provided; 1110 * this is the intent that will be credited with the launch. 1111 * 1112 * @return the resolved intent for this target 1113 */ getResolvedIntent()1114 Intent getResolvedIntent(); 1115 1116 /** 1117 * Get the resolved component name that represents this target. Note that this may not 1118 * be the component that will be directly launched by calling one of the <code>start</code> 1119 * methods provided; this is the component that will be credited with the launch. 1120 * 1121 * @return the resolved ComponentName for this target 1122 */ getResolvedComponentName()1123 ComponentName getResolvedComponentName(); 1124 1125 /** 1126 * Start the activity referenced by this target. 1127 * 1128 * @param activity calling Activity performing the launch 1129 * @param options ActivityOptions bundle 1130 * @return true if the start completed successfully 1131 */ start(Activity activity, Bundle options)1132 boolean start(Activity activity, Bundle options); 1133 1134 /** 1135 * Start the activity referenced by this target as if the ResolverActivity's caller 1136 * was performing the start operation. 1137 * 1138 * @param activity calling Activity (actually) performing the launch 1139 * @param options ActivityOptions bundle 1140 * @param userId userId to start as or {@link UserHandle#USER_NULL} for activity's caller 1141 * @return true if the start completed successfully 1142 */ startAsCaller(Activity activity, Bundle options, int userId)1143 boolean startAsCaller(Activity activity, Bundle options, int userId); 1144 1145 /** 1146 * Start the activity referenced by this target as a given user. 1147 * 1148 * @param activity calling activity performing the launch 1149 * @param options ActivityOptions bundle 1150 * @param user handle for the user to start the activity as 1151 * @return true if the start completed successfully 1152 */ startAsUser(Activity activity, Bundle options, UserHandle user)1153 boolean startAsUser(Activity activity, Bundle options, UserHandle user); 1154 1155 /** 1156 * Return the ResolveInfo about how and why this target matched the original query 1157 * for available targets. 1158 * 1159 * @return ResolveInfo representing this target's match 1160 */ getResolveInfo()1161 ResolveInfo getResolveInfo(); 1162 1163 /** 1164 * Return the human-readable text label for this target. 1165 * 1166 * @return user-visible target label 1167 */ getDisplayLabel()1168 CharSequence getDisplayLabel(); 1169 1170 /** 1171 * Return any extended info for this target. This may be used to disambiguate 1172 * otherwise identical targets. 1173 * 1174 * @return human-readable disambig string or null if none present 1175 */ getExtendedInfo()1176 CharSequence getExtendedInfo(); 1177 1178 /** 1179 * @return The drawable that should be used to represent this target 1180 */ getDisplayIcon()1181 Drawable getDisplayIcon(); 1182 1183 /** 1184 * @return The (small) icon to badge the target with 1185 */ getBadgeIcon()1186 Drawable getBadgeIcon(); 1187 1188 /** 1189 * @return The content description for the badge icon 1190 */ getBadgeContentDescription()1191 CharSequence getBadgeContentDescription(); 1192 1193 /** 1194 * Clone this target with the given fill-in information. 1195 */ cloneFilledIn(Intent fillInIntent, int flags)1196 TargetInfo cloneFilledIn(Intent fillInIntent, int flags); 1197 1198 /** 1199 * @return the list of supported source intents deduped against this single target 1200 */ getAllSourceIntents()1201 List<Intent> getAllSourceIntents(); 1202 1203 /** 1204 * @return true if this target should be pinned to the front by the request of the user 1205 */ isPinned()1206 boolean isPinned(); 1207 } 1208 1209 public class ResolveListAdapter extends BaseAdapter { 1210 private final List<Intent> mIntents; 1211 private final Intent[] mInitialIntents; 1212 private final List<ResolveInfo> mBaseResolveList; 1213 private ResolveInfo mLastChosen; 1214 private DisplayResolveInfo mOtherProfile; 1215 private final int mLaunchedFromUid; 1216 private boolean mHasExtendedInfo; 1217 1218 protected final LayoutInflater mInflater; 1219 1220 List<DisplayResolveInfo> mDisplayList; 1221 List<ResolvedComponentInfo> mOrigResolveList; 1222 1223 private int mLastChosenPosition = -1; 1224 private boolean mFilterLastUsed; 1225 ResolveListAdapter(Context context, List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid, boolean filterLastUsed)1226 public ResolveListAdapter(Context context, List<Intent> payloadIntents, 1227 Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid, 1228 boolean filterLastUsed) { 1229 mIntents = payloadIntents; 1230 mInitialIntents = initialIntents; 1231 mBaseResolveList = rList; 1232 mLaunchedFromUid = launchedFromUid; 1233 mInflater = LayoutInflater.from(context); 1234 mDisplayList = new ArrayList<>(); 1235 mFilterLastUsed = filterLastUsed; 1236 rebuildList(); 1237 } 1238 handlePackagesChanged()1239 public void handlePackagesChanged() { 1240 rebuildList(); 1241 notifyDataSetChanged(); 1242 if (getCount() == 0) { 1243 // We no longer have any items... just finish the activity. 1244 finish(); 1245 } 1246 } 1247 getFilteredItem()1248 public DisplayResolveInfo getFilteredItem() { 1249 if (mFilterLastUsed && mLastChosenPosition >= 0) { 1250 // Not using getItem since it offsets to dodge this position for the list 1251 return mDisplayList.get(mLastChosenPosition); 1252 } 1253 return null; 1254 } 1255 getOtherProfile()1256 public DisplayResolveInfo getOtherProfile() { 1257 return mOtherProfile; 1258 } 1259 getFilteredPosition()1260 public int getFilteredPosition() { 1261 if (mFilterLastUsed && mLastChosenPosition >= 0) { 1262 return mLastChosenPosition; 1263 } 1264 return AbsListView.INVALID_POSITION; 1265 } 1266 hasFilteredItem()1267 public boolean hasFilteredItem() { 1268 return mFilterLastUsed && mLastChosenPosition >= 0; 1269 } 1270 getScore(DisplayResolveInfo target)1271 public float getScore(DisplayResolveInfo target) { 1272 return mResolverComparator.getScore(target.getResolvedComponentName()); 1273 } 1274 rebuildList()1275 private void rebuildList() { 1276 List<ResolvedComponentInfo> currentResolveList = null; 1277 1278 try { 1279 final Intent primaryIntent = getTargetIntent(); 1280 mLastChosen = AppGlobals.getPackageManager().getLastChosenActivity( 1281 primaryIntent, primaryIntent.resolveTypeIfNeeded(getContentResolver()), 1282 PackageManager.MATCH_DEFAULT_ONLY); 1283 } catch (RemoteException re) { 1284 Log.d(TAG, "Error calling setLastChosenActivity\n" + re); 1285 } 1286 1287 // Clear the value of mOtherProfile from previous call. 1288 mOtherProfile = null; 1289 mDisplayList.clear(); 1290 if (mBaseResolveList != null) { 1291 currentResolveList = mOrigResolveList = new ArrayList<>(); 1292 addResolveListDedupe(currentResolveList, getTargetIntent(), mBaseResolveList); 1293 } else { 1294 final boolean shouldGetResolvedFilter = shouldGetResolvedFilter(); 1295 final boolean shouldGetActivityMetadata = shouldGetActivityMetadata(); 1296 for (int i = 0, N = mIntents.size(); i < N; i++) { 1297 final Intent intent = mIntents.get(i); 1298 final List<ResolveInfo> infos = mPm.queryIntentActivities(intent, 1299 PackageManager.MATCH_DEFAULT_ONLY 1300 | (shouldGetResolvedFilter ? PackageManager.GET_RESOLVED_FILTER : 0) 1301 | (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0)); 1302 if (infos != null) { 1303 if (currentResolveList == null) { 1304 currentResolveList = mOrigResolveList = new ArrayList<>(); 1305 } 1306 addResolveListDedupe(currentResolveList, intent, infos); 1307 } 1308 } 1309 1310 // Filter out any activities that the launched uid does not 1311 // have permission for. 1312 // Also filter out those that are suspended because they couldn't 1313 // be started. We don't do this when we have an explicit 1314 // list of resolved activities, because that only happens when 1315 // we are being subclassed, so we can safely launch whatever 1316 // they gave us. 1317 if (currentResolveList != null) { 1318 for (int i=currentResolveList.size()-1; i >= 0; i--) { 1319 ActivityInfo ai = currentResolveList.get(i) 1320 .getResolveInfoAt(0).activityInfo; 1321 int granted = ActivityManager.checkComponentPermission( 1322 ai.permission, mLaunchedFromUid, 1323 ai.applicationInfo.uid, ai.exported); 1324 boolean suspended = (ai.applicationInfo.flags 1325 & ApplicationInfo.FLAG_SUSPENDED) != 0; 1326 if (granted != PackageManager.PERMISSION_GRANTED || suspended 1327 || isComponentFiltered(ai)) { 1328 // Access not allowed! 1329 if (mOrigResolveList == currentResolveList) { 1330 mOrigResolveList = new ArrayList<>(mOrigResolveList); 1331 } 1332 currentResolveList.remove(i); 1333 } 1334 } 1335 } 1336 } 1337 int N; 1338 if ((currentResolveList != null) && ((N = currentResolveList.size()) > 0)) { 1339 // Only display the first matches that are either of equal 1340 // priority or have asked to be default options. 1341 ResolvedComponentInfo rci0 = currentResolveList.get(0); 1342 ResolveInfo r0 = rci0.getResolveInfoAt(0); 1343 for (int i=1; i<N; i++) { 1344 ResolveInfo ri = currentResolveList.get(i).getResolveInfoAt(0); 1345 if (DEBUG) Log.v( 1346 TAG, 1347 r0.activityInfo.name + "=" + 1348 r0.priority + "/" + r0.isDefault + " vs " + 1349 ri.activityInfo.name + "=" + 1350 ri.priority + "/" + ri.isDefault); 1351 if (r0.priority != ri.priority || 1352 r0.isDefault != ri.isDefault) { 1353 while (i < N) { 1354 if (mOrigResolveList == currentResolveList) { 1355 mOrigResolveList = new ArrayList<>(mOrigResolveList); 1356 } 1357 currentResolveList.remove(i); 1358 N--; 1359 } 1360 } 1361 } 1362 if (N > 1) { 1363 mResolverComparator.compute(currentResolveList); 1364 Collections.sort(currentResolveList, mResolverComparator); 1365 } 1366 // First put the initial items at the top. 1367 if (mInitialIntents != null) { 1368 for (int i=0; i<mInitialIntents.length; i++) { 1369 Intent ii = mInitialIntents[i]; 1370 if (ii == null) { 1371 continue; 1372 } 1373 ActivityInfo ai = ii.resolveActivityInfo( 1374 getPackageManager(), 0); 1375 if (ai == null) { 1376 Log.w(TAG, "No activity found for " + ii); 1377 continue; 1378 } 1379 ResolveInfo ri = new ResolveInfo(); 1380 ri.activityInfo = ai; 1381 UserManager userManager = 1382 (UserManager) getSystemService(Context.USER_SERVICE); 1383 if (ii instanceof LabeledIntent) { 1384 LabeledIntent li = (LabeledIntent)ii; 1385 ri.resolvePackageName = li.getSourcePackage(); 1386 ri.labelRes = li.getLabelResource(); 1387 ri.nonLocalizedLabel = li.getNonLocalizedLabel(); 1388 ri.icon = li.getIconResource(); 1389 ri.iconResourceId = ri.icon; 1390 } 1391 if (userManager.isManagedProfile()) { 1392 ri.noResourceId = true; 1393 ri.icon = 0; 1394 } 1395 addResolveInfo(new DisplayResolveInfo(ii, ri, 1396 ri.loadLabel(getPackageManager()), null, ii)); 1397 } 1398 } 1399 1400 // Check for applications with same name and use application name or 1401 // package name if necessary 1402 rci0 = currentResolveList.get(0); 1403 r0 = rci0.getResolveInfoAt(0); 1404 int start = 0; 1405 CharSequence r0Label = r0.loadLabel(mPm); 1406 mHasExtendedInfo = false; 1407 for (int i = 1; i < N; i++) { 1408 if (r0Label == null) { 1409 r0Label = r0.activityInfo.packageName; 1410 } 1411 ResolvedComponentInfo rci = currentResolveList.get(i); 1412 ResolveInfo ri = rci.getResolveInfoAt(0); 1413 CharSequence riLabel = ri.loadLabel(mPm); 1414 if (riLabel == null) { 1415 riLabel = ri.activityInfo.packageName; 1416 } 1417 if (riLabel.equals(r0Label)) { 1418 continue; 1419 } 1420 processGroup(currentResolveList, start, (i-1), rci0, r0Label); 1421 rci0 = rci; 1422 r0 = ri; 1423 r0Label = riLabel; 1424 start = i; 1425 } 1426 // Process last group 1427 processGroup(currentResolveList, start, (N-1), rci0, r0Label); 1428 } 1429 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 onListRebuilt(); 1438 } 1439 addResolveListDedupe(List<ResolvedComponentInfo> into, Intent intent, List<ResolveInfo> from)1440 private void addResolveListDedupe(List<ResolvedComponentInfo> into, Intent intent, 1441 List<ResolveInfo> from) { 1442 final int fromCount = from.size(); 1443 final int intoCount = into.size(); 1444 for (int i = 0; i < fromCount; i++) { 1445 final ResolveInfo newInfo = from.get(i); 1446 boolean found = false; 1447 // Only loop to the end of into as it was before we started; no dupes in from. 1448 for (int j = 0; j < intoCount; j++) { 1449 final ResolvedComponentInfo rci = into.get(i); 1450 if (isSameResolvedComponent(newInfo, rci)) { 1451 found = true; 1452 rci.add(intent, newInfo); 1453 break; 1454 } 1455 } 1456 if (!found) { 1457 final ComponentName name = new ComponentName( 1458 newInfo.activityInfo.packageName, newInfo.activityInfo.name); 1459 final ResolvedComponentInfo rci = new ResolvedComponentInfo(name, 1460 intent, newInfo); 1461 rci.setPinned(isComponentPinned(name)); 1462 into.add(rci); 1463 } 1464 } 1465 } 1466 isSameResolvedComponent(ResolveInfo a, ResolvedComponentInfo b)1467 private boolean isSameResolvedComponent(ResolveInfo a, ResolvedComponentInfo b) { 1468 final ActivityInfo ai = a.activityInfo; 1469 return ai.packageName.equals(b.name.getPackageName()) 1470 && ai.name.equals(b.name.getClassName()); 1471 } 1472 onListRebuilt()1473 public void onListRebuilt() { 1474 // This space for rent 1475 } 1476 shouldGetResolvedFilter()1477 public boolean shouldGetResolvedFilter() { 1478 return mFilterLastUsed; 1479 } 1480 processGroup(List<ResolvedComponentInfo> rList, int start, int end, ResolvedComponentInfo ro, CharSequence roLabel)1481 private void processGroup(List<ResolvedComponentInfo> rList, int start, int end, 1482 ResolvedComponentInfo ro, CharSequence roLabel) { 1483 // Process labels from start to i 1484 int num = end - start+1; 1485 if (num == 1) { 1486 // No duplicate labels. Use label for entry at start 1487 addResolveInfoWithAlternates(ro, null, roLabel); 1488 } else { 1489 mHasExtendedInfo = true; 1490 boolean usePkg = false; 1491 final ApplicationInfo ai = ro.getResolveInfoAt(0).activityInfo.applicationInfo; 1492 final CharSequence startApp = ai.loadLabel(mPm); 1493 if (startApp == null) { 1494 usePkg = true; 1495 } 1496 if (!usePkg) { 1497 // Use HashSet to track duplicates 1498 HashSet<CharSequence> duplicates = 1499 new HashSet<CharSequence>(); 1500 duplicates.add(startApp); 1501 for (int j = start+1; j <= end ; j++) { 1502 ResolveInfo jRi = rList.get(j).getResolveInfoAt(0); 1503 CharSequence jApp = jRi.activityInfo.applicationInfo.loadLabel(mPm); 1504 if ( (jApp == null) || (duplicates.contains(jApp))) { 1505 usePkg = true; 1506 break; 1507 } else { 1508 duplicates.add(jApp); 1509 } 1510 } 1511 // Clear HashSet for later use 1512 duplicates.clear(); 1513 } 1514 for (int k = start; k <= end; k++) { 1515 final ResolvedComponentInfo rci = rList.get(k); 1516 final ResolveInfo add = rci.getResolveInfoAt(0); 1517 final CharSequence extraInfo; 1518 if (usePkg) { 1519 // Use package name for all entries from start to end-1 1520 extraInfo = add.activityInfo.packageName; 1521 } else { 1522 // Use application name for all entries from start to end-1 1523 extraInfo = add.activityInfo.applicationInfo.loadLabel(mPm); 1524 } 1525 addResolveInfoWithAlternates(rci, extraInfo, roLabel); 1526 } 1527 } 1528 } 1529 addResolveInfoWithAlternates(ResolvedComponentInfo rci, CharSequence extraInfo, CharSequence roLabel)1530 private void addResolveInfoWithAlternates(ResolvedComponentInfo rci, 1531 CharSequence extraInfo, CharSequence roLabel) { 1532 final int count = rci.getCount(); 1533 final Intent intent = rci.getIntentAt(0); 1534 final ResolveInfo add = rci.getResolveInfoAt(0); 1535 final Intent replaceIntent = getReplacementIntent(add.activityInfo, intent); 1536 final DisplayResolveInfo dri = new DisplayResolveInfo(intent, add, roLabel, 1537 extraInfo, replaceIntent); 1538 dri.setPinned(rci.isPinned()); 1539 addResolveInfo(dri); 1540 if (replaceIntent == intent) { 1541 // Only add alternates if we didn't get a specific replacement from 1542 // the caller. If we have one it trumps potential alternates. 1543 for (int i = 1, N = count; i < N; i++) { 1544 final Intent altIntent = rci.getIntentAt(i); 1545 dri.addAlternateSourceIntent(altIntent); 1546 } 1547 } 1548 updateLastChosenPosition(add); 1549 } 1550 updateLastChosenPosition(ResolveInfo info)1551 private void updateLastChosenPosition(ResolveInfo info) { 1552 if (mLastChosen != null 1553 && mLastChosen.activityInfo.packageName.equals(info.activityInfo.packageName) 1554 && mLastChosen.activityInfo.name.equals(info.activityInfo.name)) { 1555 mLastChosenPosition = mDisplayList.size() - 1; 1556 } 1557 } 1558 addResolveInfo(DisplayResolveInfo dri)1559 private void addResolveInfo(DisplayResolveInfo dri) { 1560 if (dri.mResolveInfo.targetUserId != UserHandle.USER_CURRENT && mOtherProfile == null) { 1561 // So far we only support a single other profile at a time. 1562 // The first one we see gets special treatment. 1563 mOtherProfile = dri; 1564 } else { 1565 mDisplayList.add(dri); 1566 } 1567 } 1568 resolveInfoForPosition(int position, boolean filtered)1569 public ResolveInfo resolveInfoForPosition(int position, boolean filtered) { 1570 return (filtered ? getItem(position) : mDisplayList.get(position)) 1571 .getResolveInfo(); 1572 } 1573 targetInfoForPosition(int position, boolean filtered)1574 public TargetInfo targetInfoForPosition(int position, boolean filtered) { 1575 return filtered ? getItem(position) : mDisplayList.get(position); 1576 } 1577 getCount()1578 public int getCount() { 1579 int result = mDisplayList.size(); 1580 if (mFilterLastUsed && mLastChosenPosition >= 0) { 1581 result--; 1582 } 1583 return result; 1584 } 1585 getUnfilteredCount()1586 public int getUnfilteredCount() { 1587 return mDisplayList.size(); 1588 } 1589 getDisplayInfoCount()1590 public int getDisplayInfoCount() { 1591 return mDisplayList.size(); 1592 } 1593 getDisplayInfoAt(int index)1594 public DisplayResolveInfo getDisplayInfoAt(int index) { 1595 return mDisplayList.get(index); 1596 } 1597 getItem(int position)1598 public TargetInfo getItem(int position) { 1599 if (mFilterLastUsed && mLastChosenPosition >= 0 && position >= mLastChosenPosition) { 1600 position++; 1601 } 1602 return mDisplayList.get(position); 1603 } 1604 getItemId(int position)1605 public long getItemId(int position) { 1606 return position; 1607 } 1608 hasExtendedInfo()1609 public boolean hasExtendedInfo() { 1610 return mHasExtendedInfo; 1611 } 1612 hasResolvedTarget(ResolveInfo info)1613 public boolean hasResolvedTarget(ResolveInfo info) { 1614 for (int i = 0, N = mDisplayList.size(); i < N; i++) { 1615 if (resolveInfoMatch(info, mDisplayList.get(i).getResolveInfo())) { 1616 return true; 1617 } 1618 } 1619 return false; 1620 } 1621 getDisplayResolveInfoCount()1622 public int getDisplayResolveInfoCount() { 1623 return mDisplayList.size(); 1624 } 1625 getDisplayResolveInfo(int index)1626 public DisplayResolveInfo getDisplayResolveInfo(int index) { 1627 // Used to query services. We only query services for primary targets, not alternates. 1628 return mDisplayList.get(index); 1629 } 1630 getView(int position, View convertView, ViewGroup parent)1631 public final View getView(int position, View convertView, ViewGroup parent) { 1632 View view = convertView; 1633 if (view == null) { 1634 view = createView(parent); 1635 } 1636 onBindView(view, getItem(position)); 1637 return view; 1638 } 1639 createView(ViewGroup parent)1640 public final View createView(ViewGroup parent) { 1641 final View view = onCreateView(parent); 1642 final ViewHolder holder = new ViewHolder(view); 1643 view.setTag(holder); 1644 return view; 1645 } 1646 onCreateView(ViewGroup parent)1647 public View onCreateView(ViewGroup parent) { 1648 return mInflater.inflate( 1649 com.android.internal.R.layout.resolve_list_item, parent, false); 1650 } 1651 showsExtendedInfo(TargetInfo info)1652 public boolean showsExtendedInfo(TargetInfo info) { 1653 return !TextUtils.isEmpty(info.getExtendedInfo()); 1654 } 1655 isComponentPinned(ComponentName name)1656 public boolean isComponentPinned(ComponentName name) { 1657 return false; 1658 } 1659 bindView(int position, View view)1660 public final void bindView(int position, View view) { 1661 onBindView(view, getItem(position)); 1662 } 1663 onBindView(View view, TargetInfo info)1664 private void onBindView(View view, TargetInfo info) { 1665 final ViewHolder holder = (ViewHolder) view.getTag(); 1666 final CharSequence label = info.getDisplayLabel(); 1667 if (!TextUtils.equals(holder.text.getText(), label)) { 1668 holder.text.setText(info.getDisplayLabel()); 1669 } 1670 if (showsExtendedInfo(info)) { 1671 holder.text2.setVisibility(View.VISIBLE); 1672 holder.text2.setText(info.getExtendedInfo()); 1673 } else { 1674 holder.text2.setVisibility(View.GONE); 1675 } 1676 if (info instanceof DisplayResolveInfo 1677 && !((DisplayResolveInfo) info).hasDisplayIcon()) { 1678 new LoadAdapterIconTask((DisplayResolveInfo) info).execute(); 1679 } 1680 holder.icon.setImageDrawable(info.getDisplayIcon()); 1681 if (holder.badge != null) { 1682 final Drawable badge = info.getBadgeIcon(); 1683 if (badge != null) { 1684 holder.badge.setImageDrawable(badge); 1685 holder.badge.setContentDescription(info.getBadgeContentDescription()); 1686 holder.badge.setVisibility(View.VISIBLE); 1687 } else { 1688 holder.badge.setVisibility(View.GONE); 1689 } 1690 } 1691 } 1692 } 1693 1694 static final class ResolvedComponentInfo { 1695 public final ComponentName name; 1696 private boolean mPinned; 1697 private final List<Intent> mIntents = new ArrayList<>(); 1698 private final List<ResolveInfo> mResolveInfos = new ArrayList<>(); 1699 ResolvedComponentInfo(ComponentName name, Intent intent, ResolveInfo info)1700 public ResolvedComponentInfo(ComponentName name, Intent intent, ResolveInfo info) { 1701 this.name = name; 1702 add(intent, info); 1703 } 1704 add(Intent intent, ResolveInfo info)1705 public void add(Intent intent, ResolveInfo info) { 1706 mIntents.add(intent); 1707 mResolveInfos.add(info); 1708 } 1709 getCount()1710 public int getCount() { 1711 return mIntents.size(); 1712 } 1713 getIntentAt(int index)1714 public Intent getIntentAt(int index) { 1715 return index >= 0 ? mIntents.get(index) : null; 1716 } 1717 getResolveInfoAt(int index)1718 public ResolveInfo getResolveInfoAt(int index) { 1719 return index >= 0 ? mResolveInfos.get(index) : null; 1720 } 1721 findIntent(Intent intent)1722 public int findIntent(Intent intent) { 1723 for (int i = 0, N = mIntents.size(); i < N; i++) { 1724 if (intent.equals(mIntents.get(i))) { 1725 return i; 1726 } 1727 } 1728 return -1; 1729 } 1730 findResolveInfo(ResolveInfo info)1731 public int findResolveInfo(ResolveInfo info) { 1732 for (int i = 0, N = mResolveInfos.size(); i < N; i++) { 1733 if (info.equals(mResolveInfos.get(i))) { 1734 return i; 1735 } 1736 } 1737 return -1; 1738 } 1739 isPinned()1740 public boolean isPinned() { 1741 return mPinned; 1742 } 1743 setPinned(boolean pinned)1744 public void setPinned(boolean pinned) { 1745 mPinned = pinned; 1746 } 1747 } 1748 1749 static class ViewHolder { 1750 public TextView text; 1751 public TextView text2; 1752 public ImageView icon; 1753 public ImageView badge; 1754 ViewHolder(View view)1755 public ViewHolder(View view) { 1756 text = (TextView) view.findViewById(com.android.internal.R.id.text1); 1757 text2 = (TextView) view.findViewById(com.android.internal.R.id.text2); 1758 icon = (ImageView) view.findViewById(R.id.icon); 1759 badge = (ImageView) view.findViewById(R.id.target_badge); 1760 } 1761 } 1762 1763 class ItemClickListener implements AdapterView.OnItemClickListener, 1764 AdapterView.OnItemLongClickListener { 1765 @Override onItemClick(AdapterView<?> parent, View view, int position, long id)1766 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 1767 final ListView listView = parent instanceof ListView ? (ListView) parent : null; 1768 if (listView != null) { 1769 position -= listView.getHeaderViewsCount(); 1770 } 1771 if (position < 0) { 1772 // Header views don't count. 1773 return; 1774 } 1775 final int checkedPos = mAdapterView.getCheckedItemPosition(); 1776 final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION; 1777 if (mAlwaysUseOption && (!hasValidSelection || mLastSelected != checkedPos)) { 1778 setAlwaysButtonEnabled(hasValidSelection, checkedPos, true); 1779 mOnceButton.setEnabled(hasValidSelection); 1780 if (hasValidSelection) { 1781 mAdapterView.smoothScrollToPosition(checkedPos); 1782 } 1783 mLastSelected = checkedPos; 1784 } else { 1785 startSelected(position, false, true); 1786 } 1787 } 1788 1789 @Override onItemLongClick(AdapterView<?> parent, View view, int position, long id)1790 public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { 1791 final ListView listView = parent instanceof ListView ? (ListView) parent : null; 1792 if (listView != null) { 1793 position -= listView.getHeaderViewsCount(); 1794 } 1795 if (position < 0) { 1796 // Header views don't count. 1797 return false; 1798 } 1799 ResolveInfo ri = mAdapter.resolveInfoForPosition(position, true); 1800 showTargetDetails(ri); 1801 return true; 1802 } 1803 1804 } 1805 1806 abstract class LoadIconTask extends AsyncTask<Void, Void, Drawable> { 1807 protected final DisplayResolveInfo mDisplayResolveInfo; 1808 private final ResolveInfo mResolveInfo; 1809 LoadIconTask(DisplayResolveInfo dri)1810 public LoadIconTask(DisplayResolveInfo dri) { 1811 mDisplayResolveInfo = dri; 1812 mResolveInfo = dri.getResolveInfo(); 1813 } 1814 1815 @Override doInBackground(Void... params)1816 protected Drawable doInBackground(Void... params) { 1817 return loadIconForResolveInfo(mResolveInfo); 1818 } 1819 1820 @Override onPostExecute(Drawable d)1821 protected void onPostExecute(Drawable d) { 1822 mDisplayResolveInfo.setDisplayIcon(d); 1823 } 1824 } 1825 1826 class LoadAdapterIconTask extends LoadIconTask { LoadAdapterIconTask(DisplayResolveInfo dri)1827 public LoadAdapterIconTask(DisplayResolveInfo dri) { 1828 super(dri); 1829 } 1830 1831 @Override onPostExecute(Drawable d)1832 protected void onPostExecute(Drawable d) { 1833 super.onPostExecute(d); 1834 if (mProfileView != null && mAdapter.getOtherProfile() == mDisplayResolveInfo) { 1835 bindProfileView(); 1836 } 1837 mAdapter.notifyDataSetChanged(); 1838 } 1839 } 1840 1841 class LoadIconIntoViewTask extends LoadIconTask { 1842 private final ImageView mTargetView; 1843 LoadIconIntoViewTask(DisplayResolveInfo dri, ImageView target)1844 public LoadIconIntoViewTask(DisplayResolveInfo dri, ImageView target) { 1845 super(dri); 1846 mTargetView = target; 1847 } 1848 1849 @Override onPostExecute(Drawable d)1850 protected void onPostExecute(Drawable d) { 1851 super.onPostExecute(d); 1852 mTargetView.setImageDrawable(d); 1853 } 1854 } 1855 isSpecificUriMatch(int match)1856 static final boolean isSpecificUriMatch(int match) { 1857 match = match&IntentFilter.MATCH_CATEGORY_MASK; 1858 return match >= IntentFilter.MATCH_CATEGORY_HOST 1859 && match <= IntentFilter.MATCH_CATEGORY_PATH; 1860 } 1861 1862 static class PickTargetOptionRequest extends PickOptionRequest { PickTargetOptionRequest(@ullable Prompt prompt, Option[] options, @Nullable Bundle extras)1863 public PickTargetOptionRequest(@Nullable Prompt prompt, Option[] options, 1864 @Nullable Bundle extras) { 1865 super(prompt, options, extras); 1866 } 1867 1868 @Override onCancel()1869 public void onCancel() { 1870 super.onCancel(); 1871 final ResolverActivity ra = (ResolverActivity) getActivity(); 1872 if (ra != null) { 1873 ra.mPickOptionRequest = null; 1874 ra.finish(); 1875 } 1876 } 1877 1878 @Override onPickOptionResult(boolean finished, Option[] selections, Bundle result)1879 public void onPickOptionResult(boolean finished, Option[] selections, Bundle result) { 1880 super.onPickOptionResult(finished, selections, result); 1881 if (selections.length != 1) { 1882 // TODO In a better world we would filter the UI presented here and let the 1883 // user refine. Maybe later. 1884 return; 1885 } 1886 1887 final ResolverActivity ra = (ResolverActivity) getActivity(); 1888 if (ra != null) { 1889 final TargetInfo ti = ra.mAdapter.getItem(selections[0].getIndex()); 1890 if (ra.onTargetSelected(ti, false)) { 1891 ra.mPickOptionRequest = null; 1892 ra.finish(); 1893 } 1894 } 1895 } 1896 } 1897 } 1898