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