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