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