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