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