1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.internal.app; 18 19 import static android.Manifest.permission.INTERACT_ACROSS_PROFILES; 20 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; 21 import static android.content.PermissionChecker.PID_UNKNOWN; 22 23 import android.annotation.Nullable; 24 import android.annotation.StringRes; 25 import android.annotation.UiThread; 26 import android.app.Activity; 27 import android.app.ActivityManager; 28 import android.app.ActivityTaskManager; 29 import android.app.ActivityThread; 30 import android.app.VoiceInteractor.PickOptionRequest; 31 import android.app.VoiceInteractor.PickOptionRequest.Option; 32 import android.app.VoiceInteractor.Prompt; 33 import android.app.admin.DevicePolicyEventLogger; 34 import android.compat.annotation.UnsupportedAppUsage; 35 import android.content.BroadcastReceiver; 36 import android.content.ComponentName; 37 import android.content.Context; 38 import android.content.Intent; 39 import android.content.IntentFilter; 40 import android.content.PermissionChecker; 41 import android.content.pm.ActivityInfo; 42 import android.content.pm.ApplicationInfo; 43 import android.content.pm.PackageManager; 44 import android.content.pm.PackageManager.NameNotFoundException; 45 import android.content.pm.ResolveInfo; 46 import android.content.pm.UserInfo; 47 import android.content.res.Configuration; 48 import android.content.res.Resources; 49 import android.content.res.TypedArray; 50 import android.graphics.Insets; 51 import android.net.Uri; 52 import android.os.Build; 53 import android.os.Bundle; 54 import android.os.IBinder; 55 import android.os.PatternMatcher; 56 import android.os.RemoteException; 57 import android.os.StrictMode; 58 import android.os.UserHandle; 59 import android.os.UserManager; 60 import android.provider.MediaStore; 61 import android.provider.Settings; 62 import android.stats.devicepolicy.DevicePolicyEnums; 63 import android.text.TextUtils; 64 import android.util.Log; 65 import android.util.Slog; 66 import android.util.TypedValue; 67 import android.view.Gravity; 68 import android.view.LayoutInflater; 69 import android.view.View; 70 import android.view.ViewGroup; 71 import android.view.ViewGroup.LayoutParams; 72 import android.view.WindowInsets; 73 import android.widget.AbsListView; 74 import android.widget.AdapterView; 75 import android.widget.Button; 76 import android.widget.FrameLayout; 77 import android.widget.ImageView; 78 import android.widget.ListView; 79 import android.widget.Space; 80 import android.widget.TabHost; 81 import android.widget.TabWidget; 82 import android.widget.TextView; 83 import android.widget.Toast; 84 85 import com.android.internal.R; 86 import com.android.internal.annotations.VisibleForTesting; 87 import com.android.internal.app.AbstractMultiProfilePagerAdapter.Profile; 88 import com.android.internal.app.chooser.ChooserTargetInfo; 89 import com.android.internal.app.chooser.DisplayResolveInfo; 90 import com.android.internal.app.chooser.TargetInfo; 91 import com.android.internal.content.PackageMonitor; 92 import com.android.internal.logging.MetricsLogger; 93 import com.android.internal.logging.nano.MetricsProto; 94 import com.android.internal.widget.ResolverDrawerLayout; 95 import com.android.internal.widget.ViewPager; 96 97 import java.util.ArrayList; 98 import java.util.Arrays; 99 import java.util.Iterator; 100 import java.util.List; 101 import java.util.Objects; 102 import java.util.Set; 103 104 105 /** 106 * This activity is displayed when the system attempts to start an Intent for 107 * which there is more than one matching activity, allowing the user to decide 108 * which to go to. It is not normally used directly by application developers. 109 */ 110 @UiThread 111 public class ResolverActivity extends Activity implements 112 ResolverListAdapter.ResolverListCommunicator { 113 114 @UnsupportedAppUsage ResolverActivity()115 public ResolverActivity() { 116 } 117 118 private boolean mSafeForwardingMode; 119 private Button mAlwaysButton; 120 private Button mOnceButton; 121 protected View mProfileView; 122 private int mLastSelected = AbsListView.INVALID_POSITION; 123 private boolean mResolvingHome = false; 124 private int mProfileSwitchMessageId = -1; 125 private int mLayoutId; 126 @VisibleForTesting 127 protected final ArrayList<Intent> mIntents = new ArrayList<>(); 128 private PickTargetOptionRequest mPickOptionRequest; 129 private String mReferrerPackage; 130 private CharSequence mTitle; 131 private int mDefaultTitleResId; 132 133 // Whether or not this activity supports choosing a default handler for the intent. 134 @VisibleForTesting 135 protected boolean mSupportsAlwaysUseOption; 136 protected ResolverDrawerLayout mResolverDrawerLayout; 137 @UnsupportedAppUsage 138 protected PackageManager mPm; 139 protected int mLaunchedFromUid; 140 141 private static final String TAG = "ResolverActivity"; 142 private static final boolean DEBUG = false; 143 private static final String LAST_SHOWN_TAB_KEY = "last_shown_tab_key"; 144 145 private boolean mRegistered; 146 147 protected Insets mSystemWindowInsets = null; 148 private Space mFooterSpacer = null; 149 150 /** See {@link #setRetainInOnStop}. */ 151 private boolean mRetainInOnStop; 152 153 private static final String EXTRA_SHOW_FRAGMENT_ARGS = ":settings:show_fragment_args"; 154 private static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key"; 155 private static final String OPEN_LINKS_COMPONENT_KEY = "app_link_state"; 156 protected static final String METRICS_CATEGORY_RESOLVER = "intent_resolver"; 157 protected static final String METRICS_CATEGORY_CHOOSER = "intent_chooser"; 158 159 /** Tracks if we should ignore future broadcasts telling us the work profile is enabled */ 160 private boolean mWorkProfileHasBeenEnabled = false; 161 162 @VisibleForTesting 163 public static boolean ENABLE_TABBED_VIEW = true; 164 private static final String TAB_TAG_PERSONAL = "personal"; 165 private static final String TAB_TAG_WORK = "work"; 166 167 private PackageMonitor mPersonalPackageMonitor; 168 private PackageMonitor mWorkPackageMonitor; 169 170 @VisibleForTesting 171 protected AbstractMultiProfilePagerAdapter mMultiProfilePagerAdapter; 172 173 // Intent extra for connected audio devices 174 public static final String EXTRA_IS_AUDIO_CAPTURE_DEVICE = "is_audio_capture_device"; 175 176 /** 177 * Integer extra to indicate which profile should be automatically selected. 178 * <p>Can only be used if there is a work profile. 179 * <p>Possible values can be either {@link #PROFILE_PERSONAL} or {@link #PROFILE_WORK}. 180 */ 181 static final String EXTRA_SELECTED_PROFILE = 182 "com.android.internal.app.ResolverActivity.EXTRA_SELECTED_PROFILE"; 183 184 /** 185 * {@link UserHandle} extra to indicate the user of the user that the starting intent 186 * originated from. 187 * <p>This is not necessarily the same as {@link #getUserId()} or {@link UserHandle#myUserId()}, 188 * as there are edge cases when the intent resolver is launched in the other profile. 189 * For example, when we have 0 resolved apps in current profile and multiple resolved 190 * apps in the other profile, opening a link from the current profile launches the intent 191 * resolver in the other one. b/148536209 for more info. 192 */ 193 static final String EXTRA_CALLING_USER = 194 "com.android.internal.app.ResolverActivity.EXTRA_CALLING_USER"; 195 196 static final int PROFILE_PERSONAL = AbstractMultiProfilePagerAdapter.PROFILE_PERSONAL; 197 static final int PROFILE_WORK = AbstractMultiProfilePagerAdapter.PROFILE_WORK; 198 199 private BroadcastReceiver mWorkProfileStateReceiver; 200 private UserHandle mHeaderCreatorUser; 201 202 private UserHandle mWorkProfileUserHandle; 203 204 /** 205 * Get the string resource to be used as a label for the link to the resolver activity for an 206 * action. 207 * 208 * @param action The action to resolve 209 * 210 * @return The string resource to be used as a label 211 */ getLabelRes(String action)212 public static @StringRes int getLabelRes(String action) { 213 return ActionTitle.forAction(action).labelRes; 214 } 215 216 private enum ActionTitle { 217 VIEW(Intent.ACTION_VIEW, 218 com.android.internal.R.string.whichViewApplication, 219 com.android.internal.R.string.whichViewApplicationNamed, 220 com.android.internal.R.string.whichViewApplicationLabel), 221 EDIT(Intent.ACTION_EDIT, 222 com.android.internal.R.string.whichEditApplication, 223 com.android.internal.R.string.whichEditApplicationNamed, 224 com.android.internal.R.string.whichEditApplicationLabel), 225 SEND(Intent.ACTION_SEND, 226 com.android.internal.R.string.whichSendApplication, 227 com.android.internal.R.string.whichSendApplicationNamed, 228 com.android.internal.R.string.whichSendApplicationLabel), 229 SENDTO(Intent.ACTION_SENDTO, 230 com.android.internal.R.string.whichSendToApplication, 231 com.android.internal.R.string.whichSendToApplicationNamed, 232 com.android.internal.R.string.whichSendToApplicationLabel), 233 SEND_MULTIPLE(Intent.ACTION_SEND_MULTIPLE, 234 com.android.internal.R.string.whichSendApplication, 235 com.android.internal.R.string.whichSendApplicationNamed, 236 com.android.internal.R.string.whichSendApplicationLabel), 237 CAPTURE_IMAGE(MediaStore.ACTION_IMAGE_CAPTURE, 238 com.android.internal.R.string.whichImageCaptureApplication, 239 com.android.internal.R.string.whichImageCaptureApplicationNamed, 240 com.android.internal.R.string.whichImageCaptureApplicationLabel), 241 DEFAULT(null, 242 com.android.internal.R.string.whichApplication, 243 com.android.internal.R.string.whichApplicationNamed, 244 com.android.internal.R.string.whichApplicationLabel), 245 HOME(Intent.ACTION_MAIN, 246 com.android.internal.R.string.whichHomeApplication, 247 com.android.internal.R.string.whichHomeApplicationNamed, 248 com.android.internal.R.string.whichHomeApplicationLabel); 249 250 // titles for layout that deals with http(s) intents 251 public static final int BROWSABLE_TITLE_RES = 252 com.android.internal.R.string.whichOpenLinksWith; 253 public static final int BROWSABLE_HOST_TITLE_RES = 254 com.android.internal.R.string.whichOpenHostLinksWith; 255 public static final int BROWSABLE_HOST_APP_TITLE_RES = 256 com.android.internal.R.string.whichOpenHostLinksWithApp; 257 public static final int BROWSABLE_APP_TITLE_RES = 258 com.android.internal.R.string.whichOpenLinksWithApp; 259 260 public final String action; 261 public final int titleRes; 262 public final int namedTitleRes; 263 public final @StringRes int labelRes; 264 ActionTitle(String action, int titleRes, int namedTitleRes, @StringRes int labelRes)265 ActionTitle(String action, int titleRes, int namedTitleRes, @StringRes int labelRes) { 266 this.action = action; 267 this.titleRes = titleRes; 268 this.namedTitleRes = namedTitleRes; 269 this.labelRes = labelRes; 270 } 271 forAction(String action)272 public static ActionTitle forAction(String action) { 273 for (ActionTitle title : values()) { 274 if (title != HOME && action != null && action.equals(title.action)) { 275 return title; 276 } 277 } 278 return DEFAULT; 279 } 280 } 281 createPackageMonitor(ResolverListAdapter listAdapter)282 protected PackageMonitor createPackageMonitor(ResolverListAdapter listAdapter) { 283 return new PackageMonitor() { 284 @Override 285 public void onSomePackagesChanged() { 286 listAdapter.handlePackagesChanged(); 287 updateProfileViewButton(); 288 } 289 290 @Override 291 public boolean onPackageChanged(String packageName, int uid, String[] components) { 292 // We care about all package changes, not just the whole package itself which is 293 // default behavior. 294 return true; 295 } 296 }; 297 } 298 299 private Intent makeMyIntent() { 300 Intent intent = new Intent(getIntent()); 301 intent.setComponent(null); 302 // The resolver activity is set to be hidden from recent tasks. 303 // we don't want this attribute to be propagated to the next activity 304 // being launched. Note that if the original Intent also had this 305 // flag set, we are now losing it. That should be a very rare case 306 // and we can live with this. 307 intent.setFlags(intent.getFlags()&~Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 308 return intent; 309 } 310 311 @Override 312 protected void onCreate(Bundle savedInstanceState) { 313 // Use a specialized prompt when we're handling the 'Home' app startActivity() 314 final Intent intent = makeMyIntent(); 315 final Set<String> categories = intent.getCategories(); 316 if (Intent.ACTION_MAIN.equals(intent.getAction()) 317 && categories != null 318 && categories.size() == 1 319 && categories.contains(Intent.CATEGORY_HOME)) { 320 // Note: this field is not set to true in the compatibility version. 321 mResolvingHome = true; 322 } 323 324 setSafeForwardingMode(true); 325 326 onCreate(savedInstanceState, intent, null, 0, null, null, true); 327 } 328 329 /** 330 * Compatibility version for other bundled services that use this overload without 331 * a default title resource 332 */ 333 @UnsupportedAppUsage 334 protected void onCreate(Bundle savedInstanceState, Intent intent, 335 CharSequence title, Intent[] initialIntents, 336 List<ResolveInfo> rList, boolean supportsAlwaysUseOption) { 337 onCreate(savedInstanceState, intent, title, 0, initialIntents, rList, 338 supportsAlwaysUseOption); 339 } 340 341 protected void onCreate(Bundle savedInstanceState, Intent intent, 342 CharSequence title, int defaultTitleRes, Intent[] initialIntents, 343 List<ResolveInfo> rList, boolean supportsAlwaysUseOption) { 344 setTheme(appliedThemeResId()); 345 super.onCreate(savedInstanceState); 346 347 // Determine whether we should show that intent is forwarded 348 // from managed profile to owner or other way around. 349 setProfileSwitchMessageId(intent.getContentUserHint()); 350 351 try { 352 mLaunchedFromUid = ActivityTaskManager.getService().getLaunchedFromUid( 353 getActivityToken()); 354 } catch (RemoteException e) { 355 mLaunchedFromUid = -1; 356 } 357 358 if (mLaunchedFromUid < 0 || UserHandle.isIsolated(mLaunchedFromUid)) { 359 // Gulp! 360 finish(); 361 return; 362 } 363 364 mPm = getPackageManager(); 365 366 mReferrerPackage = getReferrerPackageName(); 367 368 // Add our initial intent as the first item, regardless of what else has already been added. 369 mIntents.add(0, new Intent(intent)); 370 mTitle = title; 371 mDefaultTitleResId = defaultTitleRes; 372 373 mSupportsAlwaysUseOption = supportsAlwaysUseOption; 374 mWorkProfileUserHandle = fetchWorkProfileUserProfile(); 375 376 // The last argument of createResolverListAdapter is whether to do special handling 377 // of the last used choice to highlight it in the list. We need to always 378 // turn this off when running under voice interaction, since it results in 379 // a more complicated UI that the current voice interaction flow is not able 380 // to handle. We also turn it off when the work tab is shown to simplify the UX. 381 boolean filterLastUsed = mSupportsAlwaysUseOption && !isVoiceInteraction() 382 && !shouldShowTabs(); 383 mMultiProfilePagerAdapter = createMultiProfilePagerAdapter(initialIntents, rList, filterLastUsed); 384 if (configureContentView()) { 385 return; 386 } 387 388 mPersonalPackageMonitor = createPackageMonitor( 389 mMultiProfilePagerAdapter.getPersonalListAdapter()); 390 mPersonalPackageMonitor.register( 391 this, getMainLooper(), getPersonalProfileUserHandle(), false); 392 if (shouldShowTabs()) { 393 mWorkPackageMonitor = createPackageMonitor( 394 mMultiProfilePagerAdapter.getWorkListAdapter()); 395 mWorkPackageMonitor.register(this, getMainLooper(), getWorkProfileUserHandle(), false); 396 } 397 398 mRegistered = true; 399 400 final ResolverDrawerLayout rdl = findViewById(R.id.contentPanel); 401 if (rdl != null) { 402 rdl.setOnDismissedListener(new ResolverDrawerLayout.OnDismissedListener() { 403 @Override 404 public void onDismissed() { 405 finish(); 406 } 407 }); 408 409 boolean hasTouchScreen = getPackageManager() 410 .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN); 411 412 if (isVoiceInteraction() || !hasTouchScreen) { 413 rdl.setCollapsed(false); 414 } 415 416 rdl.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 417 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); 418 rdl.setOnApplyWindowInsetsListener(this::onApplyWindowInsets); 419 420 mResolverDrawerLayout = rdl; 421 } 422 423 mProfileView = findViewById(R.id.profile_button); 424 if (mProfileView != null) { 425 mProfileView.setOnClickListener(this::onProfileClick); 426 updateProfileViewButton(); 427 } 428 429 final Set<String> categories = intent.getCategories(); 430 MetricsLogger.action(this, mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem() 431 ? MetricsProto.MetricsEvent.ACTION_SHOW_APP_DISAMBIG_APP_FEATURED 432 : MetricsProto.MetricsEvent.ACTION_SHOW_APP_DISAMBIG_NONE_FEATURED, 433 intent.getAction() + ":" + intent.getType() + ":" 434 + (categories != null ? Arrays.toString(categories.toArray()) : "")); 435 } 436 437 private boolean isIntentPicker() { 438 return getClass().equals(ResolverActivity.class); 439 } 440 441 protected AbstractMultiProfilePagerAdapter createMultiProfilePagerAdapter( 442 Intent[] initialIntents, 443 List<ResolveInfo> rList, 444 boolean filterLastUsed) { 445 AbstractMultiProfilePagerAdapter resolverMultiProfilePagerAdapter = null; 446 if (shouldShowTabs()) { 447 resolverMultiProfilePagerAdapter = 448 createResolverMultiProfilePagerAdapterForTwoProfiles( 449 initialIntents, rList, filterLastUsed); 450 } else { 451 resolverMultiProfilePagerAdapter = createResolverMultiProfilePagerAdapterForOneProfile( 452 initialIntents, rList, filterLastUsed); 453 } 454 return resolverMultiProfilePagerAdapter; 455 } 456 457 private ResolverMultiProfilePagerAdapter createResolverMultiProfilePagerAdapterForOneProfile( 458 Intent[] initialIntents, 459 List<ResolveInfo> rList, boolean filterLastUsed) { 460 ResolverListAdapter adapter = createResolverListAdapter( 461 /* context */ this, 462 /* payloadIntents */ mIntents, 463 initialIntents, 464 rList, 465 filterLastUsed, 466 /* userHandle */ UserHandle.of(UserHandle.myUserId())); 467 return new ResolverMultiProfilePagerAdapter( 468 /* context */ this, 469 adapter, 470 getPersonalProfileUserHandle(), 471 /* workProfileUserHandle= */ null); 472 } 473 474 private ResolverMultiProfilePagerAdapter createResolverMultiProfilePagerAdapterForTwoProfiles( 475 Intent[] initialIntents, 476 List<ResolveInfo> rList, 477 boolean filterLastUsed) { 478 // In the edge case when we have 0 apps in the current profile and >1 apps in the other, 479 // the intent resolver is started in the other profile. Since this is the only case when 480 // this happens, we check for it here and set the current profile's tab. 481 int selectedProfile = getCurrentProfile(); 482 UserHandle intentUser = getIntent().hasExtra(EXTRA_CALLING_USER) 483 ? getIntent().getParcelableExtra(EXTRA_CALLING_USER) 484 : getUser(); 485 if (!getUser().equals(intentUser)) { 486 if (getPersonalProfileUserHandle().equals(intentUser)) { 487 selectedProfile = PROFILE_PERSONAL; 488 } else if (getWorkProfileUserHandle().equals(intentUser)) { 489 selectedProfile = PROFILE_WORK; 490 } 491 } else { 492 int selectedProfileExtra = getSelectedProfileExtra(); 493 if (selectedProfileExtra != -1) { 494 selectedProfile = selectedProfileExtra; 495 } 496 } 497 // We only show the default app for the profile of the current user. The filterLastUsed 498 // flag determines whether to show a default app and that app is not shown in the 499 // resolver list. So filterLastUsed should be false for the other profile. 500 ResolverListAdapter personalAdapter = createResolverListAdapter( 501 /* context */ this, 502 /* payloadIntents */ mIntents, 503 selectedProfile == PROFILE_PERSONAL ? initialIntents : null, 504 rList, 505 (filterLastUsed && UserHandle.myUserId() 506 == getPersonalProfileUserHandle().getIdentifier()), 507 /* userHandle */ getPersonalProfileUserHandle()); 508 UserHandle workProfileUserHandle = getWorkProfileUserHandle(); 509 ResolverListAdapter workAdapter = createResolverListAdapter( 510 /* context */ this, 511 /* payloadIntents */ mIntents, 512 selectedProfile == PROFILE_WORK ? initialIntents : null, 513 rList, 514 (filterLastUsed && UserHandle.myUserId() 515 == workProfileUserHandle.getIdentifier()), 516 /* userHandle */ workProfileUserHandle); 517 return new ResolverMultiProfilePagerAdapter( 518 /* context */ this, 519 personalAdapter, 520 workAdapter, 521 selectedProfile, 522 getPersonalProfileUserHandle(), 523 getWorkProfileUserHandle(), 524 /* shouldShowNoCrossProfileIntentsEmptyState= */ getUser().equals(intentUser)); 525 } 526 527 protected int appliedThemeResId() { 528 return R.style.Theme_DeviceDefault_Resolver; 529 } 530 531 /** 532 * Returns {@link #PROFILE_PERSONAL} or {@link #PROFILE_WORK} if the {@link 533 * #EXTRA_SELECTED_PROFILE} extra was supplied, or {@code -1} if no extra was supplied. 534 * @throws IllegalArgumentException if the value passed to the {@link #EXTRA_SELECTED_PROFILE} 535 * extra is not {@link #PROFILE_PERSONAL} or {@link #PROFILE_WORK} 536 */ 537 int getSelectedProfileExtra() { 538 int selectedProfile = -1; 539 if (getIntent().hasExtra(EXTRA_SELECTED_PROFILE)) { 540 selectedProfile = getIntent().getIntExtra(EXTRA_SELECTED_PROFILE, /* defValue = */ -1); 541 if (selectedProfile != PROFILE_PERSONAL && selectedProfile != PROFILE_WORK) { 542 throw new IllegalArgumentException(EXTRA_SELECTED_PROFILE + " has invalid value " 543 + selectedProfile + ". Must be either ResolverActivity.PROFILE_PERSONAL or " 544 + "ResolverActivity.PROFILE_WORK."); 545 } 546 } 547 return selectedProfile; 548 } 549 550 protected @Profile int getCurrentProfile() { 551 return (UserHandle.myUserId() == UserHandle.USER_SYSTEM ? PROFILE_PERSONAL : PROFILE_WORK); 552 } 553 554 protected UserHandle getPersonalProfileUserHandle() { 555 return UserHandle.of(ActivityManager.getCurrentUser()); 556 } 557 protected @Nullable UserHandle getWorkProfileUserHandle() { 558 return mWorkProfileUserHandle; 559 } 560 561 protected @Nullable UserHandle fetchWorkProfileUserProfile() { 562 mWorkProfileUserHandle = null; 563 UserManager userManager = getSystemService(UserManager.class); 564 for (final UserInfo userInfo : userManager.getProfiles(ActivityManager.getCurrentUser())) { 565 if (userInfo.isManagedProfile()) { 566 mWorkProfileUserHandle = userInfo.getUserHandle(); 567 } 568 } 569 return mWorkProfileUserHandle; 570 } 571 572 private boolean hasWorkProfile() { 573 return getWorkProfileUserHandle() != null; 574 } 575 576 protected boolean shouldShowTabs() { 577 return hasWorkProfile() && ENABLE_TABBED_VIEW; 578 } 579 580 protected void onProfileClick(View v) { 581 final DisplayResolveInfo dri = 582 mMultiProfilePagerAdapter.getActiveListAdapter().getOtherProfile(); 583 if (dri == null) { 584 return; 585 } 586 587 // Do not show the profile switch message anymore. 588 mProfileSwitchMessageId = -1; 589 590 onTargetSelected(dri, false); 591 finish(); 592 } 593 594 /** 595 * Numerous layouts are supported, each with optional ViewGroups. 596 * Make sure the inset gets added to the correct View, using 597 * a footer for Lists so it can properly scroll under the navbar. 598 */ 599 protected boolean shouldAddFooterView() { 600 if (useLayoutWithDefault()) return true; 601 602 View buttonBar = findViewById(R.id.button_bar); 603 if (buttonBar == null || buttonBar.getVisibility() == View.GONE) return true; 604 605 return false; 606 } 607 608 protected void applyFooterView(int height) { 609 if (mFooterSpacer == null) { 610 mFooterSpacer = new Space(getApplicationContext()); 611 } else { 612 ((ResolverMultiProfilePagerAdapter) mMultiProfilePagerAdapter) 613 .getActiveAdapterView().removeFooterView(mFooterSpacer); 614 } 615 mFooterSpacer.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT, 616 mSystemWindowInsets.bottom)); 617 ((ResolverMultiProfilePagerAdapter) mMultiProfilePagerAdapter) 618 .getActiveAdapterView().addFooterView(mFooterSpacer); 619 } 620 621 protected WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { 622 mSystemWindowInsets = insets.getSystemWindowInsets(); 623 624 mResolverDrawerLayout.setPadding(mSystemWindowInsets.left, mSystemWindowInsets.top, 625 mSystemWindowInsets.right, 0); 626 627 resetButtonBar(); 628 629 // Need extra padding so the list can fully scroll up 630 if (shouldAddFooterView()) { 631 applyFooterView(mSystemWindowInsets.bottom); 632 } 633 634 return insets.consumeSystemWindowInsets(); 635 } 636 637 @Override 638 public void onConfigurationChanged(Configuration newConfig) { 639 super.onConfigurationChanged(newConfig); 640 mMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged(); 641 if (isIntentPicker() && shouldShowTabs() && !useLayoutWithDefault()) { 642 updateIntentPickerPaddings(); 643 } 644 645 if (mSystemWindowInsets != null) { 646 mResolverDrawerLayout.setPadding(mSystemWindowInsets.left, mSystemWindowInsets.top, 647 mSystemWindowInsets.right, 0); 648 } 649 } 650 651 private void updateIntentPickerPaddings() { 652 View titleCont = findViewById(R.id.title_container); 653 titleCont.setPadding( 654 titleCont.getPaddingLeft(), 655 titleCont.getPaddingTop(), 656 titleCont.getPaddingRight(), 657 getResources().getDimensionPixelSize(R.dimen.resolver_title_padding_bottom)); 658 View buttonBar = findViewById(R.id.button_bar); 659 buttonBar.setPadding( 660 buttonBar.getPaddingLeft(), 661 getResources().getDimensionPixelSize(R.dimen.resolver_button_bar_spacing), 662 buttonBar.getPaddingRight(), 663 getResources().getDimensionPixelSize(R.dimen.resolver_button_bar_spacing)); 664 mMultiProfilePagerAdapter.updateAfterConfigChange(); 665 } 666 667 @Override // ResolverListCommunicator 668 public void sendVoiceChoicesIfNeeded() { 669 if (!isVoiceInteraction()) { 670 // Clearly not needed. 671 return; 672 } 673 674 int count = mMultiProfilePagerAdapter.getActiveListAdapter().getCount(); 675 final Option[] options = new Option[count]; 676 for (int i = 0, N = options.length; i < N; i++) { 677 TargetInfo target = mMultiProfilePagerAdapter.getActiveListAdapter().getItem(i); 678 if (target == null) { 679 // If this occurs, a new set of targets is being loaded. Let that complete, 680 // and have the next call to send voice choices proceed instead. 681 return; 682 } 683 options[i] = optionForChooserTarget(target, i); 684 } 685 686 mPickOptionRequest = new PickTargetOptionRequest( 687 new Prompt(getTitle()), options, null); 688 getVoiceInteractor().submitRequest(mPickOptionRequest); 689 } 690 691 Option optionForChooserTarget(TargetInfo target, int index) { 692 return new Option(target.getDisplayLabel(), index); 693 } 694 695 protected final void setAdditionalTargets(Intent[] intents) { 696 if (intents != null) { 697 for (Intent intent : intents) { 698 mIntents.add(intent); 699 } 700 } 701 } 702 703 @Override // SelectableTargetInfoCommunicator ResolverListCommunicator 704 public Intent getTargetIntent() { 705 return mIntents.isEmpty() ? null : mIntents.get(0); 706 } 707 708 protected String getReferrerPackageName() { 709 final Uri referrer = getReferrer(); 710 if (referrer != null && "android-app".equals(referrer.getScheme())) { 711 return referrer.getHost(); 712 } 713 return null; 714 } 715 716 public int getLayoutResource() { 717 return R.layout.resolver_list; 718 } 719 720 @Override // ResolverListCommunicator 721 public void updateProfileViewButton() { 722 if (mProfileView == null) { 723 return; 724 } 725 726 final DisplayResolveInfo dri = 727 mMultiProfilePagerAdapter.getActiveListAdapter().getOtherProfile(); 728 if (dri != null && !shouldShowTabs()) { 729 mProfileView.setVisibility(View.VISIBLE); 730 View text = mProfileView.findViewById(R.id.profile_button); 731 if (!(text instanceof TextView)) { 732 text = mProfileView.findViewById(R.id.text1); 733 } 734 ((TextView) text).setText(dri.getDisplayLabel()); 735 } else { 736 mProfileView.setVisibility(View.GONE); 737 } 738 } 739 740 private void setProfileSwitchMessageId(int contentUserHint) { 741 if (contentUserHint != UserHandle.USER_CURRENT && 742 contentUserHint != UserHandle.myUserId()) { 743 UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE); 744 UserInfo originUserInfo = userManager.getUserInfo(contentUserHint); 745 boolean originIsManaged = originUserInfo != null ? originUserInfo.isManagedProfile() 746 : false; 747 boolean targetIsManaged = userManager.isManagedProfile(); 748 if (originIsManaged && !targetIsManaged) { 749 mProfileSwitchMessageId = com.android.internal.R.string.forward_intent_to_owner; 750 } else if (!originIsManaged && targetIsManaged) { 751 mProfileSwitchMessageId = com.android.internal.R.string.forward_intent_to_work; 752 } 753 } 754 } 755 756 /** 757 * Turn on launch mode that is safe to use when forwarding intents received from 758 * applications and running in system processes. This mode uses Activity.startActivityAsCaller 759 * instead of the normal Activity.startActivity for launching the activity selected 760 * by the user. 761 * 762 * <p>This mode is set to true by default if the activity is initialized through 763 * {@link #onCreate(android.os.Bundle)}. If a subclass calls one of the other onCreate 764 * methods, it is set to false by default. You must set it before calling one of the 765 * more detailed onCreate methods, so that it will be set correctly in the case where 766 * there is only one intent to resolve and it is thus started immediately.</p> 767 */ 768 public void setSafeForwardingMode(boolean safeForwarding) { 769 mSafeForwardingMode = safeForwarding; 770 } 771 772 protected CharSequence getTitleForAction(Intent intent, int defaultTitleRes) { 773 final ActionTitle title = mResolvingHome 774 ? ActionTitle.HOME 775 : ActionTitle.forAction(intent.getAction()); 776 777 // While there may already be a filtered item, we can only use it in the title if the list 778 // is already sorted and all information relevant to it is already in the list. 779 final boolean named = 780 mMultiProfilePagerAdapter.getActiveListAdapter().getFilteredPosition() >= 0; 781 if (title == ActionTitle.DEFAULT && defaultTitleRes != 0) { 782 return getString(defaultTitleRes); 783 } else { 784 return named 785 ? getString(title.namedTitleRes, mMultiProfilePagerAdapter 786 .getActiveListAdapter().getFilteredItem().getDisplayLabel()) 787 : getString(title.titleRes); 788 } 789 } 790 791 void dismiss() { 792 if (!isFinishing()) { 793 finish(); 794 } 795 } 796 797 @Override 798 protected void onRestart() { 799 super.onRestart(); 800 if (!mRegistered) { 801 mPersonalPackageMonitor.register(this, getMainLooper(), 802 getPersonalProfileUserHandle(), false); 803 if (shouldShowTabs()) { 804 if (mWorkPackageMonitor == null) { 805 mWorkPackageMonitor = createPackageMonitor( 806 mMultiProfilePagerAdapter.getWorkListAdapter()); 807 } 808 mWorkPackageMonitor.register(this, getMainLooper(), 809 getWorkProfileUserHandle(), false); 810 } 811 mRegistered = true; 812 } 813 if (shouldShowTabs() && mMultiProfilePagerAdapter.isWaitingToEnableWorkProfile()) { 814 if (mMultiProfilePagerAdapter.isQuietModeEnabled(getWorkProfileUserHandle())) { 815 mMultiProfilePagerAdapter.markWorkProfileEnabledBroadcastReceived(); 816 } 817 } 818 mMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged(); 819 updateProfileViewButton(); 820 } 821 822 @Override 823 protected void onStart() { 824 super.onStart(); 825 if (shouldShowTabs()) { 826 mWorkProfileStateReceiver = createWorkProfileStateReceiver(); 827 registerWorkProfileStateReceiver(); 828 829 mWorkProfileHasBeenEnabled = isWorkProfileEnabled(); 830 } 831 } 832 833 private boolean isWorkProfileEnabled() { 834 UserHandle workUserHandle = getWorkProfileUserHandle(); 835 UserManager userManager = getSystemService(UserManager.class); 836 837 return !userManager.isQuietModeEnabled(workUserHandle) 838 && userManager.isUserUnlocked(workUserHandle); 839 } 840 841 private void registerWorkProfileStateReceiver() { 842 IntentFilter filter = new IntentFilter(); 843 filter.addAction(Intent.ACTION_USER_UNLOCKED); 844 filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE); 845 filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); 846 registerReceiverAsUser(mWorkProfileStateReceiver, UserHandle.ALL, filter, null, null); 847 } 848 849 @Override 850 protected void onStop() { 851 super.onStop(); 852 if (mRegistered) { 853 mPersonalPackageMonitor.unregister(); 854 if (mWorkPackageMonitor != null) { 855 mWorkPackageMonitor.unregister(); 856 } 857 mRegistered = false; 858 } 859 final Intent intent = getIntent(); 860 if ((intent.getFlags() & FLAG_ACTIVITY_NEW_TASK) != 0 && !isVoiceInteraction() 861 && !mResolvingHome && !mRetainInOnStop) { 862 // This resolver is in the unusual situation where it has been 863 // launched at the top of a new task. We don't let it be added 864 // to the recent tasks shown to the user, and we need to make sure 865 // that each time we are launched we get the correct launching 866 // uid (not re-using the same resolver from an old launching uid), 867 // so we will now finish ourself since being no longer visible, 868 // the user probably can't get back to us. 869 if (!isChangingConfigurations()) { 870 finish(); 871 } 872 } 873 if (mWorkPackageMonitor != null) { 874 unregisterReceiver(mWorkProfileStateReceiver); 875 mWorkPackageMonitor = null; 876 } 877 } 878 879 @Override 880 protected void onDestroy() { 881 super.onDestroy(); 882 if (!isChangingConfigurations() && mPickOptionRequest != null) { 883 mPickOptionRequest.cancel(); 884 } 885 if (mMultiProfilePagerAdapter.getActiveListAdapter() != null) { 886 mMultiProfilePagerAdapter.getActiveListAdapter().onDestroy(); 887 } 888 } 889 890 @Override 891 protected void onSaveInstanceState(Bundle outState) { 892 super.onSaveInstanceState(outState); 893 ViewPager viewPager = findViewById(R.id.profile_pager); 894 outState.putInt(LAST_SHOWN_TAB_KEY, viewPager.getCurrentItem()); 895 } 896 897 @Override 898 protected void onRestoreInstanceState(Bundle savedInstanceState) { 899 super.onRestoreInstanceState(savedInstanceState); 900 resetButtonBar(); 901 ViewPager viewPager = findViewById(R.id.profile_pager); 902 viewPager.setCurrentItem(savedInstanceState.getInt(LAST_SHOWN_TAB_KEY)); 903 mMultiProfilePagerAdapter.clearInactiveProfileCache(); 904 } 905 906 private boolean hasManagedProfile() { 907 UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE); 908 if (userManager == null) { 909 return false; 910 } 911 912 try { 913 List<UserInfo> profiles = userManager.getProfiles(getUserId()); 914 for (UserInfo userInfo : profiles) { 915 if (userInfo != null && userInfo.isManagedProfile()) { 916 return true; 917 } 918 } 919 } catch (SecurityException e) { 920 return false; 921 } 922 return false; 923 } 924 925 private boolean supportsManagedProfiles(ResolveInfo resolveInfo) { 926 try { 927 ApplicationInfo appInfo = getPackageManager().getApplicationInfo( 928 resolveInfo.activityInfo.packageName, 0 /* default flags */); 929 return appInfo.targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP; 930 } catch (NameNotFoundException e) { 931 return false; 932 } 933 } 934 935 private void setAlwaysButtonEnabled(boolean hasValidSelection, int checkedPos, 936 boolean filtered) { 937 if (!mMultiProfilePagerAdapter.getCurrentUserHandle().equals(getUser())) { 938 // Never allow the inactive profile to always open an app. 939 mAlwaysButton.setEnabled(false); 940 return; 941 } 942 boolean enabled = false; 943 ResolveInfo ri = null; 944 if (hasValidSelection) { 945 ri = mMultiProfilePagerAdapter.getActiveListAdapter() 946 .resolveInfoForPosition(checkedPos, filtered); 947 if (ri == null) { 948 Log.e(TAG, "Invalid position supplied to setAlwaysButtonEnabled"); 949 return; 950 } else if (ri.targetUserId != UserHandle.USER_CURRENT) { 951 Log.e(TAG, "Attempted to set selection to resolve info for another user"); 952 return; 953 } else { 954 enabled = true; 955 } 956 957 mAlwaysButton.setText(getResources() 958 .getString(R.string.activity_resolver_use_always)); 959 } 960 961 if (ri != null) { 962 ActivityInfo activityInfo = ri.activityInfo; 963 964 boolean hasRecordPermission = 965 mPm.checkPermission(android.Manifest.permission.RECORD_AUDIO, 966 activityInfo.packageName) 967 == android.content.pm.PackageManager.PERMISSION_GRANTED; 968 969 if (!hasRecordPermission) { 970 // OK, we know the record permission, is this a capture device 971 boolean hasAudioCapture = 972 getIntent().getBooleanExtra( 973 ResolverActivity.EXTRA_IS_AUDIO_CAPTURE_DEVICE, false); 974 enabled = !hasAudioCapture; 975 } 976 } 977 mAlwaysButton.setEnabled(enabled); 978 } 979 980 public void onButtonClick(View v) { 981 final int id = v.getId(); 982 ListView listView = (ListView) mMultiProfilePagerAdapter.getActiveAdapterView(); 983 ResolverListAdapter currentListAdapter = mMultiProfilePagerAdapter.getActiveListAdapter(); 984 int which = currentListAdapter.hasFilteredItem() 985 ? currentListAdapter.getFilteredPosition() 986 : listView.getCheckedItemPosition(); 987 boolean hasIndexBeenFiltered = !currentListAdapter.hasFilteredItem(); 988 startSelected(which, id == R.id.button_always, hasIndexBeenFiltered); 989 } 990 991 public void startSelected(int which, boolean always, boolean hasIndexBeenFiltered) { 992 if (isFinishing()) { 993 return; 994 } 995 ResolveInfo ri = mMultiProfilePagerAdapter.getActiveListAdapter() 996 .resolveInfoForPosition(which, hasIndexBeenFiltered); 997 if (mResolvingHome && hasManagedProfile() && !supportsManagedProfiles(ri)) { 998 Toast.makeText(this, String.format(getResources().getString( 999 com.android.internal.R.string.activity_resolver_work_profiles_support), 1000 ri.activityInfo.loadLabel(getPackageManager()).toString()), 1001 Toast.LENGTH_LONG).show(); 1002 return; 1003 } 1004 1005 TargetInfo target = mMultiProfilePagerAdapter.getActiveListAdapter() 1006 .targetInfoForPosition(which, hasIndexBeenFiltered); 1007 if (target == null) { 1008 return; 1009 } 1010 if (onTargetSelected(target, always)) { 1011 if (always && mSupportsAlwaysUseOption) { 1012 MetricsLogger.action( 1013 this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_ALWAYS); 1014 } else if (mSupportsAlwaysUseOption) { 1015 MetricsLogger.action( 1016 this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_JUST_ONCE); 1017 } else { 1018 MetricsLogger.action( 1019 this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_TAP); 1020 } 1021 MetricsLogger.action(this, 1022 mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem() 1023 ? MetricsProto.MetricsEvent.ACTION_HIDE_APP_DISAMBIG_APP_FEATURED 1024 : MetricsProto.MetricsEvent.ACTION_HIDE_APP_DISAMBIG_NONE_FEATURED); 1025 finish(); 1026 } 1027 } 1028 1029 /** 1030 * Replace me in subclasses! 1031 */ 1032 @Override // ResolverListCommunicator 1033 public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) { 1034 return defIntent; 1035 } 1036 1037 @Override // ResolverListCommunicator 1038 public final void onPostListReady(ResolverListAdapter listAdapter, boolean doPostProcessing, 1039 boolean rebuildCompleted) { 1040 if (isAutolaunching()) { 1041 return; 1042 } 1043 if (isIntentPicker()) { 1044 ((ResolverMultiProfilePagerAdapter) mMultiProfilePagerAdapter) 1045 .setUseLayoutWithDefault(useLayoutWithDefault()); 1046 } 1047 if (mMultiProfilePagerAdapter.shouldShowEmptyStateScreen(listAdapter)) { 1048 mMultiProfilePagerAdapter.showEmptyResolverListEmptyState(listAdapter); 1049 } else { 1050 mMultiProfilePagerAdapter.showListView(listAdapter); 1051 } 1052 // showEmptyResolverListEmptyState can mark the tab as loaded, 1053 // which is a precondition for auto launching 1054 if (rebuildCompleted && maybeAutolaunchActivity()) { 1055 return; 1056 } 1057 if (doPostProcessing) { 1058 maybeCreateHeader(listAdapter); 1059 resetButtonBar(); 1060 onListRebuilt(listAdapter); 1061 } 1062 } 1063 1064 protected void onListRebuilt(ResolverListAdapter listAdapter) { 1065 final ItemClickListener listener = new ItemClickListener(); 1066 setupAdapterListView((ListView) mMultiProfilePagerAdapter.getActiveAdapterView(), listener); 1067 if (shouldShowTabs() && isIntentPicker()) { 1068 final ResolverDrawerLayout rdl = findViewById(R.id.contentPanel); 1069 if (rdl != null) { 1070 rdl.setMaxCollapsedHeight(getResources() 1071 .getDimensionPixelSize(useLayoutWithDefault() 1072 ? R.dimen.resolver_max_collapsed_height_with_default_with_tabs 1073 : R.dimen.resolver_max_collapsed_height_with_tabs)); 1074 } 1075 } 1076 } 1077 1078 protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) { 1079 final ResolveInfo ri = target.getResolveInfo(); 1080 final Intent intent = target != null ? target.getResolvedIntent() : null; 1081 1082 if (intent != null && (mSupportsAlwaysUseOption 1083 || mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem()) 1084 && mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredResolveList() != null) { 1085 // Build a reasonable intent filter, based on what matched. 1086 IntentFilter filter = new IntentFilter(); 1087 Intent filterIntent; 1088 1089 if (intent.getSelector() != null) { 1090 filterIntent = intent.getSelector(); 1091 } else { 1092 filterIntent = intent; 1093 } 1094 1095 String action = filterIntent.getAction(); 1096 if (action != null) { 1097 filter.addAction(action); 1098 } 1099 Set<String> categories = filterIntent.getCategories(); 1100 if (categories != null) { 1101 for (String cat : categories) { 1102 filter.addCategory(cat); 1103 } 1104 } 1105 filter.addCategory(Intent.CATEGORY_DEFAULT); 1106 1107 int cat = ri.match & IntentFilter.MATCH_CATEGORY_MASK; 1108 Uri data = filterIntent.getData(); 1109 if (cat == IntentFilter.MATCH_CATEGORY_TYPE) { 1110 String mimeType = filterIntent.resolveType(this); 1111 if (mimeType != null) { 1112 try { 1113 filter.addDataType(mimeType); 1114 } catch (IntentFilter.MalformedMimeTypeException e) { 1115 Log.w("ResolverActivity", e); 1116 filter = null; 1117 } 1118 } 1119 } 1120 if (data != null && data.getScheme() != null) { 1121 // We need the data specification if there was no type, 1122 // OR if the scheme is not one of our magical "file:" 1123 // or "content:" schemes (see IntentFilter for the reason). 1124 if (cat != IntentFilter.MATCH_CATEGORY_TYPE 1125 || (!"file".equals(data.getScheme()) 1126 && !"content".equals(data.getScheme()))) { 1127 filter.addDataScheme(data.getScheme()); 1128 1129 // Look through the resolved filter to determine which part 1130 // of it matched the original Intent. 1131 Iterator<PatternMatcher> pIt = ri.filter.schemeSpecificPartsIterator(); 1132 if (pIt != null) { 1133 String ssp = data.getSchemeSpecificPart(); 1134 while (ssp != null && pIt.hasNext()) { 1135 PatternMatcher p = pIt.next(); 1136 if (p.match(ssp)) { 1137 filter.addDataSchemeSpecificPart(p.getPath(), p.getType()); 1138 break; 1139 } 1140 } 1141 } 1142 Iterator<IntentFilter.AuthorityEntry> aIt = ri.filter.authoritiesIterator(); 1143 if (aIt != null) { 1144 while (aIt.hasNext()) { 1145 IntentFilter.AuthorityEntry a = aIt.next(); 1146 if (a.match(data) >= 0) { 1147 int port = a.getPort(); 1148 filter.addDataAuthority(a.getHost(), 1149 port >= 0 ? Integer.toString(port) : null); 1150 break; 1151 } 1152 } 1153 } 1154 pIt = ri.filter.pathsIterator(); 1155 if (pIt != null) { 1156 String path = data.getPath(); 1157 while (path != null && pIt.hasNext()) { 1158 PatternMatcher p = pIt.next(); 1159 if (p.match(path)) { 1160 filter.addDataPath(p.getPath(), p.getType()); 1161 break; 1162 } 1163 } 1164 } 1165 } 1166 } 1167 1168 if (filter != null) { 1169 final int N = mMultiProfilePagerAdapter.getActiveListAdapter() 1170 .getUnfilteredResolveList().size(); 1171 ComponentName[] set; 1172 // If we don't add back in the component for forwarding the intent to a managed 1173 // profile, the preferred activity may not be updated correctly (as the set of 1174 // components we tell it we knew about will have changed). 1175 final boolean needToAddBackProfileForwardingComponent = 1176 mMultiProfilePagerAdapter.getActiveListAdapter().getOtherProfile() != null; 1177 if (!needToAddBackProfileForwardingComponent) { 1178 set = new ComponentName[N]; 1179 } else { 1180 set = new ComponentName[N + 1]; 1181 } 1182 1183 int bestMatch = 0; 1184 for (int i=0; i<N; i++) { 1185 ResolveInfo r = mMultiProfilePagerAdapter.getActiveListAdapter() 1186 .getUnfilteredResolveList().get(i).getResolveInfoAt(0); 1187 set[i] = new ComponentName(r.activityInfo.packageName, 1188 r.activityInfo.name); 1189 if (r.match > bestMatch) bestMatch = r.match; 1190 } 1191 1192 if (needToAddBackProfileForwardingComponent) { 1193 set[N] = mMultiProfilePagerAdapter.getActiveListAdapter() 1194 .getOtherProfile().getResolvedComponentName(); 1195 final int otherProfileMatch = mMultiProfilePagerAdapter.getActiveListAdapter() 1196 .getOtherProfile().getResolveInfo().match; 1197 if (otherProfileMatch > bestMatch) bestMatch = otherProfileMatch; 1198 } 1199 1200 if (alwaysCheck) { 1201 final int userId = getUserId(); 1202 final PackageManager pm = getPackageManager(); 1203 1204 // Set the preferred Activity 1205 pm.addPreferredActivity(filter, bestMatch, set, intent.getComponent()); 1206 1207 if (ri.handleAllWebDataURI) { 1208 // Set default Browser if needed 1209 final String packageName = pm.getDefaultBrowserPackageNameAsUser(userId); 1210 if (TextUtils.isEmpty(packageName)) { 1211 pm.setDefaultBrowserPackageNameAsUser(ri.activityInfo.packageName, userId); 1212 } 1213 } 1214 } else { 1215 try { 1216 mMultiProfilePagerAdapter.getActiveListAdapter() 1217 .mResolverListController.setLastChosen(intent, filter, bestMatch); 1218 } catch (RemoteException re) { 1219 Log.d(TAG, "Error calling setLastChosenActivity\n" + re); 1220 } 1221 } 1222 } 1223 } 1224 1225 if (target != null) { 1226 if (intent != null && isLaunchingTargetInOtherProfile()) { 1227 prepareIntentForCrossProfileLaunch(intent); 1228 } 1229 safelyStartActivity(target); 1230 1231 // Rely on the ActivityManager to pop up a dialog regarding app suspension 1232 // and return false 1233 if (target.isSuspended()) { 1234 return false; 1235 } 1236 } 1237 1238 return true; 1239 } 1240 1241 private void prepareIntentForCrossProfileLaunch(Intent intent) { 1242 intent.fixUris(UserHandle.myUserId()); 1243 } 1244 1245 private boolean isLaunchingTargetInOtherProfile() { 1246 return mMultiProfilePagerAdapter.getCurrentUserHandle().getIdentifier() 1247 != UserHandle.myUserId(); 1248 } 1249 1250 @VisibleForTesting 1251 public void safelyStartActivity(TargetInfo cti) { 1252 // We're dispatching intents that might be coming from legacy apps, so 1253 // don't kill ourselves. 1254 StrictMode.disableDeathOnFileUriExposure(); 1255 try { 1256 safelyStartActivityInternal(cti); 1257 } finally { 1258 StrictMode.enableDeathOnFileUriExposure(); 1259 } 1260 } 1261 1262 private void safelyStartActivityInternal(TargetInfo cti) { 1263 // If the target is suspended, the activity will not be successfully launched. 1264 // Do not unregister from package manager updates in this case 1265 if (!cti.isSuspended()) { 1266 if (mPersonalPackageMonitor != null) { 1267 mPersonalPackageMonitor.unregister(); 1268 } 1269 if (mWorkPackageMonitor != null) { 1270 mWorkPackageMonitor.unregister(); 1271 } 1272 mRegistered = false; 1273 } 1274 // If needed, show that intent is forwarded 1275 // from managed profile to owner or other way around. 1276 if (mProfileSwitchMessageId != -1) { 1277 Toast.makeText(this, getString(mProfileSwitchMessageId), Toast.LENGTH_LONG).show(); 1278 } 1279 UserHandle currentUserHandle = mMultiProfilePagerAdapter.getCurrentUserHandle(); 1280 if (!mSafeForwardingMode) { 1281 if (cti.startAsUser(this, null, currentUserHandle)) { 1282 onActivityStarted(cti); 1283 maybeLogCrossProfileTargetLaunch(cti, currentUserHandle); 1284 } 1285 return; 1286 } 1287 try { 1288 if (cti.startAsCaller(this, null, currentUserHandle.getIdentifier())) { 1289 onActivityStarted(cti); 1290 maybeLogCrossProfileTargetLaunch(cti, currentUserHandle); 1291 } 1292 } catch (RuntimeException e) { 1293 String launchedFromPackage; 1294 try { 1295 launchedFromPackage = ActivityTaskManager.getService().getLaunchedFromPackage( 1296 getActivityToken()); 1297 } catch (RemoteException e2) { 1298 launchedFromPackage = "??"; 1299 } 1300 Slog.wtf(TAG, "Unable to launch as uid " + mLaunchedFromUid 1301 + " package " + launchedFromPackage + ", while running in " 1302 + ActivityThread.currentProcessName(), e); 1303 } 1304 } 1305 1306 private void maybeLogCrossProfileTargetLaunch(TargetInfo cti, UserHandle currentUserHandle) { 1307 if (!hasWorkProfile() || currentUserHandle.equals(getUser())) { 1308 return; 1309 } 1310 DevicePolicyEventLogger 1311 .createEvent(DevicePolicyEnums.RESOLVER_CROSS_PROFILE_TARGET_OPENED) 1312 .setBoolean(currentUserHandle.equals(getPersonalProfileUserHandle())) 1313 .setStrings(getMetricsCategory(), 1314 cti instanceof ChooserTargetInfo ? "direct_share" : "other_target") 1315 .write(); 1316 } 1317 1318 1319 public boolean startAsCallerImpl(Intent intent, Bundle options, boolean ignoreTargetSecurity, 1320 int userId) { 1321 // Pass intent to delegate chooser activity with permission token. 1322 // TODO: This should move to a trampoline Activity in the system when the ChooserActivity 1323 // moves into systemui 1324 try { 1325 // TODO: Once this is a small springboard activity, it can move off the UI process 1326 // and we can move the request method to ActivityManagerInternal. 1327 IBinder permissionToken = ActivityTaskManager.getService() 1328 .requestStartActivityPermissionToken(getActivityToken()); 1329 final Intent chooserIntent = new Intent(); 1330 final ComponentName delegateActivity = ComponentName.unflattenFromString( 1331 Resources.getSystem().getString(R.string.config_chooserActivity)); 1332 chooserIntent.setClassName(delegateActivity.getPackageName(), 1333 delegateActivity.getClassName()); 1334 chooserIntent.putExtra(ActivityTaskManager.EXTRA_PERMISSION_TOKEN, permissionToken); 1335 1336 // TODO: These extras will change as chooser activity moves into systemui 1337 chooserIntent.putExtra(Intent.EXTRA_INTENT, intent); 1338 chooserIntent.putExtra(ActivityTaskManager.EXTRA_OPTIONS, options); 1339 chooserIntent.putExtra(ActivityTaskManager.EXTRA_IGNORE_TARGET_SECURITY, 1340 ignoreTargetSecurity); 1341 chooserIntent.putExtra(Intent.EXTRA_USER_ID, userId); 1342 chooserIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT 1343 | Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP); 1344 startActivity(chooserIntent); 1345 } catch (RemoteException e) { 1346 Log.e(TAG, e.toString()); 1347 } 1348 return true; 1349 } 1350 1351 public void onActivityStarted(TargetInfo cti) { 1352 // Do nothing 1353 } 1354 1355 @Override // ResolverListCommunicator 1356 public boolean shouldGetActivityMetadata() { 1357 return false; 1358 } 1359 1360 public boolean shouldAutoLaunchSingleChoice(TargetInfo target) { 1361 return !target.isSuspended(); 1362 } 1363 1364 void showTargetDetails(ResolveInfo ri) { 1365 Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) 1366 .setData(Uri.fromParts("package", ri.activityInfo.packageName, null)) 1367 .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); 1368 startActivityAsUser(in, mMultiProfilePagerAdapter.getCurrentUserHandle()); 1369 } 1370 1371 @VisibleForTesting 1372 protected ResolverListAdapter createResolverListAdapter(Context context, 1373 List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList, 1374 boolean filterLastUsed, UserHandle userHandle) { 1375 Intent startIntent = getIntent(); 1376 boolean isAudioCaptureDevice = 1377 startIntent.getBooleanExtra(EXTRA_IS_AUDIO_CAPTURE_DEVICE, false); 1378 return new ResolverListAdapter(context, payloadIntents, initialIntents, rList, 1379 filterLastUsed, createListController(userHandle), this, 1380 isAudioCaptureDevice); 1381 } 1382 1383 @VisibleForTesting 1384 protected ResolverListController createListController(UserHandle userHandle) { 1385 return new ResolverListController( 1386 this, 1387 mPm, 1388 getTargetIntent(), 1389 getReferrerPackageName(), 1390 mLaunchedFromUid, 1391 userHandle); 1392 } 1393 1394 /** 1395 * Sets up the content view. 1396 * @return <code>true</code> if the activity is finishing and creation should halt. 1397 */ 1398 private boolean configureContentView() { 1399 if (mMultiProfilePagerAdapter.getActiveListAdapter() == null) { 1400 throw new IllegalStateException("mMultiProfilePagerAdapter.getCurrentListAdapter() " 1401 + "cannot be null."); 1402 } 1403 // We partially rebuild the inactive adapter to determine if we should auto launch 1404 // isTabLoaded will be true here if the empty state screen is shown instead of the list. 1405 boolean rebuildCompleted = mMultiProfilePagerAdapter.rebuildActiveTab(true) 1406 || mMultiProfilePagerAdapter.getActiveListAdapter().isTabLoaded(); 1407 if (shouldShowTabs()) { 1408 boolean rebuildInactiveCompleted = mMultiProfilePagerAdapter.rebuildInactiveTab(false) 1409 || mMultiProfilePagerAdapter.getInactiveListAdapter().isTabLoaded(); 1410 rebuildCompleted = rebuildCompleted && rebuildInactiveCompleted; 1411 } 1412 1413 if (useLayoutWithDefault()) { 1414 mLayoutId = R.layout.resolver_list_with_default; 1415 } else { 1416 mLayoutId = getLayoutResource(); 1417 } 1418 setContentView(mLayoutId); 1419 mMultiProfilePagerAdapter.setupViewPager(findViewById(R.id.profile_pager)); 1420 return postRebuildList(rebuildCompleted); 1421 } 1422 1423 /** 1424 * Finishing procedures to be performed after the list has been rebuilt. 1425 * </p>Subclasses must call postRebuildListInternal at the end of postRebuildList. 1426 * @param rebuildCompleted 1427 * @return <code>true</code> if the activity is finishing and creation should halt. 1428 */ 1429 protected boolean postRebuildList(boolean rebuildCompleted) { 1430 return postRebuildListInternal(rebuildCompleted); 1431 } 1432 1433 /** 1434 * Finishing procedures to be performed after the list has been rebuilt. 1435 * @param rebuildCompleted 1436 * @return <code>true</code> if the activity is finishing and creation should halt. 1437 */ 1438 final boolean postRebuildListInternal(boolean rebuildCompleted) { 1439 int count = mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredCount(); 1440 1441 // We only rebuild asynchronously when we have multiple elements to sort. In the case where 1442 // we're already done, we can check if we should auto-launch immediately. 1443 if (rebuildCompleted && maybeAutolaunchActivity()) { 1444 return true; 1445 } 1446 1447 setupViewVisibilities(); 1448 1449 if (shouldShowTabs()) { 1450 setupProfileTabs(); 1451 } 1452 1453 return false; 1454 } 1455 1456 private int isPermissionGranted(String permission, int uid) { 1457 return ActivityManager.checkComponentPermission(permission, uid, 1458 /* owningUid= */-1, /* exported= */ true); 1459 } 1460 1461 /** 1462 * @return {@code true} if a resolved target is autolaunched, otherwise {@code false} 1463 */ 1464 private boolean maybeAutolaunchActivity() { 1465 int numberOfProfiles = mMultiProfilePagerAdapter.getItemCount(); 1466 if (numberOfProfiles == 1 && maybeAutolaunchIfSingleTarget()) { 1467 return true; 1468 } else if (numberOfProfiles == 2 1469 && mMultiProfilePagerAdapter.getActiveListAdapter().isTabLoaded() 1470 && mMultiProfilePagerAdapter.getInactiveListAdapter().isTabLoaded() 1471 && (maybeAutolaunchIfNoAppsOnInactiveTab() 1472 || maybeAutolaunchIfCrossProfileSupported())) { 1473 return true; 1474 } 1475 return false; 1476 } 1477 1478 private boolean maybeAutolaunchIfSingleTarget() { 1479 int count = mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredCount(); 1480 if (count != 1) { 1481 return false; 1482 } 1483 1484 if (mMultiProfilePagerAdapter.getActiveListAdapter().getOtherProfile() != null) { 1485 return false; 1486 } 1487 1488 // Only one target, so we're a candidate to auto-launch! 1489 final TargetInfo target = mMultiProfilePagerAdapter.getActiveListAdapter() 1490 .targetInfoForPosition(0, false); 1491 if (shouldAutoLaunchSingleChoice(target)) { 1492 safelyStartActivity(target); 1493 finish(); 1494 return true; 1495 } 1496 return false; 1497 } 1498 1499 private boolean maybeAutolaunchIfNoAppsOnInactiveTab() { 1500 int count = mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredCount(); 1501 if (count != 1) { 1502 return false; 1503 } 1504 ResolverListAdapter inactiveListAdapter = 1505 mMultiProfilePagerAdapter.getInactiveListAdapter(); 1506 if (inactiveListAdapter.getUnfilteredCount() != 0) { 1507 return false; 1508 } 1509 TargetInfo target = mMultiProfilePagerAdapter.getActiveListAdapter() 1510 .targetInfoForPosition(0, false); 1511 safelyStartActivity(target); 1512 finish(); 1513 return true; 1514 } 1515 1516 /** 1517 * When we have a personal and a work profile, we auto launch in the following scenario: 1518 * - There is 1 resolved target on each profile 1519 * - That target is the same app on both profiles 1520 * - The target app has permission to communicate cross profiles 1521 * - The target app has declared it supports cross-profile communication via manifest metadata 1522 */ 1523 private boolean maybeAutolaunchIfCrossProfileSupported() { 1524 ResolverListAdapter activeListAdapter = mMultiProfilePagerAdapter.getActiveListAdapter(); 1525 int count = activeListAdapter.getUnfilteredCount(); 1526 if (count != 1) { 1527 return false; 1528 } 1529 ResolverListAdapter inactiveListAdapter = 1530 mMultiProfilePagerAdapter.getInactiveListAdapter(); 1531 if (inactiveListAdapter.getUnfilteredCount() != 1) { 1532 return false; 1533 } 1534 TargetInfo activeProfileTarget = activeListAdapter 1535 .targetInfoForPosition(0, false); 1536 TargetInfo inactiveProfileTarget = inactiveListAdapter.targetInfoForPosition(0, false); 1537 if (!Objects.equals(activeProfileTarget.getResolvedComponentName(), 1538 inactiveProfileTarget.getResolvedComponentName())) { 1539 return false; 1540 } 1541 if (!shouldAutoLaunchSingleChoice(activeProfileTarget)) { 1542 return false; 1543 } 1544 String packageName = activeProfileTarget.getResolvedComponentName().getPackageName(); 1545 if (!canAppInteractCrossProfiles(packageName)) { 1546 return false; 1547 } 1548 1549 DevicePolicyEventLogger 1550 .createEvent(DevicePolicyEnums.RESOLVER_AUTOLAUNCH_CROSS_PROFILE_TARGET) 1551 .setBoolean(activeListAdapter.getUserHandle() 1552 .equals(getPersonalProfileUserHandle())) 1553 .setStrings(getMetricsCategory()) 1554 .write(); 1555 safelyStartActivity(activeProfileTarget); 1556 finish(); 1557 return true; 1558 } 1559 1560 /** 1561 * Returns whether the package has the necessary permissions to interact across profiles on 1562 * behalf of a given user. 1563 * 1564 * <p>This means meeting the following condition: 1565 * <ul> 1566 * <li>The app's {@link ApplicationInfo#crossProfile} flag must be true, and at least 1567 * one of the following conditions must be fulfilled</li> 1568 * <li>{@code Manifest.permission.INTERACT_ACROSS_USERS_FULL} granted.</li> 1569 * <li>{@code Manifest.permission.INTERACT_ACROSS_USERS} granted.</li> 1570 * <li>{@code Manifest.permission.INTERACT_ACROSS_PROFILES} granted, or the corresponding 1571 * AppOps {@code android:interact_across_profiles} is set to "allow".</li> 1572 * </ul> 1573 * 1574 */ 1575 private boolean canAppInteractCrossProfiles(String packageName) { 1576 ApplicationInfo applicationInfo; 1577 try { 1578 applicationInfo = getPackageManager().getApplicationInfo(packageName, 0); 1579 } catch (NameNotFoundException e) { 1580 Log.e(TAG, "Package " + packageName + " does not exist on current user."); 1581 return false; 1582 } 1583 if (!applicationInfo.crossProfile) { 1584 return false; 1585 } 1586 1587 int packageUid = applicationInfo.uid; 1588 1589 if (isPermissionGranted(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, 1590 packageUid) == PackageManager.PERMISSION_GRANTED) { 1591 return true; 1592 } 1593 if (isPermissionGranted(android.Manifest.permission.INTERACT_ACROSS_USERS, packageUid) 1594 == PackageManager.PERMISSION_GRANTED) { 1595 return true; 1596 } 1597 if (PermissionChecker.checkPermissionForPreflight(this, INTERACT_ACROSS_PROFILES, 1598 PID_UNKNOWN, packageUid, packageName) == PackageManager.PERMISSION_GRANTED) { 1599 return true; 1600 } 1601 return false; 1602 } 1603 1604 private boolean isAutolaunching() { 1605 return !mRegistered && isFinishing(); 1606 } 1607 1608 private void setupProfileTabs() { 1609 maybeHideDivider(); 1610 TabHost tabHost = findViewById(R.id.profile_tabhost); 1611 tabHost.setup(); 1612 ViewPager viewPager = findViewById(R.id.profile_pager); 1613 viewPager.setSaveEnabled(false); 1614 TabHost.TabSpec tabSpec = tabHost.newTabSpec(TAB_TAG_PERSONAL) 1615 .setContent(R.id.profile_pager) 1616 .setIndicator(getString(R.string.resolver_personal_tab)); 1617 tabHost.addTab(tabSpec); 1618 1619 tabSpec = tabHost.newTabSpec(TAB_TAG_WORK) 1620 .setContent(R.id.profile_pager) 1621 .setIndicator(getString(R.string.resolver_work_tab)); 1622 tabHost.addTab(tabSpec); 1623 1624 TabWidget tabWidget = tabHost.getTabWidget(); 1625 tabWidget.setVisibility(View.VISIBLE); 1626 resetTabsHeaderStyle(tabWidget); 1627 updateActiveTabStyle(tabHost); 1628 1629 tabHost.setOnTabChangedListener(tabId -> { 1630 resetTabsHeaderStyle(tabWidget); 1631 updateActiveTabStyle(tabHost); 1632 if (TAB_TAG_PERSONAL.equals(tabId)) { 1633 viewPager.setCurrentItem(0); 1634 } else { 1635 viewPager.setCurrentItem(1); 1636 } 1637 setupViewVisibilities(); 1638 maybeLogProfileChange(); 1639 onProfileTabSelected(); 1640 DevicePolicyEventLogger 1641 .createEvent(DevicePolicyEnums.RESOLVER_SWITCH_TABS) 1642 .setInt(viewPager.getCurrentItem()) 1643 .setStrings(getMetricsCategory()) 1644 .write(); 1645 }); 1646 1647 viewPager.setVisibility(View.VISIBLE); 1648 tabHost.setCurrentTab(mMultiProfilePagerAdapter.getCurrentPage()); 1649 mMultiProfilePagerAdapter.setOnProfileSelectedListener( 1650 new AbstractMultiProfilePagerAdapter.OnProfileSelectedListener() { 1651 @Override 1652 public void onProfileSelected(int index) { 1653 tabHost.setCurrentTab(index); 1654 resetButtonBar(); 1655 resetCheckedItem(); 1656 } 1657 1658 @Override 1659 public void onProfilePageStateChanged(int state) { 1660 onHorizontalSwipeStateChanged(state); 1661 } 1662 }); 1663 mMultiProfilePagerAdapter.setOnSwitchOnWorkSelectedListener( 1664 () -> { 1665 final View workTab = tabHost.getTabWidget().getChildAt(1); 1666 workTab.setFocusable(true); 1667 workTab.setFocusableInTouchMode(true); 1668 workTab.requestFocus(); 1669 }); 1670 findViewById(R.id.resolver_tab_divider).setVisibility(View.VISIBLE); 1671 } 1672 1673 void onHorizontalSwipeStateChanged(int state) {} 1674 1675 private void maybeHideDivider() { 1676 if (!isIntentPicker()) { 1677 return; 1678 } 1679 final View divider = findViewById(R.id.divider); 1680 if (divider == null) { 1681 return; 1682 } 1683 divider.setVisibility(View.GONE); 1684 } 1685 1686 /** 1687 * Callback called when user changes the profile tab. 1688 * <p>This method is intended to be overridden by subclasses. 1689 */ 1690 protected void onProfileTabSelected() { } 1691 1692 private void resetCheckedItem() { 1693 if (!isIntentPicker()) { 1694 return; 1695 } 1696 mLastSelected = ListView.INVALID_POSITION; 1697 ListView inactiveListView = (ListView) mMultiProfilePagerAdapter.getInactiveAdapterView(); 1698 if (inactiveListView.getCheckedItemCount() > 0) { 1699 inactiveListView.setItemChecked(inactiveListView.getCheckedItemPosition(), false); 1700 } 1701 } 1702 1703 private void resetTabsHeaderStyle(TabWidget tabWidget) { 1704 String workContentDescription = getString(R.string.resolver_work_tab_accessibility); 1705 String personalContentDescription = getString(R.string.resolver_personal_tab_accessibility); 1706 for (int i = 0; i < tabWidget.getChildCount(); i++) { 1707 View tabView = tabWidget.getChildAt(i); 1708 TextView title = tabView.findViewById(android.R.id.title); 1709 title.setTextAppearance(android.R.style.TextAppearance_DeviceDefault_DialogWindowTitle); 1710 title.setTextColor(getAttrColor(this, android.R.attr.textColorTertiary)); 1711 title.setTextSize(TypedValue.COMPLEX_UNIT_PX, 1712 getResources().getDimension(R.dimen.resolver_tab_text_size)); 1713 if (title.getText().equals(getString(R.string.resolver_personal_tab))) { 1714 tabView.setContentDescription(personalContentDescription); 1715 } else if (title.getText().equals(getString(R.string.resolver_work_tab))) { 1716 tabView.setContentDescription(workContentDescription); 1717 } 1718 } 1719 } 1720 1721 private static int getAttrColor(Context context, int attr) { 1722 TypedArray ta = context.obtainStyledAttributes(new int[]{attr}); 1723 int colorAccent = ta.getColor(0, 0); 1724 ta.recycle(); 1725 return colorAccent; 1726 } 1727 1728 private void updateActiveTabStyle(TabHost tabHost) { 1729 TextView title = tabHost.getTabWidget().getChildAt(tabHost.getCurrentTab()) 1730 .findViewById(android.R.id.title); 1731 title.setTextColor(getAttrColor(this, android.R.attr.colorAccent)); 1732 } 1733 1734 private void setupViewVisibilities() { 1735 ResolverListAdapter activeListAdapter = mMultiProfilePagerAdapter.getActiveListAdapter(); 1736 if (!mMultiProfilePagerAdapter.shouldShowEmptyStateScreen(activeListAdapter)) { 1737 addUseDifferentAppLabelIfNecessary(activeListAdapter); 1738 } 1739 } 1740 1741 /** 1742 * Add a label to signify that the user can pick a different app. 1743 * @param adapter The adapter used to provide data to item views. 1744 */ 1745 public void addUseDifferentAppLabelIfNecessary(ResolverListAdapter adapter) { 1746 final boolean useHeader = adapter.hasFilteredItem(); 1747 if (useHeader) { 1748 FrameLayout stub = findViewById(R.id.stub); 1749 stub.setVisibility(View.VISIBLE); 1750 TextView textView = (TextView) LayoutInflater.from(this).inflate( 1751 R.layout.resolver_different_item_header, null, false); 1752 if (shouldShowTabs()) { 1753 textView.setGravity(Gravity.CENTER); 1754 } 1755 stub.addView(textView); 1756 } 1757 } 1758 1759 private void setupAdapterListView(ListView listView, ItemClickListener listener) { 1760 listView.setOnItemClickListener(listener); 1761 listView.setOnItemLongClickListener(listener); 1762 1763 if (mSupportsAlwaysUseOption) { 1764 listView.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE); 1765 } 1766 } 1767 1768 /** 1769 * Configure the area above the app selection list (title, content preview, etc). 1770 */ 1771 private void maybeCreateHeader(ResolverListAdapter listAdapter) { 1772 if (mHeaderCreatorUser != null 1773 && !listAdapter.getUserHandle().equals(mHeaderCreatorUser)) { 1774 return; 1775 } 1776 if (!shouldShowTabs() 1777 && listAdapter.getCount() == 0 && listAdapter.getPlaceholderCount() == 0) { 1778 final TextView titleView = findViewById(R.id.title); 1779 if (titleView != null) { 1780 titleView.setVisibility(View.GONE); 1781 } 1782 } 1783 1784 CharSequence title = mTitle != null 1785 ? mTitle 1786 : getTitleForAction(getTargetIntent(), mDefaultTitleResId); 1787 1788 if (!TextUtils.isEmpty(title)) { 1789 final TextView titleView = findViewById(R.id.title); 1790 if (titleView != null) { 1791 titleView.setText(title); 1792 } 1793 setTitle(title); 1794 } 1795 1796 final ImageView iconView = findViewById(R.id.icon); 1797 if (iconView != null) { 1798 listAdapter.loadFilteredItemIconTaskAsync(iconView); 1799 } 1800 mHeaderCreatorUser = listAdapter.getUserHandle(); 1801 } 1802 1803 protected void resetButtonBar() { 1804 if (!mSupportsAlwaysUseOption) { 1805 return; 1806 } 1807 final ViewGroup buttonLayout = findViewById(R.id.button_bar); 1808 if (buttonLayout == null) { 1809 Log.e(TAG, "Layout unexpectedly does not have a button bar"); 1810 return; 1811 } 1812 ResolverListAdapter activeListAdapter = 1813 mMultiProfilePagerAdapter.getActiveListAdapter(); 1814 View buttonBarDivider = findViewById(R.id.resolver_button_bar_divider); 1815 if (!useLayoutWithDefault()) { 1816 int inset = mSystemWindowInsets != null ? mSystemWindowInsets.bottom : 0; 1817 buttonLayout.setPadding(buttonLayout.getPaddingLeft(), buttonLayout.getPaddingTop(), 1818 buttonLayout.getPaddingRight(), getResources().getDimensionPixelSize( 1819 R.dimen.resolver_button_bar_spacing) + inset); 1820 } 1821 if (activeListAdapter.isTabLoaded() 1822 && mMultiProfilePagerAdapter.shouldShowEmptyStateScreen(activeListAdapter) 1823 && !useLayoutWithDefault()) { 1824 buttonLayout.setVisibility(View.INVISIBLE); 1825 if (buttonBarDivider != null) { 1826 buttonBarDivider.setVisibility(View.INVISIBLE); 1827 } 1828 setButtonBarIgnoreOffset(/* ignoreOffset */ false); 1829 return; 1830 } 1831 if (buttonBarDivider != null) { 1832 buttonBarDivider.setVisibility(View.VISIBLE); 1833 } 1834 buttonLayout.setVisibility(View.VISIBLE); 1835 setButtonBarIgnoreOffset(/* ignoreOffset */ true); 1836 1837 mOnceButton = (Button) buttonLayout.findViewById(R.id.button_once); 1838 mAlwaysButton = (Button) buttonLayout.findViewById(R.id.button_always); 1839 1840 resetAlwaysOrOnceButtonBar(); 1841 } 1842 1843 /** 1844 * Updates the button bar container {@code ignoreOffset} layout param. 1845 * <p>Setting this to {@code true} means that the button bar will be glued to the bottom of 1846 * the screen. 1847 */ 1848 private void setButtonBarIgnoreOffset(boolean ignoreOffset) { 1849 View buttonBarContainer = findViewById(R.id.button_bar_container); 1850 if (buttonBarContainer != null) { 1851 ResolverDrawerLayout.LayoutParams layoutParams = 1852 (ResolverDrawerLayout.LayoutParams) buttonBarContainer.getLayoutParams(); 1853 layoutParams.ignoreOffset = ignoreOffset; 1854 buttonBarContainer.setLayoutParams(layoutParams); 1855 } 1856 } 1857 1858 private void resetAlwaysOrOnceButtonBar() { 1859 // Disable both buttons initially 1860 setAlwaysButtonEnabled(false, ListView.INVALID_POSITION, false); 1861 mOnceButton.setEnabled(false); 1862 1863 int filteredPosition = mMultiProfilePagerAdapter.getActiveListAdapter() 1864 .getFilteredPosition(); 1865 if (useLayoutWithDefault() && filteredPosition != ListView.INVALID_POSITION) { 1866 setAlwaysButtonEnabled(true, filteredPosition, false); 1867 mOnceButton.setEnabled(true); 1868 // Focus the button if we already have the default option 1869 mOnceButton.requestFocus(); 1870 return; 1871 } 1872 1873 // When the items load in, if an item was already selected, enable the buttons 1874 ListView currentAdapterView = (ListView) mMultiProfilePagerAdapter.getActiveAdapterView(); 1875 if (currentAdapterView != null 1876 && currentAdapterView.getCheckedItemPosition() != ListView.INVALID_POSITION) { 1877 setAlwaysButtonEnabled(true, currentAdapterView.getCheckedItemPosition(), true); 1878 mOnceButton.setEnabled(true); 1879 } 1880 } 1881 1882 @Override // ResolverListCommunicator 1883 public boolean useLayoutWithDefault() { 1884 // We only use the default app layout when the profile of the active user has a 1885 // filtered item. We always show the same default app even in the inactive user profile. 1886 boolean currentUserAdapterHasFilteredItem; 1887 if (mMultiProfilePagerAdapter.getCurrentUserHandle().getIdentifier() 1888 == UserHandle.myUserId()) { 1889 currentUserAdapterHasFilteredItem = 1890 mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem(); 1891 } else { 1892 currentUserAdapterHasFilteredItem = 1893 mMultiProfilePagerAdapter.getInactiveListAdapter().hasFilteredItem(); 1894 } 1895 return mSupportsAlwaysUseOption && currentUserAdapterHasFilteredItem; 1896 } 1897 1898 /** 1899 * If {@code retainInOnStop} is set to true, we will not finish ourselves when onStop gets 1900 * called and we are launched in a new task. 1901 */ 1902 protected void setRetainInOnStop(boolean retainInOnStop) { 1903 mRetainInOnStop = retainInOnStop; 1904 } 1905 1906 /** 1907 * Check a simple match for the component of two ResolveInfos. 1908 */ 1909 @Override // ResolverListCommunicator 1910 public boolean resolveInfoMatch(ResolveInfo lhs, ResolveInfo rhs) { 1911 return lhs == null ? rhs == null 1912 : lhs.activityInfo == null ? rhs.activityInfo == null 1913 : Objects.equals(lhs.activityInfo.name, rhs.activityInfo.name) 1914 && Objects.equals(lhs.activityInfo.packageName, rhs.activityInfo.packageName); 1915 } 1916 1917 protected String getMetricsCategory() { 1918 return METRICS_CATEGORY_RESOLVER; 1919 } 1920 1921 @Override // ResolverListCommunicator 1922 public void onHandlePackagesChanged(ResolverListAdapter listAdapter) { 1923 if (listAdapter == mMultiProfilePagerAdapter.getActiveListAdapter()) { 1924 if (listAdapter.getUserHandle().equals(getWorkProfileUserHandle()) 1925 && mMultiProfilePagerAdapter.isWaitingToEnableWorkProfile()) { 1926 // We have just turned on the work profile and entered the pass code to start it, 1927 // now we are waiting to receive the ACTION_USER_UNLOCKED broadcast. There is no 1928 // point in reloading the list now, since the work profile user is still 1929 // turning on. 1930 return; 1931 } 1932 boolean listRebuilt = mMultiProfilePagerAdapter.rebuildActiveTab(true); 1933 if (listRebuilt) { 1934 ResolverListAdapter activeListAdapter = 1935 mMultiProfilePagerAdapter.getActiveListAdapter(); 1936 activeListAdapter.notifyDataSetChanged(); 1937 if (activeListAdapter.getCount() == 0 && !inactiveListAdapterHasItems()) { 1938 // We no longer have any items... just finish the activity. 1939 finish(); 1940 } 1941 } 1942 } else { 1943 mMultiProfilePagerAdapter.clearInactiveProfileCache(); 1944 } 1945 } 1946 1947 private boolean inactiveListAdapterHasItems() { 1948 if (!shouldShowTabs()) { 1949 return false; 1950 } 1951 return mMultiProfilePagerAdapter.getInactiveListAdapter().getCount() > 0; 1952 } 1953 1954 private BroadcastReceiver createWorkProfileStateReceiver() { 1955 return new BroadcastReceiver() { 1956 @Override 1957 public void onReceive(Context context, Intent intent) { 1958 String action = intent.getAction(); 1959 if (!TextUtils.equals(action, Intent.ACTION_USER_UNLOCKED) 1960 && !TextUtils.equals(action, Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE) 1961 && !TextUtils.equals(action, Intent.ACTION_MANAGED_PROFILE_AVAILABLE)) { 1962 return; 1963 } 1964 1965 int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); 1966 1967 if (userId != getWorkProfileUserHandle().getIdentifier()) { 1968 return; 1969 } 1970 1971 if (isWorkProfileEnabled()) { 1972 if (mWorkProfileHasBeenEnabled) { 1973 return; 1974 } 1975 1976 mWorkProfileHasBeenEnabled = true; 1977 mMultiProfilePagerAdapter.markWorkProfileEnabledBroadcastReceived(); 1978 } else { 1979 // Must be an UNAVAILABLE broadcast, so we watch for the next availability 1980 mWorkProfileHasBeenEnabled = false; 1981 } 1982 1983 if (mMultiProfilePagerAdapter.getCurrentUserHandle() 1984 .equals(getWorkProfileUserHandle())) { 1985 mMultiProfilePagerAdapter.rebuildActiveTab(true); 1986 } else { 1987 mMultiProfilePagerAdapter.clearInactiveProfileCache(); 1988 } 1989 } 1990 }; 1991 } 1992 1993 @VisibleForTesting 1994 public static final class ResolvedComponentInfo { 1995 public final ComponentName name; 1996 private final List<Intent> mIntents = new ArrayList<>(); 1997 private final List<ResolveInfo> mResolveInfos = new ArrayList<>(); 1998 private boolean mPinned; 1999 2000 public ResolvedComponentInfo(ComponentName name, Intent intent, ResolveInfo info) { 2001 this.name = name; 2002 add(intent, info); 2003 } 2004 2005 public void add(Intent intent, ResolveInfo info) { 2006 mIntents.add(intent); 2007 mResolveInfos.add(info); 2008 } 2009 2010 public int getCount() { 2011 return mIntents.size(); 2012 } 2013 2014 public Intent getIntentAt(int index) { 2015 return index >= 0 ? mIntents.get(index) : null; 2016 } 2017 2018 public ResolveInfo getResolveInfoAt(int index) { 2019 return index >= 0 ? mResolveInfos.get(index) : null; 2020 } 2021 2022 public int findIntent(Intent intent) { 2023 for (int i = 0, N = mIntents.size(); i < N; i++) { 2024 if (intent.equals(mIntents.get(i))) { 2025 return i; 2026 } 2027 } 2028 return -1; 2029 } 2030 2031 public int findResolveInfo(ResolveInfo info) { 2032 for (int i = 0, N = mResolveInfos.size(); i < N; i++) { 2033 if (info.equals(mResolveInfos.get(i))) { 2034 return i; 2035 } 2036 } 2037 return -1; 2038 } 2039 2040 public boolean isPinned() { 2041 return mPinned; 2042 } 2043 2044 public void setPinned(boolean pinned) { 2045 mPinned = pinned; 2046 } 2047 } 2048 2049 class ItemClickListener implements AdapterView.OnItemClickListener, 2050 AdapterView.OnItemLongClickListener { 2051 @Override 2052 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 2053 final ListView listView = parent instanceof ListView ? (ListView) parent : null; 2054 if (listView != null) { 2055 position -= listView.getHeaderViewsCount(); 2056 } 2057 if (position < 0) { 2058 // Header views don't count. 2059 return; 2060 } 2061 // If we're still loading, we can't yet enable the buttons. 2062 if (mMultiProfilePagerAdapter.getActiveListAdapter() 2063 .resolveInfoForPosition(position, true) == null) { 2064 return; 2065 } 2066 ListView currentAdapterView = 2067 (ListView) mMultiProfilePagerAdapter.getActiveAdapterView(); 2068 final int checkedPos = currentAdapterView.getCheckedItemPosition(); 2069 final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION; 2070 if (!useLayoutWithDefault() 2071 && (!hasValidSelection || mLastSelected != checkedPos) 2072 && mAlwaysButton != null) { 2073 setAlwaysButtonEnabled(hasValidSelection, checkedPos, true); 2074 mOnceButton.setEnabled(hasValidSelection); 2075 if (hasValidSelection) { 2076 currentAdapterView.smoothScrollToPosition(checkedPos); 2077 mOnceButton.requestFocus(); 2078 } 2079 mLastSelected = checkedPos; 2080 } else { 2081 startSelected(position, false, true); 2082 } 2083 } 2084 2085 @Override 2086 public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { 2087 final ListView listView = parent instanceof ListView ? (ListView) parent : null; 2088 if (listView != null) { 2089 position -= listView.getHeaderViewsCount(); 2090 } 2091 if (position < 0) { 2092 // Header views don't count. 2093 return false; 2094 } 2095 ResolveInfo ri = mMultiProfilePagerAdapter.getActiveListAdapter() 2096 .resolveInfoForPosition(position, true); 2097 showTargetDetails(ri); 2098 return true; 2099 } 2100 2101 } 2102 2103 static final boolean isSpecificUriMatch(int match) { 2104 match = match&IntentFilter.MATCH_CATEGORY_MASK; 2105 return match >= IntentFilter.MATCH_CATEGORY_HOST 2106 && match <= IntentFilter.MATCH_CATEGORY_PATH; 2107 } 2108 2109 static class PickTargetOptionRequest extends PickOptionRequest { 2110 public PickTargetOptionRequest(@Nullable Prompt prompt, Option[] options, 2111 @Nullable Bundle extras) { 2112 super(prompt, options, extras); 2113 } 2114 2115 @Override 2116 public void onCancel() { 2117 super.onCancel(); 2118 final ResolverActivity ra = (ResolverActivity) getActivity(); 2119 if (ra != null) { 2120 ra.mPickOptionRequest = null; 2121 ra.finish(); 2122 } 2123 } 2124 2125 @Override 2126 public void onPickOptionResult(boolean finished, Option[] selections, Bundle result) { 2127 super.onPickOptionResult(finished, selections, result); 2128 if (selections.length != 1) { 2129 // TODO In a better world we would filter the UI presented here and let the 2130 // user refine. Maybe later. 2131 return; 2132 } 2133 2134 final ResolverActivity ra = (ResolverActivity) getActivity(); 2135 if (ra != null) { 2136 final TargetInfo ti = ra.mMultiProfilePagerAdapter.getActiveListAdapter() 2137 .getItem(selections[0].getIndex()); 2138 if (ra.onTargetSelected(ti, false)) { 2139 ra.mPickOptionRequest = null; 2140 ra.finish(); 2141 } 2142 } 2143 } 2144 } 2145 2146 protected void maybeLogProfileChange() {} 2147 } 2148