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