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