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 buttonContainer.setPadding(0, 0, 0, mSystemWindowInsets.bottom 933 + getResources().getDimensionPixelOffset(R.dimen.resolver_button_bar_spacing)); 934 } 935 936 // Need extra padding so the list can fully scroll up 937 if (shouldAddFooterView()) { 938 applyFooterView(mSystemWindowInsets.bottom); 939 } 940 941 return insets.consumeSystemWindowInsets(); 942 } 943 944 @Override 945 public void onConfigurationChanged(Configuration newConfig) { 946 super.onConfigurationChanged(newConfig); 947 mMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged(); 948 if (mIsIntentPicker && shouldShowTabs() && !useLayoutWithDefault() 949 && !shouldUseMiniResolver()) { 950 updateIntentPickerPaddings(); 951 } 952 953 if (mSystemWindowInsets != null) { 954 mResolverDrawerLayout.setPadding(mSystemWindowInsets.left, mSystemWindowInsets.top, 955 mSystemWindowInsets.right, 0); 956 } 957 } 958 959 private void updateIntentPickerPaddings() { 960 View titleCont = findViewById(R.id.title_container); 961 titleCont.setPadding( 962 titleCont.getPaddingLeft(), 963 titleCont.getPaddingTop(), 964 titleCont.getPaddingRight(), 965 getResources().getDimensionPixelSize(R.dimen.resolver_title_padding_bottom)); 966 View buttonBar = findViewById(R.id.button_bar); 967 buttonBar.setPadding( 968 buttonBar.getPaddingLeft(), 969 getResources().getDimensionPixelSize(R.dimen.resolver_button_bar_spacing), 970 buttonBar.getPaddingRight(), 971 getResources().getDimensionPixelSize(R.dimen.resolver_button_bar_spacing)); 972 } 973 974 @Override // ResolverListCommunicator 975 public void sendVoiceChoicesIfNeeded() { 976 if (!isVoiceInteraction()) { 977 // Clearly not needed. 978 return; 979 } 980 981 int count = mMultiProfilePagerAdapter.getActiveListAdapter().getCount(); 982 final Option[] options = new Option[count]; 983 for (int i = 0, N = options.length; i < N; i++) { 984 TargetInfo target = mMultiProfilePagerAdapter.getActiveListAdapter().getItem(i); 985 if (target == null) { 986 // If this occurs, a new set of targets is being loaded. Let that complete, 987 // and have the next call to send voice choices proceed instead. 988 return; 989 } 990 options[i] = optionForChooserTarget(target, i); 991 } 992 993 mPickOptionRequest = new PickTargetOptionRequest( 994 new Prompt(getTitle()), options, null); 995 getVoiceInteractor().submitRequest(mPickOptionRequest); 996 } 997 998 Option optionForChooserTarget(TargetInfo target, int index) { 999 return new Option(target.getDisplayLabel(), index); 1000 } 1001 1002 protected final void setAdditionalTargets(Intent[] intents) { 1003 if (intents != null) { 1004 for (Intent intent : intents) { 1005 mIntents.add(intent); 1006 } 1007 } 1008 } 1009 1010 @Override // SelectableTargetInfoCommunicator ResolverListCommunicator 1011 public Intent getTargetIntent() { 1012 return mIntents.isEmpty() ? null : mIntents.get(0); 1013 } 1014 1015 protected String getReferrerPackageName() { 1016 final Uri referrer = getReferrer(); 1017 if (referrer != null && "android-app".equals(referrer.getScheme())) { 1018 return referrer.getHost(); 1019 } 1020 return null; 1021 } 1022 1023 public int getLayoutResource() { 1024 return R.layout.resolver_list; 1025 } 1026 1027 @Override // ResolverListCommunicator 1028 public void updateProfileViewButton() { 1029 if (mProfileView == null) { 1030 return; 1031 } 1032 1033 final DisplayResolveInfo dri = 1034 mMultiProfilePagerAdapter.getActiveListAdapter().getOtherProfile(); 1035 if (dri != null && !shouldShowTabs()) { 1036 mProfileView.setVisibility(View.VISIBLE); 1037 View text = mProfileView.findViewById(R.id.profile_button); 1038 if (!(text instanceof TextView)) { 1039 text = mProfileView.findViewById(R.id.text1); 1040 } 1041 ((TextView) text).setText(dri.getDisplayLabel()); 1042 } else { 1043 mProfileView.setVisibility(View.GONE); 1044 } 1045 } 1046 1047 private void setProfileSwitchMessage(int contentUserHint) { 1048 if (contentUserHint != UserHandle.USER_CURRENT && 1049 contentUserHint != UserHandle.myUserId()) { 1050 UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE); 1051 UserInfo originUserInfo = userManager.getUserInfo(contentUserHint); 1052 boolean originIsManaged = originUserInfo != null ? originUserInfo.isManagedProfile() 1053 : false; 1054 boolean targetIsManaged = userManager.isManagedProfile(); 1055 if (originIsManaged && !targetIsManaged) { 1056 mProfileSwitchMessage = getForwardToPersonalMsg(); 1057 } else if (!originIsManaged && targetIsManaged) { 1058 mProfileSwitchMessage = getForwardToWorkMsg(); 1059 } 1060 } 1061 } 1062 1063 private String getForwardToPersonalMsg() { 1064 return getSystemService(DevicePolicyManager.class).getResources().getString( 1065 FORWARD_INTENT_TO_PERSONAL, 1066 () -> getString(com.android.internal.R.string.forward_intent_to_owner)); 1067 } 1068 1069 private String getForwardToWorkMsg() { 1070 return getSystemService(DevicePolicyManager.class).getResources().getString( 1071 FORWARD_INTENT_TO_WORK, 1072 () -> getString(com.android.internal.R.string.forward_intent_to_work)); 1073 } 1074 1075 /** 1076 * Turn on launch mode that is safe to use when forwarding intents received from 1077 * applications and running in system processes. This mode uses Activity.startActivityAsCaller 1078 * instead of the normal Activity.startActivity for launching the activity selected 1079 * by the user. 1080 * 1081 * <p>This mode is set to true by default if the activity is initialized through 1082 * {@link #onCreate(android.os.Bundle)}. If a subclass calls one of the other onCreate 1083 * methods, it is set to false by default. You must set it before calling one of the 1084 * more detailed onCreate methods, so that it will be set correctly in the case where 1085 * there is only one intent to resolve and it is thus started immediately.</p> 1086 */ 1087 public void setSafeForwardingMode(boolean safeForwarding) { 1088 mSafeForwardingMode = safeForwarding; 1089 } 1090 1091 protected CharSequence getTitleForAction(Intent intent, int defaultTitleRes) { 1092 final ActionTitle title = mResolvingHome 1093 ? ActionTitle.HOME 1094 : ActionTitle.forAction(intent.getAction()); 1095 1096 // While there may already be a filtered item, we can only use it in the title if the list 1097 // is already sorted and all information relevant to it is already in the list. 1098 final boolean named = 1099 mMultiProfilePagerAdapter.getActiveListAdapter().getFilteredPosition() >= 0; 1100 if (title == ActionTitle.DEFAULT && defaultTitleRes != 0) { 1101 return getString(defaultTitleRes); 1102 } else { 1103 return named 1104 ? getString(title.namedTitleRes, mMultiProfilePagerAdapter 1105 .getActiveListAdapter().getFilteredItem().getDisplayLabel()) 1106 : getString(title.titleRes); 1107 } 1108 } 1109 1110 void dismiss() { 1111 if (!isFinishing()) { 1112 finish(); 1113 } 1114 } 1115 1116 @Override 1117 protected void onRestart() { 1118 super.onRestart(); 1119 if (!mRegistered) { 1120 mPersonalPackageMonitor.register(this, getMainLooper(), 1121 getPersonalProfileUserHandle(), false); 1122 if (shouldShowTabs()) { 1123 if (mWorkPackageMonitor == null) { 1124 mWorkPackageMonitor = createPackageMonitor( 1125 mMultiProfilePagerAdapter.getWorkListAdapter()); 1126 } 1127 mWorkPackageMonitor.register(this, getMainLooper(), 1128 getWorkProfileUserHandle(), false); 1129 } 1130 mRegistered = true; 1131 } 1132 if (shouldShowTabs() && mQuietModeManager.isWaitingToEnableWorkProfile()) { 1133 if (mQuietModeManager.isQuietModeEnabled(getWorkProfileUserHandle())) { 1134 mQuietModeManager.markWorkProfileEnabledBroadcastReceived(); 1135 } 1136 } 1137 mMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged(); 1138 updateProfileViewButton(); 1139 } 1140 1141 @Override 1142 protected void onStart() { 1143 super.onStart(); 1144 1145 this.getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); 1146 if (shouldShowTabs()) { 1147 mWorkProfileStateReceiver = createWorkProfileStateReceiver(); 1148 registerWorkProfileStateReceiver(); 1149 1150 mWorkProfileHasBeenEnabled = isWorkProfileEnabled(); 1151 } 1152 } 1153 1154 private boolean isWorkProfileEnabled() { 1155 UserHandle workUserHandle = getWorkProfileUserHandle(); 1156 UserManager userManager = getSystemService(UserManager.class); 1157 1158 return !userManager.isQuietModeEnabled(workUserHandle) 1159 && userManager.isUserUnlocked(workUserHandle); 1160 } 1161 1162 private void registerWorkProfileStateReceiver() { 1163 IntentFilter filter = new IntentFilter(); 1164 filter.addAction(Intent.ACTION_USER_UNLOCKED); 1165 filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE); 1166 filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); 1167 registerReceiverAsUser(mWorkProfileStateReceiver, UserHandle.ALL, filter, null, null); 1168 } 1169 1170 @Override 1171 protected void onStop() { 1172 super.onStop(); 1173 1174 final Window window = this.getWindow(); 1175 final WindowManager.LayoutParams attrs = window.getAttributes(); 1176 attrs.privateFlags &= ~SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; 1177 window.setAttributes(attrs); 1178 1179 if (mRegistered) { 1180 mPersonalPackageMonitor.unregister(); 1181 if (mWorkPackageMonitor != null) { 1182 mWorkPackageMonitor.unregister(); 1183 } 1184 mRegistered = false; 1185 } 1186 final Intent intent = getIntent(); 1187 if ((intent.getFlags() & FLAG_ACTIVITY_NEW_TASK) != 0 && !isVoiceInteraction() 1188 && !mResolvingHome && !mRetainInOnStop) { 1189 // This resolver is in the unusual situation where it has been 1190 // launched at the top of a new task. We don't let it be added 1191 // to the recent tasks shown to the user, and we need to make sure 1192 // that each time we are launched we get the correct launching 1193 // uid (not re-using the same resolver from an old launching uid), 1194 // so we will now finish ourself since being no longer visible, 1195 // the user probably can't get back to us. 1196 if (!isChangingConfigurations()) { 1197 finish(); 1198 } 1199 } 1200 if (mWorkPackageMonitor != null) { 1201 unregisterReceiver(mWorkProfileStateReceiver); 1202 mWorkPackageMonitor = null; 1203 } 1204 } 1205 1206 @Override 1207 protected void onDestroy() { 1208 super.onDestroy(); 1209 if (!isChangingConfigurations() && mPickOptionRequest != null) { 1210 mPickOptionRequest.cancel(); 1211 } 1212 if (mMultiProfilePagerAdapter != null) { 1213 ResolverListAdapter activeAdapter = 1214 mMultiProfilePagerAdapter.getActiveListAdapter(); 1215 if (activeAdapter != null) { 1216 activeAdapter.onDestroy(); 1217 } 1218 if (android.service.chooser.Flags.fixResolverMemoryLeak()) { 1219 ResolverListAdapter inactiveAdapter = 1220 mMultiProfilePagerAdapter.getInactiveListAdapter(); 1221 if (inactiveAdapter != null) { 1222 inactiveAdapter.onDestroy(); 1223 } 1224 } 1225 } 1226 } 1227 1228 @Override 1229 protected void onSaveInstanceState(Bundle outState) { 1230 super.onSaveInstanceState(outState); 1231 ViewPager viewPager = findViewById(R.id.profile_pager); 1232 if (viewPager != null) { 1233 outState.putInt(LAST_SHOWN_TAB_KEY, viewPager.getCurrentItem()); 1234 } 1235 } 1236 1237 @Override 1238 protected void onRestoreInstanceState(Bundle savedInstanceState) { 1239 super.onRestoreInstanceState(savedInstanceState); 1240 resetButtonBar(); 1241 ViewPager viewPager = findViewById(R.id.profile_pager); 1242 if (viewPager != null) { 1243 viewPager.setCurrentItem(savedInstanceState.getInt(LAST_SHOWN_TAB_KEY)); 1244 } 1245 mMultiProfilePagerAdapter.clearInactiveProfileCache(); 1246 } 1247 1248 private boolean hasManagedProfile() { 1249 UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE); 1250 if (userManager == null) { 1251 return false; 1252 } 1253 1254 try { 1255 List<UserInfo> profiles = userManager.getProfiles(getUserId()); 1256 for (UserInfo userInfo : profiles) { 1257 if (userInfo != null && userInfo.isManagedProfile()) { 1258 return true; 1259 } 1260 } 1261 } catch (SecurityException e) { 1262 return false; 1263 } 1264 return false; 1265 } 1266 1267 private boolean supportsManagedProfiles(ResolveInfo resolveInfo) { 1268 try { 1269 ApplicationInfo appInfo = getPackageManager().getApplicationInfo( 1270 resolveInfo.activityInfo.packageName, 0 /* default flags */); 1271 return appInfo.targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP; 1272 } catch (NameNotFoundException e) { 1273 return false; 1274 } 1275 } 1276 1277 private void setAlwaysButtonEnabled(boolean hasValidSelection, int checkedPos, 1278 boolean filtered) { 1279 if (!mMultiProfilePagerAdapter.getCurrentUserHandle().equals(getUser())) { 1280 // Never allow the inactive profile to always open an app. 1281 mAlwaysButton.setEnabled(false); 1282 return; 1283 } 1284 // In case of clonedProfile being active, we do not allow the 'Always' option in the 1285 // disambiguation dialog of Personal Profile as the package manager cannot distinguish 1286 // between cross-profile preferred activities. 1287 if (hasCloneProfile() && !mMultiProfilePagerAdapter 1288 .getCurrentUserHandle().equals(mWorkProfileUserHandle)) { 1289 mAlwaysButton.setEnabled(false); 1290 return; 1291 } 1292 boolean enabled = false; 1293 ResolveInfo ri = null; 1294 if (hasValidSelection) { 1295 ri = mMultiProfilePagerAdapter.getActiveListAdapter() 1296 .resolveInfoForPosition(checkedPos, filtered); 1297 if (ri == null) { 1298 Log.e(TAG, "Invalid position supplied to setAlwaysButtonEnabled"); 1299 return; 1300 } else if (ri.targetUserId != UserHandle.USER_CURRENT) { 1301 Log.e(TAG, "Attempted to set selection to resolve info for another user"); 1302 return; 1303 } else { 1304 enabled = true; 1305 } 1306 1307 mAlwaysButton.setText(getResources() 1308 .getString(R.string.activity_resolver_use_always)); 1309 } 1310 1311 if (ri != null) { 1312 ActivityInfo activityInfo = ri.activityInfo; 1313 1314 boolean hasRecordPermission = 1315 mPm.checkPermission(android.Manifest.permission.RECORD_AUDIO, 1316 activityInfo.packageName) 1317 == android.content.pm.PackageManager.PERMISSION_GRANTED; 1318 1319 if (!hasRecordPermission) { 1320 // OK, we know the record permission, is this a capture device 1321 boolean hasAudioCapture = 1322 getIntent().getBooleanExtra( 1323 ResolverActivity.EXTRA_IS_AUDIO_CAPTURE_DEVICE, false); 1324 enabled = !hasAudioCapture; 1325 } 1326 } 1327 mAlwaysButton.setEnabled(enabled); 1328 } 1329 1330 public void onButtonClick(View v) { 1331 final int id = v.getId(); 1332 ListView listView = (ListView) mMultiProfilePagerAdapter.getActiveAdapterView(); 1333 ResolverListAdapter currentListAdapter = mMultiProfilePagerAdapter.getActiveListAdapter(); 1334 int which = currentListAdapter.hasFilteredItem() 1335 ? currentListAdapter.getFilteredPosition() 1336 : listView.getCheckedItemPosition(); 1337 boolean hasIndexBeenFiltered = !currentListAdapter.hasFilteredItem(); 1338 startSelected(which, id == R.id.button_always, hasIndexBeenFiltered); 1339 } 1340 1341 public void startSelected(int which, boolean always, boolean hasIndexBeenFiltered) { 1342 if (isFinishing()) { 1343 return; 1344 } 1345 ResolveInfo ri = mMultiProfilePagerAdapter.getActiveListAdapter() 1346 .resolveInfoForPosition(which, hasIndexBeenFiltered); 1347 if (mResolvingHome && hasManagedProfile() && !supportsManagedProfiles(ri)) { 1348 Toast.makeText(this, 1349 getWorkProfileNotSupportedMsg( 1350 ri.activityInfo.loadLabel(getPackageManager()).toString()), 1351 Toast.LENGTH_LONG).show(); 1352 return; 1353 } 1354 1355 TargetInfo target = mMultiProfilePagerAdapter.getActiveListAdapter() 1356 .targetInfoForPosition(which, hasIndexBeenFiltered); 1357 if (target == null) { 1358 return; 1359 } 1360 if (onTargetSelected(target, always)) { 1361 if (always && mSupportsAlwaysUseOption) { 1362 MetricsLogger.action( 1363 this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_ALWAYS); 1364 } else if (mSupportsAlwaysUseOption) { 1365 MetricsLogger.action( 1366 this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_JUST_ONCE); 1367 } else { 1368 MetricsLogger.action( 1369 this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_TAP); 1370 } 1371 MetricsLogger.action(this, 1372 mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem() 1373 ? MetricsProto.MetricsEvent.ACTION_HIDE_APP_DISAMBIG_APP_FEATURED 1374 : MetricsProto.MetricsEvent.ACTION_HIDE_APP_DISAMBIG_NONE_FEATURED); 1375 finish(); 1376 } 1377 } 1378 1379 private String getWorkProfileNotSupportedMsg(String launcherName) { 1380 return getSystemService(DevicePolicyManager.class).getResources().getString( 1381 RESOLVER_WORK_PROFILE_NOT_SUPPORTED, 1382 () -> getString( 1383 com.android.internal.R.string.activity_resolver_work_profiles_support, 1384 launcherName), 1385 launcherName); 1386 } 1387 1388 /** 1389 * Replace me in subclasses! 1390 */ 1391 @Override // ResolverListCommunicator 1392 public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) { 1393 return defIntent; 1394 } 1395 1396 @Override // ResolverListCommunicator 1397 public final void onPostListReady(ResolverListAdapter listAdapter, boolean doPostProcessing, 1398 boolean rebuildCompleted) { 1399 if (isDestroyed()) { 1400 return; 1401 } 1402 if (isAutolaunching()) { 1403 return; 1404 } 1405 if (mIsIntentPicker) { 1406 ((ResolverMultiProfilePagerAdapter) mMultiProfilePagerAdapter) 1407 .setUseLayoutWithDefault(useLayoutWithDefault()); 1408 } 1409 if (mMultiProfilePagerAdapter.shouldShowEmptyStateScreen(listAdapter)) { 1410 mMultiProfilePagerAdapter.showEmptyResolverListEmptyState(listAdapter); 1411 } else { 1412 mMultiProfilePagerAdapter.showListView(listAdapter); 1413 } 1414 // showEmptyResolverListEmptyState can mark the tab as loaded, 1415 // which is a precondition for auto launching 1416 if (rebuildCompleted && maybeAutolaunchActivity()) { 1417 return; 1418 } 1419 if (doPostProcessing) { 1420 maybeCreateHeader(listAdapter); 1421 resetButtonBar(); 1422 onListRebuilt(listAdapter, rebuildCompleted); 1423 } 1424 } 1425 1426 protected void onListRebuilt(ResolverListAdapter listAdapter, boolean rebuildCompleted) { 1427 final ItemClickListener listener = new ItemClickListener(); 1428 setupAdapterListView((ListView) mMultiProfilePagerAdapter.getActiveAdapterView(), listener); 1429 if (shouldShowTabs() && mIsIntentPicker) { 1430 final ResolverDrawerLayout rdl = findViewById(R.id.contentPanel); 1431 if (rdl != null) { 1432 rdl.setMaxCollapsedHeight(getResources() 1433 .getDimensionPixelSize(useLayoutWithDefault() 1434 ? R.dimen.resolver_max_collapsed_height_with_default_with_tabs 1435 : R.dimen.resolver_max_collapsed_height_with_tabs)); 1436 } 1437 } 1438 } 1439 1440 protected boolean onTargetSelected(TargetInfo target, boolean always) { 1441 final ResolveInfo ri = target.getResolveInfo(); 1442 final Intent intent = target != null ? target.getResolvedIntent() : null; 1443 1444 if (intent != null && (mSupportsAlwaysUseOption 1445 || mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem()) 1446 && mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredResolveList() != null) { 1447 // Build a reasonable intent filter, based on what matched. 1448 IntentFilter filter = new IntentFilter(); 1449 Intent filterIntent; 1450 1451 if (intent.getSelector() != null) { 1452 filterIntent = intent.getSelector(); 1453 } else { 1454 filterIntent = intent; 1455 } 1456 1457 String action = filterIntent.getAction(); 1458 if (action != null) { 1459 filter.addAction(action); 1460 } 1461 Set<String> categories = filterIntent.getCategories(); 1462 if (categories != null) { 1463 for (String cat : categories) { 1464 filter.addCategory(cat); 1465 } 1466 } 1467 filter.addCategory(Intent.CATEGORY_DEFAULT); 1468 1469 int cat = ri.match & IntentFilter.MATCH_CATEGORY_MASK; 1470 Uri data = filterIntent.getData(); 1471 if (cat == IntentFilter.MATCH_CATEGORY_TYPE) { 1472 String mimeType = filterIntent.resolveType(this); 1473 if (mimeType != null) { 1474 try { 1475 filter.addDataType(mimeType); 1476 } catch (IntentFilter.MalformedMimeTypeException e) { 1477 Log.w("ResolverActivity", e); 1478 filter = null; 1479 } 1480 } 1481 } 1482 if (data != null && data.getScheme() != null) { 1483 // We need the data specification if there was no type, 1484 // OR if the scheme is not one of our magical "file:" 1485 // or "content:" schemes (see IntentFilter for the reason). 1486 if (cat != IntentFilter.MATCH_CATEGORY_TYPE 1487 || (!"file".equals(data.getScheme()) 1488 && !"content".equals(data.getScheme()))) { 1489 filter.addDataScheme(data.getScheme()); 1490 1491 // Look through the resolved filter to determine which part 1492 // of it matched the original Intent. 1493 Iterator<PatternMatcher> pIt = ri.filter.schemeSpecificPartsIterator(); 1494 if (pIt != null) { 1495 String ssp = data.getSchemeSpecificPart(); 1496 while (ssp != null && pIt.hasNext()) { 1497 PatternMatcher p = pIt.next(); 1498 if (p.match(ssp)) { 1499 filter.addDataSchemeSpecificPart(p.getPath(), p.getType()); 1500 break; 1501 } 1502 } 1503 } 1504 Iterator<IntentFilter.AuthorityEntry> aIt = ri.filter.authoritiesIterator(); 1505 if (aIt != null) { 1506 while (aIt.hasNext()) { 1507 IntentFilter.AuthorityEntry a = aIt.next(); 1508 if (a.match(data) >= 0) { 1509 int port = a.getPort(); 1510 filter.addDataAuthority(a.getHost(), 1511 port >= 0 ? Integer.toString(port) : null); 1512 break; 1513 } 1514 } 1515 } 1516 pIt = ri.filter.pathsIterator(); 1517 if (pIt != null) { 1518 String path = data.getPath(); 1519 while (path != null && pIt.hasNext()) { 1520 PatternMatcher p = pIt.next(); 1521 if (p.match(path)) { 1522 filter.addDataPath(p.getPath(), p.getType()); 1523 break; 1524 } 1525 } 1526 } 1527 } 1528 } 1529 1530 if (filter != null) { 1531 final int N = mMultiProfilePagerAdapter.getActiveListAdapter() 1532 .getUnfilteredResolveList().size(); 1533 ComponentName[] set; 1534 // If we don't add back in the component for forwarding the intent to a managed 1535 // profile, the preferred activity may not be updated correctly (as the set of 1536 // components we tell it we knew about will have changed). 1537 final boolean needToAddBackProfileForwardingComponent = 1538 mMultiProfilePagerAdapter.getActiveListAdapter().getOtherProfile() != null; 1539 if (!needToAddBackProfileForwardingComponent) { 1540 set = new ComponentName[N]; 1541 } else { 1542 set = new ComponentName[N + 1]; 1543 } 1544 1545 int bestMatch = 0; 1546 for (int i=0; i<N; i++) { 1547 ResolveInfo r = mMultiProfilePagerAdapter.getActiveListAdapter() 1548 .getUnfilteredResolveList().get(i).getResolveInfoAt(0); 1549 set[i] = new ComponentName(r.activityInfo.packageName, 1550 r.activityInfo.name); 1551 if (r.match > bestMatch) bestMatch = r.match; 1552 } 1553 1554 if (needToAddBackProfileForwardingComponent) { 1555 set[N] = mMultiProfilePagerAdapter.getActiveListAdapter() 1556 .getOtherProfile().getResolvedComponentName(); 1557 final int otherProfileMatch = mMultiProfilePagerAdapter.getActiveListAdapter() 1558 .getOtherProfile().getResolveInfo().match; 1559 if (otherProfileMatch > bestMatch) bestMatch = otherProfileMatch; 1560 } 1561 1562 if (always) { 1563 final int userId = getUserId(); 1564 final PackageManager pm = getPackageManager(); 1565 1566 // Set the preferred Activity 1567 pm.addUniquePreferredActivity(filter, bestMatch, set, intent.getComponent()); 1568 1569 if (ri.handleAllWebDataURI) { 1570 // Set default Browser if needed 1571 final String packageName = pm.getDefaultBrowserPackageNameAsUser(userId); 1572 if (TextUtils.isEmpty(packageName)) { 1573 pm.setDefaultBrowserPackageNameAsUser(ri.activityInfo.packageName, userId); 1574 } 1575 } 1576 } else { 1577 try { 1578 mMultiProfilePagerAdapter.getActiveListAdapter() 1579 .mResolverListController.setLastChosen(intent, filter, bestMatch); 1580 } catch (RemoteException re) { 1581 Log.d(TAG, "Error calling setLastChosenActivity\n" + re); 1582 } 1583 } 1584 } 1585 } 1586 1587 if (target != null) { 1588 safelyStartActivity(target); 1589 1590 // Rely on the ActivityManager to pop up a dialog regarding app suspension 1591 // and return false 1592 if (target.isSuspended()) { 1593 return false; 1594 } 1595 } 1596 1597 return true; 1598 } 1599 1600 /** Start the activity specified by the {@link TargetInfo}.*/ 1601 public final void safelyStartActivity(TargetInfo cti) { 1602 // In case cloned apps are present, we would want to start those apps in cloned user 1603 // space, which will not be same as adaptor's userHandle. resolveInfo.userHandle 1604 // identifies the correct user space in such cases. 1605 UserHandle activityUserHandle = getResolveInfoUserHandle( 1606 cti.getResolveInfo(), mMultiProfilePagerAdapter.getCurrentUserHandle()); 1607 safelyStartActivityAsUser(cti, activityUserHandle, null); 1608 } 1609 1610 /** 1611 * Start activity as a fixed user handle. 1612 * @param cti TargetInfo to be launched. 1613 * @param user User to launch this activity as. 1614 */ 1615 public final void safelyStartActivityAsUser(TargetInfo cti, UserHandle user) { 1616 safelyStartActivityAsUser(cti, user, null); 1617 } 1618 1619 protected final void safelyStartActivityAsUser( 1620 TargetInfo cti, UserHandle user, @Nullable Bundle options) { 1621 // We're dispatching intents that might be coming from legacy apps, so 1622 // don't kill ourselves. 1623 StrictMode.disableDeathOnFileUriExposure(); 1624 try { 1625 safelyStartActivityInternal(cti, user, options); 1626 } finally { 1627 StrictMode.enableDeathOnFileUriExposure(); 1628 } 1629 } 1630 1631 @VisibleForTesting 1632 protected void safelyStartActivityInternal( 1633 TargetInfo cti, UserHandle user, @Nullable Bundle options) { 1634 // If the target is suspended, the activity will not be successfully launched. 1635 // Do not unregister from package manager updates in this case 1636 if (!cti.isSuspended() && mRegistered) { 1637 if (mPersonalPackageMonitor != null) { 1638 mPersonalPackageMonitor.unregister(); 1639 } 1640 if (mWorkPackageMonitor != null) { 1641 mWorkPackageMonitor.unregister(); 1642 } 1643 mRegistered = false; 1644 } 1645 // If needed, show that intent is forwarded 1646 // from managed profile to owner or other way around. 1647 if (mProfileSwitchMessage != null) { 1648 Toast.makeText(this, mProfileSwitchMessage, Toast.LENGTH_LONG).show(); 1649 } 1650 if (!mSafeForwardingMode) { 1651 if (cti.startAsUser(this, options, user)) { 1652 onActivityStarted(cti); 1653 maybeLogCrossProfileTargetLaunch(cti, user); 1654 } 1655 return; 1656 } 1657 try { 1658 if (cti.startAsCaller(this, options, user.getIdentifier())) { 1659 onActivityStarted(cti); 1660 maybeLogCrossProfileTargetLaunch(cti, user); 1661 } 1662 } catch (RuntimeException e) { 1663 Slog.wtf(TAG, "Unable to launch as uid " + mLaunchedFromUid 1664 + " package " + getLaunchedFromPackage() + ", while running in " 1665 + ActivityThread.currentProcessName(), e); 1666 } 1667 } 1668 1669 private void maybeLogCrossProfileTargetLaunch(TargetInfo cti, UserHandle currentUserHandle) { 1670 if (!hasWorkProfile() || currentUserHandle.equals(getUser())) { 1671 return; 1672 } 1673 DevicePolicyEventLogger 1674 .createEvent(DevicePolicyEnums.RESOLVER_CROSS_PROFILE_TARGET_OPENED) 1675 .setBoolean(currentUserHandle.equals(getPersonalProfileUserHandle())) 1676 .setStrings(getMetricsCategory(), 1677 cti instanceof ChooserTargetInfo ? "direct_share" : "other_target") 1678 .write(); 1679 } 1680 1681 1682 public void onActivityStarted(TargetInfo cti) { 1683 // Do nothing 1684 } 1685 1686 @Override // ResolverListCommunicator 1687 public boolean shouldGetActivityMetadata() { 1688 return false; 1689 } 1690 1691 public boolean shouldAutoLaunchSingleChoice(TargetInfo target) { 1692 return !target.isSuspended(); 1693 } 1694 1695 void showTargetDetails(ResolveInfo ri) { 1696 Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) 1697 .setData(Uri.fromParts("package", ri.activityInfo.packageName, null)) 1698 .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); 1699 startActivityAsUser(in, mMultiProfilePagerAdapter.getCurrentUserHandle()); 1700 } 1701 1702 @VisibleForTesting 1703 protected ResolverListAdapter createResolverListAdapter(Context context, 1704 List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList, 1705 boolean filterLastUsed, UserHandle userHandle) { 1706 Intent startIntent = getIntent(); 1707 boolean isAudioCaptureDevice = 1708 startIntent.getBooleanExtra(EXTRA_IS_AUDIO_CAPTURE_DEVICE, false); 1709 UserHandle initialIntentsUserSpace = isLaunchedAsCloneProfile() 1710 && userHandle.equals(getPersonalProfileUserHandle()) 1711 ? getCloneProfileUserHandle() : userHandle; 1712 return new ResolverListAdapter(context, payloadIntents, initialIntents, rList, 1713 filterLastUsed, createListController(userHandle), this, 1714 isAudioCaptureDevice, initialIntentsUserSpace); 1715 } 1716 1717 private AbstractResolverComparator makeResolverComparator(UserHandle userHandle) { 1718 if (mHasSubclassSpecifiedResolutions) { 1719 return new NoOpResolverComparator( 1720 this, getTargetIntent(), getResolverRankerServiceUserHandleList(userHandle)); 1721 } else { 1722 return new ResolverRankerServiceResolverComparator( 1723 this, 1724 getTargetIntent(), 1725 getReferrerPackageName(), 1726 null, 1727 null, 1728 getResolverRankerServiceUserHandleList(userHandle)); 1729 } 1730 } 1731 1732 @VisibleForTesting 1733 protected ResolverListController createListController(UserHandle userHandle) { 1734 UserHandle queryIntentsUser = getQueryIntentsUser(userHandle); 1735 AbstractResolverComparator resolverComparator = makeResolverComparator(userHandle); 1736 return new ResolverListController( 1737 this, 1738 mPm, 1739 getTargetIntent(), 1740 getReferrerPackageName(), 1741 mLaunchedFromUid, 1742 userHandle, 1743 resolverComparator, 1744 queryIntentsUser); 1745 } 1746 1747 /** 1748 * Sets up the content view. 1749 * @return <code>true</code> if the activity is finishing and creation should halt. 1750 */ 1751 private boolean configureContentView() { 1752 if (mMultiProfilePagerAdapter.getActiveListAdapter() == null) { 1753 throw new IllegalStateException("mMultiProfilePagerAdapter.getCurrentListAdapter() " 1754 + "cannot be null."); 1755 } 1756 Trace.beginSection("configureContentView"); 1757 // We partially rebuild the inactive adapter to determine if we should auto launch 1758 // isTabLoaded will be true here if the empty state screen is shown instead of the list. 1759 boolean rebuildCompleted = mMultiProfilePagerAdapter.rebuildActiveTab(true) 1760 || mMultiProfilePagerAdapter.getActiveListAdapter().isTabLoaded(); 1761 if (shouldShowTabs()) { 1762 boolean rebuildInactiveCompleted = mMultiProfilePagerAdapter.rebuildInactiveTab(false) 1763 || mMultiProfilePagerAdapter.getInactiveListAdapter().isTabLoaded(); 1764 rebuildCompleted = rebuildCompleted && rebuildInactiveCompleted; 1765 } 1766 1767 if (shouldUseMiniResolver()) { 1768 configureMiniResolverContent(); 1769 Trace.endSection(); 1770 return false; 1771 } 1772 1773 if (useLayoutWithDefault()) { 1774 mLayoutId = R.layout.resolver_list_with_default; 1775 } else { 1776 mLayoutId = getLayoutResource(); 1777 } 1778 setContentView(mLayoutId); 1779 mMultiProfilePagerAdapter.setupViewPager(findViewById(R.id.profile_pager)); 1780 boolean result = postRebuildList(rebuildCompleted); 1781 Trace.endSection(); 1782 return result; 1783 } 1784 1785 /** 1786 * Mini resolver is shown when the user is choosing between browser[s] in this profile and a 1787 * single app in the other profile (see shouldUseMiniResolver()). It shows the single app icon 1788 * and asks the user if they'd like to open that cross-profile app or use the in-profile 1789 * browser. 1790 */ 1791 private void configureMiniResolverContent() { 1792 mLayoutId = R.layout.miniresolver; 1793 setContentView(mLayoutId); 1794 1795 DisplayResolveInfo sameProfileResolveInfo = 1796 mMultiProfilePagerAdapter.getActiveListAdapter().mDisplayList.get(0); 1797 boolean inWorkProfile = getCurrentProfile() == PROFILE_WORK; 1798 1799 final ResolverListAdapter inactiveAdapter = 1800 mMultiProfilePagerAdapter.getInactiveListAdapter(); 1801 final DisplayResolveInfo otherProfileResolveInfo = inactiveAdapter.mDisplayList.get(0); 1802 1803 // Load the icon asynchronously 1804 ImageView icon = findViewById(R.id.icon); 1805 inactiveAdapter.new LoadIconTask(otherProfileResolveInfo) { 1806 @Override 1807 protected void onPostExecute(Drawable drawable) { 1808 if (!isDestroyed()) { 1809 otherProfileResolveInfo.setDisplayIcon(drawable); 1810 new ResolverListAdapter.ViewHolder(icon).bindIcon(otherProfileResolveInfo); 1811 } 1812 } 1813 }.execute(); 1814 1815 CharSequence targetDisplayLabel = otherProfileResolveInfo.getDisplayLabel(); 1816 1817 DevicePolicyResourcesManager devicePolicyResourcesManager = getSystemService( 1818 DevicePolicyManager.class).getResources(); 1819 1820 if (inWorkProfile) { 1821 ((TextView) findViewById(R.id.open_cross_profile)).setText( 1822 devicePolicyResourcesManager.getString(MINIRESOLVER_OPEN_IN_PERSONAL, 1823 () -> getString(R.string.miniresolver_open_in_personal, 1824 targetDisplayLabel), 1825 targetDisplayLabel)); 1826 ((Button) findViewById(R.id.use_same_profile_browser)).setText( 1827 devicePolicyResourcesManager.getString(MINIRESOLVER_USE_WORK_BROWSER, 1828 () -> getString(R.string.miniresolver_use_work_browser))); 1829 } else { 1830 ((TextView) findViewById(R.id.open_cross_profile)).setText( 1831 devicePolicyResourcesManager.getString(MINIRESOLVER_OPEN_IN_WORK, 1832 () -> getString(R.string.miniresolver_open_in_work, 1833 targetDisplayLabel), 1834 targetDisplayLabel)); 1835 ((Button) findViewById(R.id.use_same_profile_browser)).setText( 1836 devicePolicyResourcesManager.getString(MINIRESOLVER_USE_PERSONAL_BROWSER, 1837 () -> getString(R.string.miniresolver_use_personal_browser))); 1838 } 1839 1840 findViewById(R.id.use_same_profile_browser).setOnClickListener( 1841 v -> { 1842 safelyStartActivity(sameProfileResolveInfo); 1843 finish(); 1844 }); 1845 1846 findViewById(R.id.button_open).setOnClickListener(v -> { 1847 Intent intent = otherProfileResolveInfo.getResolvedIntent(); 1848 safelyStartActivityAsUser(otherProfileResolveInfo, 1849 inactiveAdapter.mResolverListController.getUserHandle()); 1850 finish(); 1851 }); 1852 } 1853 1854 /** 1855 * Mini resolver should be used when all of the following are true: 1856 * 1. This is the intent picker (ResolverActivity). 1857 * 2. This profile only has web browser matches. 1858 * 3. The other profile has a single non-browser match. 1859 */ 1860 private boolean shouldUseMiniResolver() { 1861 if (!mIsIntentPicker) { 1862 return false; 1863 } 1864 if (mMultiProfilePagerAdapter.getActiveListAdapter() == null 1865 || mMultiProfilePagerAdapter.getInactiveListAdapter() == null) { 1866 return false; 1867 } 1868 List<DisplayResolveInfo> sameProfileList = 1869 mMultiProfilePagerAdapter.getActiveListAdapter().mDisplayList; 1870 List<DisplayResolveInfo> otherProfileList = 1871 mMultiProfilePagerAdapter.getInactiveListAdapter().mDisplayList; 1872 1873 if (sameProfileList.isEmpty()) { 1874 Log.d(TAG, "No targets in the current profile"); 1875 return false; 1876 } 1877 1878 if (otherProfileList.size() != 1) { 1879 Log.d(TAG, "Found " + otherProfileList.size() + " resolvers in the other profile"); 1880 return false; 1881 } 1882 1883 if (otherProfileList.get(0).getResolveInfo().handleAllWebDataURI) { 1884 Log.d(TAG, "Other profile is a web browser"); 1885 return false; 1886 } 1887 1888 for (DisplayResolveInfo info : sameProfileList) { 1889 if (!info.getResolveInfo().handleAllWebDataURI) { 1890 Log.d(TAG, "Non-browser found in this profile"); 1891 return false; 1892 } 1893 } 1894 1895 return true; 1896 } 1897 1898 /** 1899 * Finishing procedures to be performed after the list has been rebuilt. 1900 * </p>Subclasses must call postRebuildListInternal at the end of postRebuildList. 1901 * @param rebuildCompleted 1902 * @return <code>true</code> if the activity is finishing and creation should halt. 1903 */ 1904 protected boolean postRebuildList(boolean rebuildCompleted) { 1905 return postRebuildListInternal(rebuildCompleted); 1906 } 1907 1908 /** 1909 * Finishing procedures to be performed after the list has been rebuilt. 1910 * @param rebuildCompleted 1911 * @return <code>true</code> if the activity is finishing and creation should halt. 1912 */ 1913 final boolean postRebuildListInternal(boolean rebuildCompleted) { 1914 int count = mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredCount(); 1915 1916 // We only rebuild asynchronously when we have multiple elements to sort. In the case where 1917 // we're already done, we can check if we should auto-launch immediately. 1918 if (rebuildCompleted && maybeAutolaunchActivity()) { 1919 return true; 1920 } 1921 1922 setupViewVisibilities(); 1923 1924 if (shouldShowTabs()) { 1925 setupProfileTabs(); 1926 } 1927 1928 return false; 1929 } 1930 1931 private int isPermissionGranted(String permission, int uid) { 1932 return ActivityManager.checkComponentPermission(permission, uid, 1933 /* owningUid= */-1, /* exported= */ true); 1934 } 1935 1936 /** 1937 * @return {@code true} if a resolved target is autolaunched, otherwise {@code false} 1938 */ 1939 private boolean maybeAutolaunchActivity() { 1940 int numberOfProfiles = mMultiProfilePagerAdapter.getItemCount(); 1941 if (numberOfProfiles == 1 && maybeAutolaunchIfSingleTarget()) { 1942 return true; 1943 } else if (numberOfProfiles == 2 1944 && mMultiProfilePagerAdapter.getActiveListAdapter().isTabLoaded() 1945 && mMultiProfilePagerAdapter.getInactiveListAdapter().isTabLoaded() 1946 && (maybeAutolaunchIfNoAppsOnInactiveTab() 1947 || maybeAutolaunchIfCrossProfileSupported())) { 1948 return true; 1949 } 1950 return false; 1951 } 1952 1953 private boolean maybeAutolaunchIfSingleTarget() { 1954 int count = mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredCount(); 1955 if (count != 1) { 1956 return false; 1957 } 1958 1959 if (mMultiProfilePagerAdapter.getActiveListAdapter().getOtherProfile() != null) { 1960 return false; 1961 } 1962 1963 // Only one target, so we're a candidate to auto-launch! 1964 final TargetInfo target = mMultiProfilePagerAdapter.getActiveListAdapter() 1965 .targetInfoForPosition(0, false); 1966 if (shouldAutoLaunchSingleChoice(target)) { 1967 safelyStartActivity(target); 1968 finish(); 1969 return true; 1970 } 1971 return false; 1972 } 1973 1974 private boolean maybeAutolaunchIfNoAppsOnInactiveTab() { 1975 int count = mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredCount(); 1976 if (count != 1) { 1977 return false; 1978 } 1979 ResolverListAdapter inactiveListAdapter = 1980 mMultiProfilePagerAdapter.getInactiveListAdapter(); 1981 if (inactiveListAdapter.getUnfilteredCount() != 0) { 1982 return false; 1983 } 1984 TargetInfo target = mMultiProfilePagerAdapter.getActiveListAdapter() 1985 .targetInfoForPosition(0, false); 1986 safelyStartActivity(target); 1987 finish(); 1988 return true; 1989 } 1990 1991 /** 1992 * When we have a personal and a work profile, we auto launch in the following scenario: 1993 * - There is 1 resolved target on each profile 1994 * - That target is the same app on both profiles 1995 * - The target app has permission to communicate cross profiles 1996 * - The target app has declared it supports cross-profile communication via manifest metadata 1997 */ 1998 private boolean maybeAutolaunchIfCrossProfileSupported() { 1999 ResolverListAdapter activeListAdapter = mMultiProfilePagerAdapter.getActiveListAdapter(); 2000 int count = activeListAdapter.getUnfilteredCount(); 2001 if (count != 1) { 2002 return false; 2003 } 2004 ResolverListAdapter inactiveListAdapter = 2005 mMultiProfilePagerAdapter.getInactiveListAdapter(); 2006 if (inactiveListAdapter.getUnfilteredCount() != 1) { 2007 return false; 2008 } 2009 TargetInfo activeProfileTarget = activeListAdapter 2010 .targetInfoForPosition(0, false); 2011 TargetInfo inactiveProfileTarget = inactiveListAdapter.targetInfoForPosition(0, false); 2012 if (!Objects.equals(activeProfileTarget.getResolvedComponentName(), 2013 inactiveProfileTarget.getResolvedComponentName())) { 2014 return false; 2015 } 2016 if (!shouldAutoLaunchSingleChoice(activeProfileTarget)) { 2017 return false; 2018 } 2019 String packageName = activeProfileTarget.getResolvedComponentName().getPackageName(); 2020 if (!canAppInteractCrossProfiles(packageName)) { 2021 return false; 2022 } 2023 2024 DevicePolicyEventLogger 2025 .createEvent(DevicePolicyEnums.RESOLVER_AUTOLAUNCH_CROSS_PROFILE_TARGET) 2026 .setBoolean(activeListAdapter.getUserHandle() 2027 .equals(getPersonalProfileUserHandle())) 2028 .setStrings(getMetricsCategory()) 2029 .write(); 2030 safelyStartActivity(activeProfileTarget); 2031 finish(); 2032 return true; 2033 } 2034 2035 /** 2036 * Returns whether the package has the necessary permissions to interact across profiles on 2037 * behalf of a given user. 2038 * 2039 * <p>This means meeting the following condition: 2040 * <ul> 2041 * <li>The app's {@link ApplicationInfo#crossProfile} flag must be true, and at least 2042 * one of the following conditions must be fulfilled</li> 2043 * <li>{@code Manifest.permission.INTERACT_ACROSS_USERS_FULL} granted.</li> 2044 * <li>{@code Manifest.permission.INTERACT_ACROSS_USERS} granted.</li> 2045 * <li>{@code Manifest.permission.INTERACT_ACROSS_PROFILES} granted, or the corresponding 2046 * AppOps {@code android:interact_across_profiles} is set to "allow".</li> 2047 * </ul> 2048 * 2049 */ 2050 private boolean canAppInteractCrossProfiles(String packageName) { 2051 ApplicationInfo applicationInfo; 2052 try { 2053 applicationInfo = getPackageManager().getApplicationInfo(packageName, 0); 2054 } catch (NameNotFoundException e) { 2055 Log.e(TAG, "Package " + packageName + " does not exist on current user."); 2056 return false; 2057 } 2058 if (!applicationInfo.crossProfile) { 2059 return false; 2060 } 2061 2062 int packageUid = applicationInfo.uid; 2063 2064 if (isPermissionGranted(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, 2065 packageUid) == PackageManager.PERMISSION_GRANTED) { 2066 return true; 2067 } 2068 if (isPermissionGranted(android.Manifest.permission.INTERACT_ACROSS_USERS, packageUid) 2069 == PackageManager.PERMISSION_GRANTED) { 2070 return true; 2071 } 2072 if (PermissionChecker.checkPermissionForPreflight(this, INTERACT_ACROSS_PROFILES, 2073 PID_UNKNOWN, packageUid, packageName) == PackageManager.PERMISSION_GRANTED) { 2074 return true; 2075 } 2076 return false; 2077 } 2078 2079 private boolean isAutolaunching() { 2080 return !mRegistered && isFinishing(); 2081 } 2082 2083 private void setupProfileTabs() { 2084 maybeHideDivider(); 2085 TabHost tabHost = findViewById(R.id.profile_tabhost); 2086 tabHost.setup(); 2087 ViewPager viewPager = findViewById(R.id.profile_pager); 2088 viewPager.setSaveEnabled(false); 2089 2090 Button personalButton = (Button) getLayoutInflater().inflate( 2091 R.layout.resolver_profile_tab_button, tabHost.getTabWidget(), false); 2092 personalButton.setText(getPersonalTabLabel()); 2093 personalButton.setContentDescription(getPersonalTabAccessibilityLabel()); 2094 2095 TabHost.TabSpec tabSpec = tabHost.newTabSpec(TAB_TAG_PERSONAL) 2096 .setContent(R.id.profile_pager) 2097 .setIndicator(personalButton); 2098 tabHost.addTab(tabSpec); 2099 2100 Button workButton = (Button) getLayoutInflater().inflate( 2101 R.layout.resolver_profile_tab_button, tabHost.getTabWidget(), false); 2102 workButton.setText(getWorkTabLabel()); 2103 workButton.setContentDescription(getWorkTabAccessibilityLabel()); 2104 2105 tabSpec = tabHost.newTabSpec(TAB_TAG_WORK) 2106 .setContent(R.id.profile_pager) 2107 .setIndicator(workButton); 2108 tabHost.addTab(tabSpec); 2109 2110 TabWidget tabWidget = tabHost.getTabWidget(); 2111 tabWidget.setVisibility(View.VISIBLE); 2112 updateActiveTabStyle(tabHost); 2113 2114 tabHost.setOnTabChangedListener(tabId -> { 2115 updateActiveTabStyle(tabHost); 2116 if (TAB_TAG_PERSONAL.equals(tabId)) { 2117 viewPager.setCurrentItem(0); 2118 } else { 2119 viewPager.setCurrentItem(1); 2120 } 2121 setupViewVisibilities(); 2122 maybeLogProfileChange(); 2123 onProfileTabSelected(); 2124 DevicePolicyEventLogger 2125 .createEvent(DevicePolicyEnums.RESOLVER_SWITCH_TABS) 2126 .setInt(viewPager.getCurrentItem()) 2127 .setStrings(getMetricsCategory()) 2128 .write(); 2129 }); 2130 2131 viewPager.setVisibility(View.VISIBLE); 2132 tabHost.setCurrentTab(mMultiProfilePagerAdapter.getCurrentPage()); 2133 mMultiProfilePagerAdapter.setOnProfileSelectedListener( 2134 new AbstractMultiProfilePagerAdapter.OnProfileSelectedListener() { 2135 @Override 2136 public void onProfileSelected(int index) { 2137 tabHost.setCurrentTab(index); 2138 resetButtonBar(); 2139 resetCheckedItem(); 2140 } 2141 2142 @Override 2143 public void onProfilePageStateChanged(int state) { 2144 onHorizontalSwipeStateChanged(state); 2145 } 2146 }); 2147 mOnSwitchOnWorkSelectedListener = () -> { 2148 final View workTab = tabHost.getTabWidget().getChildAt(1); 2149 workTab.setFocusable(true); 2150 workTab.setFocusableInTouchMode(true); 2151 workTab.requestFocus(); 2152 }; 2153 } 2154 2155 private String getPersonalTabLabel() { 2156 return getSystemService(DevicePolicyManager.class).getResources().getString( 2157 RESOLVER_PERSONAL_TAB, () -> getString(R.string.resolver_personal_tab)); 2158 } 2159 2160 private String getWorkTabLabel() { 2161 return getSystemService(DevicePolicyManager.class).getResources().getString( 2162 RESOLVER_WORK_TAB, () -> getString(R.string.resolver_work_tab)); 2163 } 2164 2165 void onHorizontalSwipeStateChanged(int state) {} 2166 2167 private void maybeHideDivider() { 2168 if (!mIsIntentPicker) { 2169 return; 2170 } 2171 final View divider = findViewById(R.id.divider); 2172 if (divider == null) { 2173 return; 2174 } 2175 divider.setVisibility(View.GONE); 2176 } 2177 2178 /** 2179 * Callback called when user changes the profile tab. 2180 * <p>This method is intended to be overridden by subclasses. 2181 */ 2182 protected void onProfileTabSelected() { } 2183 2184 private void resetCheckedItem() { 2185 if (!mIsIntentPicker) { 2186 return; 2187 } 2188 mLastSelected = ListView.INVALID_POSITION; 2189 ListView inactiveListView = (ListView) mMultiProfilePagerAdapter.getInactiveAdapterView(); 2190 if (inactiveListView.getCheckedItemCount() > 0) { 2191 inactiveListView.setItemChecked(inactiveListView.getCheckedItemPosition(), false); 2192 } 2193 } 2194 2195 private String getPersonalTabAccessibilityLabel() { 2196 return getSystemService(DevicePolicyManager.class).getResources().getString( 2197 RESOLVER_PERSONAL_TAB_ACCESSIBILITY, 2198 () -> getString(R.string.resolver_personal_tab_accessibility)); 2199 } 2200 2201 private String getWorkTabAccessibilityLabel() { 2202 return getSystemService(DevicePolicyManager.class).getResources().getString( 2203 RESOLVER_WORK_TAB_ACCESSIBILITY, 2204 () -> getString(R.string.resolver_work_tab_accessibility)); 2205 } 2206 2207 private static int getAttrColor(Context context, int attr) { 2208 TypedArray ta = context.obtainStyledAttributes(new int[]{attr}); 2209 int colorAccent = ta.getColor(0, 0); 2210 ta.recycle(); 2211 return colorAccent; 2212 } 2213 2214 private void updateActiveTabStyle(TabHost tabHost) { 2215 int currentTab = tabHost.getCurrentTab(); 2216 TextView selected = (TextView) tabHost.getTabWidget().getChildAt(currentTab); 2217 TextView unselected = (TextView) tabHost.getTabWidget().getChildAt(1 - currentTab); 2218 selected.setSelected(true); 2219 unselected.setSelected(false); 2220 } 2221 2222 private void setupViewVisibilities() { 2223 ResolverListAdapter activeListAdapter = mMultiProfilePagerAdapter.getActiveListAdapter(); 2224 if (!mMultiProfilePagerAdapter.shouldShowEmptyStateScreen(activeListAdapter)) { 2225 addUseDifferentAppLabelIfNecessary(activeListAdapter); 2226 } 2227 } 2228 2229 /** 2230 * Add a label to signify that the user can pick a different app. 2231 * @param adapter The adapter used to provide data to item views. 2232 */ 2233 public void addUseDifferentAppLabelIfNecessary(ResolverListAdapter adapter) { 2234 final boolean useHeader = adapter.hasFilteredItem(); 2235 if (useHeader) { 2236 FrameLayout stub = findViewById(R.id.stub); 2237 stub.setVisibility(View.VISIBLE); 2238 TextView textView = (TextView) LayoutInflater.from(this).inflate( 2239 R.layout.resolver_different_item_header, null, false); 2240 if (shouldShowTabs()) { 2241 textView.setGravity(Gravity.CENTER); 2242 } 2243 stub.addView(textView); 2244 } 2245 } 2246 2247 private void setupAdapterListView(ListView listView, ItemClickListener listener) { 2248 listView.setOnItemClickListener(listener); 2249 listView.setOnItemLongClickListener(listener); 2250 2251 if (mSupportsAlwaysUseOption) { 2252 listView.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE); 2253 } 2254 } 2255 2256 /** 2257 * Configure the area above the app selection list (title, content preview, etc). 2258 */ 2259 private void maybeCreateHeader(ResolverListAdapter listAdapter) { 2260 if (mHeaderCreatorUser != null 2261 && !listAdapter.getUserHandle().equals(mHeaderCreatorUser)) { 2262 return; 2263 } 2264 if (!shouldShowTabs() 2265 && listAdapter.getCount() == 0 && listAdapter.getPlaceholderCount() == 0) { 2266 final TextView titleView = findViewById(R.id.title); 2267 if (titleView != null) { 2268 titleView.setVisibility(View.GONE); 2269 } 2270 } 2271 2272 CharSequence title = mTitle != null 2273 ? mTitle 2274 : getTitleForAction(getTargetIntent(), mDefaultTitleResId); 2275 2276 if (!TextUtils.isEmpty(title)) { 2277 final TextView titleView = findViewById(R.id.title); 2278 if (titleView != null) { 2279 titleView.setText(title); 2280 } 2281 setTitle(title); 2282 } 2283 2284 final ImageView iconView = findViewById(R.id.icon); 2285 if (iconView != null) { 2286 listAdapter.loadFilteredItemIconTaskAsync(iconView); 2287 } 2288 mHeaderCreatorUser = listAdapter.getUserHandle(); 2289 } 2290 2291 protected void resetButtonBar() { 2292 if (!mSupportsAlwaysUseOption) { 2293 return; 2294 } 2295 final ViewGroup buttonLayout = findViewById(R.id.button_bar); 2296 if (buttonLayout == null) { 2297 Log.e(TAG, "Layout unexpectedly does not have a button bar"); 2298 return; 2299 } 2300 ResolverListAdapter activeListAdapter = 2301 mMultiProfilePagerAdapter.getActiveListAdapter(); 2302 View buttonBarDivider = findViewById(R.id.resolver_button_bar_divider); 2303 if (!useLayoutWithDefault()) { 2304 int inset = mSystemWindowInsets != null ? mSystemWindowInsets.bottom : 0; 2305 buttonLayout.setPadding(buttonLayout.getPaddingLeft(), buttonLayout.getPaddingTop(), 2306 buttonLayout.getPaddingRight(), getResources().getDimensionPixelSize( 2307 R.dimen.resolver_button_bar_spacing) + inset); 2308 } 2309 if (activeListAdapter.isTabLoaded() 2310 && mMultiProfilePagerAdapter.shouldShowEmptyStateScreen(activeListAdapter) 2311 && !useLayoutWithDefault()) { 2312 buttonLayout.setVisibility(View.INVISIBLE); 2313 if (buttonBarDivider != null) { 2314 buttonBarDivider.setVisibility(View.INVISIBLE); 2315 } 2316 setButtonBarIgnoreOffset(/* ignoreOffset */ false); 2317 return; 2318 } 2319 if (buttonBarDivider != null) { 2320 buttonBarDivider.setVisibility(View.VISIBLE); 2321 } 2322 buttonLayout.setVisibility(View.VISIBLE); 2323 setButtonBarIgnoreOffset(/* ignoreOffset */ true); 2324 2325 mOnceButton = (Button) buttonLayout.findViewById(R.id.button_once); 2326 mAlwaysButton = (Button) buttonLayout.findViewById(R.id.button_always); 2327 2328 resetAlwaysOrOnceButtonBar(); 2329 } 2330 2331 /** 2332 * Updates the button bar container {@code ignoreOffset} layout param. 2333 * <p>Setting this to {@code true} means that the button bar will be glued to the bottom of 2334 * the screen. 2335 */ 2336 private void setButtonBarIgnoreOffset(boolean ignoreOffset) { 2337 View buttonBarContainer = findViewById(R.id.button_bar_container); 2338 if (buttonBarContainer != null) { 2339 ResolverDrawerLayout.LayoutParams layoutParams = 2340 (ResolverDrawerLayout.LayoutParams) buttonBarContainer.getLayoutParams(); 2341 layoutParams.ignoreOffset = ignoreOffset; 2342 buttonBarContainer.setLayoutParams(layoutParams); 2343 } 2344 } 2345 2346 private void resetAlwaysOrOnceButtonBar() { 2347 // Disable both buttons initially 2348 setAlwaysButtonEnabled(false, ListView.INVALID_POSITION, false); 2349 mOnceButton.setEnabled(false); 2350 2351 int filteredPosition = mMultiProfilePagerAdapter.getActiveListAdapter() 2352 .getFilteredPosition(); 2353 if (useLayoutWithDefault() && filteredPosition != ListView.INVALID_POSITION) { 2354 setAlwaysButtonEnabled(true, filteredPosition, false); 2355 mOnceButton.setEnabled(true); 2356 // Focus the button if we already have the default option 2357 mOnceButton.requestFocus(); 2358 return; 2359 } 2360 2361 // When the items load in, if an item was already selected, enable the buttons 2362 ListView currentAdapterView = (ListView) mMultiProfilePagerAdapter.getActiveAdapterView(); 2363 if (currentAdapterView != null 2364 && currentAdapterView.getCheckedItemPosition() != ListView.INVALID_POSITION) { 2365 setAlwaysButtonEnabled(true, currentAdapterView.getCheckedItemPosition(), true); 2366 mOnceButton.setEnabled(true); 2367 } 2368 } 2369 2370 @Override // ResolverListCommunicator 2371 public boolean useLayoutWithDefault() { 2372 // We only use the default app layout when the profile of the active user has a 2373 // filtered item. We always show the same default app even in the inactive user profile. 2374 boolean adapterForCurrentUserHasFilteredItem = 2375 mMultiProfilePagerAdapter.getListAdapterForUserHandle( 2376 getTabOwnerUserHandleForLaunch()).hasFilteredItem(); 2377 return mSupportsAlwaysUseOption && adapterForCurrentUserHasFilteredItem; 2378 } 2379 2380 /** 2381 * If {@code retainInOnStop} is set to true, we will not finish ourselves when onStop gets 2382 * called and we are launched in a new task. 2383 */ 2384 protected void setRetainInOnStop(boolean retainInOnStop) { 2385 mRetainInOnStop = retainInOnStop; 2386 } 2387 2388 /** 2389 * Check a simple match for the component of two ResolveInfos. 2390 */ 2391 @Override // ResolverListCommunicator 2392 public boolean resolveInfoMatch(ResolveInfo lhs, ResolveInfo rhs) { 2393 return lhs == null ? rhs == null 2394 : lhs.activityInfo == null ? rhs.activityInfo == null 2395 : Objects.equals(lhs.activityInfo.name, rhs.activityInfo.name) 2396 && Objects.equals(lhs.activityInfo.packageName, rhs.activityInfo.packageName) 2397 // Comparing against resolveInfo.userHandle in case cloned apps are present, 2398 // as they will have the same activityInfo. 2399 && Objects.equals( 2400 getResolveInfoUserHandle(lhs, 2401 mMultiProfilePagerAdapter.getActiveListAdapter().getUserHandle()), 2402 getResolveInfoUserHandle(rhs, 2403 mMultiProfilePagerAdapter.getActiveListAdapter().getUserHandle())); 2404 } 2405 2406 protected String getMetricsCategory() { 2407 return METRICS_CATEGORY_RESOLVER; 2408 } 2409 2410 @Override // ResolverListCommunicator 2411 public void onHandlePackagesChanged(ResolverListAdapter listAdapter) { 2412 if (listAdapter == mMultiProfilePagerAdapter.getActiveListAdapter()) { 2413 if (listAdapter.getUserHandle().equals(getWorkProfileUserHandle()) 2414 && mQuietModeManager.isWaitingToEnableWorkProfile()) { 2415 // We have just turned on the work profile and entered the pass code to start it, 2416 // now we are waiting to receive the ACTION_USER_UNLOCKED broadcast. There is no 2417 // point in reloading the list now, since the work profile user is still 2418 // turning on. 2419 return; 2420 } 2421 boolean listRebuilt = mMultiProfilePagerAdapter.rebuildActiveTab(true); 2422 if (listRebuilt) { 2423 ResolverListAdapter activeListAdapter = 2424 mMultiProfilePagerAdapter.getActiveListAdapter(); 2425 activeListAdapter.notifyDataSetChanged(); 2426 if (activeListAdapter.getCount() == 0 && !inactiveListAdapterHasItems()) { 2427 // We no longer have any items... just finish the activity. 2428 finish(); 2429 } 2430 } 2431 } else { 2432 mMultiProfilePagerAdapter.clearInactiveProfileCache(); 2433 } 2434 } 2435 2436 private boolean inactiveListAdapterHasItems() { 2437 if (!shouldShowTabs()) { 2438 return false; 2439 } 2440 return mMultiProfilePagerAdapter.getInactiveListAdapter().getCount() > 0; 2441 } 2442 2443 private BroadcastReceiver createWorkProfileStateReceiver() { 2444 return new BroadcastReceiver() { 2445 @Override 2446 public void onReceive(Context context, Intent intent) { 2447 String action = intent.getAction(); 2448 if (!TextUtils.equals(action, Intent.ACTION_USER_UNLOCKED) 2449 && !TextUtils.equals(action, Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE) 2450 && !TextUtils.equals(action, Intent.ACTION_MANAGED_PROFILE_AVAILABLE)) { 2451 return; 2452 } 2453 2454 int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); 2455 2456 if (userId != getWorkProfileUserHandle().getIdentifier()) { 2457 return; 2458 } 2459 2460 if (isWorkProfileEnabled()) { 2461 if (mWorkProfileHasBeenEnabled) { 2462 return; 2463 } 2464 2465 mWorkProfileHasBeenEnabled = true; 2466 mQuietModeManager.markWorkProfileEnabledBroadcastReceived(); 2467 } else { 2468 // Must be an UNAVAILABLE broadcast, so we watch for the next availability 2469 mWorkProfileHasBeenEnabled = false; 2470 } 2471 2472 if (mMultiProfilePagerAdapter.getCurrentUserHandle() 2473 .equals(getWorkProfileUserHandle())) { 2474 mMultiProfilePagerAdapter.rebuildActiveTab(true); 2475 } else { 2476 mMultiProfilePagerAdapter.clearInactiveProfileCache(); 2477 } 2478 } 2479 }; 2480 } 2481 2482 public static final class ResolvedComponentInfo { 2483 public final ComponentName name; 2484 private final List<Intent> mIntents = new ArrayList<>(); 2485 private final List<ResolveInfo> mResolveInfos = new ArrayList<>(); 2486 private boolean mPinned; 2487 private boolean mFixedAtTop; 2488 2489 public ResolvedComponentInfo(ComponentName name, Intent intent, ResolveInfo info) { 2490 this.name = name; 2491 add(intent, info); 2492 } 2493 2494 public void add(Intent intent, ResolveInfo info) { 2495 mIntents.add(intent); 2496 mResolveInfos.add(info); 2497 } 2498 2499 public int getCount() { 2500 return mIntents.size(); 2501 } 2502 2503 public Intent getIntentAt(int index) { 2504 return index >= 0 ? mIntents.get(index) : null; 2505 } 2506 2507 public ResolveInfo getResolveInfoAt(int index) { 2508 return index >= 0 ? mResolveInfos.get(index) : null; 2509 } 2510 2511 public int findIntent(Intent intent) { 2512 for (int i = 0, N = mIntents.size(); i < N; i++) { 2513 if (intent.equals(mIntents.get(i))) { 2514 return i; 2515 } 2516 } 2517 return -1; 2518 } 2519 2520 public int findResolveInfo(ResolveInfo info) { 2521 for (int i = 0, N = mResolveInfos.size(); i < N; i++) { 2522 if (info.equals(mResolveInfos.get(i))) { 2523 return i; 2524 } 2525 } 2526 return -1; 2527 } 2528 2529 public boolean isPinned() { 2530 return mPinned; 2531 } 2532 2533 public void setPinned(boolean pinned) { 2534 mPinned = pinned; 2535 } 2536 2537 public boolean isFixedAtTop() { 2538 return mFixedAtTop; 2539 } 2540 2541 public void setFixedAtTop(boolean isFixedAtTop) { 2542 mFixedAtTop = isFixedAtTop; 2543 } 2544 } 2545 2546 class ItemClickListener implements AdapterView.OnItemClickListener, 2547 AdapterView.OnItemLongClickListener { 2548 @Override 2549 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 2550 final ListView listView = parent instanceof ListView ? (ListView) parent : null; 2551 if (listView != null) { 2552 position -= listView.getHeaderViewsCount(); 2553 } 2554 if (position < 0) { 2555 // Header views don't count. 2556 return; 2557 } 2558 // If we're still loading, we can't yet enable the buttons. 2559 if (mMultiProfilePagerAdapter.getActiveListAdapter() 2560 .resolveInfoForPosition(position, true) == null) { 2561 return; 2562 } 2563 ListView currentAdapterView = 2564 (ListView) mMultiProfilePagerAdapter.getActiveAdapterView(); 2565 final int checkedPos = currentAdapterView.getCheckedItemPosition(); 2566 final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION; 2567 if (!useLayoutWithDefault() 2568 && (!hasValidSelection || mLastSelected != checkedPos) 2569 && mAlwaysButton != null) { 2570 setAlwaysButtonEnabled(hasValidSelection, checkedPos, true); 2571 mOnceButton.setEnabled(hasValidSelection); 2572 if (hasValidSelection) { 2573 currentAdapterView.smoothScrollToPosition(checkedPos); 2574 mOnceButton.requestFocus(); 2575 } 2576 mLastSelected = checkedPos; 2577 } else { 2578 startSelected(position, false, true); 2579 } 2580 } 2581 2582 @Override 2583 public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { 2584 final ListView listView = parent instanceof ListView ? (ListView) parent : null; 2585 if (listView != null) { 2586 position -= listView.getHeaderViewsCount(); 2587 } 2588 if (position < 0) { 2589 // Header views don't count. 2590 return false; 2591 } 2592 ResolveInfo ri = mMultiProfilePagerAdapter.getActiveListAdapter() 2593 .resolveInfoForPosition(position, true); 2594 showTargetDetails(ri); 2595 return true; 2596 } 2597 2598 } 2599 2600 static final boolean isSpecificUriMatch(int match) { 2601 match = match&IntentFilter.MATCH_CATEGORY_MASK; 2602 return match >= IntentFilter.MATCH_CATEGORY_HOST 2603 && match <= IntentFilter.MATCH_CATEGORY_PATH; 2604 } 2605 2606 static class PickTargetOptionRequest extends PickOptionRequest { 2607 public PickTargetOptionRequest(@Nullable Prompt prompt, Option[] options, 2608 @Nullable Bundle extras) { 2609 super(prompt, options, extras); 2610 } 2611 2612 @Override 2613 public void onCancel() { 2614 super.onCancel(); 2615 final ResolverActivity ra = (ResolverActivity) getActivity(); 2616 if (ra != null) { 2617 ra.mPickOptionRequest = null; 2618 ra.finish(); 2619 } 2620 } 2621 2622 @Override 2623 public void onPickOptionResult(boolean finished, Option[] selections, Bundle result) { 2624 super.onPickOptionResult(finished, selections, result); 2625 if (selections.length != 1) { 2626 // TODO In a better world we would filter the UI presented here and let the 2627 // user refine. Maybe later. 2628 return; 2629 } 2630 2631 final ResolverActivity ra = (ResolverActivity) getActivity(); 2632 if (ra != null) { 2633 final TargetInfo ti = ra.mMultiProfilePagerAdapter.getActiveListAdapter() 2634 .getItem(selections[0].getIndex()); 2635 if (ra.onTargetSelected(ti, false)) { 2636 ra.mPickOptionRequest = null; 2637 ra.finish(); 2638 } 2639 } 2640 } 2641 } 2642 2643 protected void maybeLogProfileChange() {} 2644 2645 /** 2646 * Returns the {@link UserHandle} to use when querying resolutions for intents in a 2647 * {@link ResolverListController} configured for the provided {@code userHandle}. 2648 */ 2649 protected final UserHandle getQueryIntentsUser(UserHandle userHandle) { 2650 // In case launching app is in clonedProfile, and we are building the personal tab, intent 2651 // resolution will be attempted as clonedUser instead of user 0. This is because intent 2652 // resolution from user 0 and clonedUser is not guaranteed to return same results. 2653 // We do not care about the case when personal adapter is started with non-root user 2654 // (secondary user case), as clone profile is guaranteed to be non-active in that case. 2655 UserHandle queryIntentsUser = userHandle; 2656 if (isLaunchedAsCloneProfile() && userHandle.equals(getPersonalProfileUserHandle())) { 2657 queryIntentsUser = getCloneProfileUserHandle(); 2658 } 2659 return queryIntentsUser; 2660 } 2661 2662 /** 2663 * Returns the {@link List} of {@link UserHandle} to pass on to the 2664 * {@link ResolverRankerServiceResolverComparator} as per the provided {@code userHandle}. 2665 */ 2666 @VisibleForTesting(visibility = PROTECTED) 2667 public final List<UserHandle> getResolverRankerServiceUserHandleList(UserHandle userHandle) { 2668 return getResolverRankerServiceUserHandleListInternal(userHandle); 2669 } 2670 2671 @VisibleForTesting 2672 protected List<UserHandle> getResolverRankerServiceUserHandleListInternal(UserHandle 2673 userHandle) { 2674 List<UserHandle> userList = new ArrayList<>(); 2675 userList.add(userHandle); 2676 // Add clonedProfileUserHandle to the list only if we are: 2677 // a. Building the Personal Tab. 2678 // b. CloneProfile exists on the device. 2679 if (userHandle.equals(getPersonalProfileUserHandle()) 2680 && getCloneProfileUserHandle() != null) { 2681 userList.add(getCloneProfileUserHandle()); 2682 } 2683 return userList; 2684 } 2685 2686 /** 2687 * This function is temporary in nature, and its usages will be replaced with just 2688 * resolveInfo.userHandle, once it is available, once sharesheet is stable. 2689 */ 2690 public static UserHandle getResolveInfoUserHandle(ResolveInfo resolveInfo, 2691 UserHandle predictedHandle) { 2692 if (resolveInfo.userHandle == null) { 2693 Log.e(TAG, "ResolveInfo with null UserHandle found: " + resolveInfo); 2694 } 2695 return resolveInfo.userHandle; 2696 } 2697 2698 private boolean privateSpaceEnabled() { 2699 return mIsIntentPicker && android.os.Flags.allowPrivateProfile() 2700 && android.multiuser.Flags.allowResolverSheetForPrivateSpace() 2701 && android.multiuser.Flags.enablePrivateSpaceFeatures(); 2702 } 2703 2704 /** 2705 * An a11y delegate that expands resolver drawer when gesture navigation reaches a partially 2706 * invisible target in the list. 2707 */ 2708 public static class AppListAccessibilityDelegate extends View.AccessibilityDelegate { 2709 private final ResolverDrawerLayout mDrawer; 2710 @Nullable 2711 private final View mBottomBar; 2712 private final Rect mRect = new Rect(); 2713 2714 public AppListAccessibilityDelegate(ResolverDrawerLayout drawer) { 2715 mDrawer = drawer; 2716 mBottomBar = mDrawer.findViewById(R.id.button_bar_container); 2717 } 2718 2719 @Override 2720 public boolean onRequestSendAccessibilityEvent(@androidx.annotation.NonNull ViewGroup host, 2721 @NonNull View child, 2722 @NonNull AccessibilityEvent event) { 2723 boolean result = super.onRequestSendAccessibilityEvent(host, child, event); 2724 if (result && event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED 2725 && mDrawer.isCollapsed()) { 2726 child.getBoundsOnScreen(mRect); 2727 int childTop = mRect.top; 2728 int childBottom = mRect.bottom; 2729 mDrawer.getBoundsOnScreen(mRect, true); 2730 int bottomBarHeight = mBottomBar == null ? 0 : mBottomBar.getHeight(); 2731 int drawerTop = mRect.top; 2732 int drawerBottom = mRect.bottom - bottomBarHeight; 2733 if (drawerTop > childTop || childBottom > drawerBottom) { 2734 mDrawer.setCollapsed(false); 2735 } 2736 } 2737 return result; 2738 } 2739 } 2740 } 2741