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