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