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