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