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