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