1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.intentresolver; 18 19 import static android.app.VoiceInteractor.PickOptionRequest.Option; 20 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; 21 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; 22 23 import static androidx.lifecycle.LifecycleKt.getCoroutineScope; 24 25 import static com.android.intentresolver.ChooserActionFactory.EDIT_SOURCE; 26 import static com.android.intentresolver.ext.CreationExtrasExtKt.addDefaultArgs; 27 import static com.android.intentresolver.profiles.MultiProfilePagerAdapter.PROFILE_PERSONAL; 28 import static com.android.intentresolver.profiles.MultiProfilePagerAdapter.PROFILE_WORK; 29 import static com.android.intentresolver.ui.model.ActivityModel.ACTIVITY_MODEL_KEY; 30 import static com.android.internal.util.LatencyTracker.ACTION_LOAD_SHARE_SHEET; 31 32 import static java.util.Objects.requireNonNull; 33 34 import android.app.ActivityManager; 35 import android.app.ActivityOptions; 36 import android.app.ActivityThread; 37 import android.app.VoiceInteractor; 38 import android.app.admin.DevicePolicyEventLogger; 39 import android.app.prediction.AppPredictor; 40 import android.app.prediction.AppTarget; 41 import android.app.prediction.AppTargetEvent; 42 import android.app.prediction.AppTargetId; 43 import android.content.ClipboardManager; 44 import android.content.ComponentName; 45 import android.content.ContentResolver; 46 import android.content.Context; 47 import android.content.Intent; 48 import android.content.IntentFilter; 49 import android.content.IntentSender; 50 import android.content.SharedPreferences; 51 import android.content.pm.ActivityInfo; 52 import android.content.pm.PackageManager; 53 import android.content.pm.ResolveInfo; 54 import android.content.pm.ShortcutInfo; 55 import android.content.res.Configuration; 56 import android.database.Cursor; 57 import android.graphics.Insets; 58 import android.net.Uri; 59 import android.os.Bundle; 60 import android.os.StrictMode; 61 import android.os.SystemClock; 62 import android.os.Trace; 63 import android.os.UserHandle; 64 import android.service.chooser.ChooserTarget; 65 import android.stats.devicepolicy.DevicePolicyEnums; 66 import android.text.TextUtils; 67 import android.util.Log; 68 import android.util.Slog; 69 import android.view.Gravity; 70 import android.view.LayoutInflater; 71 import android.view.View; 72 import android.view.ViewGroup; 73 import android.view.ViewGroup.LayoutParams; 74 import android.view.ViewTreeObserver; 75 import android.view.Window; 76 import android.view.WindowInsets; 77 import android.view.WindowManager; 78 import android.widget.FrameLayout; 79 import android.widget.ImageView; 80 import android.widget.TabHost; 81 import android.widget.TabWidget; 82 import android.widget.TextView; 83 import android.widget.Toast; 84 85 import androidx.annotation.MainThread; 86 import androidx.annotation.NonNull; 87 import androidx.annotation.Nullable; 88 import androidx.fragment.app.FragmentActivity; 89 import androidx.lifecycle.ViewModelProvider; 90 import androidx.lifecycle.viewmodel.CreationExtras; 91 import androidx.recyclerview.widget.GridLayoutManager; 92 import androidx.recyclerview.widget.RecyclerView; 93 import androidx.viewpager.widget.ViewPager; 94 95 import com.android.intentresolver.ChooserRefinementManager.RefinementType; 96 import com.android.intentresolver.chooser.DisplayResolveInfo; 97 import com.android.intentresolver.chooser.MultiDisplayResolveInfo; 98 import com.android.intentresolver.chooser.TargetInfo; 99 import com.android.intentresolver.contentpreview.BasePreviewViewModel; 100 import com.android.intentresolver.contentpreview.ChooserContentPreviewUi; 101 import com.android.intentresolver.contentpreview.HeadlineGeneratorImpl; 102 import com.android.intentresolver.contentpreview.PreviewViewModel; 103 import com.android.intentresolver.data.model.ChooserRequest; 104 import com.android.intentresolver.data.repository.DevicePolicyResources; 105 import com.android.intentresolver.domain.interactor.UserInteractor; 106 import com.android.intentresolver.emptystate.CompositeEmptyStateProvider; 107 import com.android.intentresolver.emptystate.CrossProfileIntentsChecker; 108 import com.android.intentresolver.emptystate.EmptyStateProvider; 109 import com.android.intentresolver.emptystate.NoAppsAvailableEmptyStateProvider; 110 import com.android.intentresolver.emptystate.NoCrossProfileEmptyStateProvider; 111 import com.android.intentresolver.emptystate.WorkProfilePausedEmptyStateProvider; 112 import com.android.intentresolver.grid.ChooserGridAdapter; 113 import com.android.intentresolver.icons.Caching; 114 import com.android.intentresolver.icons.TargetDataLoader; 115 import com.android.intentresolver.inject.Background; 116 import com.android.intentresolver.logging.EventLog; 117 import com.android.intentresolver.measurements.Tracer; 118 import com.android.intentresolver.model.AbstractResolverComparator; 119 import com.android.intentresolver.model.AppPredictionServiceResolverComparator; 120 import com.android.intentresolver.model.ResolverRankerServiceResolverComparator; 121 import com.android.intentresolver.platform.AppPredictionAvailable; 122 import com.android.intentresolver.platform.ImageEditor; 123 import com.android.intentresolver.platform.NearbyShare; 124 import com.android.intentresolver.profiles.ChooserMultiProfilePagerAdapter; 125 import com.android.intentresolver.profiles.MultiProfilePagerAdapter.ProfileType; 126 import com.android.intentresolver.profiles.OnProfileSelectedListener; 127 import com.android.intentresolver.profiles.OnSwitchOnWorkSelectedListener; 128 import com.android.intentresolver.profiles.TabConfig; 129 import com.android.intentresolver.shared.model.Profile; 130 import com.android.intentresolver.shortcuts.AppPredictorFactory; 131 import com.android.intentresolver.shortcuts.ShortcutLoader; 132 import com.android.intentresolver.ui.ActionTitle; 133 import com.android.intentresolver.ui.ProfilePagerResources; 134 import com.android.intentresolver.ui.ShareResultSender; 135 import com.android.intentresolver.ui.ShareResultSenderFactory; 136 import com.android.intentresolver.ui.model.ActivityModel; 137 import com.android.intentresolver.ui.viewmodel.ChooserViewModel; 138 import com.android.intentresolver.widget.ActionRow; 139 import com.android.intentresolver.widget.ImagePreviewView; 140 import com.android.intentresolver.widget.ResolverDrawerLayout; 141 import com.android.internal.annotations.VisibleForTesting; 142 import com.android.internal.content.PackageMonitor; 143 import com.android.internal.logging.MetricsLogger; 144 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 145 import com.android.internal.util.LatencyTracker; 146 147 import com.google.common.collect.ImmutableList; 148 149 import dagger.hilt.android.AndroidEntryPoint; 150 151 import kotlin.Pair; 152 153 import kotlinx.coroutines.CoroutineDispatcher; 154 155 import java.util.ArrayList; 156 import java.util.Arrays; 157 import java.util.Collections; 158 import java.util.HashMap; 159 import java.util.List; 160 import java.util.Map; 161 import java.util.Objects; 162 import java.util.Optional; 163 import java.util.Set; 164 import java.util.concurrent.ExecutorService; 165 import java.util.concurrent.Executors; 166 import java.util.concurrent.atomic.AtomicLong; 167 import java.util.function.Consumer; 168 import java.util.function.Supplier; 169 170 import javax.inject.Inject; 171 import javax.inject.Provider; 172 173 /** 174 * The Chooser Activity handles intent resolution specifically for sharing intents - 175 * for example, as generated by {@see android.content.Intent#createChooser(Intent, CharSequence)}. 176 * 177 */ 178 @SuppressWarnings("OptionalUsedAsFieldOrParameterType") 179 @AndroidEntryPoint(FragmentActivity.class) 180 public class ChooserActivity extends Hilt_ChooserActivity implements 181 ResolverListAdapter.ResolverListCommunicator, PackagesChangedListener, StartsSelectedItem { 182 private static final String TAG = "ChooserActivity"; 183 184 /** 185 * Boolean extra to change the following behavior: Normally, ChooserActivity finishes itself 186 * in onStop when launched in a new task. If this extra is set to true, we do not finish 187 * ourselves when onStop gets called. 188 */ 189 public static final String EXTRA_PRIVATE_RETAIN_IN_ON_STOP 190 = "com.android.internal.app.ChooserActivity.EXTRA_PRIVATE_RETAIN_IN_ON_STOP"; 191 192 /** 193 * Transition name for the first image preview. 194 * To be used for shared element transition into this activity. 195 */ 196 public static final String FIRST_IMAGE_PREVIEW_TRANSITION_NAME = "screenshot_preview_image"; 197 198 private static final boolean DEBUG = true; 199 200 public static final String LAUNCH_LOCATION_DIRECT_SHARE = "direct_share"; 201 private static final String SHORTCUT_TARGET = "shortcut_target"; 202 203 ////////////////////////////////////////////////////////////////////////////////////////////// 204 // Inherited properties. 205 ////////////////////////////////////////////////////////////////////////////////////////////// 206 private static final String TAB_TAG_PERSONAL = "personal"; 207 private static final String TAB_TAG_WORK = "work"; 208 209 private static final String LAST_SHOWN_TAB_KEY = "last_shown_tab_key"; 210 public static final String METRICS_CATEGORY_CHOOSER = "intent_chooser"; 211 212 private int mLayoutId; 213 private UserHandle mHeaderCreatorUser; 214 private boolean mRegistered; 215 private PackageMonitor mPersonalPackageMonitor; 216 private PackageMonitor mWorkPackageMonitor; 217 218 protected ResolverDrawerLayout mResolverDrawerLayout; 219 private TabHost mTabHost; 220 private ResolverViewPager mViewPager; 221 protected ChooserMultiProfilePagerAdapter mChooserMultiProfilePagerAdapter; 222 protected final LatencyTracker mLatencyTracker = getLatencyTracker(); 223 224 /** See {@link #setRetainInOnStop}. */ 225 private boolean mRetainInOnStop; 226 protected Insets mSystemWindowInsets = null; 227 private ResolverActivity.PickTargetOptionRequest mPickOptionRequest; 228 229 @Nullable 230 private OnSwitchOnWorkSelectedListener mOnSwitchOnWorkSelectedListener; 231 232 ////////////////////////////////////////////////////////////////////////////////////////////// 233 ////////////////////////////////////////////////////////////////////////////////////////////// 234 235 236 // TODO: these data structures are for one-time use in shuttling data from where they're 237 // populated in `ShortcutToChooserTargetConverter` to where they're consumed in 238 // `ShortcutSelectionLogic` which packs the appropriate elements into the final `TargetInfo`. 239 // That flow should be refactored so that `ChooserActivity` isn't responsible for holding their 240 // intermediate data, and then these members can be removed. 241 private final Map<ChooserTarget, AppTarget> mDirectShareAppTargetCache = new HashMap<>(); 242 private final Map<ChooserTarget, ShortcutInfo> mDirectShareShortcutInfoCache = new HashMap<>(); 243 244 static final int TARGET_TYPE_DEFAULT = 0; 245 static final int TARGET_TYPE_CHOOSER_TARGET = 1; 246 static final int TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER = 2; 247 static final int TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE = 3; 248 249 private static final int SCROLL_STATUS_IDLE = 0; 250 private static final int SCROLL_STATUS_SCROLLING_VERTICAL = 1; 251 private static final int SCROLL_STATUS_SCROLLING_HORIZONTAL = 2; 252 253 @Inject public UserInteractor mUserInteractor; 254 @Inject @Background public CoroutineDispatcher mBackgroundDispatcher; 255 @Inject public ChooserHelper mChooserHelper; 256 @Inject public FeatureFlags mFeatureFlags; 257 @Inject public android.service.chooser.FeatureFlags mChooserServiceFeatureFlags; 258 @Inject public EventLog mEventLog; 259 @Inject @AppPredictionAvailable public boolean mAppPredictionAvailable; 260 @Inject @ImageEditor public Optional<ComponentName> mImageEditor; 261 @Inject @NearbyShare public Optional<ComponentName> mNearbyShare; 262 protected TargetDataLoader mTargetDataLoader; 263 @Inject public Provider<TargetDataLoader> mTargetDataLoaderProvider; 264 @Inject 265 @Caching 266 public Provider<TargetDataLoader> mCachingTargetDataLoaderProvider; 267 @Inject public DevicePolicyResources mDevicePolicyResources; 268 @Inject public ProfilePagerResources mProfilePagerResources; 269 @Inject public PackageManager mPackageManager; 270 @Inject public ClipboardManager mClipboardManager; 271 @Inject public IntentForwarding mIntentForwarding; 272 @Inject public ShareResultSenderFactory mShareResultSenderFactory; 273 274 private ActivityModel mActivityModel; 275 private ChooserRequest mRequest; 276 private ProfileHelper mProfiles; 277 private ProfileAvailability mProfileAvailability; 278 @Nullable private ShareResultSender mShareResultSender; 279 280 private ChooserRefinementManager mRefinementManager; 281 282 private ChooserContentPreviewUi mChooserContentPreviewUi; 283 284 private boolean mShouldDisplayLandscape; 285 private long mChooserShownTime; 286 protected boolean mIsSuccessfullySelected; 287 288 private int mCurrAvailableWidth = 0; 289 private Insets mLastAppliedInsets = null; 290 private int mLastNumberOfChildren = -1; 291 private int mMaxTargetsPerRow = 1; 292 293 private static final int MAX_LOG_RANK_POSITION = 12; 294 295 // TODO: are these used anywhere? They should probably be migrated to ChooserRequestParameters. 296 private static final int MAX_EXTRA_INITIAL_INTENTS = 2; 297 private static final int MAX_EXTRA_CHOOSER_TARGETS = 2; 298 299 private SharedPreferences mPinnedSharedPrefs; 300 private static final String PINNED_SHARED_PREFS_NAME = "chooser_pin_settings"; 301 302 private final ExecutorService mBackgroundThreadPoolExecutor = Executors.newFixedThreadPool(5); 303 304 private int mScrollStatus = SCROLL_STATUS_IDLE; 305 306 private final EnterTransitionAnimationDelegate mEnterTransitionAnimationDelegate = 307 new EnterTransitionAnimationDelegate(this, () -> mResolverDrawerLayout); 308 309 private final Map<Integer, ProfileRecord> mProfileRecords = new HashMap<>(); 310 311 private boolean mExcludeSharedText = false; 312 /** 313 * When we intend to finish the activity with a shared element transition, we can't immediately 314 * finish() when the transition is invoked, as the receiving end may not be able to start the 315 * animation and the UI breaks if this takes too long. Instead we defer finishing until onStop 316 * in order to wait for the transition to begin. 317 */ 318 private boolean mFinishWhenStopped = false; 319 320 private final AtomicLong mIntentReceivedTime = new AtomicLong(-1); 321 createActivityModel()322 protected ActivityModel createActivityModel() { 323 return ActivityModel.createFrom(this); 324 } 325 326 private ChooserViewModel mViewModel; 327 328 @NonNull 329 @Override getDefaultViewModelCreationExtras()330 public CreationExtras getDefaultViewModelCreationExtras() { 331 return addDefaultArgs( 332 super.getDefaultViewModelCreationExtras(), 333 new Pair<>(ACTIVITY_MODEL_KEY, createActivityModel())); 334 } 335 336 @Override onCreate(Bundle savedInstanceState)337 protected void onCreate(Bundle savedInstanceState) { 338 super.onCreate(savedInstanceState); 339 Log.i(TAG, "onCreate"); 340 341 mTargetDataLoader = mChooserServiceFeatureFlags.chooserPayloadToggling() 342 ? mCachingTargetDataLoaderProvider.get() 343 : mTargetDataLoaderProvider.get(); 344 345 setTheme(R.style.Theme_DeviceDefault_Chooser); 346 347 // Initializer is invoked when this function returns, via Lifecycle. 348 mChooserHelper.setInitializer(this::initialize); 349 if (mChooserServiceFeatureFlags.chooserPayloadToggling()) { 350 mChooserHelper.setOnChooserRequestChanged(this::onChooserRequestChanged); 351 mChooserHelper.setOnPendingSelection(this::onPendingSelection); 352 } 353 } 354 355 @Override onStart()356 protected final void onStart() { 357 super.onStart(); 358 this.getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); 359 } 360 361 @Override onResume()362 protected final void onResume() { 363 super.onResume(); 364 Log.d(TAG, "onResume: " + getComponentName().flattenToShortString()); 365 mFinishWhenStopped = false; 366 mRefinementManager.onActivityResume(); 367 } 368 369 @Override onStop()370 protected final void onStop() { 371 super.onStop(); 372 373 final Window window = this.getWindow(); 374 final WindowManager.LayoutParams attrs = window.getAttributes(); 375 attrs.privateFlags &= ~SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; 376 window.setAttributes(attrs); 377 378 if (mRegistered) { 379 mPersonalPackageMonitor.unregister(); 380 if (mWorkPackageMonitor != null) { 381 mWorkPackageMonitor.unregister(); 382 } 383 mRegistered = false; 384 } 385 final Intent intent = getIntent(); 386 if ((intent.getFlags() & FLAG_ACTIVITY_NEW_TASK) != 0 && !isVoiceInteraction() 387 && !mRetainInOnStop) { 388 // This resolver is in the unusual situation where it has been 389 // launched at the top of a new task. We don't let it be added 390 // to the recent tasks shown to the user, and we need to make sure 391 // that each time we are launched we get the correct launching 392 // uid (not re-using the same resolver from an old launching uid), 393 // so we will now finish ourself since being no longer visible, 394 // the user probably can't get back to us. 395 if (!isChangingConfigurations()) { 396 Log.d(TAG, "finishing in onStop"); 397 finish(); 398 } 399 } 400 401 if (mRefinementManager != null) { 402 mRefinementManager.onActivityStop(isChangingConfigurations()); 403 } 404 405 if (mFinishWhenStopped) { 406 mFinishWhenStopped = false; 407 finish(); 408 } 409 } 410 411 @Override onSaveInstanceState(Bundle outState)412 protected final void onSaveInstanceState(Bundle outState) { 413 super.onSaveInstanceState(outState); 414 if (mViewPager != null) { 415 outState.putInt(LAST_SHOWN_TAB_KEY, mViewPager.getCurrentItem()); 416 } 417 } 418 419 @Override onRestart()420 protected final void onRestart() { 421 super.onRestart(); 422 if (mFeatureFlags.fixPrivateSpaceLockedOnRestart()) { 423 if (mChooserMultiProfilePagerAdapter.hasPageForProfile(Profile.Type.PRIVATE.ordinal()) 424 && !mProfileAvailability.isAvailable(mProfiles.getPrivateProfile())) { 425 Log.d(TAG, "Exiting due to unavailable profile"); 426 finish(); 427 return; 428 } 429 } 430 431 if (!mRegistered) { 432 mPersonalPackageMonitor.register( 433 this, 434 getMainLooper(), 435 mProfiles.getPersonalHandle(), 436 false); 437 if (mProfiles.getWorkProfilePresent()) { 438 if (mWorkPackageMonitor == null) { 439 mWorkPackageMonitor = createPackageMonitor( 440 mChooserMultiProfilePagerAdapter.getWorkListAdapter()); 441 } 442 mWorkPackageMonitor.register( 443 this, 444 getMainLooper(), 445 mProfiles.getWorkHandle(), 446 false); 447 } 448 mRegistered = true; 449 } 450 mChooserMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged(); 451 } 452 453 @Override onDestroy()454 protected void onDestroy() { 455 super.onDestroy(); 456 if (!isChangingConfigurations() && mPickOptionRequest != null) { 457 mPickOptionRequest.cancel(); 458 } 459 if (mChooserMultiProfilePagerAdapter != null) { 460 mChooserMultiProfilePagerAdapter.destroy(); 461 } 462 463 if (isFinishing()) { 464 mLatencyTracker.onActionCancel(ACTION_LOAD_SHARE_SHEET); 465 } 466 467 mBackgroundThreadPoolExecutor.shutdownNow(); 468 469 destroyProfileRecords(); 470 } 471 472 /** DO NOT CALL. Only for use from ChooserHelper as a callback. */ initialize()473 private void initialize() { 474 475 mViewModel = new ViewModelProvider(this).get(ChooserViewModel.class); 476 mRequest = mViewModel.getRequest().getValue(); 477 mActivityModel = mViewModel.getActivityModel(); 478 479 mProfiles = new ProfileHelper( 480 mUserInteractor, 481 getCoroutineScope(getLifecycle()), 482 mBackgroundDispatcher, 483 mFeatureFlags); 484 485 mProfileAvailability = new ProfileAvailability( 486 mUserInteractor, 487 getCoroutineScope(getLifecycle()), 488 mBackgroundDispatcher); 489 490 mProfileAvailability.setOnProfileStatusChange(this::onWorkProfileStatusUpdated); 491 492 mIntentReceivedTime.set(System.currentTimeMillis()); 493 mLatencyTracker.onActionStart(ACTION_LOAD_SHARE_SHEET); 494 495 mPinnedSharedPrefs = getPinnedSharedPrefs(this); 496 updateShareResultSender(); 497 498 mMaxTargetsPerRow = 499 getResources().getInteger(R.integer.config_chooser_max_targets_per_row); 500 mShouldDisplayLandscape = 501 shouldDisplayLandscape(getResources().getConfiguration().orientation); 502 503 setRetainInOnStop(mRequest.shouldRetainInOnStop()); 504 createProfileRecords( 505 new AppPredictorFactory( 506 this, 507 Objects.toString(mRequest.getSharedText(), null), 508 mRequest.getShareTargetFilter(), 509 mAppPredictionAvailable 510 ), 511 mRequest.getShareTargetFilter() 512 ); 513 514 515 mChooserMultiProfilePagerAdapter = createMultiProfilePagerAdapter( 516 /* context = */ this, 517 mProfilePagerResources, 518 mRequest, 519 mProfiles, 520 mProfileAvailability, 521 mRequest.getInitialIntents(), 522 mMaxTargetsPerRow); 523 524 maybeDisableRecentsScreenshot(mProfiles, mProfileAvailability); 525 526 if (!configureContentView(mTargetDataLoader)) { 527 mPersonalPackageMonitor = createPackageMonitor( 528 mChooserMultiProfilePagerAdapter.getPersonalListAdapter()); 529 mPersonalPackageMonitor.register( 530 this, 531 getMainLooper(), 532 mProfiles.getPersonalHandle(), 533 false 534 ); 535 if (mProfiles.getWorkProfilePresent()) { 536 mWorkPackageMonitor = createPackageMonitor( 537 mChooserMultiProfilePagerAdapter.getWorkListAdapter()); 538 mWorkPackageMonitor.register( 539 this, 540 getMainLooper(), 541 mProfiles.getWorkHandle(), 542 false 543 ); 544 } 545 mRegistered = true; 546 final ResolverDrawerLayout rdl = findViewById( 547 com.android.internal.R.id.contentPanel); 548 if (rdl != null) { 549 rdl.setOnDismissedListener(new ResolverDrawerLayout.OnDismissedListener() { 550 @Override 551 public void onDismissed() { 552 finish(); 553 } 554 }); 555 556 boolean hasTouchScreen = mPackageManager 557 .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN); 558 559 if (isVoiceInteraction() || !hasTouchScreen) { 560 rdl.setCollapsed(false); 561 } 562 563 rdl.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 564 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); 565 rdl.setOnApplyWindowInsetsListener(this::onApplyWindowInsets); 566 567 mResolverDrawerLayout = rdl; 568 } 569 570 Intent intent = mRequest.getTargetIntent(); 571 final Set<String> categories = intent.getCategories(); 572 MetricsLogger.action(this, 573 mChooserMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem() 574 ? MetricsEvent.ACTION_SHOW_APP_DISAMBIG_APP_FEATURED 575 : MetricsEvent.ACTION_SHOW_APP_DISAMBIG_NONE_FEATURED, 576 intent.getAction() + ":" + intent.getType() + ":" 577 + (categories != null ? Arrays.toString(categories.toArray()) 578 : "")); 579 } 580 581 getEventLog().logSharesheetTriggered(); 582 mRefinementManager = new ViewModelProvider(this).get(ChooserRefinementManager.class); 583 mRefinementManager.getRefinementCompletion().observe(this, completion -> { 584 if (completion.consume()) { 585 if (completion.getRefinedIntent() == null) { 586 finish(); 587 return; 588 } 589 590 // Prepare to regenerate our "system actions" based on the refined intent. 591 // TODO: optimize if needed. `TARGET_INFO` cases don't require a new action 592 // factory at all. And if we break up `ChooserActionFactory`, we could avoid 593 // resolving a new editor intent unless we're handling an `EDIT_ACTION`. 594 ChooserActionFactory refinedActionFactory = 595 createChooserActionFactory(completion.getRefinedIntent()); 596 switch (completion.getType()) { 597 case TARGET_INFO: { 598 TargetInfo refinedTarget = completion 599 .getOriginalTargetInfo() 600 .tryToCloneWithAppliedRefinement( 601 completion.getRefinedIntent()); 602 if (refinedTarget == null) { 603 Log.e(TAG, "Failed to apply refinement to any matching source intent"); 604 } else { 605 maybeRemoveSharedText(refinedTarget); 606 607 // We already block suspended targets from going to refinement, and we 608 // probably can't recover a Chooser session if that's the reason the 609 // refined target fails to launch now. Fire-and-forget the refined 610 // launch, and make sure Sharesheet gets cleaned up regardless of the 611 // outcome of that launch.launch; ignore 612 613 safelyStartActivity(refinedTarget); 614 } 615 } 616 break; 617 618 case COPY_ACTION: { 619 if (refinedActionFactory.getCopyButtonRunnable() != null) { 620 refinedActionFactory.getCopyButtonRunnable().run(); 621 } 622 } 623 break; 624 625 case EDIT_ACTION: { 626 if (refinedActionFactory.getEditButtonRunnable() != null) { 627 refinedActionFactory.getEditButtonRunnable().run(); 628 } 629 } 630 break; 631 } 632 633 finish(); 634 } 635 }); 636 BasePreviewViewModel previewViewModel = 637 new ViewModelProvider(this, createPreviewViewModelFactory()) 638 .get(BasePreviewViewModel.class); 639 previewViewModel.init( 640 mRequest.getTargetIntent(), 641 mRequest.getAdditionalContentUri(), 642 mChooserServiceFeatureFlags.chooserPayloadToggling()); 643 ChooserContentPreviewUi.ActionFactory actionFactory = 644 decorateActionFactoryWithRefinement( 645 createChooserActionFactory(mRequest.getTargetIntent())); 646 mChooserContentPreviewUi = new ChooserContentPreviewUi( 647 getCoroutineScope(getLifecycle()), 648 previewViewModel.getPreviewDataProvider(), 649 mRequest.getTargetIntent(), 650 previewViewModel.getImageLoader(), 651 actionFactory, 652 createModifyShareActionFactory(), 653 mEnterTransitionAnimationDelegate, 654 new HeadlineGeneratorImpl(this), 655 mRequest.getContentTypeHint(), 656 mRequest.getMetadataText(), 657 mChooserServiceFeatureFlags.chooserPayloadToggling()); 658 updateStickyContentPreview(); 659 if (shouldShowStickyContentPreview()) { 660 getEventLog().logActionShareWithPreview( 661 mChooserContentPreviewUi.getPreferredContentPreview()); 662 } 663 mChooserShownTime = System.currentTimeMillis(); 664 final long systemCost = mChooserShownTime - mIntentReceivedTime.get(); 665 getEventLog().logChooserActivityShown( 666 isWorkProfile(), mRequest.getTargetType(), systemCost); 667 if (mResolverDrawerLayout != null) { 668 mResolverDrawerLayout.addOnLayoutChangeListener(this::handleLayoutChange); 669 670 mResolverDrawerLayout.setOnCollapsedChangedListener( 671 isCollapsed -> { 672 mChooserMultiProfilePagerAdapter.setIsCollapsed(isCollapsed); 673 getEventLog().logSharesheetExpansionChanged(isCollapsed); 674 }); 675 } 676 if (DEBUG) { 677 Log.d(TAG, "System Time Cost is " + systemCost); 678 } 679 getEventLog().logShareStarted( 680 mRequest.getReferrerPackage(), 681 mRequest.getTargetType(), 682 mRequest.getCallerChooserTargets().size(), 683 mRequest.getInitialIntents().size(), 684 isWorkProfile(), 685 mChooserContentPreviewUi.getPreferredContentPreview(), 686 mRequest.getTargetAction(), 687 mRequest.getChooserActions().size(), 688 mRequest.getModifyShareAction() != null 689 ); 690 mEnterTransitionAnimationDelegate.postponeTransition(); 691 Tracer.INSTANCE.markLaunched(); 692 } 693 maybeDisableRecentsScreenshot( ProfileHelper profileHelper, ProfileAvailability profileAvailability)694 private void maybeDisableRecentsScreenshot( 695 ProfileHelper profileHelper, ProfileAvailability profileAvailability) { 696 for (Profile profile : profileHelper.getProfiles()) { 697 if (profile.getType() == Profile.Type.PRIVATE) { 698 if (profileAvailability.isAvailable(profile)) { 699 // Show blank screen in Recent preview if private profile is available 700 // to not leak its presence. 701 setRecentsScreenshotEnabled(false); 702 } 703 return; 704 } 705 } 706 } 707 onChooserRequestChanged(ChooserRequest chooserRequest)708 private void onChooserRequestChanged(ChooserRequest chooserRequest) { 709 // intentional reference comparison 710 if (mRequest == chooserRequest) { 711 return; 712 } 713 boolean recreateAdapters = shouldUpdateAdapters(mRequest, chooserRequest); 714 mRequest = chooserRequest; 715 updateShareResultSender(); 716 mChooserContentPreviewUi.updateModifyShareAction(); 717 if (recreateAdapters) { 718 recreatePagerAdapter(); 719 } else { 720 setTabsViewEnabled(true); 721 } 722 } 723 onPendingSelection()724 private void onPendingSelection() { 725 setTabsViewEnabled(false); 726 } 727 onAppTargetsLoaded(ResolverListAdapter listAdapter)728 private void onAppTargetsLoaded(ResolverListAdapter listAdapter) { 729 Log.d(TAG, "onAppTargetsLoaded(" 730 + "listAdapter.userHandle=" + listAdapter.getUserHandle() + ")"); 731 732 if (mChooserMultiProfilePagerAdapter == null) { 733 return; 734 } 735 if (!isProfilePagerAdapterAttached() 736 && listAdapter == mChooserMultiProfilePagerAdapter.getActiveListAdapter()) { 737 mChooserMultiProfilePagerAdapter.setupViewPager(mViewPager); 738 setTabsViewEnabled(true); 739 } 740 } 741 updateShareResultSender()742 private void updateShareResultSender() { 743 IntentSender chosenComponentSender = mRequest.getChosenComponentSender(); 744 if (chosenComponentSender != null) { 745 mShareResultSender = mShareResultSenderFactory.create( 746 mViewModel.getActivityModel().getLaunchedFromUid(), chosenComponentSender); 747 } else { 748 mShareResultSender = null; 749 } 750 } 751 shouldUpdateAdapters( ChooserRequest oldChooserRequest, ChooserRequest newChooserRequest)752 private boolean shouldUpdateAdapters( 753 ChooserRequest oldChooserRequest, ChooserRequest newChooserRequest) { 754 Intent oldTargetIntent = oldChooserRequest.getTargetIntent(); 755 Intent newTargetIntent = newChooserRequest.getTargetIntent(); 756 List<Intent> oldAltIntents = oldChooserRequest.getAdditionalTargets(); 757 List<Intent> newAltIntents = newChooserRequest.getAdditionalTargets(); 758 759 // TODO: a workaround for the unnecessary target reloading caused by multiple flow updates - 760 // an artifact of the current implementation; revisit. 761 return !oldTargetIntent.equals(newTargetIntent) || !oldAltIntents.equals(newAltIntents); 762 } 763 recreatePagerAdapter()764 private void recreatePagerAdapter() { 765 if (!mChooserServiceFeatureFlags.chooserPayloadToggling()) { 766 return; 767 } 768 destroyProfileRecords(); 769 createProfileRecords( 770 new AppPredictorFactory( 771 this, 772 Objects.toString(mRequest.getSharedText(), null), 773 mRequest.getShareTargetFilter(), 774 mAppPredictionAvailable 775 ), 776 mRequest.getShareTargetFilter() 777 ); 778 779 int currentPage = mChooserMultiProfilePagerAdapter.getCurrentPage(); 780 if (mChooserMultiProfilePagerAdapter != null) { 781 mChooserMultiProfilePagerAdapter.destroy(); 782 } 783 // Update the pager adapter but do not attach it to the view till the targets are reloaded, 784 // see onChooserAppTargetsLoaded method. 785 mChooserMultiProfilePagerAdapter = createMultiProfilePagerAdapter( 786 /* context = */ this, 787 mProfilePagerResources, 788 mRequest, 789 mProfiles, 790 mProfileAvailability, 791 mRequest.getInitialIntents(), 792 mMaxTargetsPerRow); 793 mChooserMultiProfilePagerAdapter.setCurrentPage(currentPage); 794 for (int i = 0, count = mChooserMultiProfilePagerAdapter.getItemCount(); i < count; i++) { 795 mChooserMultiProfilePagerAdapter.getPageAdapterForIndex(i) 796 .getListAdapter().setAnimateItems(false); 797 } 798 if (mPersonalPackageMonitor != null) { 799 mPersonalPackageMonitor.unregister(); 800 } 801 mPersonalPackageMonitor = createPackageMonitor( 802 mChooserMultiProfilePagerAdapter.getPersonalListAdapter()); 803 mPersonalPackageMonitor.register( 804 this, 805 getMainLooper(), 806 mProfiles.getPersonalHandle(), 807 false); 808 if (mProfiles.getWorkProfilePresent()) { 809 if (mWorkPackageMonitor != null) { 810 mWorkPackageMonitor.unregister(); 811 } 812 mWorkPackageMonitor = createPackageMonitor( 813 mChooserMultiProfilePagerAdapter.getWorkListAdapter()); 814 mWorkPackageMonitor.register( 815 this, 816 getMainLooper(), 817 mProfiles.getWorkHandle(), 818 false); 819 } 820 postRebuildList( 821 mChooserMultiProfilePagerAdapter.rebuildTabs( 822 mProfiles.getWorkProfilePresent() || mProfiles.getPrivateProfilePresent())); 823 setTabsViewEnabled(false); 824 } 825 setTabsViewEnabled(boolean isEnabled)826 private void setTabsViewEnabled(boolean isEnabled) { 827 TabWidget tabs = mTabHost.getTabWidget(); 828 if (tabs != null) { 829 tabs.setEnabled(isEnabled); 830 } 831 View tabContent = mTabHost.findViewById(com.android.internal.R.id.profile_pager); 832 if (tabContent != null) { 833 tabContent.setEnabled(isEnabled); 834 } 835 } 836 837 @Override onRestoreInstanceState(@onNull Bundle savedInstanceState)838 protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) { 839 if (mViewPager != null) { 840 mViewPager.setCurrentItem(savedInstanceState.getInt(LAST_SHOWN_TAB_KEY)); 841 } 842 mChooserMultiProfilePagerAdapter.clearInactiveProfileCache(); 843 } 844 845 ////////////////////////////////////////////////////////////////////////////////////////////// 846 // Inherited methods 847 ////////////////////////////////////////////////////////////////////////////////////////////// 848 isAutolaunching()849 private boolean isAutolaunching() { 850 return !mRegistered && isFinishing(); 851 } 852 maybeAutolaunchIfSingleTarget()853 private boolean maybeAutolaunchIfSingleTarget() { 854 int count = mChooserMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredCount(); 855 if (count != 1) { 856 return false; 857 } 858 859 if (mChooserMultiProfilePagerAdapter.getActiveListAdapter().getOtherProfile() != null) { 860 return false; 861 } 862 863 // Only one target, so we're a candidate to auto-launch! 864 final TargetInfo target = mChooserMultiProfilePagerAdapter.getActiveListAdapter() 865 .targetInfoForPosition(0, false); 866 if (shouldAutoLaunchSingleChoice(target)) { 867 Log.d(TAG, "auto launching " + target + " and finishing."); 868 safelyStartActivity(target); 869 finish(); 870 return true; 871 } 872 return false; 873 } 874 isTwoPagePersonalAndWorkConfiguration()875 private boolean isTwoPagePersonalAndWorkConfiguration() { 876 return (mChooserMultiProfilePagerAdapter.getCount() == 2) 877 && mChooserMultiProfilePagerAdapter.hasPageForProfile(PROFILE_PERSONAL) 878 && mChooserMultiProfilePagerAdapter.hasPageForProfile(PROFILE_WORK); 879 } 880 881 /** 882 * When we have a personal and a work profile, we auto launch in the following scenario: 883 * - There is 1 resolved target on each profile 884 * - That target is the same app on both profiles 885 * - The target app has permission to communicate cross profiles 886 * - The target app has declared it supports cross-profile communication via manifest metadata 887 */ maybeAutolaunchIfCrossProfileSupported()888 private boolean maybeAutolaunchIfCrossProfileSupported() { 889 if (!isTwoPagePersonalAndWorkConfiguration()) { 890 return false; 891 } 892 893 ResolverListAdapter activeListAdapter = 894 (mChooserMultiProfilePagerAdapter.getActiveProfile() == PROFILE_PERSONAL) 895 ? mChooserMultiProfilePagerAdapter.getPersonalListAdapter() 896 : mChooserMultiProfilePagerAdapter.getWorkListAdapter(); 897 898 ResolverListAdapter inactiveListAdapter = 899 (mChooserMultiProfilePagerAdapter.getActiveProfile() == PROFILE_PERSONAL) 900 ? mChooserMultiProfilePagerAdapter.getWorkListAdapter() 901 : mChooserMultiProfilePagerAdapter.getPersonalListAdapter(); 902 903 if (!activeListAdapter.isTabLoaded() || !inactiveListAdapter.isTabLoaded()) { 904 return false; 905 } 906 907 if ((activeListAdapter.getUnfilteredCount() != 1) 908 || (inactiveListAdapter.getUnfilteredCount() != 1)) { 909 return false; 910 } 911 912 TargetInfo activeProfileTarget = activeListAdapter.targetInfoForPosition(0, false); 913 TargetInfo inactiveProfileTarget = inactiveListAdapter.targetInfoForPosition(0, false); 914 if (!Objects.equals( 915 activeProfileTarget.getResolvedComponentName(), 916 inactiveProfileTarget.getResolvedComponentName())) { 917 return false; 918 } 919 920 if (!shouldAutoLaunchSingleChoice(activeProfileTarget)) { 921 return false; 922 } 923 924 String packageName = activeProfileTarget.getResolvedComponentName().getPackageName(); 925 if (!mIntentForwarding.canAppInteractAcrossProfiles(this, packageName)) { 926 return false; 927 } 928 929 DevicePolicyEventLogger 930 .createEvent(DevicePolicyEnums.RESOLVER_AUTOLAUNCH_CROSS_PROFILE_TARGET) 931 .setBoolean(activeListAdapter.getUserHandle() 932 .equals(mProfiles.getPersonalHandle())) 933 .setStrings(getMetricsCategory()) 934 .write(); 935 safelyStartActivity(activeProfileTarget); 936 Log.d(TAG, "auto launching! " + activeProfileTarget); 937 finish(); 938 return true; 939 } 940 941 /** 942 * @return {@code true} if a resolved target is autolaunched, otherwise {@code false} 943 */ maybeAutolaunchActivity()944 private boolean maybeAutolaunchActivity() { 945 int numberOfProfiles = mChooserMultiProfilePagerAdapter.getItemCount(); 946 // TODO(b/280988288): If the ChooserActivity is shown we should consider showing the 947 // correct intent-picker UIs (e.g., mini-resolver) if it was launched without 948 // ACTION_SEND. 949 if (numberOfProfiles == 1 && maybeAutolaunchIfSingleTarget()) { 950 return true; 951 } else if (maybeAutolaunchIfCrossProfileSupported()) { 952 return true; 953 } 954 return false; 955 } 956 957 @Override // ResolverListCommunicator onPostListReady(ResolverListAdapter listAdapter, boolean doPostProcessing, boolean rebuildCompleted)958 public final void onPostListReady(ResolverListAdapter listAdapter, boolean doPostProcessing, 959 boolean rebuildCompleted) { 960 if (isAutolaunching()) { 961 return; 962 } 963 if (mChooserMultiProfilePagerAdapter 964 .shouldShowEmptyStateScreen((ChooserListAdapter) listAdapter)) { 965 mChooserMultiProfilePagerAdapter 966 .showEmptyResolverListEmptyState((ChooserListAdapter) listAdapter); 967 } else { 968 mChooserMultiProfilePagerAdapter.showListView((ChooserListAdapter) listAdapter); 969 } 970 // showEmptyResolverListEmptyState can mark the tab as loaded, 971 // which is a precondition for auto launching 972 if (rebuildCompleted && maybeAutolaunchActivity()) { 973 return; 974 } 975 if (doPostProcessing) { 976 maybeCreateHeader(listAdapter); 977 onListRebuilt(listAdapter, rebuildCompleted); 978 } 979 } 980 getOrLoadDisplayLabel(TargetInfo info)981 private CharSequence getOrLoadDisplayLabel(TargetInfo info) { 982 if (info.isDisplayResolveInfo()) { 983 mTargetDataLoader.getOrLoadLabel((DisplayResolveInfo) info); 984 } 985 CharSequence displayLabel = info.getDisplayLabel(); 986 return displayLabel == null ? "" : displayLabel; 987 } 988 getTitleForAction(Intent intent, int defaultTitleRes)989 protected final CharSequence getTitleForAction(Intent intent, int defaultTitleRes) { 990 final ActionTitle title = ActionTitle.forAction(intent.getAction()); 991 992 // While there may already be a filtered item, we can only use it in the title if the list 993 // is already sorted and all information relevant to it is already in the list. 994 final boolean named = 995 mChooserMultiProfilePagerAdapter.getActiveListAdapter().getFilteredPosition() >= 0; 996 if (title == ActionTitle.DEFAULT && defaultTitleRes != 0) { 997 return getString(defaultTitleRes); 998 } else { 999 return named 1000 ? getString( 1001 title.namedTitleRes, 1002 getOrLoadDisplayLabel( 1003 mChooserMultiProfilePagerAdapter 1004 .getActiveListAdapter().getFilteredItem())) 1005 : getString(title.titleRes); 1006 } 1007 } 1008 1009 /** 1010 * Configure the area above the app selection list (title, content preview, etc). 1011 */ maybeCreateHeader(ResolverListAdapter listAdapter)1012 private void maybeCreateHeader(ResolverListAdapter listAdapter) { 1013 if (mHeaderCreatorUser != null 1014 && !listAdapter.getUserHandle().equals(mHeaderCreatorUser)) { 1015 return; 1016 } 1017 if (!mProfiles.getWorkProfilePresent() 1018 && listAdapter.getCount() == 0 && listAdapter.getPlaceholderCount() == 0) { 1019 final TextView titleView = findViewById(com.android.internal.R.id.title); 1020 if (titleView != null) { 1021 titleView.setVisibility(View.GONE); 1022 } 1023 } 1024 1025 CharSequence title = mRequest.getTitle() != null 1026 ? mRequest.getTitle() 1027 : getTitleForAction(mRequest.getTargetIntent(), 1028 mRequest.getDefaultTitleResource()); 1029 1030 if (!TextUtils.isEmpty(title)) { 1031 final TextView titleView = findViewById(com.android.internal.R.id.title); 1032 if (titleView != null) { 1033 titleView.setText(title); 1034 } 1035 setTitle(title); 1036 } 1037 1038 final ImageView iconView = findViewById(com.android.internal.R.id.icon); 1039 if (iconView != null) { 1040 listAdapter.loadFilteredItemIconTaskAsync(iconView); 1041 } 1042 mHeaderCreatorUser = listAdapter.getUserHandle(); 1043 } 1044 1045 /** Start the activity specified by the {@link TargetInfo}.*/ safelyStartActivity(TargetInfo cti)1046 public final void safelyStartActivity(TargetInfo cti) { 1047 // In case cloned apps are present, we would want to start those apps in cloned user 1048 // space, which will not be same as the adapter's userHandle. resolveInfo.userHandle 1049 // identifies the correct user space in such cases. 1050 UserHandle activityUserHandle = cti.getResolveInfo().userHandle; 1051 safelyStartActivityAsUser(cti, activityUserHandle, null); 1052 } 1053 safelyStartActivityAsUser( TargetInfo cti, UserHandle user, @Nullable Bundle options)1054 protected final void safelyStartActivityAsUser( 1055 TargetInfo cti, UserHandle user, @Nullable Bundle options) { 1056 // We're dispatching intents that might be coming from legacy apps, so 1057 // don't kill ourselves. 1058 StrictMode.disableDeathOnFileUriExposure(); 1059 try { 1060 safelyStartActivityInternal(cti, user, options); 1061 } finally { 1062 StrictMode.enableDeathOnFileUriExposure(); 1063 } 1064 } 1065 1066 @VisibleForTesting safelyStartActivityInternal( TargetInfo cti, UserHandle user, @Nullable Bundle options)1067 protected void safelyStartActivityInternal( 1068 TargetInfo cti, UserHandle user, @Nullable Bundle options) { 1069 // If the target is suspended, the activity will not be successfully launched. 1070 // Do not unregister from package manager updates in this case 1071 if (!cti.isSuspended() && mRegistered) { 1072 if (mPersonalPackageMonitor != null) { 1073 mPersonalPackageMonitor.unregister(); 1074 } 1075 if (mWorkPackageMonitor != null) { 1076 mWorkPackageMonitor.unregister(); 1077 } 1078 mRegistered = false; 1079 } 1080 // If needed, show that intent is forwarded 1081 // from managed profile to owner or other way around. 1082 String profileSwitchMessage = mIntentForwarding.forwardMessageFor( 1083 mRequest.getTargetIntent()); 1084 if (profileSwitchMessage != null) { 1085 Toast.makeText(this, profileSwitchMessage, Toast.LENGTH_LONG).show(); 1086 } 1087 try { 1088 if (cti.startAsCaller(this, options, user.getIdentifier())) { 1089 // Prevent sending a second chooser result when starting the edit action intent. 1090 if (!cti.getTargetIntent().hasExtra(EDIT_SOURCE)) { 1091 maybeSendShareResult(cti); 1092 } 1093 maybeLogCrossProfileTargetLaunch(cti, user); 1094 } 1095 } catch (RuntimeException e) { 1096 Slog.wtf(TAG, 1097 "Unable to launch as uid " + mActivityModel.getLaunchedFromUid() 1098 + " package " + mActivityModel.getLaunchedFromPackage() 1099 + ", while running in " + ActivityThread.currentProcessName(), e); 1100 } 1101 } 1102 maybeLogCrossProfileTargetLaunch(TargetInfo cti, UserHandle currentUserHandle)1103 private void maybeLogCrossProfileTargetLaunch(TargetInfo cti, UserHandle currentUserHandle) { 1104 if (!mProfiles.getWorkProfilePresent() || currentUserHandle.equals(getUser())) { 1105 return; 1106 } 1107 DevicePolicyEventLogger 1108 .createEvent(DevicePolicyEnums.RESOLVER_CROSS_PROFILE_TARGET_OPENED) 1109 .setBoolean(currentUserHandle.equals(mProfiles.getPersonalHandle())) 1110 .setStrings(getMetricsCategory(), 1111 cti.isInDirectShareMetricsCategory() ? "direct_share" : "other_target") 1112 .write(); 1113 } 1114 getLatencyTracker()1115 private LatencyTracker getLatencyTracker() { 1116 return LatencyTracker.getInstance(this); 1117 } 1118 1119 /** 1120 * If {@code retainInOnStop} is set to true, we will not finish ourselves when onStop gets 1121 * called and we are launched in a new task. 1122 */ setRetainInOnStop(boolean retainInOnStop)1123 protected final void setRetainInOnStop(boolean retainInOnStop) { 1124 mRetainInOnStop = retainInOnStop; 1125 } 1126 1127 // @NonFinalForTesting 1128 @VisibleForTesting createCrossProfileIntentsChecker()1129 protected CrossProfileIntentsChecker createCrossProfileIntentsChecker() { 1130 return new CrossProfileIntentsChecker(getContentResolver()); 1131 } 1132 createEmptyStateProvider( ProfileHelper profileHelper, ProfileAvailability profileAvailability)1133 protected final EmptyStateProvider createEmptyStateProvider( 1134 ProfileHelper profileHelper, 1135 ProfileAvailability profileAvailability) { 1136 EmptyStateProvider blockerEmptyStateProvider = createBlockerEmptyStateProvider(); 1137 1138 EmptyStateProvider workProfileOffEmptyStateProvider = 1139 new WorkProfilePausedEmptyStateProvider( 1140 this, 1141 profileHelper, 1142 profileAvailability, 1143 /* onSwitchOnWorkSelectedListener = */ 1144 () -> { 1145 if (mOnSwitchOnWorkSelectedListener != null) { 1146 mOnSwitchOnWorkSelectedListener.onSwitchOnWorkSelected(); 1147 } 1148 }, 1149 getMetricsCategory()); 1150 1151 EmptyStateProvider noAppsEmptyStateProvider = new NoAppsAvailableEmptyStateProvider( 1152 mProfiles, 1153 mProfileAvailability, 1154 getMetricsCategory(), 1155 mProfilePagerResources 1156 ); 1157 1158 // Return composite provider, the order matters (the higher, the more priority) 1159 return new CompositeEmptyStateProvider( 1160 blockerEmptyStateProvider, 1161 workProfileOffEmptyStateProvider, 1162 noAppsEmptyStateProvider 1163 ); 1164 } 1165 1166 /** 1167 * Returns the {@link List} of {@link UserHandle} to pass on to the 1168 * {@link ResolverRankerServiceResolverComparator} as per the provided {@code userHandle}. 1169 */ getResolverRankerServiceUserHandleList(UserHandle userHandle)1170 private List<UserHandle> getResolverRankerServiceUserHandleList(UserHandle userHandle) { 1171 return getResolverRankerServiceUserHandleListInternal(userHandle); 1172 } 1173 getResolverRankerServiceUserHandleListInternal(UserHandle userHandle)1174 private List<UserHandle> getResolverRankerServiceUserHandleListInternal(UserHandle userHandle) { 1175 List<UserHandle> userList = new ArrayList<>(); 1176 userList.add(userHandle); 1177 // Add clonedProfileUserHandle to the list only if we are: 1178 // a. Building the Personal Tab. 1179 // b. CloneProfile exists on the device. 1180 if (userHandle.equals(mProfiles.getPersonalHandle()) 1181 && mProfiles.getCloneUserPresent()) { 1182 userList.add(mProfiles.getCloneHandle()); 1183 } 1184 return userList; 1185 } 1186 1187 /** 1188 * Start activity as a fixed user handle. 1189 * @param cti TargetInfo to be launched. 1190 * @param user User to launch this activity as. 1191 */ 1192 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED) safelyStartActivityAsUser(TargetInfo cti, UserHandle user)1193 public final void safelyStartActivityAsUser(TargetInfo cti, UserHandle user) { 1194 safelyStartActivityAsUser(cti, user, null); 1195 } 1196 1197 @Override // ResolverListCommunicator onHandlePackagesChanged(ResolverListAdapter listAdapter)1198 public final void onHandlePackagesChanged(ResolverListAdapter listAdapter) { 1199 mChooserMultiProfilePagerAdapter.onHandlePackagesChanged( 1200 (ChooserListAdapter) listAdapter, 1201 mProfileAvailability.getWaitingToEnableProfile()); 1202 } 1203 optionForChooserTarget(TargetInfo target, int index)1204 final Option optionForChooserTarget(TargetInfo target, int index) { 1205 return new Option(getOrLoadDisplayLabel(target), index); 1206 } 1207 1208 @Override // ResolverListCommunicator sendVoiceChoicesIfNeeded()1209 public final void sendVoiceChoicesIfNeeded() { 1210 if (!isVoiceInteraction()) { 1211 // Clearly not needed. 1212 return; 1213 } 1214 1215 int count = mChooserMultiProfilePagerAdapter.getActiveListAdapter().getCount(); 1216 final Option[] options = new Option[count]; 1217 for (int i = 0; i < options.length; i++) { 1218 TargetInfo target = mChooserMultiProfilePagerAdapter.getActiveListAdapter().getItem(i); 1219 if (target == null) { 1220 // If this occurs, a new set of targets is being loaded. Let that complete, 1221 // and have the next call to send voice choices proceed instead. 1222 return; 1223 } 1224 options[i] = optionForChooserTarget(target, i); 1225 } 1226 1227 mPickOptionRequest = new ResolverActivity.PickTargetOptionRequest( 1228 new VoiceInteractor.Prompt(getTitle()), options, null); 1229 getVoiceInteractor().submitRequest(mPickOptionRequest); 1230 } 1231 1232 /** 1233 * Sets up the content view. 1234 * @return <code>true</code> if the activity is finishing and creation should halt. 1235 */ configureContentView(TargetDataLoader targetDataLoader)1236 private boolean configureContentView(TargetDataLoader targetDataLoader) { 1237 if (mChooserMultiProfilePagerAdapter.getActiveListAdapter() == null) { 1238 throw new IllegalStateException("mMultiProfilePagerAdapter.getCurrentListAdapter() " 1239 + "cannot be null."); 1240 } 1241 Trace.beginSection("configureContentView"); 1242 // We partially rebuild the inactive adapter to determine if we should auto launch 1243 // isTabLoaded will be true here if the empty state screen is shown instead of the list. 1244 boolean rebuildCompleted = mChooserMultiProfilePagerAdapter.rebuildTabs( 1245 mProfiles.getWorkProfilePresent()); 1246 1247 mLayoutId = R.layout.chooser_grid_scrollable_preview; 1248 1249 setContentView(mLayoutId); 1250 mTabHost = findViewById(com.android.internal.R.id.profile_tabhost); 1251 mViewPager = requireViewById(com.android.internal.R.id.profile_pager); 1252 mChooserMultiProfilePagerAdapter.setupViewPager(mViewPager); 1253 boolean result = postRebuildList(rebuildCompleted); 1254 Trace.endSection(); 1255 return result; 1256 } 1257 1258 /** 1259 * Finishing procedures to be performed after the list has been rebuilt. 1260 * </p>Subclasses must call postRebuildListInternal at the end of postRebuildList. 1261 * @param rebuildCompleted 1262 * @return <code>true</code> if the activity is finishing and creation should halt. 1263 */ postRebuildList(boolean rebuildCompleted)1264 protected boolean postRebuildList(boolean rebuildCompleted) { 1265 return postRebuildListInternal(rebuildCompleted); 1266 } 1267 1268 /** 1269 * Add a label to signify that the user can pick a different app. 1270 * @param adapter The adapter used to provide data to item views. 1271 */ addUseDifferentAppLabelIfNecessary(ResolverListAdapter adapter)1272 public void addUseDifferentAppLabelIfNecessary(ResolverListAdapter adapter) { 1273 final boolean useHeader = adapter.hasFilteredItem(); 1274 if (useHeader) { 1275 FrameLayout stub = findViewById(com.android.internal.R.id.stub); 1276 stub.setVisibility(View.VISIBLE); 1277 TextView textView = (TextView) LayoutInflater.from(this).inflate( 1278 R.layout.resolver_different_item_header, null, false); 1279 if (mProfiles.getWorkProfilePresent()) { 1280 textView.setGravity(Gravity.CENTER); 1281 } 1282 stub.addView(textView); 1283 } 1284 } setupViewVisibilities()1285 private void setupViewVisibilities() { 1286 ChooserListAdapter activeListAdapter = 1287 mChooserMultiProfilePagerAdapter.getActiveListAdapter(); 1288 if (!mChooserMultiProfilePagerAdapter.shouldShowEmptyStateScreen(activeListAdapter)) { 1289 addUseDifferentAppLabelIfNecessary(activeListAdapter); 1290 } 1291 } 1292 /** 1293 * Finishing procedures to be performed after the list has been rebuilt. 1294 * @param rebuildCompleted 1295 * @return <code>true</code> if the activity is finishing and creation should halt. 1296 */ postRebuildListInternal(boolean rebuildCompleted)1297 final boolean postRebuildListInternal(boolean rebuildCompleted) { 1298 int count = mChooserMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredCount(); 1299 1300 // We only rebuild asynchronously when we have multiple elements to sort. In the case where 1301 // we're already done, we can check if we should auto-launch immediately. 1302 if (rebuildCompleted && maybeAutolaunchActivity()) { 1303 return true; 1304 } 1305 1306 setupViewVisibilities(); 1307 1308 if (mProfiles.getWorkProfilePresent() 1309 || (mProfiles.getPrivateProfilePresent() 1310 && mProfileAvailability.isAvailable( 1311 requireNonNull(mProfiles.getPrivateProfile())))) { 1312 setupProfileTabs(); 1313 } 1314 1315 return false; 1316 } 1317 setupProfileTabs()1318 private void setupProfileTabs() { 1319 mChooserMultiProfilePagerAdapter.setupProfileTabs( 1320 getLayoutInflater(), 1321 mTabHost, 1322 mViewPager, 1323 R.layout.resolver_profile_tab_button, 1324 com.android.internal.R.id.profile_pager, 1325 () -> onProfileTabSelected(mViewPager.getCurrentItem()), 1326 new OnProfileSelectedListener() { 1327 @Override 1328 public void onProfilePageSelected(@ProfileType int profileId, int pageNumber) {} 1329 1330 @Override 1331 public void onProfilePageStateChanged(int state) { 1332 onHorizontalSwipeStateChanged(state); 1333 } 1334 }); 1335 mOnSwitchOnWorkSelectedListener = () -> { 1336 View workTab = mTabHost.getTabWidget().getChildAt( 1337 mChooserMultiProfilePagerAdapter.getPageNumberForProfile(PROFILE_WORK)); 1338 workTab.setFocusable(true); 1339 workTab.setFocusableInTouchMode(true); 1340 workTab.requestFocus(); 1341 }; 1342 } 1343 1344 ////////////////////////////////////////////////////////////////////////////////////////////// 1345 ////////////////////////////////////////////////////////////////////////////////////////////// 1346 createProfileRecords( AppPredictorFactory factory, IntentFilter targetIntentFilter)1347 private void createProfileRecords( 1348 AppPredictorFactory factory, IntentFilter targetIntentFilter) { 1349 UserHandle mainUserHandle = mProfiles.getPersonalHandle(); 1350 ProfileRecord record = createProfileRecord(mainUserHandle, targetIntentFilter, factory); 1351 if (record.shortcutLoader == null) { 1352 Tracer.INSTANCE.endLaunchToShortcutTrace(); 1353 } 1354 1355 UserHandle workUserHandle = mProfiles.getWorkHandle(); 1356 if (workUserHandle != null) { 1357 createProfileRecord(workUserHandle, targetIntentFilter, factory); 1358 } 1359 1360 UserHandle privateUserHandle = mProfiles.getPrivateHandle(); 1361 if (privateUserHandle != null && mProfileAvailability.isAvailable( 1362 requireNonNull(mProfiles.getPrivateProfile()))) { 1363 createProfileRecord(privateUserHandle, targetIntentFilter, factory); 1364 } 1365 } 1366 createProfileRecord( UserHandle userHandle, IntentFilter targetIntentFilter, AppPredictorFactory factory)1367 private ProfileRecord createProfileRecord( 1368 UserHandle userHandle, IntentFilter targetIntentFilter, AppPredictorFactory factory) { 1369 AppPredictor appPredictor = factory.create(userHandle); 1370 ShortcutLoader shortcutLoader = ActivityManager.isLowRamDeviceStatic() 1371 ? null 1372 : createShortcutLoader( 1373 this, 1374 appPredictor, 1375 userHandle, 1376 targetIntentFilter, 1377 shortcutsResult -> onShortcutsLoaded(userHandle, shortcutsResult)); 1378 ProfileRecord record = new ProfileRecord(appPredictor, shortcutLoader); 1379 mProfileRecords.put(userHandle.getIdentifier(), record); 1380 return record; 1381 } 1382 1383 @Nullable getProfileRecord(UserHandle userHandle)1384 private ProfileRecord getProfileRecord(UserHandle userHandle) { 1385 return mProfileRecords.get(userHandle.getIdentifier()); 1386 } 1387 1388 @VisibleForTesting createShortcutLoader( Context context, AppPredictor appPredictor, UserHandle userHandle, IntentFilter targetIntentFilter, Consumer<ShortcutLoader.Result> callback)1389 protected ShortcutLoader createShortcutLoader( 1390 Context context, 1391 AppPredictor appPredictor, 1392 UserHandle userHandle, 1393 IntentFilter targetIntentFilter, 1394 Consumer<ShortcutLoader.Result> callback) { 1395 return new ShortcutLoader( 1396 context, 1397 getCoroutineScope(getLifecycle()), 1398 appPredictor, 1399 userHandle, 1400 targetIntentFilter, 1401 callback); 1402 } 1403 getPinnedSharedPrefs(Context context)1404 static SharedPreferences getPinnedSharedPrefs(Context context) { 1405 return context.getSharedPreferences(PINNED_SHARED_PREFS_NAME, MODE_PRIVATE); 1406 } 1407 createMultiProfilePagerAdapter( Context context, ProfilePagerResources profilePagerResources, ChooserRequest request, ProfileHelper profileHelper, ProfileAvailability profileAvailability, List<Intent> initialIntents, int maxTargetsPerRow)1408 private ChooserMultiProfilePagerAdapter createMultiProfilePagerAdapter( 1409 Context context, 1410 ProfilePagerResources profilePagerResources, 1411 ChooserRequest request, 1412 ProfileHelper profileHelper, 1413 ProfileAvailability profileAvailability, 1414 List<Intent> initialIntents, 1415 int maxTargetsPerRow) { 1416 Log.d(TAG, "createMultiProfilePagerAdapter"); 1417 1418 Profile launchedAs = profileHelper.getLaunchedAsProfile(); 1419 1420 Intent[] initialIntentArray = initialIntents.toArray(new Intent[0]); 1421 List<Intent> payloadIntents = request.getPayloadIntents(); 1422 1423 List<TabConfig<ChooserGridAdapter>> tabs = new ArrayList<>(); 1424 for (Profile profile : profileHelper.getProfiles()) { 1425 if (profile.getType() == Profile.Type.PRIVATE 1426 && !profileAvailability.isAvailable(profile)) { 1427 continue; 1428 } 1429 ChooserGridAdapter adapter = createChooserGridAdapter( 1430 context, 1431 payloadIntents, 1432 profile.equals(launchedAs) ? initialIntentArray : null, 1433 profile.getPrimary().getHandle() 1434 ); 1435 tabs.add(new TabConfig<>( 1436 /* profile = */ profile.getType().ordinal(), 1437 profilePagerResources.profileTabLabel(profile.getType()), 1438 profilePagerResources.profileTabAccessibilityLabel(profile.getType()), 1439 /* tabTag = */ profile.getType().name(), 1440 adapter)); 1441 } 1442 1443 EmptyStateProvider emptyStateProvider = 1444 createEmptyStateProvider(profileHelper, profileAvailability); 1445 1446 Supplier<Boolean> workProfileQuietModeChecker = 1447 () -> !(profileHelper.getWorkProfilePresent() 1448 && profileAvailability.isAvailable( 1449 requireNonNull(profileHelper.getWorkProfile()))); 1450 1451 return new ChooserMultiProfilePagerAdapter( 1452 /* context */ this, 1453 ImmutableList.copyOf(tabs), 1454 emptyStateProvider, 1455 workProfileQuietModeChecker, 1456 launchedAs.getType().ordinal(), 1457 profileHelper.getWorkHandle(), 1458 profileHelper.getCloneHandle(), 1459 maxTargetsPerRow); 1460 } 1461 createBlockerEmptyStateProvider()1462 protected EmptyStateProvider createBlockerEmptyStateProvider() { 1463 return new NoCrossProfileEmptyStateProvider( 1464 mProfiles, 1465 mDevicePolicyResources, 1466 createCrossProfileIntentsChecker(), 1467 mRequest.isSendActionTarget()); 1468 } 1469 findSelectedProfile()1470 private int findSelectedProfile() { 1471 return mProfiles.getLaunchedAsProfileType().ordinal(); 1472 } 1473 1474 /** 1475 * Check if the profile currently used is a work profile. 1476 * @return true if it is work profile, false if it is parent profile (or no work profile is 1477 * set up) 1478 */ isWorkProfile()1479 private boolean isWorkProfile() { 1480 return mProfiles.getLaunchedAsProfileType() == Profile.Type.WORK; 1481 } 1482 1483 //@Override createPackageMonitor(ResolverListAdapter listAdapter)1484 protected PackageMonitor createPackageMonitor(ResolverListAdapter listAdapter) { 1485 return new PackageMonitor() { 1486 @Override 1487 public void onSomePackagesChanged() { 1488 handlePackagesChanged(listAdapter); 1489 } 1490 }; 1491 } 1492 1493 /** 1494 * Update UI to reflect changes in data. 1495 */ 1496 @Override 1497 public void handlePackagesChanged() { 1498 handlePackagesChanged(/* listAdapter */ null); 1499 } 1500 1501 /** 1502 * Update UI to reflect changes in data. 1503 * <p>If {@code listAdapter} is {@code null}, both profile list adapters are updated if 1504 * available. 1505 */ 1506 private void handlePackagesChanged(@Nullable ResolverListAdapter listAdapter) { 1507 // Refresh pinned items 1508 mPinnedSharedPrefs = getPinnedSharedPrefs(this); 1509 if (listAdapter == null) { 1510 mChooserMultiProfilePagerAdapter.refreshPackagesInAllTabs(); 1511 } else { 1512 listAdapter.handlePackagesChanged(); 1513 } 1514 } 1515 1516 @Override 1517 public void onConfigurationChanged(Configuration newConfig) { 1518 super.onConfigurationChanged(newConfig); 1519 mChooserMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged(); 1520 1521 if (mSystemWindowInsets != null) { 1522 mResolverDrawerLayout.setPadding(mSystemWindowInsets.left, mSystemWindowInsets.top, 1523 mSystemWindowInsets.right, 0); 1524 } 1525 if (mViewPager.isLayoutRtl()) { 1526 mChooserMultiProfilePagerAdapter.setupViewPager(mViewPager); 1527 } 1528 1529 mShouldDisplayLandscape = shouldDisplayLandscape(newConfig.orientation); 1530 mMaxTargetsPerRow = getResources().getInteger(R.integer.config_chooser_max_targets_per_row); 1531 mChooserMultiProfilePagerAdapter.setMaxTargetsPerRow(mMaxTargetsPerRow); 1532 adjustPreviewWidth(newConfig.orientation, null); 1533 updateStickyContentPreview(); 1534 updateTabPadding(); 1535 } 1536 1537 private boolean shouldDisplayLandscape(int orientation) { 1538 // Sharesheet fixes the # of items per row and therefore can not correctly lay out 1539 // when in the restricted size of multi-window mode. In the future, would be nice 1540 // to use minimum dp size requirements instead 1541 return orientation == Configuration.ORIENTATION_LANDSCAPE && !isInMultiWindowMode(); 1542 } 1543 1544 private void adjustPreviewWidth(int orientation, View parent) { 1545 int width = -1; 1546 if (mShouldDisplayLandscape) { 1547 width = getResources().getDimensionPixelSize(R.dimen.chooser_preview_width); 1548 } 1549 1550 parent = parent == null ? getWindow().getDecorView() : parent; 1551 1552 updateLayoutWidth(com.android.internal.R.id.content_preview_file_layout, width, parent); 1553 } 1554 1555 private void updateTabPadding() { 1556 if (mProfiles.getWorkProfilePresent()) { 1557 View tabs = findViewById(com.android.internal.R.id.tabs); 1558 float iconSize = getResources().getDimension(R.dimen.chooser_icon_size); 1559 // The entire width consists of icons or padding. Divide the item padding in half to get 1560 // paddingHorizontal. 1561 float padding = (tabs.getWidth() - mMaxTargetsPerRow * iconSize) 1562 / mMaxTargetsPerRow / 2; 1563 // Subtract the margin the buttons already have. 1564 padding -= getResources().getDimension(R.dimen.resolver_profile_tab_margin); 1565 tabs.setPadding((int) padding, 0, (int) padding, 0); 1566 } 1567 } 1568 1569 private void updateLayoutWidth(int layoutResourceId, int width, View parent) { 1570 View view = parent.findViewById(layoutResourceId); 1571 if (view != null && view.getLayoutParams() != null) { 1572 LayoutParams params = view.getLayoutParams(); 1573 params.width = width; 1574 view.setLayoutParams(params); 1575 } 1576 } 1577 1578 /** 1579 * Create a view that will be shown in the content preview area 1580 * @param parent reference to the parent container where the view should be attached to 1581 * @return content preview view 1582 */ 1583 protected ViewGroup createContentPreviewView(ViewGroup parent) { 1584 ViewGroup layout = mChooserContentPreviewUi.displayContentPreview( 1585 getResources(), 1586 getLayoutInflater(), 1587 parent, 1588 requireViewById(R.id.chooser_headline_row_container)); 1589 1590 if (layout != null) { 1591 adjustPreviewWidth(getResources().getConfiguration().orientation, layout); 1592 } 1593 1594 return layout; 1595 } 1596 1597 @Nullable 1598 private View getFirstVisibleImgPreviewView() { 1599 View imagePreview = findViewById(R.id.scrollable_image_preview); 1600 return imagePreview instanceof ImagePreviewView 1601 ? ((ImagePreviewView) imagePreview).getTransitionView() 1602 : null; 1603 } 1604 1605 /** 1606 * Wrapping the ContentResolver call to expose for easier mocking, 1607 * and to avoid mocking Android core classes. 1608 */ 1609 @VisibleForTesting 1610 public Cursor queryResolver(ContentResolver resolver, Uri uri) { 1611 return resolver.query(uri, null, null, null, null); 1612 } 1613 1614 private void destroyProfileRecords() { 1615 mProfileRecords.values().forEach(ProfileRecord::destroy); 1616 mProfileRecords.clear(); 1617 } 1618 1619 @Override // ResolverListCommunicator 1620 public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) { 1621 Intent result = defIntent; 1622 if (mRequest.getReplacementExtras() != null) { 1623 final Bundle replExtras = 1624 mRequest.getReplacementExtras().getBundle(aInfo.packageName); 1625 if (replExtras != null) { 1626 result = new Intent(defIntent); 1627 result.putExtras(replExtras); 1628 } 1629 } 1630 if (aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_PARENT) 1631 || aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE)) { 1632 result = Intent.createChooser(result, 1633 getIntent().getCharSequenceExtra(Intent.EXTRA_TITLE)); 1634 1635 // Don't auto-launch single intents if the intent is being forwarded. This is done 1636 // because automatically launching a resolving application as a response to the user 1637 // action of switching accounts is pretty unexpected. 1638 result.putExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, false); 1639 } 1640 return result; 1641 } 1642 1643 private void maybeSendShareResult(TargetInfo cti) { 1644 if (mShareResultSender != null) { 1645 final ComponentName target = cti.getResolvedComponentName(); 1646 if (target != null) { 1647 mShareResultSender.onComponentSelected(target, cti.isChooserTargetInfo()); 1648 } 1649 } 1650 } 1651 1652 private void addCallerChooserTargets() { 1653 if (!mRequest.getCallerChooserTargets().isEmpty()) { 1654 // Send the caller's chooser targets only to the default profile. 1655 if (mChooserMultiProfilePagerAdapter.getActiveProfile() == findSelectedProfile()) { 1656 mChooserMultiProfilePagerAdapter.getActiveListAdapter().addServiceResults( 1657 /* origTarget */ null, 1658 new ArrayList<>(mRequest.getCallerChooserTargets()), 1659 TARGET_TYPE_DEFAULT, 1660 /* directShareShortcutInfoCache */ Collections.emptyMap(), 1661 /* directShareAppTargetCache */ Collections.emptyMap()); 1662 } 1663 } 1664 } 1665 1666 @Override // ResolverListCommunicator 1667 public boolean shouldGetActivityMetadata() { 1668 return true; 1669 } 1670 1671 public boolean shouldAutoLaunchSingleChoice(TargetInfo target) { 1672 if (target.isSuspended()) { 1673 return false; 1674 } 1675 1676 // TODO: migrate to ChooserRequest 1677 return mViewModel.getActivityModel().getIntent() 1678 .getBooleanExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, true); 1679 } 1680 1681 private void showTargetDetails(TargetInfo targetInfo) { 1682 if (targetInfo == null) return; 1683 1684 List<DisplayResolveInfo> targetList = targetInfo.getAllDisplayTargets(); 1685 if (targetList.isEmpty()) { 1686 Log.e(TAG, "No displayable data to show target details"); 1687 return; 1688 } 1689 1690 // TODO: implement these type-conditioned behaviors polymorphically, and consider moving 1691 // the logic into `ChooserTargetActionsDialogFragment.show()`. 1692 boolean isShortcutPinned = targetInfo.isSelectableTargetInfo() && targetInfo.isPinned(); 1693 IntentFilter intentFilter; 1694 intentFilter = targetInfo.isSelectableTargetInfo() 1695 ? mRequest.getShareTargetFilter() : null; 1696 String shortcutTitle = targetInfo.isSelectableTargetInfo() 1697 ? targetInfo.getDisplayLabel().toString() : null; 1698 String shortcutIdKey = targetInfo.getDirectShareShortcutId(); 1699 1700 ChooserTargetActionsDialogFragment.show( 1701 getSupportFragmentManager(), 1702 targetList, 1703 // Adding userHandle from ResolveInfo allows the app icon in Dialog Box to be 1704 // resolved correctly within the same tab. 1705 targetInfo.getResolveInfo().userHandle, 1706 shortcutIdKey, 1707 shortcutTitle, 1708 isShortcutPinned, 1709 intentFilter); 1710 } 1711 1712 protected boolean onTargetSelected(TargetInfo target) { 1713 if (mRefinementManager.maybeHandleSelection( 1714 target, 1715 mRequest.getRefinementIntentSender(), 1716 getApplication(), 1717 getMainThreadHandler())) { 1718 return false; 1719 } 1720 updateModelAndChooserCounts(target); 1721 maybeRemoveSharedText(target); 1722 safelyStartActivity(target); 1723 1724 // Rely on the ActivityManager to pop up a dialog regarding app suspension 1725 // and return false 1726 return !target.isSuspended(); 1727 } 1728 1729 @Override 1730 public void startSelected(int which, /* unused */ boolean always, boolean filtered) { 1731 ChooserListAdapter currentListAdapter = 1732 mChooserMultiProfilePagerAdapter.getActiveListAdapter(); 1733 TargetInfo targetInfo = currentListAdapter 1734 .targetInfoForPosition(which, filtered); 1735 if (targetInfo != null && targetInfo.isNotSelectableTargetInfo()) { 1736 return; 1737 } 1738 1739 final long selectionCost = System.currentTimeMillis() - mChooserShownTime; 1740 1741 if ((targetInfo != null) && targetInfo.isMultiDisplayResolveInfo()) { 1742 MultiDisplayResolveInfo mti = (MultiDisplayResolveInfo) targetInfo; 1743 if (!mti.hasSelected()) { 1744 // Add userHandle based badge to the stackedAppDialogBox. 1745 ChooserStackedAppDialogFragment.show( 1746 getSupportFragmentManager(), 1747 mti, 1748 which, 1749 targetInfo.getResolveInfo().userHandle); 1750 return; 1751 } 1752 } 1753 if (isFinishing()) { 1754 return; 1755 } 1756 1757 TargetInfo target = mChooserMultiProfilePagerAdapter.getActiveListAdapter() 1758 .targetInfoForPosition(which, filtered); 1759 if (target != null) { 1760 if (onTargetSelected(target)) { 1761 MetricsLogger.action( 1762 this, MetricsEvent.ACTION_APP_DISAMBIG_TAP); 1763 MetricsLogger.action(this, 1764 mChooserMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem() 1765 ? MetricsEvent.ACTION_HIDE_APP_DISAMBIG_APP_FEATURED 1766 : MetricsEvent.ACTION_HIDE_APP_DISAMBIG_NONE_FEATURED); 1767 Log.d(TAG, "onTargetSelected() returned true, finishing! " + target); 1768 finish(); 1769 } 1770 } 1771 1772 // TODO: both of the conditions around this switch logic *should* be redundant, and 1773 // can be removed if certain invariants can be guaranteed. In particular, it seems 1774 // like targetInfo (from `ChooserListAdapter.targetInfoForPosition()`) is *probably* 1775 // expected to be null only at out-of-bounds indexes where `getPositionTargetType()` 1776 // returns TARGET_BAD; then the switch falls through to a default no-op, and we don't 1777 // need to null-check targetInfo. We only need the null check if it's possible that 1778 // the ChooserListAdapter contains null elements "in the middle" of its list data, 1779 // such that they're classified as belonging to one of the real target types. That 1780 // should probably never happen. But why would this method ever be invoked with a 1781 // null target at all? Even an out-of-bounds index should never be "selected"... 1782 if ((currentListAdapter.getCount() > 0) && (targetInfo != null)) { 1783 switch (currentListAdapter.getPositionTargetType(which)) { 1784 case ChooserListAdapter.TARGET_SERVICE: 1785 getEventLog().logShareTargetSelected( 1786 EventLog.SELECTION_TYPE_SERVICE, 1787 targetInfo.getResolveInfo().activityInfo.processName, 1788 which, 1789 /* directTargetAlsoRanked= */ getRankedPosition(targetInfo), 1790 mRequest.getCallerChooserTargets().size(), 1791 targetInfo.getHashedTargetIdForMetrics(this), 1792 targetInfo.isPinned(), 1793 mIsSuccessfullySelected, 1794 selectionCost 1795 ); 1796 return; 1797 case ChooserListAdapter.TARGET_CALLER: 1798 case ChooserListAdapter.TARGET_STANDARD: 1799 getEventLog().logShareTargetSelected( 1800 EventLog.SELECTION_TYPE_APP, 1801 targetInfo.getResolveInfo().activityInfo.processName, 1802 (which - currentListAdapter.getSurfacedTargetInfo().size()), 1803 /* directTargetAlsoRanked= */ -1, 1804 currentListAdapter.getCallerTargetCount(), 1805 /* directTargetHashed= */ null, 1806 targetInfo.isPinned(), 1807 mIsSuccessfullySelected, 1808 selectionCost 1809 ); 1810 return; 1811 case ChooserListAdapter.TARGET_STANDARD_AZ: 1812 // A-Z targets are unranked standard targets; we use a value of -1 to mark that 1813 // they are from the alphabetical pool. 1814 // TODO: why do we log a different selection type if the -1 value already 1815 // designates the same condition? 1816 getEventLog().logShareTargetSelected( 1817 EventLog.SELECTION_TYPE_STANDARD, 1818 targetInfo.getResolveInfo().activityInfo.processName, 1819 /* value= */ -1, 1820 /* directTargetAlsoRanked= */ -1, 1821 /* numCallerProvided= */ 0, 1822 /* directTargetHashed= */ null, 1823 /* isPinned= */ false, 1824 mIsSuccessfullySelected, 1825 selectionCost 1826 ); 1827 } 1828 } 1829 } 1830 1831 private int getRankedPosition(TargetInfo targetInfo) { 1832 String targetPackageName = 1833 targetInfo.getChooserTargetComponentName().getPackageName(); 1834 ChooserListAdapter currentListAdapter = 1835 mChooserMultiProfilePagerAdapter.getActiveListAdapter(); 1836 int maxRankedResults = Math.min( 1837 currentListAdapter.getDisplayResolveInfoCount(), MAX_LOG_RANK_POSITION); 1838 1839 for (int i = 0; i < maxRankedResults; i++) { 1840 if (currentListAdapter.getDisplayResolveInfo(i) 1841 .getResolveInfo().activityInfo.packageName.equals(targetPackageName)) { 1842 return i; 1843 } 1844 } 1845 return -1; 1846 } 1847 1848 protected void applyFooterView(int height) { 1849 mChooserMultiProfilePagerAdapter.setFooterHeightInEveryAdapter(height); 1850 } 1851 1852 private void logDirectShareTargetReceived(UserHandle forUser) { 1853 ProfileRecord profileRecord = getProfileRecord(forUser); 1854 if (profileRecord == null) { 1855 return; 1856 } 1857 getEventLog().logDirectShareTargetReceived( 1858 MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER, 1859 (int) (SystemClock.elapsedRealtime() - profileRecord.loadingStartTime)); 1860 } 1861 1862 void updateModelAndChooserCounts(TargetInfo info) { 1863 if (info != null && info.isMultiDisplayResolveInfo()) { 1864 info = ((MultiDisplayResolveInfo) info).getSelectedTarget(); 1865 } 1866 if (info != null) { 1867 sendClickToAppPredictor(info); 1868 final ResolveInfo ri = info.getResolveInfo(); 1869 Intent targetIntent = mRequest.getTargetIntent(); 1870 if (ri != null && ri.activityInfo != null && targetIntent != null) { 1871 ChooserListAdapter currentListAdapter = 1872 mChooserMultiProfilePagerAdapter.getActiveListAdapter(); 1873 if (currentListAdapter != null) { 1874 sendImpressionToAppPredictor(info, currentListAdapter); 1875 currentListAdapter.updateModel(info); 1876 currentListAdapter.updateChooserCounts( 1877 ri.activityInfo.packageName, 1878 targetIntent.getAction(), 1879 ri.userHandle); 1880 } 1881 if (DEBUG) { 1882 Log.d(TAG, "ResolveInfo Package is " + ri.activityInfo.packageName); 1883 Log.d(TAG, "Action to be updated is " + targetIntent.getAction()); 1884 } 1885 } else if (DEBUG) { 1886 Log.d(TAG, "Can not log Chooser Counts of null ResolveInfo"); 1887 } 1888 } 1889 mIsSuccessfullySelected = true; 1890 } 1891 1892 private void maybeRemoveSharedText(@NonNull TargetInfo targetInfo) { 1893 Intent targetIntent = targetInfo.getTargetIntent(); 1894 if (targetIntent == null) { 1895 return; 1896 } 1897 Intent originalTargetIntent = new Intent(mRequest.getTargetIntent()); 1898 // Our TargetInfo implementations add associated component to the intent, let's do the same 1899 // for the sake of the comparison below. 1900 if (targetIntent.getComponent() != null) { 1901 originalTargetIntent.setComponent(targetIntent.getComponent()); 1902 } 1903 // Use filterEquals as a way to check that the primary intent is in use (and not an 1904 // alternative one). For example, an app is sharing an image and a link with mime type 1905 // "image/png" and provides an alternative intent to share only the link with mime type 1906 // "text/uri". Should there be a target that accepts only the latter, the alternative intent 1907 // will be used and we don't want to exclude the link from it. 1908 if (mExcludeSharedText && originalTargetIntent.filterEquals(targetIntent)) { 1909 targetIntent.removeExtra(Intent.EXTRA_TEXT); 1910 } 1911 } 1912 1913 private void sendImpressionToAppPredictor(TargetInfo targetInfo, ChooserListAdapter adapter) { 1914 // Send DS target impression info to AppPredictor, only when user chooses app share. 1915 if (targetInfo.isChooserTargetInfo()) { 1916 return; 1917 } 1918 1919 AppPredictor directShareAppPredictor = getAppPredictor( 1920 mChooserMultiProfilePagerAdapter.getCurrentUserHandle()); 1921 if (directShareAppPredictor == null) { 1922 return; 1923 } 1924 List<TargetInfo> surfacedTargetInfo = adapter.getSurfacedTargetInfo(); 1925 List<AppTargetId> targetIds = new ArrayList<>(); 1926 for (TargetInfo chooserTargetInfo : surfacedTargetInfo) { 1927 ShortcutInfo shortcutInfo = chooserTargetInfo.getDirectShareShortcutInfo(); 1928 if (shortcutInfo != null) { 1929 ComponentName componentName = 1930 chooserTargetInfo.getChooserTargetComponentName(); 1931 targetIds.add(new AppTargetId( 1932 String.format( 1933 "%s/%s/%s", 1934 shortcutInfo.getId(), 1935 componentName.flattenToString(), 1936 SHORTCUT_TARGET))); 1937 } 1938 } 1939 directShareAppPredictor.notifyLaunchLocationShown(LAUNCH_LOCATION_DIRECT_SHARE, targetIds); 1940 } 1941 1942 private void sendClickToAppPredictor(TargetInfo targetInfo) { 1943 if (!targetInfo.isChooserTargetInfo()) { 1944 return; 1945 } 1946 1947 AppPredictor directShareAppPredictor = getAppPredictor( 1948 mChooserMultiProfilePagerAdapter.getCurrentUserHandle()); 1949 if (directShareAppPredictor == null) { 1950 return; 1951 } 1952 AppTarget appTarget = targetInfo.getDirectShareAppTarget(); 1953 if (appTarget != null) { 1954 // This is a direct share click that was provided by the APS 1955 directShareAppPredictor.notifyAppTargetEvent( 1956 new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_LAUNCH) 1957 .setLaunchLocation(LAUNCH_LOCATION_DIRECT_SHARE) 1958 .build()); 1959 } 1960 } 1961 1962 @Nullable 1963 private AppPredictor getAppPredictor(UserHandle userHandle) { 1964 ProfileRecord record = getProfileRecord(userHandle); 1965 // We cannot use APS service when clone profile is present as APS service cannot sort 1966 // cross profile targets as of now. 1967 return ((record == null) || (mProfiles.getCloneUserPresent())) 1968 ? null : record.appPredictor; 1969 } 1970 1971 protected EventLog getEventLog() { 1972 return mEventLog; 1973 } 1974 1975 private ChooserGridAdapter createChooserGridAdapter( 1976 Context context, 1977 List<Intent> payloadIntents, 1978 Intent[] initialIntents, 1979 UserHandle userHandle) { 1980 ChooserListAdapter chooserListAdapter = createChooserListAdapter( 1981 context, 1982 payloadIntents, 1983 initialIntents, 1984 /* TODO: not used, remove. rList= */ null, 1985 /* TODO: not used, remove. filterLastUsed= */ false, 1986 createListController(userHandle), 1987 userHandle, 1988 mRequest.getTargetIntent(), 1989 mRequest.getReferrerFillInIntent(), 1990 mMaxTargetsPerRow 1991 ); 1992 1993 return new ChooserGridAdapter( 1994 context, 1995 new ChooserGridAdapter.ChooserActivityDelegate() { 1996 @Override 1997 public void onTargetSelected(int itemIndex) { 1998 startSelected(itemIndex, false, true); 1999 } 2000 2001 @Override 2002 public void onTargetLongPressed(int selectedPosition) { 2003 final TargetInfo longPressedTargetInfo = 2004 mChooserMultiProfilePagerAdapter 2005 .getActiveListAdapter() 2006 .targetInfoForPosition( 2007 selectedPosition, /* filtered= */ true); 2008 // Only a direct share target or an app target is expected 2009 if (longPressedTargetInfo.isDisplayResolveInfo() 2010 || longPressedTargetInfo.isSelectableTargetInfo()) { 2011 showTargetDetails(longPressedTargetInfo); 2012 } 2013 } 2014 }, 2015 chooserListAdapter, 2016 shouldShowContentPreview(), 2017 mMaxTargetsPerRow, 2018 mFeatureFlags); 2019 } 2020 2021 @VisibleForTesting 2022 public ChooserListAdapter createChooserListAdapter( 2023 Context context, 2024 List<Intent> payloadIntents, 2025 Intent[] initialIntents, 2026 List<ResolveInfo> rList, 2027 boolean filterLastUsed, 2028 ResolverListController resolverListController, 2029 UserHandle userHandle, 2030 Intent targetIntent, 2031 Intent referrerFillInIntent, 2032 int maxTargetsPerRow) { 2033 UserHandle initialIntentsUserSpace = mProfiles.getQueryIntentsHandle(userHandle); 2034 return new ChooserListAdapter( 2035 context, 2036 payloadIntents, 2037 initialIntents, 2038 rList, 2039 filterLastUsed, 2040 createListController(userHandle), 2041 userHandle, 2042 targetIntent, 2043 referrerFillInIntent, 2044 this, 2045 mPackageManager, 2046 getEventLog(), 2047 maxTargetsPerRow, 2048 initialIntentsUserSpace, 2049 mTargetDataLoader, 2050 () -> { 2051 ProfileRecord record = getProfileRecord(userHandle); 2052 if (record != null && record.shortcutLoader != null) { 2053 record.shortcutLoader.reset(); 2054 } 2055 }, 2056 mFeatureFlags); 2057 } 2058 2059 private void onWorkProfileStatusUpdated() { 2060 UserHandle workUser = mProfiles.getWorkHandle(); 2061 ProfileRecord record = workUser == null ? null : getProfileRecord(workUser); 2062 if (record != null && record.shortcutLoader != null) { 2063 record.shortcutLoader.reset(); 2064 } 2065 if (mChooserMultiProfilePagerAdapter.getCurrentUserHandle().equals( 2066 mProfiles.getWorkHandle())) { 2067 mChooserMultiProfilePagerAdapter.rebuildActiveTab(true); 2068 } else { 2069 mChooserMultiProfilePagerAdapter.clearInactiveProfileCache(); 2070 } 2071 } 2072 2073 @VisibleForTesting 2074 protected ChooserListController createListController(UserHandle userHandle) { 2075 AppPredictor appPredictor = getAppPredictor(userHandle); 2076 AbstractResolverComparator resolverComparator; 2077 if (appPredictor != null) { 2078 resolverComparator = new AppPredictionServiceResolverComparator( 2079 this, 2080 mRequest.getTargetIntent(), 2081 mRequest.getLaunchedFromPackage(), 2082 appPredictor, 2083 userHandle, 2084 getEventLog(), 2085 mNearbyShare.orElse(null) 2086 ); 2087 } else { 2088 resolverComparator = 2089 new ResolverRankerServiceResolverComparator( 2090 this, 2091 mRequest.getTargetIntent(), 2092 mRequest.getReferrerPackage(), 2093 null, 2094 getEventLog(), 2095 getResolverRankerServiceUserHandleList(userHandle), 2096 mNearbyShare.orElse(null)); 2097 } 2098 2099 return new ChooserListController( 2100 this, 2101 mPackageManager, 2102 mRequest.getTargetIntent(), 2103 mRequest.getReferrerPackage(), 2104 mViewModel.getActivityModel().getLaunchedFromUid(), 2105 resolverComparator, 2106 mProfiles.getQueryIntentsHandle(userHandle), 2107 mRequest.getFilteredComponentNames(), 2108 mPinnedSharedPrefs); 2109 } 2110 2111 @VisibleForTesting 2112 protected ViewModelProvider.Factory createPreviewViewModelFactory() { 2113 return PreviewViewModel.Companion.getFactory(); 2114 } 2115 2116 private ChooserContentPreviewUi.ActionFactory decorateActionFactoryWithRefinement( 2117 ChooserContentPreviewUi.ActionFactory originalFactory) { 2118 if (!mFeatureFlags.refineSystemActions()) { 2119 return originalFactory; 2120 } 2121 2122 return new ChooserContentPreviewUi.ActionFactory() { 2123 @Override 2124 @Nullable 2125 public Runnable getEditButtonRunnable() { 2126 return () -> { 2127 if (!mRefinementManager.maybeHandleSelection( 2128 RefinementType.EDIT_ACTION, 2129 List.of(mRequest.getTargetIntent()), 2130 null, 2131 mRequest.getRefinementIntentSender(), 2132 getApplication(), 2133 getMainThreadHandler())) { 2134 originalFactory.getEditButtonRunnable().run(); 2135 } 2136 }; 2137 } 2138 2139 @Override 2140 @Nullable 2141 public Runnable getCopyButtonRunnable() { 2142 return () -> { 2143 if (!mRefinementManager.maybeHandleSelection( 2144 RefinementType.COPY_ACTION, 2145 List.of(mRequest.getTargetIntent()), 2146 null, 2147 mRequest.getRefinementIntentSender(), 2148 getApplication(), 2149 getMainThreadHandler())) { 2150 originalFactory.getCopyButtonRunnable().run(); 2151 } 2152 }; 2153 } 2154 2155 @Override 2156 public List<ActionRow.Action> createCustomActions() { 2157 return originalFactory.createCustomActions(); 2158 } 2159 2160 @Override 2161 @Nullable 2162 public ActionRow.Action getModifyShareAction() { 2163 return originalFactory.getModifyShareAction(); 2164 } 2165 2166 @Override 2167 public Consumer<Boolean> getExcludeSharedTextAction() { 2168 return originalFactory.getExcludeSharedTextAction(); 2169 } 2170 }; 2171 } 2172 2173 private ChooserActionFactory createChooserActionFactory(Intent targetIntent) { 2174 return new ChooserActionFactory( 2175 this, 2176 targetIntent, 2177 mRequest.getLaunchedFromPackage(), 2178 mRequest.getChooserActions(), 2179 mImageEditor, 2180 getEventLog(), 2181 (isExcluded) -> mExcludeSharedText = isExcluded, 2182 this::getFirstVisibleImgPreviewView, 2183 new ChooserActionFactory.ActionActivityStarter() { 2184 @Override 2185 public void safelyStartActivityAsPersonalProfileUser(TargetInfo targetInfo) { 2186 safelyStartActivityAsUser( 2187 targetInfo, 2188 mProfiles.getPersonalHandle() 2189 ); 2190 Log.d(TAG, "safelyStartActivityAsPersonalProfileUser(" 2191 + targetInfo + "): finishing!"); 2192 finish(); 2193 } 2194 2195 @Override 2196 public void safelyStartActivityAsPersonalProfileUserWithSharedElementTransition( 2197 TargetInfo targetInfo, View sharedElement, String sharedElementName) { 2198 ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation( 2199 ChooserActivity.this, sharedElement, sharedElementName); 2200 safelyStartActivityAsUser( 2201 targetInfo, 2202 mProfiles.getPersonalHandle(), 2203 options.toBundle()); 2204 // Can't finish right away because the shared element transition may not 2205 // be ready to start. 2206 mFinishWhenStopped = true; 2207 } 2208 }, 2209 mShareResultSender, 2210 this::finishWithStatus, 2211 mClipboardManager, 2212 mFeatureFlags); 2213 } 2214 2215 private Supplier<ActionRow.Action> createModifyShareActionFactory() { 2216 return () -> ChooserActionFactory.createCustomAction( 2217 ChooserActivity.this, 2218 mRequest.getModifyShareAction(), 2219 () -> getEventLog().logActionSelected(EventLog.SELECTION_TYPE_MODIFY_SHARE), 2220 mShareResultSender, 2221 this::finishWithStatus); 2222 } 2223 2224 private void finishWithStatus(@Nullable Integer status) { 2225 if (status != null) { 2226 setResult(status); 2227 } 2228 Log.d(TAG, "finishWithStatus: result=" + status); 2229 finish(); 2230 } 2231 2232 /* 2233 * Need to dynamically adjust how many icons can fit per row before we add them, 2234 * which also means setting the correct offset to initially show the content 2235 * preview area + 2 rows of targets 2236 */ 2237 private void handleLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, 2238 int oldTop, int oldRight, int oldBottom) { 2239 if (mChooserMultiProfilePagerAdapter == null || !isProfilePagerAdapterAttached()) { 2240 return; 2241 } 2242 RecyclerView recyclerView = mChooserMultiProfilePagerAdapter.getActiveAdapterView(); 2243 ChooserGridAdapter gridAdapter = mChooserMultiProfilePagerAdapter.getCurrentRootAdapter(); 2244 // Skip height calculation if recycler view was scrolled to prevent it inaccurately 2245 // calculating the height, as the logic below does not account for the scrolled offset. 2246 if (gridAdapter == null || recyclerView == null 2247 || recyclerView.computeVerticalScrollOffset() != 0) { 2248 return; 2249 } 2250 2251 final int availableWidth = right - left - v.getPaddingLeft() - v.getPaddingRight(); 2252 boolean isLayoutUpdated = 2253 gridAdapter.calculateChooserTargetWidth(availableWidth) 2254 || recyclerView.getAdapter() == null 2255 || availableWidth != mCurrAvailableWidth; 2256 2257 boolean insetsChanged = !Objects.equals(mLastAppliedInsets, mSystemWindowInsets); 2258 2259 if (isLayoutUpdated 2260 || insetsChanged 2261 || mLastNumberOfChildren != recyclerView.getChildCount()) { 2262 mCurrAvailableWidth = availableWidth; 2263 if (isLayoutUpdated) { 2264 // It is very important we call setAdapter from here. Otherwise in some cases 2265 // the resolver list doesn't get populated, such as b/150922090, b/150918223 2266 // and b/150936654 2267 recyclerView.setAdapter(gridAdapter); 2268 ((GridLayoutManager) recyclerView.getLayoutManager()).setSpanCount( 2269 mMaxTargetsPerRow); 2270 2271 updateTabPadding(); 2272 } 2273 2274 int currentProfile = mChooserMultiProfilePagerAdapter.getActiveProfile(); 2275 int initialProfile = findSelectedProfile(); 2276 if (currentProfile != initialProfile) { 2277 return; 2278 } 2279 2280 if (mLastNumberOfChildren == recyclerView.getChildCount() && !insetsChanged) { 2281 return; 2282 } 2283 2284 getMainThreadHandler().post(() -> { 2285 if (mResolverDrawerLayout == null || gridAdapter == null) { 2286 return; 2287 } 2288 int offset = calculateDrawerOffset(top, bottom, recyclerView, gridAdapter); 2289 mResolverDrawerLayout.setCollapsibleHeightReserved(offset); 2290 mEnterTransitionAnimationDelegate.markOffsetCalculated(); 2291 mLastAppliedInsets = mSystemWindowInsets; 2292 }); 2293 } 2294 } 2295 2296 private int calculateDrawerOffset( 2297 int top, int bottom, RecyclerView recyclerView, ChooserGridAdapter gridAdapter) { 2298 2299 int offset = mSystemWindowInsets != null ? mSystemWindowInsets.bottom : 0; 2300 int rowsToShow = gridAdapter.getServiceTargetRowCount() 2301 + gridAdapter.getCallerAndRankedTargetRowCount(); 2302 2303 // then this is most likely not a SEND_* action, so check 2304 // the app target count 2305 if (rowsToShow == 0) { 2306 rowsToShow = gridAdapter.getRowCount(); 2307 } 2308 2309 // still zero? then use a default height and leave, which 2310 // can happen when there are no targets to show 2311 if (rowsToShow == 0 && !shouldShowStickyContentPreview()) { 2312 offset += getResources().getDimensionPixelSize( 2313 R.dimen.chooser_max_collapsed_height); 2314 return offset; 2315 } 2316 2317 View stickyContentPreview = findViewById(com.android.internal.R.id.content_preview_container); 2318 if (shouldShowStickyContentPreview() && isStickyContentPreviewShowing()) { 2319 offset += stickyContentPreview.getHeight(); 2320 } 2321 2322 if (mProfiles.getWorkProfilePresent()) { 2323 offset += findViewById(com.android.internal.R.id.tabs).getHeight(); 2324 } 2325 2326 if (recyclerView.getVisibility() == View.VISIBLE) { 2327 rowsToShow = Math.min(4, rowsToShow); 2328 boolean shouldShowExtraRow = shouldShowExtraRow(rowsToShow); 2329 mLastNumberOfChildren = recyclerView.getChildCount(); 2330 for (int i = 0, childCount = recyclerView.getChildCount(); 2331 i < childCount && rowsToShow > 0; i++) { 2332 View child = recyclerView.getChildAt(i); 2333 if (((GridLayoutManager.LayoutParams) 2334 child.getLayoutParams()).getSpanIndex() != 0) { 2335 continue; 2336 } 2337 int height = child.getHeight(); 2338 offset += height; 2339 if (shouldShowExtraRow) { 2340 offset += height; 2341 } 2342 rowsToShow--; 2343 } 2344 } else { 2345 ViewGroup currentEmptyStateView = 2346 mChooserMultiProfilePagerAdapter.getActiveEmptyStateView(); 2347 if (currentEmptyStateView.getVisibility() == View.VISIBLE) { 2348 offset += currentEmptyStateView.getHeight(); 2349 } 2350 } 2351 2352 return Math.min(offset, bottom - top); 2353 } 2354 2355 private boolean isProfilePagerAdapterAttached() { 2356 return mChooserMultiProfilePagerAdapter == mViewPager.getAdapter(); 2357 } 2358 2359 /** 2360 * If we have a tabbed view and are showing 1 row in the current profile and an empty 2361 * state screen in another profile, to prevent cropping of the empty state screen we show 2362 * a second row in the current profile. 2363 */ 2364 private boolean shouldShowExtraRow(int rowsToShow) { 2365 return rowsToShow == 1 2366 && mChooserMultiProfilePagerAdapter 2367 .shouldShowEmptyStateScreenInAnyInactiveAdapter(); 2368 } 2369 2370 protected void onListRebuilt(ResolverListAdapter listAdapter, boolean rebuildComplete) { 2371 Log.d(TAG, "onListRebuilt(listAdapter.userHandle=" + listAdapter.getUserHandle() + ", " 2372 + "rebuildComplete=" + rebuildComplete + ")"); 2373 setupScrollListener(); 2374 maybeSetupGlobalLayoutListener(); 2375 2376 ChooserListAdapter chooserListAdapter = (ChooserListAdapter) listAdapter; 2377 UserHandle listProfileUserHandle = chooserListAdapter.getUserHandle(); 2378 if (listProfileUserHandle.equals(mChooserMultiProfilePagerAdapter.getCurrentUserHandle())) { 2379 mChooserMultiProfilePagerAdapter.getActiveAdapterView() 2380 .setAdapter(mChooserMultiProfilePagerAdapter.getCurrentRootAdapter()); 2381 mChooserMultiProfilePagerAdapter 2382 .setupListAdapter(mChooserMultiProfilePagerAdapter.getCurrentPage()); 2383 } 2384 2385 //TODO: move this block inside ChooserListAdapter (should be called when 2386 // ResolverListAdapter#mPostListReadyRunnable is executed. 2387 if (chooserListAdapter.getDisplayResolveInfoCount() == 0) { 2388 Log.d(TAG, "getDisplayResolveInfoCount() == 0"); 2389 if (rebuildComplete && mChooserServiceFeatureFlags.chooserPayloadToggling()) { 2390 onAppTargetsLoaded(listAdapter); 2391 } 2392 chooserListAdapter.notifyDataSetChanged(); 2393 } else { 2394 if (mChooserServiceFeatureFlags.chooserPayloadToggling()) { 2395 chooserListAdapter.updateAlphabeticalList( 2396 () -> onAppTargetsLoaded(listAdapter)); 2397 } else { 2398 chooserListAdapter.updateAlphabeticalList(); 2399 } 2400 } 2401 2402 if (rebuildComplete) { 2403 long duration = Tracer.INSTANCE.endAppTargetLoadingSection(listProfileUserHandle); 2404 if (duration >= 0) { 2405 Log.d(TAG, "app target loading time " + duration + " ms"); 2406 } 2407 addCallerChooserTargets(); 2408 getEventLog().logSharesheetAppLoadComplete(); 2409 maybeQueryAdditionalPostProcessingTargets( 2410 listProfileUserHandle, 2411 chooserListAdapter.getDisplayResolveInfos()); 2412 mLatencyTracker.onActionEnd(ACTION_LOAD_SHARE_SHEET); 2413 } 2414 } 2415 2416 private void maybeQueryAdditionalPostProcessingTargets( 2417 UserHandle userHandle, 2418 DisplayResolveInfo[] displayResolveInfos) { 2419 ProfileRecord record = getProfileRecord(userHandle); 2420 if (record == null || record.shortcutLoader == null) { 2421 return; 2422 } 2423 record.loadingStartTime = SystemClock.elapsedRealtime(); 2424 record.shortcutLoader.updateAppTargets(displayResolveInfos); 2425 } 2426 2427 @MainThread 2428 private void onShortcutsLoaded(UserHandle userHandle, ShortcutLoader.Result result) { 2429 if (DEBUG) { 2430 Log.d(TAG, "onShortcutsLoaded for user: " + userHandle); 2431 } 2432 mDirectShareShortcutInfoCache.putAll(result.getDirectShareShortcutInfoCache()); 2433 mDirectShareAppTargetCache.putAll(result.getDirectShareAppTargetCache()); 2434 ChooserListAdapter adapter = 2435 mChooserMultiProfilePagerAdapter.getListAdapterForUserHandle(userHandle); 2436 if (adapter != null) { 2437 for (ShortcutLoader.ShortcutResultInfo resultInfo : result.getShortcutsByApp()) { 2438 adapter.addServiceResults( 2439 resultInfo.getAppTarget(), 2440 resultInfo.getShortcuts(), 2441 result.isFromAppPredictor() 2442 ? TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE 2443 : TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER, 2444 mDirectShareShortcutInfoCache, 2445 mDirectShareAppTargetCache); 2446 } 2447 adapter.completeServiceTargetLoading(); 2448 } 2449 2450 if (mChooserMultiProfilePagerAdapter.getActiveListAdapter() == adapter) { 2451 long duration = Tracer.INSTANCE.endLaunchToShortcutTrace(); 2452 if (duration >= 0) { 2453 Log.d(TAG, "stat to first shortcut time: " + duration + " ms"); 2454 } 2455 } 2456 logDirectShareTargetReceived(userHandle); 2457 sendVoiceChoicesIfNeeded(); 2458 getEventLog().logSharesheetDirectLoadComplete(); 2459 } 2460 2461 private void setupScrollListener() { 2462 if (mResolverDrawerLayout == null) { 2463 return; 2464 } 2465 int elevatedViewResId = mProfiles.getWorkProfilePresent() 2466 ? com.android.internal.R.id.tabs : com.android.internal.R.id.chooser_header; 2467 final View elevatedView = mResolverDrawerLayout.findViewById(elevatedViewResId); 2468 final float defaultElevation = elevatedView.getElevation(); 2469 final float chooserHeaderScrollElevation = 2470 getResources().getDimensionPixelSize(R.dimen.chooser_header_scroll_elevation); 2471 mChooserMultiProfilePagerAdapter.getActiveAdapterView().addOnScrollListener( 2472 new RecyclerView.OnScrollListener() { 2473 @Override 2474 public void onScrollStateChanged(RecyclerView view, int scrollState) { 2475 if (scrollState == RecyclerView.SCROLL_STATE_IDLE) { 2476 if (mScrollStatus == SCROLL_STATUS_SCROLLING_VERTICAL) { 2477 mScrollStatus = SCROLL_STATUS_IDLE; 2478 setHorizontalScrollingEnabled(true); 2479 } 2480 } else if (scrollState == RecyclerView.SCROLL_STATE_DRAGGING) { 2481 if (mScrollStatus == SCROLL_STATUS_IDLE) { 2482 mScrollStatus = SCROLL_STATUS_SCROLLING_VERTICAL; 2483 setHorizontalScrollingEnabled(false); 2484 } 2485 } 2486 } 2487 2488 @Override 2489 public void onScrolled(RecyclerView view, int dx, int dy) { 2490 if (view.getChildCount() > 0) { 2491 View child = view.getLayoutManager().findViewByPosition(0); 2492 if (child == null || child.getTop() < 0) { 2493 elevatedView.setElevation(chooserHeaderScrollElevation); 2494 return; 2495 } 2496 } 2497 2498 elevatedView.setElevation(defaultElevation); 2499 } 2500 }); 2501 } 2502 2503 private void maybeSetupGlobalLayoutListener() { 2504 if (mProfiles.getWorkProfilePresent()) { 2505 return; 2506 } 2507 final View recyclerView = mChooserMultiProfilePagerAdapter.getActiveAdapterView(); 2508 recyclerView.getViewTreeObserver() 2509 .addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 2510 @Override 2511 public void onGlobalLayout() { 2512 // Fixes an issue were the accessibility border disappears on list creation. 2513 recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this); 2514 final TextView titleView = findViewById(com.android.internal.R.id.title); 2515 if (titleView != null) { 2516 titleView.setFocusable(true); 2517 titleView.setFocusableInTouchMode(true); 2518 titleView.requestFocus(); 2519 titleView.requestAccessibilityFocus(); 2520 } 2521 } 2522 }); 2523 } 2524 2525 /** 2526 * The sticky content preview is shown only when we have a tabbed view. It's shown above 2527 * the tabs so it is not part of the scrollable list. If we are not in tabbed view, 2528 * we instead show the content preview as a regular list item. 2529 */ 2530 private boolean shouldShowStickyContentPreview() { 2531 return shouldShowStickyContentPreviewNoOrientationCheck(); 2532 } 2533 2534 private boolean shouldShowStickyContentPreviewNoOrientationCheck() { 2535 if (!shouldShowContentPreview()) { 2536 return false; 2537 } 2538 ResolverListAdapter adapter = mChooserMultiProfilePagerAdapter.getListAdapterForUserHandle( 2539 UserHandle.of(UserHandle.myUserId())); 2540 boolean isEmpty = adapter == null || adapter.getCount() == 0; 2541 return !isEmpty || shouldShowContentPreviewWhenEmpty(); 2542 } 2543 2544 /** 2545 * This method could be used to override the default behavior when we hide the preview area 2546 * when the current tab doesn't have any items. 2547 * 2548 * @return true if we want to show the content preview area even if the tab for the current 2549 * user is empty 2550 */ 2551 protected boolean shouldShowContentPreviewWhenEmpty() { 2552 return false; 2553 } 2554 2555 /** 2556 * @return true if we want to show the content preview area 2557 */ 2558 protected boolean shouldShowContentPreview() { 2559 return mRequest.isSendActionTarget(); 2560 } 2561 2562 private void updateStickyContentPreview() { 2563 if (shouldShowStickyContentPreviewNoOrientationCheck()) { 2564 // The sticky content preview is only shown when we show the work and personal tabs. 2565 // We don't show it in landscape as otherwise there is no room for scrolling. 2566 // If the sticky content preview will be shown at some point with orientation change, 2567 // then always preload it to avoid subsequent resizing of the share sheet. 2568 ViewGroup contentPreviewContainer = 2569 findViewById(com.android.internal.R.id.content_preview_container); 2570 if (contentPreviewContainer.getChildCount() == 0) { 2571 ViewGroup contentPreviewView = createContentPreviewView(contentPreviewContainer); 2572 contentPreviewContainer.addView(contentPreviewView); 2573 } 2574 } 2575 if (shouldShowStickyContentPreview()) { 2576 showStickyContentPreview(); 2577 } else { 2578 hideStickyContentPreview(); 2579 } 2580 } 2581 2582 private void showStickyContentPreview() { 2583 if (isStickyContentPreviewShowing()) { 2584 return; 2585 } 2586 ViewGroup contentPreviewContainer = findViewById(com.android.internal.R.id.content_preview_container); 2587 contentPreviewContainer.setVisibility(View.VISIBLE); 2588 } 2589 2590 private boolean isStickyContentPreviewShowing() { 2591 ViewGroup contentPreviewContainer = findViewById(com.android.internal.R.id.content_preview_container); 2592 return contentPreviewContainer.getVisibility() == View.VISIBLE; 2593 } 2594 2595 private void hideStickyContentPreview() { 2596 if (!isStickyContentPreviewShowing()) { 2597 return; 2598 } 2599 ViewGroup contentPreviewContainer = findViewById(com.android.internal.R.id.content_preview_container); 2600 contentPreviewContainer.setVisibility(View.GONE); 2601 } 2602 2603 protected String getMetricsCategory() { 2604 return METRICS_CATEGORY_CHOOSER; 2605 } 2606 2607 protected void onProfileTabSelected(int currentPage) { 2608 setupViewVisibilities(); 2609 maybeLogProfileChange(); 2610 if (mProfiles.getWorkProfilePresent()) { 2611 // The device policy logger is only concerned with sessions that include a work profile. 2612 DevicePolicyEventLogger 2613 .createEvent(DevicePolicyEnums.RESOLVER_SWITCH_TABS) 2614 .setInt(currentPage) 2615 .setStrings(getMetricsCategory()) 2616 .write(); 2617 } 2618 2619 // This fixes an edge case where after performing a variety of gestures, vertical scrolling 2620 // ends up disabled. That's because at some point the old tab's vertical scrolling is 2621 // disabled and the new tab's is enabled. For context, see b/159997845 2622 setVerticalScrollEnabled(true); 2623 if (mResolverDrawerLayout != null) { 2624 mResolverDrawerLayout.scrollNestedScrollableChildBackToTop(); 2625 } 2626 } 2627 2628 protected WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { 2629 mSystemWindowInsets = insets.getInsets(WindowInsets.Type.systemBars()); 2630 if (mFeatureFlags.fixEmptyStatePaddingBug() || mProfiles.getWorkProfilePresent()) { 2631 mChooserMultiProfilePagerAdapter 2632 .setEmptyStateBottomOffset(mSystemWindowInsets.bottom); 2633 } 2634 2635 mResolverDrawerLayout.setPadding(mSystemWindowInsets.left, mSystemWindowInsets.top, 2636 mSystemWindowInsets.right, 0); 2637 2638 // Need extra padding so the list can fully scroll up 2639 // To accommodate for window insets 2640 applyFooterView(mSystemWindowInsets.bottom); 2641 2642 if (mResolverDrawerLayout != null) { 2643 mResolverDrawerLayout.requestLayout(); 2644 } 2645 return WindowInsets.CONSUMED; 2646 } 2647 2648 private void setHorizontalScrollingEnabled(boolean enabled) { 2649 mViewPager.setSwipingEnabled(enabled); 2650 } 2651 2652 private void setVerticalScrollEnabled(boolean enabled) { 2653 ChooserGridLayoutManager layoutManager = 2654 (ChooserGridLayoutManager) mChooserMultiProfilePagerAdapter.getActiveAdapterView() 2655 .getLayoutManager(); 2656 layoutManager.setVerticalScrollEnabled(enabled); 2657 } 2658 2659 void onHorizontalSwipeStateChanged(int state) { 2660 if (state == ViewPager.SCROLL_STATE_DRAGGING) { 2661 if (mScrollStatus == SCROLL_STATUS_IDLE) { 2662 mScrollStatus = SCROLL_STATUS_SCROLLING_HORIZONTAL; 2663 setVerticalScrollEnabled(false); 2664 } 2665 } else if (state == ViewPager.SCROLL_STATE_IDLE) { 2666 if (mScrollStatus == SCROLL_STATUS_SCROLLING_HORIZONTAL) { 2667 mScrollStatus = SCROLL_STATUS_IDLE; 2668 setVerticalScrollEnabled(true); 2669 } 2670 } 2671 } 2672 2673 protected void maybeLogProfileChange() { 2674 getEventLog().logSharesheetProfileChanged(); 2675 } 2676 2677 private static class ProfileRecord { 2678 /** The {@link AppPredictor} for this profile, if any. */ 2679 @Nullable 2680 public final AppPredictor appPredictor; 2681 /** 2682 * null if we should not load shortcuts. 2683 */ 2684 @Nullable 2685 public final ShortcutLoader shortcutLoader; 2686 public long loadingStartTime; 2687 2688 private ProfileRecord( 2689 @Nullable AppPredictor appPredictor, 2690 @Nullable ShortcutLoader shortcutLoader) { 2691 this.appPredictor = appPredictor; 2692 this.shortcutLoader = shortcutLoader; 2693 } 2694 2695 public void destroy() { 2696 if (appPredictor != null) { 2697 appPredictor.destroy(); 2698 } 2699 } 2700 } 2701 } 2702