• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.intentresolver;
18 
19 import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_PERSONAL;
20 import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_WORK;
21 import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_PERSONAL;
22 import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_WORK;
23 import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE;
24 import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL;
25 import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK;
26 
27 import static com.android.internal.util.LatencyTracker.ACTION_LOAD_SHARE_SHEET;
28 
29 import android.annotation.IntDef;
30 import android.annotation.Nullable;
31 import android.app.Activity;
32 import android.app.ActivityManager;
33 import android.app.ActivityOptions;
34 import android.app.prediction.AppPredictor;
35 import android.app.prediction.AppTarget;
36 import android.app.prediction.AppTargetEvent;
37 import android.app.prediction.AppTargetId;
38 import android.content.ComponentName;
39 import android.content.ContentResolver;
40 import android.content.Context;
41 import android.content.Intent;
42 import android.content.IntentFilter;
43 import android.content.IntentSender;
44 import android.content.SharedPreferences;
45 import android.content.pm.ActivityInfo;
46 import android.content.pm.PackageManager;
47 import android.content.pm.ResolveInfo;
48 import android.content.pm.ShortcutInfo;
49 import android.content.res.Configuration;
50 import android.database.Cursor;
51 import android.graphics.Insets;
52 import android.net.Uri;
53 import android.os.Bundle;
54 import android.os.Environment;
55 import android.os.SystemClock;
56 import android.os.UserHandle;
57 import android.os.UserManager;
58 import android.os.storage.StorageManager;
59 import android.service.chooser.ChooserTarget;
60 import android.util.Log;
61 import android.util.Slog;
62 import android.util.SparseArray;
63 import android.view.View;
64 import android.view.ViewGroup;
65 import android.view.ViewGroup.LayoutParams;
66 import android.view.ViewTreeObserver;
67 import android.view.WindowInsets;
68 import android.widget.TextView;
69 
70 import androidx.annotation.MainThread;
71 import androidx.lifecycle.ViewModelProvider;
72 import androidx.recyclerview.widget.GridLayoutManager;
73 import androidx.recyclerview.widget.RecyclerView;
74 import androidx.viewpager.widget.ViewPager;
75 
76 import com.android.intentresolver.AbstractMultiProfilePagerAdapter.EmptyState;
77 import com.android.intentresolver.AbstractMultiProfilePagerAdapter.EmptyStateProvider;
78 import com.android.intentresolver.NoCrossProfileEmptyStateProvider.DevicePolicyBlockerEmptyState;
79 import com.android.intentresolver.chooser.DisplayResolveInfo;
80 import com.android.intentresolver.chooser.MultiDisplayResolveInfo;
81 import com.android.intentresolver.chooser.TargetInfo;
82 import com.android.intentresolver.contentpreview.BasePreviewViewModel;
83 import com.android.intentresolver.contentpreview.ChooserContentPreviewUi;
84 import com.android.intentresolver.contentpreview.HeadlineGeneratorImpl;
85 import com.android.intentresolver.contentpreview.PreviewViewModel;
86 import com.android.intentresolver.flags.FeatureFlagRepository;
87 import com.android.intentresolver.flags.FeatureFlagRepositoryFactory;
88 import com.android.intentresolver.grid.ChooserGridAdapter;
89 import com.android.intentresolver.icons.DefaultTargetDataLoader;
90 import com.android.intentresolver.icons.TargetDataLoader;
91 import com.android.intentresolver.logging.EventLog;
92 import com.android.intentresolver.measurements.Tracer;
93 import com.android.intentresolver.model.AbstractResolverComparator;
94 import com.android.intentresolver.model.AppPredictionServiceResolverComparator;
95 import com.android.intentresolver.model.ResolverRankerServiceResolverComparator;
96 import com.android.intentresolver.shortcuts.AppPredictorFactory;
97 import com.android.intentresolver.shortcuts.ShortcutLoader;
98 import com.android.intentresolver.widget.ImagePreviewView;
99 import com.android.internal.annotations.VisibleForTesting;
100 import com.android.internal.content.PackageMonitor;
101 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
102 
103 import java.io.File;
104 import java.lang.annotation.Retention;
105 import java.lang.annotation.RetentionPolicy;
106 import java.text.Collator;
107 import java.util.ArrayList;
108 import java.util.Collections;
109 import java.util.Comparator;
110 import java.util.HashMap;
111 import java.util.List;
112 import java.util.Map;
113 import java.util.Objects;
114 import java.util.concurrent.ExecutorService;
115 import java.util.concurrent.Executors;
116 import java.util.function.Consumer;
117 
118 /**
119  * The Chooser Activity handles intent resolution specifically for sharing intents -
120  * for example, as generated by {@see android.content.Intent#createChooser(Intent, CharSequence)}.
121  *
122  */
123 public class ChooserActivity extends ResolverActivity implements
124         ResolverListAdapter.ResolverListCommunicator {
125     private static final String TAG = "ChooserActivity";
126 
127     /**
128      * Boolean extra to change the following behavior: Normally, ChooserActivity finishes itself
129      * in onStop when launched in a new task. If this extra is set to true, we do not finish
130      * ourselves when onStop gets called.
131      */
132     public static final String EXTRA_PRIVATE_RETAIN_IN_ON_STOP
133             = "com.android.internal.app.ChooserActivity.EXTRA_PRIVATE_RETAIN_IN_ON_STOP";
134 
135     /**
136      * Transition name for the first image preview.
137      * To be used for shared element transition into this activity.
138      * @hide
139      */
140     public static final String FIRST_IMAGE_PREVIEW_TRANSITION_NAME = "screenshot_preview_image";
141 
142     private static final boolean DEBUG = true;
143 
144     public static final String LAUNCH_LOCATION_DIRECT_SHARE = "direct_share";
145     private static final String SHORTCUT_TARGET = "shortcut_target";
146 
147     // TODO: these data structures are for one-time use in shuttling data from where they're
148     // populated in `ShortcutToChooserTargetConverter` to where they're consumed in
149     // `ShortcutSelectionLogic` which packs the appropriate elements into the final `TargetInfo`.
150     // That flow should be refactored so that `ChooserActivity` isn't responsible for holding their
151     // intermediate data, and then these members can be removed.
152     private final Map<ChooserTarget, AppTarget> mDirectShareAppTargetCache = new HashMap<>();
153     private final Map<ChooserTarget, ShortcutInfo> mDirectShareShortcutInfoCache = new HashMap<>();
154 
155     public static final int TARGET_TYPE_DEFAULT = 0;
156     public static final int TARGET_TYPE_CHOOSER_TARGET = 1;
157     public static final int TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER = 2;
158     public static final int TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE = 3;
159 
160     private static final int SCROLL_STATUS_IDLE = 0;
161     private static final int SCROLL_STATUS_SCROLLING_VERTICAL = 1;
162     private static final int SCROLL_STATUS_SCROLLING_HORIZONTAL = 2;
163 
164     @IntDef(flag = false, prefix = { "TARGET_TYPE_" }, value = {
165             TARGET_TYPE_DEFAULT,
166             TARGET_TYPE_CHOOSER_TARGET,
167             TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER,
168             TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE
169     })
170     @Retention(RetentionPolicy.SOURCE)
171     public @interface ShareTargetType {}
172 
173     private ChooserIntegratedDeviceComponents mIntegratedDeviceComponents;
174 
175     /* TODO: this is `nullable` because we have to defer the assignment til onCreate(). We make the
176      * only assignment there, and expect it to be ready by the time we ever use it --
177      * someday if we move all the usage to a component with a narrower lifecycle (something that
178      * matches our Activity's create/destroy lifecycle, not its Java object lifecycle) then we
179      * should be able to make this assignment as "final."
180      */
181     @Nullable
182     private ChooserRequestParameters mChooserRequest;
183 
184     private ChooserRefinementManager mRefinementManager;
185 
186     private FeatureFlagRepository mFeatureFlagRepository;
187     private ChooserContentPreviewUi mChooserContentPreviewUi;
188 
189     private boolean mShouldDisplayLandscape;
190     // statsd logger wrapper
191     protected EventLog mEventLog;
192 
193     private long mChooserShownTime;
194     protected boolean mIsSuccessfullySelected;
195 
196     private int mCurrAvailableWidth = 0;
197     private Insets mLastAppliedInsets = null;
198     private int mLastNumberOfChildren = -1;
199     private int mMaxTargetsPerRow = 1;
200 
201     private static final int MAX_LOG_RANK_POSITION = 12;
202 
203     // TODO: are these used anywhere? They should probably be migrated to ChooserRequestParameters.
204     private static final int MAX_EXTRA_INITIAL_INTENTS = 2;
205     private static final int MAX_EXTRA_CHOOSER_TARGETS = 2;
206 
207     private SharedPreferences mPinnedSharedPrefs;
208     private static final String PINNED_SHARED_PREFS_NAME = "chooser_pin_settings";
209 
210     private final ExecutorService mBackgroundThreadPoolExecutor = Executors.newFixedThreadPool(5);
211 
212     private int mScrollStatus = SCROLL_STATUS_IDLE;
213 
214     @VisibleForTesting
215     protected ChooserMultiProfilePagerAdapter mChooserMultiProfilePagerAdapter;
216     private final EnterTransitionAnimationDelegate mEnterTransitionAnimationDelegate =
217             new EnterTransitionAnimationDelegate(this, () -> mResolverDrawerLayout);
218 
219     private View mContentView = null;
220 
221     private final SparseArray<ProfileRecord> mProfileRecords = new SparseArray<>();
222 
223     private boolean mExcludeSharedText = false;
224     /**
225      * When we intend to finish the activity with a shared element transition, we can't immediately
226      * finish() when the transition is invoked, as the receiving end may not be able to start the
227      * animation and the UI breaks if this takes too long. Instead we defer finishing until onStop
228      * in order to wait for the transition to begin.
229      */
230     private boolean mFinishWhenStopped = false;
231 
ChooserActivity()232     public ChooserActivity() {}
233 
234     @Override
onCreate(Bundle savedInstanceState)235     protected void onCreate(Bundle savedInstanceState) {
236         Tracer.INSTANCE.markLaunched();
237         final long intentReceivedTime = System.currentTimeMillis();
238         mLatencyTracker.onActionStart(ACTION_LOAD_SHARE_SHEET);
239 
240         getEventLog().logSharesheetTriggered();
241 
242         mFeatureFlagRepository = createFeatureFlagRepository();
243         mIntegratedDeviceComponents = getIntegratedDeviceComponents();
244 
245         try {
246             mChooserRequest = new ChooserRequestParameters(
247                     getIntent(),
248                     getReferrerPackageName(),
249                     getReferrer(),
250                     mFeatureFlagRepository);
251         } catch (IllegalArgumentException e) {
252             Log.e(TAG, "Caller provided invalid Chooser request parameters", e);
253             finish();
254             super_onCreate(null);
255             return;
256         }
257 
258         mRefinementManager = new ViewModelProvider(this).get(ChooserRefinementManager.class);
259 
260         mRefinementManager.getRefinementCompletion().observe(this, completion -> {
261             if (completion.consume()) {
262                 TargetInfo targetInfo = completion.getTargetInfo();
263                 // targetInfo is non-null if the refinement process was successful.
264                 if (targetInfo != null) {
265                     maybeRemoveSharedText(targetInfo);
266 
267                     // We already block suspended targets from going to refinement, and we probably
268                     // can't recover a Chooser session if that's the reason the refined target fails
269                     // to launch now. Fire-and-forget the refined launch; ignore the return value
270                     // and just make sure the Sharesheet session gets cleaned up regardless.
271                     ChooserActivity.super.onTargetSelected(targetInfo, false);
272                 }
273 
274                 finish();
275             }
276         });
277 
278         BasePreviewViewModel previewViewModel =
279                 new ViewModelProvider(this, createPreviewViewModelFactory())
280                         .get(BasePreviewViewModel.class);
281         mChooserContentPreviewUi = new ChooserContentPreviewUi(
282                 getLifecycle(),
283                 previewViewModel.createOrReuseProvider(mChooserRequest),
284                 mChooserRequest.getTargetIntent(),
285                 previewViewModel.createOrReuseImageLoader(),
286                 createChooserActionFactory(),
287                 mEnterTransitionAnimationDelegate,
288                 new HeadlineGeneratorImpl(this));
289 
290         mPinnedSharedPrefs = getPinnedSharedPrefs(this);
291 
292         mMaxTargetsPerRow = getResources().getInteger(R.integer.config_chooser_max_targets_per_row);
293         mShouldDisplayLandscape =
294                 shouldDisplayLandscape(getResources().getConfiguration().orientation);
295         setRetainInOnStop(mChooserRequest.shouldRetainInOnStop());
296 
297         createProfileRecords(
298                 new AppPredictorFactory(
299                         getApplicationContext(),
300                         mChooserRequest.getSharedText(),
301                         mChooserRequest.getTargetIntentFilter()),
302                 mChooserRequest.getTargetIntentFilter());
303 
304         super.onCreate(
305                 savedInstanceState,
306                 mChooserRequest.getTargetIntent(),
307                 mChooserRequest.getAdditionalTargets(),
308                 mChooserRequest.getTitle(),
309                 mChooserRequest.getDefaultTitleResource(),
310                 mChooserRequest.getInitialIntents(),
311                 /* resolutionList= */ null,
312                 /* supportsAlwaysUseOption= */ false,
313                 new DefaultTargetDataLoader(this, getLifecycle(), false),
314                 /* safeForwardingMode= */ true);
315 
316         mChooserShownTime = System.currentTimeMillis();
317         final long systemCost = mChooserShownTime - intentReceivedTime;
318         getEventLog().logChooserActivityShown(
319                 isWorkProfile(), mChooserRequest.getTargetType(), systemCost);
320 
321         if (mResolverDrawerLayout != null) {
322             mResolverDrawerLayout.addOnLayoutChangeListener(this::handleLayoutChange);
323 
324             mResolverDrawerLayout.setOnCollapsedChangedListener(
325                     isCollapsed -> {
326                         mChooserMultiProfilePagerAdapter.setIsCollapsed(isCollapsed);
327                         getEventLog().logSharesheetExpansionChanged(isCollapsed);
328                     });
329         }
330 
331         if (DEBUG) {
332             Log.d(TAG, "System Time Cost is " + systemCost);
333         }
334 
335         getEventLog().logShareStarted(
336                 getReferrerPackageName(),
337                 mChooserRequest.getTargetType(),
338                 mChooserRequest.getCallerChooserTargets().size(),
339                 (mChooserRequest.getInitialIntents() == null)
340                         ? 0 : mChooserRequest.getInitialIntents().length,
341                 isWorkProfile(),
342                 mChooserContentPreviewUi.getPreferredContentPreview(),
343                 mChooserRequest.getTargetAction(),
344                 mChooserRequest.getChooserActions().size(),
345                 mChooserRequest.getModifyShareAction() != null
346         );
347 
348         mEnterTransitionAnimationDelegate.postponeTransition();
349     }
350 
351     @VisibleForTesting
getIntegratedDeviceComponents()352     protected ChooserIntegratedDeviceComponents getIntegratedDeviceComponents() {
353         return ChooserIntegratedDeviceComponents.get(this, new SecureSettings());
354     }
355 
356     @Override
appliedThemeResId()357     protected int appliedThemeResId() {
358         return R.style.Theme_DeviceDefault_Chooser;
359     }
360 
createFeatureFlagRepository()361     protected FeatureFlagRepository createFeatureFlagRepository() {
362         return new FeatureFlagRepositoryFactory().create(getApplicationContext());
363     }
364 
createProfileRecords( AppPredictorFactory factory, IntentFilter targetIntentFilter)365     private void createProfileRecords(
366             AppPredictorFactory factory, IntentFilter targetIntentFilter) {
367         UserHandle mainUserHandle = getPersonalProfileUserHandle();
368         ProfileRecord record = createProfileRecord(mainUserHandle, targetIntentFilter, factory);
369         if (record.shortcutLoader == null) {
370             Tracer.INSTANCE.endLaunchToShortcutTrace();
371         }
372 
373         UserHandle workUserHandle = getWorkProfileUserHandle();
374         if (workUserHandle != null) {
375             createProfileRecord(workUserHandle, targetIntentFilter, factory);
376         }
377     }
378 
createProfileRecord( UserHandle userHandle, IntentFilter targetIntentFilter, AppPredictorFactory factory)379     private ProfileRecord createProfileRecord(
380             UserHandle userHandle, IntentFilter targetIntentFilter, AppPredictorFactory factory) {
381         AppPredictor appPredictor = factory.create(userHandle);
382         ShortcutLoader shortcutLoader = ActivityManager.isLowRamDeviceStatic()
383                     ? null
384                     : createShortcutLoader(
385                             getApplicationContext(),
386                             appPredictor,
387                             userHandle,
388                             targetIntentFilter,
389                             shortcutsResult -> onShortcutsLoaded(userHandle, shortcutsResult));
390         ProfileRecord record = new ProfileRecord(appPredictor, shortcutLoader);
391         mProfileRecords.put(userHandle.getIdentifier(), record);
392         return record;
393     }
394 
395     @Nullable
getProfileRecord(UserHandle userHandle)396     private ProfileRecord getProfileRecord(UserHandle userHandle) {
397         return mProfileRecords.get(userHandle.getIdentifier(), null);
398     }
399 
400     @VisibleForTesting
createShortcutLoader( Context context, AppPredictor appPredictor, UserHandle userHandle, IntentFilter targetIntentFilter, Consumer<ShortcutLoader.Result> callback)401     protected ShortcutLoader createShortcutLoader(
402             Context context,
403             AppPredictor appPredictor,
404             UserHandle userHandle,
405             IntentFilter targetIntentFilter,
406             Consumer<ShortcutLoader.Result> callback) {
407         return new ShortcutLoader(
408                 context,
409                 getLifecycle(),
410                 appPredictor,
411                 userHandle,
412                 targetIntentFilter,
413                 callback);
414     }
415 
getPinnedSharedPrefs(Context context)416     static SharedPreferences getPinnedSharedPrefs(Context context) {
417         // The code below is because in the android:ui process, no one can hear you scream.
418         // The package info in the context isn't initialized in the way it is for normal apps,
419         // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we
420         // build the path manually below using the same policy that appears in ContextImpl.
421         // This fails silently under the hood if there's a problem, so if we find ourselves in
422         // the case where we don't have access to credential encrypted storage we just won't
423         // have our pinned target info.
424         final File prefsFile = new File(new File(
425                 Environment.getDataUserCePackageDirectory(StorageManager.UUID_PRIVATE_INTERNAL,
426                         context.getUserId(), context.getPackageName()),
427                 "shared_prefs"),
428                 PINNED_SHARED_PREFS_NAME + ".xml");
429         return context.getSharedPreferences(prefsFile, MODE_PRIVATE);
430     }
431 
432     @Override
createMultiProfilePagerAdapter( Intent[] initialIntents, List<ResolveInfo> rList, boolean filterLastUsed, TargetDataLoader targetDataLoader)433     protected AbstractMultiProfilePagerAdapter createMultiProfilePagerAdapter(
434             Intent[] initialIntents,
435             List<ResolveInfo> rList,
436             boolean filterLastUsed,
437             TargetDataLoader targetDataLoader) {
438         if (shouldShowTabs()) {
439             mChooserMultiProfilePagerAdapter = createChooserMultiProfilePagerAdapterForTwoProfiles(
440                     initialIntents, rList, filterLastUsed, targetDataLoader);
441         } else {
442             mChooserMultiProfilePagerAdapter = createChooserMultiProfilePagerAdapterForOneProfile(
443                     initialIntents, rList, filterLastUsed, targetDataLoader);
444         }
445         return mChooserMultiProfilePagerAdapter;
446     }
447 
448     @Override
createBlockerEmptyStateProvider()449     protected EmptyStateProvider createBlockerEmptyStateProvider() {
450         final boolean isSendAction = mChooserRequest.isSendActionTarget();
451 
452         final EmptyState noWorkToPersonalEmptyState =
453                 new DevicePolicyBlockerEmptyState(
454                         /* context= */ this,
455                         /* devicePolicyStringTitleId= */ RESOLVER_CROSS_PROFILE_BLOCKED_TITLE,
456                         /* defaultTitleResource= */ R.string.resolver_cross_profile_blocked,
457                         /* devicePolicyStringSubtitleId= */
458                         isSendAction ? RESOLVER_CANT_SHARE_WITH_PERSONAL : RESOLVER_CANT_ACCESS_PERSONAL,
459                         /* defaultSubtitleResource= */
460                         isSendAction ? R.string.resolver_cant_share_with_personal_apps_explanation
461                                 : R.string.resolver_cant_access_personal_apps_explanation,
462                         /* devicePolicyEventId= */ RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL,
463                         /* devicePolicyEventCategory= */ ResolverActivity.METRICS_CATEGORY_CHOOSER);
464 
465         final EmptyState noPersonalToWorkEmptyState =
466                 new DevicePolicyBlockerEmptyState(
467                         /* context= */ this,
468                         /* devicePolicyStringTitleId= */ RESOLVER_CROSS_PROFILE_BLOCKED_TITLE,
469                         /* defaultTitleResource= */ R.string.resolver_cross_profile_blocked,
470                         /* devicePolicyStringSubtitleId= */
471                         isSendAction ? RESOLVER_CANT_SHARE_WITH_WORK : RESOLVER_CANT_ACCESS_WORK,
472                         /* defaultSubtitleResource= */
473                         isSendAction ? R.string.resolver_cant_share_with_work_apps_explanation
474                                 : R.string.resolver_cant_access_work_apps_explanation,
475                         /* devicePolicyEventId= */ RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK,
476                         /* devicePolicyEventCategory= */ ResolverActivity.METRICS_CATEGORY_CHOOSER);
477 
478         return new NoCrossProfileEmptyStateProvider(getPersonalProfileUserHandle(),
479                 noWorkToPersonalEmptyState, noPersonalToWorkEmptyState,
480                 createCrossProfileIntentsChecker(), getTabOwnerUserHandleForLaunch());
481     }
482 
createChooserMultiProfilePagerAdapterForOneProfile( Intent[] initialIntents, List<ResolveInfo> rList, boolean filterLastUsed, TargetDataLoader targetDataLoader)483     private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForOneProfile(
484             Intent[] initialIntents,
485             List<ResolveInfo> rList,
486             boolean filterLastUsed,
487             TargetDataLoader targetDataLoader) {
488         ChooserGridAdapter adapter = createChooserGridAdapter(
489                 /* context */ this,
490                 /* payloadIntents */ mIntents,
491                 initialIntents,
492                 rList,
493                 filterLastUsed,
494                 /* userHandle */ getPersonalProfileUserHandle(),
495                 targetDataLoader);
496         return new ChooserMultiProfilePagerAdapter(
497                 /* context */ this,
498                 adapter,
499                 createEmptyStateProvider(/* workProfileUserHandle= */ null),
500                 /* workProfileQuietModeChecker= */ () -> false,
501                 /* workProfileUserHandle= */ null,
502                 getCloneProfileUserHandle(),
503                 mMaxTargetsPerRow);
504     }
505 
createChooserMultiProfilePagerAdapterForTwoProfiles( Intent[] initialIntents, List<ResolveInfo> rList, boolean filterLastUsed, TargetDataLoader targetDataLoader)506     private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForTwoProfiles(
507             Intent[] initialIntents,
508             List<ResolveInfo> rList,
509             boolean filterLastUsed,
510             TargetDataLoader targetDataLoader) {
511         int selectedProfile = findSelectedProfile();
512         ChooserGridAdapter personalAdapter = createChooserGridAdapter(
513                 /* context */ this,
514                 /* payloadIntents */ mIntents,
515                 selectedProfile == PROFILE_PERSONAL ? initialIntents : null,
516                 rList,
517                 filterLastUsed,
518                 /* userHandle */ getPersonalProfileUserHandle(),
519                 targetDataLoader);
520         ChooserGridAdapter workAdapter = createChooserGridAdapter(
521                 /* context */ this,
522                 /* payloadIntents */ mIntents,
523                 selectedProfile == PROFILE_WORK ? initialIntents : null,
524                 rList,
525                 filterLastUsed,
526                 /* userHandle */ getWorkProfileUserHandle(),
527                 targetDataLoader);
528         return new ChooserMultiProfilePagerAdapter(
529                 /* context */ this,
530                 personalAdapter,
531                 workAdapter,
532                 createEmptyStateProvider(/* workProfileUserHandle= */ getWorkProfileUserHandle()),
533                 () -> mWorkProfileAvailability.isQuietModeEnabled(),
534                 selectedProfile,
535                 getWorkProfileUserHandle(),
536                 getCloneProfileUserHandle(),
537                 mMaxTargetsPerRow);
538     }
539 
findSelectedProfile()540     private int findSelectedProfile() {
541         int selectedProfile = getSelectedProfileExtra();
542         if (selectedProfile == -1) {
543             selectedProfile = getProfileForUser(getTabOwnerUserHandleForLaunch());
544         }
545         return selectedProfile;
546     }
547 
548     @Override
postRebuildList(boolean rebuildCompleted)549     protected boolean postRebuildList(boolean rebuildCompleted) {
550         updateStickyContentPreview();
551         if (shouldShowStickyContentPreview()
552                 || mChooserMultiProfilePagerAdapter
553                         .getCurrentRootAdapter().getSystemRowCount() != 0) {
554             getEventLog().logActionShareWithPreview(
555                     mChooserContentPreviewUi.getPreferredContentPreview());
556         }
557         return postRebuildListInternal(rebuildCompleted);
558     }
559 
560     /**
561      * Check if the profile currently used is a work profile.
562      * @return true if it is work profile, false if it is parent profile (or no work profile is
563      * set up)
564      */
isWorkProfile()565     protected boolean isWorkProfile() {
566         return getSystemService(UserManager.class)
567                 .getUserInfo(UserHandle.myUserId()).isManagedProfile();
568     }
569 
570     @Override
createPackageMonitor(ResolverListAdapter listAdapter)571     protected PackageMonitor createPackageMonitor(ResolverListAdapter listAdapter) {
572         return new PackageMonitor() {
573             @Override
574             public void onSomePackagesChanged() {
575                 handlePackagesChanged(listAdapter);
576             }
577         };
578     }
579 
580     /**
581      * Update UI to reflect changes in data.
582      */
583     public void handlePackagesChanged() {
584         handlePackagesChanged(/* listAdapter */ null);
585     }
586 
587     /**
588      * Update UI to reflect changes in data.
589      * <p>If {@code listAdapter} is {@code null}, both profile list adapters are updated if
590      * available.
591      */
592     private void handlePackagesChanged(@Nullable ResolverListAdapter listAdapter) {
593         // Refresh pinned items
594         mPinnedSharedPrefs = getPinnedSharedPrefs(this);
595         if (listAdapter == null) {
596             handlePackageChangePerProfile(mChooserMultiProfilePagerAdapter.getActiveListAdapter());
597             if (mChooserMultiProfilePagerAdapter.getCount() > 1) {
598                 handlePackageChangePerProfile(
599                         mChooserMultiProfilePagerAdapter.getInactiveListAdapter());
600             }
601         } else {
602             handlePackageChangePerProfile(listAdapter);
603         }
604         updateProfileViewButton();
605     }
606 
607     private void handlePackageChangePerProfile(ResolverListAdapter adapter) {
608         ProfileRecord record = getProfileRecord(adapter.getUserHandle());
609         if (record != null && record.shortcutLoader != null) {
610             record.shortcutLoader.reset();
611         }
612         adapter.handlePackagesChanged();
613     }
614 
615     @Override
616     protected void onResume() {
617         super.onResume();
618         Log.d(TAG, "onResume: " + getComponentName().flattenToShortString());
619         mFinishWhenStopped = false;
620         mRefinementManager.onActivityResume();
621     }
622 
623     @Override
624     public void onConfigurationChanged(Configuration newConfig) {
625         super.onConfigurationChanged(newConfig);
626         ViewPager viewPager = findViewById(com.android.internal.R.id.profile_pager);
627         if (viewPager.isLayoutRtl()) {
628             mMultiProfilePagerAdapter.setupViewPager(viewPager);
629         }
630 
631         mShouldDisplayLandscape = shouldDisplayLandscape(newConfig.orientation);
632         mMaxTargetsPerRow = getResources().getInteger(R.integer.config_chooser_max_targets_per_row);
633         mChooserMultiProfilePagerAdapter.setMaxTargetsPerRow(mMaxTargetsPerRow);
634         adjustPreviewWidth(newConfig.orientation, null);
635         updateStickyContentPreview();
636         updateTabPadding();
637     }
638 
639     private boolean shouldDisplayLandscape(int orientation) {
640         // Sharesheet fixes the # of items per row and therefore can not correctly lay out
641         // when in the restricted size of multi-window mode. In the future, would be nice
642         // to use minimum dp size requirements instead
643         return orientation == Configuration.ORIENTATION_LANDSCAPE && !isInMultiWindowMode();
644     }
645 
646     private void adjustPreviewWidth(int orientation, View parent) {
647         int width = -1;
648         if (mShouldDisplayLandscape) {
649             width = getResources().getDimensionPixelSize(R.dimen.chooser_preview_width);
650         }
651 
652         parent = parent == null ? getWindow().getDecorView() : parent;
653 
654         updateLayoutWidth(com.android.internal.R.id.content_preview_file_layout, width, parent);
655     }
656 
657     private void updateTabPadding() {
658         if (shouldShowTabs()) {
659             View tabs = findViewById(com.android.internal.R.id.tabs);
660             float iconSize = getResources().getDimension(R.dimen.chooser_icon_size);
661             // The entire width consists of icons or padding. Divide the item padding in half to get
662             // paddingHorizontal.
663             float padding = (tabs.getWidth() - mMaxTargetsPerRow * iconSize)
664                     / mMaxTargetsPerRow / 2;
665             // Subtract the margin the buttons already have.
666             padding -= getResources().getDimension(R.dimen.resolver_profile_tab_margin);
667             tabs.setPadding((int) padding, 0, (int) padding, 0);
668         }
669     }
670 
671     private void updateLayoutWidth(int layoutResourceId, int width, View parent) {
672         View view = parent.findViewById(layoutResourceId);
673         if (view != null && view.getLayoutParams() != null) {
674             LayoutParams params = view.getLayoutParams();
675             params.width = width;
676             view.setLayoutParams(params);
677         }
678     }
679 
680     /**
681      * Create a view that will be shown in the content preview area
682      * @param parent reference to the parent container where the view should be attached to
683      * @return content preview view
684      */
685     protected ViewGroup createContentPreviewView(ViewGroup parent) {
686         ViewGroup layout = mChooserContentPreviewUi.displayContentPreview(
687                 getResources(),
688                 getLayoutInflater(),
689                 parent);
690 
691         if (layout != null) {
692             adjustPreviewWidth(getResources().getConfiguration().orientation, layout);
693         }
694 
695         return layout;
696     }
697 
698     @Nullable
699     private View getFirstVisibleImgPreviewView() {
700         View imagePreview = findViewById(R.id.scrollable_image_preview);
701         return imagePreview instanceof ImagePreviewView
702                 ? ((ImagePreviewView) imagePreview).getTransitionView()
703                 : null;
704     }
705 
706     /**
707      * Wrapping the ContentResolver call to expose for easier mocking,
708      * and to avoid mocking Android core classes.
709      */
710     @VisibleForTesting
711     public Cursor queryResolver(ContentResolver resolver, Uri uri) {
712         return resolver.query(uri, null, null, null, null);
713     }
714 
715     @Override
716     protected void onStop() {
717         super.onStop();
718         mRefinementManager.onActivityStop(isChangingConfigurations());
719 
720         if (mFinishWhenStopped) {
721             mFinishWhenStopped = false;
722             finish();
723         }
724     }
725 
726     @Override
727     protected void onDestroy() {
728         super.onDestroy();
729 
730         if (isFinishing()) {
731             mLatencyTracker.onActionCancel(ACTION_LOAD_SHARE_SHEET);
732         }
733 
734         mBackgroundThreadPoolExecutor.shutdownNow();
735 
736         destroyProfileRecords();
737     }
738 
739     private void destroyProfileRecords() {
740         for (int i = 0; i < mProfileRecords.size(); ++i) {
741             mProfileRecords.valueAt(i).destroy();
742         }
743         mProfileRecords.clear();
744     }
745 
746     @Override // ResolverListCommunicator
747     public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) {
748         if (mChooserRequest == null) {
749             return defIntent;
750         }
751 
752         Intent result = defIntent;
753         if (mChooserRequest.getReplacementExtras() != null) {
754             final Bundle replExtras =
755                     mChooserRequest.getReplacementExtras().getBundle(aInfo.packageName);
756             if (replExtras != null) {
757                 result = new Intent(defIntent);
758                 result.putExtras(replExtras);
759             }
760         }
761         if (aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_PARENT)
762                 || aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE)) {
763             result = Intent.createChooser(result,
764                     getIntent().getCharSequenceExtra(Intent.EXTRA_TITLE));
765 
766             // Don't auto-launch single intents if the intent is being forwarded. This is done
767             // because automatically launching a resolving application as a response to the user
768             // action of switching accounts is pretty unexpected.
769             result.putExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, false);
770         }
771         return result;
772     }
773 
774     @Override
775     public void onActivityStarted(TargetInfo cti) {
776         if (mChooserRequest.getChosenComponentSender() != null) {
777             final ComponentName target = cti.getResolvedComponentName();
778             if (target != null) {
779                 final Intent fillIn = new Intent().putExtra(Intent.EXTRA_CHOSEN_COMPONENT, target);
780                 try {
781                     mChooserRequest.getChosenComponentSender().sendIntent(
782                             this, Activity.RESULT_OK, fillIn, null, null);
783                 } catch (IntentSender.SendIntentException e) {
784                     Slog.e(TAG, "Unable to launch supplied IntentSender to report "
785                             + "the chosen component: " + e);
786                 }
787             }
788         }
789     }
790 
791     private void addCallerChooserTargets() {
792         if (!mChooserRequest.getCallerChooserTargets().isEmpty()) {
793             // Send the caller's chooser targets only to the default profile.
794             UserHandle defaultUser = (findSelectedProfile() == PROFILE_WORK)
795                     ? getAnnotatedUserHandles().workProfileUserHandle
796                     : getAnnotatedUserHandles().personalProfileUserHandle;
797             if (mChooserMultiProfilePagerAdapter.getCurrentUserHandle() == defaultUser) {
798                 mChooserMultiProfilePagerAdapter.getActiveListAdapter().addServiceResults(
799                         /* origTarget */ null,
800                         new ArrayList<>(mChooserRequest.getCallerChooserTargets()),
801                         TARGET_TYPE_DEFAULT,
802                         /* directShareShortcutInfoCache */ Collections.emptyMap(),
803                         /* directShareAppTargetCache */ Collections.emptyMap());
804             }
805         }
806     }
807 
808     @Override
809     public int getLayoutResource() {
810         return R.layout.chooser_grid;
811     }
812 
813     @Override // ResolverListCommunicator
814     public boolean shouldGetActivityMetadata() {
815         return true;
816     }
817 
818     @Override
819     public boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
820         // Note that this is only safe because the Intent handled by the ChooserActivity is
821         // guaranteed to contain no extras unknown to the local ClassLoader. That is why this
822         // method can not be replaced in the ResolverActivity whole hog.
823         if (!super.shouldAutoLaunchSingleChoice(target)) {
824             return false;
825         }
826 
827         return getIntent().getBooleanExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, true);
828     }
829 
830     private void showTargetDetails(TargetInfo targetInfo) {
831         if (targetInfo == null) return;
832 
833         List<DisplayResolveInfo> targetList = targetInfo.getAllDisplayTargets();
834         if (targetList.isEmpty()) {
835             Log.e(TAG, "No displayable data to show target details");
836             return;
837         }
838 
839         // TODO: implement these type-conditioned behaviors polymorphically, and consider moving
840         // the logic into `ChooserTargetActionsDialogFragment.show()`.
841         boolean isShortcutPinned = targetInfo.isSelectableTargetInfo() && targetInfo.isPinned();
842         IntentFilter intentFilter = targetInfo.isSelectableTargetInfo()
843                 ? mChooserRequest.getTargetIntentFilter() : null;
844         String shortcutTitle = targetInfo.isSelectableTargetInfo()
845                 ? targetInfo.getDisplayLabel().toString() : null;
846         String shortcutIdKey = targetInfo.getDirectShareShortcutId();
847 
848         ChooserTargetActionsDialogFragment.show(
849                 getSupportFragmentManager(),
850                 targetList,
851                 // Adding userHandle from ResolveInfo allows the app icon in Dialog Box to be
852                 // resolved correctly within the same tab.
853                 targetInfo.getResolveInfo().userHandle,
854                 shortcutIdKey,
855                 shortcutTitle,
856                 isShortcutPinned,
857                 intentFilter);
858     }
859 
860     @Override
861     protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) {
862         if (mRefinementManager.maybeHandleSelection(
863                 target,
864                 mChooserRequest.getRefinementIntentSender(),
865                 getApplication(),
866                 getMainThreadHandler())) {
867             return false;
868         }
869         updateModelAndChooserCounts(target);
870         maybeRemoveSharedText(target);
871         return super.onTargetSelected(target, alwaysCheck);
872     }
873 
874     @Override
875     public void startSelected(int which, boolean always, boolean filtered) {
876         ChooserListAdapter currentListAdapter =
877                 mChooserMultiProfilePagerAdapter.getActiveListAdapter();
878         TargetInfo targetInfo = currentListAdapter
879                 .targetInfoForPosition(which, filtered);
880         if (targetInfo != null && targetInfo.isNotSelectableTargetInfo()) {
881             return;
882         }
883 
884         final long selectionCost = System.currentTimeMillis() - mChooserShownTime;
885 
886         if ((targetInfo != null) && targetInfo.isMultiDisplayResolveInfo()) {
887             MultiDisplayResolveInfo mti = (MultiDisplayResolveInfo) targetInfo;
888             if (!mti.hasSelected()) {
889                 // Add userHandle based badge to the stackedAppDialogBox.
890                 ChooserStackedAppDialogFragment.show(
891                         getSupportFragmentManager(),
892                         mti,
893                         which,
894                         targetInfo.getResolveInfo().userHandle);
895                 return;
896             }
897         }
898 
899         super.startSelected(which, always, filtered);
900 
901         // TODO: both of the conditions around this switch logic *should* be redundant, and
902         // can be removed if certain invariants can be guaranteed. In particular, it seems
903         // like targetInfo (from `ChooserListAdapter.targetInfoForPosition()`) is *probably*
904         // expected to be null only at out-of-bounds indexes where `getPositionTargetType()`
905         // returns TARGET_BAD; then the switch falls through to a default no-op, and we don't
906         // need to null-check targetInfo. We only need the null check if it's possible that
907         // the ChooserListAdapter contains null elements "in the middle" of its list data,
908         // such that they're classified as belonging to one of the real target types. That
909         // should probably never happen. But why would this method ever be invoked with a
910         // null target at all? Even an out-of-bounds index should never be "selected"...
911         if ((currentListAdapter.getCount() > 0) && (targetInfo != null)) {
912             switch (currentListAdapter.getPositionTargetType(which)) {
913                 case ChooserListAdapter.TARGET_SERVICE:
914                     getEventLog().logShareTargetSelected(
915                             EventLog.SELECTION_TYPE_SERVICE,
916                             targetInfo.getResolveInfo().activityInfo.processName,
917                             which,
918                             /* directTargetAlsoRanked= */ getRankedPosition(targetInfo),
919                             mChooserRequest.getCallerChooserTargets().size(),
920                             targetInfo.getHashedTargetIdForMetrics(this),
921                             targetInfo.isPinned(),
922                             mIsSuccessfullySelected,
923                             selectionCost
924                     );
925                     return;
926                 case ChooserListAdapter.TARGET_CALLER:
927                 case ChooserListAdapter.TARGET_STANDARD:
928                     getEventLog().logShareTargetSelected(
929                             EventLog.SELECTION_TYPE_APP,
930                             targetInfo.getResolveInfo().activityInfo.processName,
931                             (which - currentListAdapter.getSurfacedTargetInfo().size()),
932                             /* directTargetAlsoRanked= */ -1,
933                             currentListAdapter.getCallerTargetCount(),
934                             /* directTargetHashed= */ null,
935                             targetInfo.isPinned(),
936                             mIsSuccessfullySelected,
937                             selectionCost
938                     );
939                     return;
940                 case ChooserListAdapter.TARGET_STANDARD_AZ:
941                     // A-Z targets are unranked standard targets; we use a value of -1 to mark that
942                     // they are from the alphabetical pool.
943                     // TODO: why do we log a different selection type if the -1 value already
944                     // designates the same condition?
945                     getEventLog().logShareTargetSelected(
946                             EventLog.SELECTION_TYPE_STANDARD,
947                             targetInfo.getResolveInfo().activityInfo.processName,
948                             /* value= */ -1,
949                             /* directTargetAlsoRanked= */ -1,
950                             /* numCallerProvided= */ 0,
951                             /* directTargetHashed= */ null,
952                             /* isPinned= */ false,
953                             mIsSuccessfullySelected,
954                             selectionCost
955                     );
956                     return;
957             }
958         }
959     }
960 
961     private int getRankedPosition(TargetInfo targetInfo) {
962         String targetPackageName =
963                 targetInfo.getChooserTargetComponentName().getPackageName();
964         ChooserListAdapter currentListAdapter =
965                 mChooserMultiProfilePagerAdapter.getActiveListAdapter();
966         int maxRankedResults = Math.min(
967                 currentListAdapter.getDisplayResolveInfoCount(), MAX_LOG_RANK_POSITION);
968 
969         for (int i = 0; i < maxRankedResults; i++) {
970             if (currentListAdapter.getDisplayResolveInfo(i)
971                     .getResolveInfo().activityInfo.packageName.equals(targetPackageName)) {
972                 return i;
973             }
974         }
975         return -1;
976     }
977 
978     @Override
979     protected boolean shouldAddFooterView() {
980         // To accommodate for window insets
981         return true;
982     }
983 
984     @Override
985     protected void applyFooterView(int height) {
986         int count = mChooserMultiProfilePagerAdapter.getItemCount();
987 
988         for (int i = 0; i < count; i++) {
989             mChooserMultiProfilePagerAdapter.getAdapterForIndex(i).setFooterHeight(height);
990         }
991     }
992 
993     private void logDirectShareTargetReceived(UserHandle forUser) {
994         ProfileRecord profileRecord = getProfileRecord(forUser);
995         if (profileRecord == null) {
996             return;
997         }
998         getEventLog().logDirectShareTargetReceived(
999                 MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER,
1000                 (int) (SystemClock.elapsedRealtime() - profileRecord.loadingStartTime));
1001     }
1002 
1003     void updateModelAndChooserCounts(TargetInfo info) {
1004         if (info != null && info.isMultiDisplayResolveInfo()) {
1005             info = ((MultiDisplayResolveInfo) info).getSelectedTarget();
1006         }
1007         if (info != null) {
1008             sendClickToAppPredictor(info);
1009             final ResolveInfo ri = info.getResolveInfo();
1010             Intent targetIntent = getTargetIntent();
1011             if (ri != null && ri.activityInfo != null && targetIntent != null) {
1012                 ChooserListAdapter currentListAdapter =
1013                         mChooserMultiProfilePagerAdapter.getActiveListAdapter();
1014                 if (currentListAdapter != null) {
1015                     sendImpressionToAppPredictor(info, currentListAdapter);
1016                     currentListAdapter.updateModel(info);
1017                     currentListAdapter.updateChooserCounts(
1018                             ri.activityInfo.packageName,
1019                             targetIntent.getAction(),
1020                             ri.userHandle);
1021                 }
1022                 if (DEBUG) {
1023                     Log.d(TAG, "ResolveInfo Package is " + ri.activityInfo.packageName);
1024                     Log.d(TAG, "Action to be updated is " + targetIntent.getAction());
1025                 }
1026             } else if (DEBUG) {
1027                 Log.d(TAG, "Can not log Chooser Counts of null ResolveInfo");
1028             }
1029         }
1030         mIsSuccessfullySelected = true;
1031     }
1032 
1033     private void maybeRemoveSharedText(@androidx.annotation.NonNull TargetInfo targetInfo) {
1034         Intent targetIntent = targetInfo.getTargetIntent();
1035         if (targetIntent == null) {
1036             return;
1037         }
1038         Intent originalTargetIntent = new Intent(mChooserRequest.getTargetIntent());
1039         // Our TargetInfo implementations add associated component to the intent, let's do the same
1040         // for the sake of the comparison below.
1041         if (targetIntent.getComponent() != null) {
1042             originalTargetIntent.setComponent(targetIntent.getComponent());
1043         }
1044         // Use filterEquals as a way to check that the primary intent is in use (and not an
1045         // alternative one). For example, an app is sharing an image and a link with mime type
1046         // "image/png" and provides an alternative intent to share only the link with mime type
1047         // "text/uri". Should there be a target that accepts only the latter, the alternative intent
1048         // will be used and we don't want to exclude the link from it.
1049         if (mExcludeSharedText && originalTargetIntent.filterEquals(targetIntent)) {
1050             targetIntent.removeExtra(Intent.EXTRA_TEXT);
1051         }
1052     }
1053 
1054     private void sendImpressionToAppPredictor(TargetInfo targetInfo, ChooserListAdapter adapter) {
1055         // Send DS target impression info to AppPredictor, only when user chooses app share.
1056         if (targetInfo.isChooserTargetInfo()) {
1057             return;
1058         }
1059 
1060         AppPredictor directShareAppPredictor = getAppPredictor(
1061                 mChooserMultiProfilePagerAdapter.getCurrentUserHandle());
1062         if (directShareAppPredictor == null) {
1063             return;
1064         }
1065         List<TargetInfo> surfacedTargetInfo = adapter.getSurfacedTargetInfo();
1066         List<AppTargetId> targetIds = new ArrayList<>();
1067         for (TargetInfo chooserTargetInfo : surfacedTargetInfo) {
1068             ShortcutInfo shortcutInfo = chooserTargetInfo.getDirectShareShortcutInfo();
1069             if (shortcutInfo != null) {
1070                 ComponentName componentName =
1071                         chooserTargetInfo.getChooserTargetComponentName();
1072                 targetIds.add(new AppTargetId(
1073                         String.format(
1074                                 "%s/%s/%s",
1075                                 shortcutInfo.getId(),
1076                                 componentName.flattenToString(),
1077                                 SHORTCUT_TARGET)));
1078             }
1079         }
1080         directShareAppPredictor.notifyLaunchLocationShown(LAUNCH_LOCATION_DIRECT_SHARE, targetIds);
1081     }
1082 
1083     private void sendClickToAppPredictor(TargetInfo targetInfo) {
1084         if (!targetInfo.isChooserTargetInfo()) {
1085             return;
1086         }
1087 
1088         AppPredictor directShareAppPredictor = getAppPredictor(
1089                 mChooserMultiProfilePagerAdapter.getCurrentUserHandle());
1090         if (directShareAppPredictor == null) {
1091             return;
1092         }
1093         AppTarget appTarget = targetInfo.getDirectShareAppTarget();
1094         if (appTarget != null) {
1095             // This is a direct share click that was provided by the APS
1096             directShareAppPredictor.notifyAppTargetEvent(
1097                     new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_LAUNCH)
1098                         .setLaunchLocation(LAUNCH_LOCATION_DIRECT_SHARE)
1099                         .build());
1100         }
1101     }
1102 
1103     @Nullable
1104     private AppPredictor getAppPredictor(UserHandle userHandle) {
1105         ProfileRecord record = getProfileRecord(userHandle);
1106         // We cannot use APS service when clone profile is present as APS service cannot sort
1107         // cross profile targets as of now.
1108         return (record == null || getCloneProfileUserHandle() != null) ? null : record.appPredictor;
1109     }
1110 
1111     /**
1112      * Sort intents alphabetically based on display label.
1113      */
1114     static class AzInfoComparator implements Comparator<DisplayResolveInfo> {
1115         Comparator<DisplayResolveInfo> mComparator;
1116         AzInfoComparator(Context context) {
1117             Collator collator = Collator
1118                         .getInstance(context.getResources().getConfiguration().locale);
1119             // Adding two stage comparator, first stage compares using displayLabel, next stage
1120             //  compares using resolveInfo.userHandle
1121             mComparator = Comparator.comparing(DisplayResolveInfo::getDisplayLabel, collator)
1122                     .thenComparingInt(target -> target.getResolveInfo().userHandle.getIdentifier());
1123         }
1124 
1125         @Override
1126         public int compare(
1127                 DisplayResolveInfo lhsp, DisplayResolveInfo rhsp) {
1128             return mComparator.compare(lhsp, rhsp);
1129         }
1130     }
1131 
1132     protected EventLog getEventLog() {
1133         if (mEventLog == null) {
1134             mEventLog = new EventLog();
1135         }
1136         return mEventLog;
1137     }
1138 
1139     public class ChooserListController extends ResolverListController {
1140         public ChooserListController(
1141                 Context context,
1142                 PackageManager pm,
1143                 Intent targetIntent,
1144                 String referrerPackageName,
1145                 int launchedFromUid,
1146                 AbstractResolverComparator resolverComparator,
1147                 UserHandle queryIntentsAsUser) {
1148             super(
1149                     context,
1150                     pm,
1151                     targetIntent,
1152                     referrerPackageName,
1153                     launchedFromUid,
1154                     resolverComparator,
1155                     queryIntentsAsUser);
1156         }
1157 
1158         @Override
1159         boolean isComponentFiltered(ComponentName name) {
1160             return mChooserRequest.getFilteredComponentNames().contains(name);
1161         }
1162 
1163         @Override
1164         public boolean isComponentPinned(ComponentName name) {
1165             return mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
1166         }
1167     }
1168 
1169     @VisibleForTesting
1170     public ChooserGridAdapter createChooserGridAdapter(
1171             Context context,
1172             List<Intent> payloadIntents,
1173             Intent[] initialIntents,
1174             List<ResolveInfo> rList,
1175             boolean filterLastUsed,
1176             UserHandle userHandle,
1177             TargetDataLoader targetDataLoader) {
1178         ChooserListAdapter chooserListAdapter = createChooserListAdapter(
1179                 context,
1180                 payloadIntents,
1181                 initialIntents,
1182                 rList,
1183                 filterLastUsed,
1184                 createListController(userHandle),
1185                 userHandle,
1186                 getTargetIntent(),
1187                 mChooserRequest,
1188                 mMaxTargetsPerRow,
1189                 targetDataLoader);
1190 
1191         return new ChooserGridAdapter(
1192                 context,
1193                 new ChooserGridAdapter.ChooserActivityDelegate() {
1194                     @Override
1195                     public boolean shouldShowTabs() {
1196                         return ChooserActivity.this.shouldShowTabs();
1197                     }
1198 
1199                     @Override
1200                     public View buildContentPreview(ViewGroup parent) {
1201                         return createContentPreviewView(parent);
1202                     }
1203 
1204                     @Override
1205                     public void onTargetSelected(int itemIndex) {
1206                         startSelected(itemIndex, false, true);
1207                     }
1208 
1209                     @Override
1210                     public void onTargetLongPressed(int selectedPosition) {
1211                         final TargetInfo longPressedTargetInfo =
1212                                 mChooserMultiProfilePagerAdapter
1213                                 .getActiveListAdapter()
1214                                 .targetInfoForPosition(
1215                                         selectedPosition, /* filtered= */ true);
1216                         // Only a direct share target or an app target is expected
1217                         if (longPressedTargetInfo.isDisplayResolveInfo()
1218                                 || longPressedTargetInfo.isSelectableTargetInfo()) {
1219                             showTargetDetails(longPressedTargetInfo);
1220                         }
1221                     }
1222 
1223                     @Override
1224                     public void updateProfileViewButton(View newButtonFromProfileRow) {
1225                         mProfileView = newButtonFromProfileRow;
1226                         mProfileView.setOnClickListener(ChooserActivity.this::onProfileClick);
1227                         ChooserActivity.this.updateProfileViewButton();
1228                     }
1229                 },
1230                 chooserListAdapter,
1231                 shouldShowContentPreview(),
1232                 mMaxTargetsPerRow);
1233     }
1234 
1235     @VisibleForTesting
1236     public ChooserListAdapter createChooserListAdapter(
1237             Context context,
1238             List<Intent> payloadIntents,
1239             Intent[] initialIntents,
1240             List<ResolveInfo> rList,
1241             boolean filterLastUsed,
1242             ResolverListController resolverListController,
1243             UserHandle userHandle,
1244             Intent targetIntent,
1245             ChooserRequestParameters chooserRequest,
1246             int maxTargetsPerRow,
1247             TargetDataLoader targetDataLoader) {
1248         UserHandle initialIntentsUserSpace = isLaunchedAsCloneProfile()
1249                 && userHandle.equals(getPersonalProfileUserHandle())
1250                 ? getCloneProfileUserHandle() : userHandle;
1251         return new ChooserListAdapter(
1252                 context,
1253                 payloadIntents,
1254                 initialIntents,
1255                 rList,
1256                 filterLastUsed,
1257                 createListController(userHandle),
1258                 userHandle,
1259                 targetIntent,
1260                 this,
1261                 context.getPackageManager(),
1262                 getEventLog(),
1263                 chooserRequest,
1264                 maxTargetsPerRow,
1265                 initialIntentsUserSpace,
1266                 targetDataLoader);
1267     }
1268 
1269     @Override
1270     protected void onWorkProfileStatusUpdated() {
1271         UserHandle workUser = getWorkProfileUserHandle();
1272         ProfileRecord record = workUser == null ? null : getProfileRecord(workUser);
1273         if (record != null && record.shortcutLoader != null) {
1274             record.shortcutLoader.reset();
1275         }
1276         super.onWorkProfileStatusUpdated();
1277     }
1278 
1279     @Override
1280     @VisibleForTesting
1281     protected ChooserListController createListController(UserHandle userHandle) {
1282         AppPredictor appPredictor = getAppPredictor(userHandle);
1283         AbstractResolverComparator resolverComparator;
1284         if (appPredictor != null) {
1285             resolverComparator = new AppPredictionServiceResolverComparator(this, getTargetIntent(),
1286                     getReferrerPackageName(), appPredictor, userHandle, getEventLog(),
1287                     getIntegratedDeviceComponents().getNearbySharingComponent());
1288         } else {
1289             resolverComparator =
1290                     new ResolverRankerServiceResolverComparator(
1291                             this,
1292                             getTargetIntent(),
1293                             getReferrerPackageName(),
1294                             null,
1295                             getEventLog(),
1296                             getResolverRankerServiceUserHandleList(userHandle),
1297                             getIntegratedDeviceComponents().getNearbySharingComponent());
1298         }
1299 
1300         return new ChooserListController(
1301                 this,
1302                 mPm,
1303                 getTargetIntent(),
1304                 getReferrerPackageName(),
1305                 getAnnotatedUserHandles().userIdOfCallingApp,
1306                 resolverComparator,
1307                 getQueryIntentsUser(userHandle));
1308     }
1309 
1310     @VisibleForTesting
1311     protected ViewModelProvider.Factory createPreviewViewModelFactory() {
1312         return PreviewViewModel.Companion.getFactory();
1313     }
1314 
1315     private ChooserActionFactory createChooserActionFactory() {
1316         return new ChooserActionFactory(
1317                 this,
1318                 mChooserRequest,
1319                 mIntegratedDeviceComponents,
1320                 getEventLog(),
1321                 (isExcluded) -> mExcludeSharedText = isExcluded,
1322                 this::getFirstVisibleImgPreviewView,
1323                 new ChooserActionFactory.ActionActivityStarter() {
1324                     @Override
1325                     public void safelyStartActivityAsPersonalProfileUser(TargetInfo targetInfo) {
1326                         safelyStartActivityAsUser(targetInfo, getPersonalProfileUserHandle());
1327                         finish();
1328                     }
1329 
1330                     @Override
1331                     public void safelyStartActivityAsPersonalProfileUserWithSharedElementTransition(
1332                             TargetInfo targetInfo, View sharedElement, String sharedElementName) {
1333                         ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(
1334                                 ChooserActivity.this, sharedElement, sharedElementName);
1335                         safelyStartActivityAsUser(
1336                                 targetInfo, getPersonalProfileUserHandle(), options.toBundle());
1337                         // Can't finish right away because the shared element transition may not
1338                         // be ready to start.
1339                         mFinishWhenStopped = true;
1340 
1341                     }
1342                 },
1343                 (status) -> {
1344                     if (status != null) {
1345                         setResult(status);
1346                     }
1347                     finish();
1348                 });
1349     }
1350 
1351     /*
1352      * Need to dynamically adjust how many icons can fit per row before we add them,
1353      * which also means setting the correct offset to initially show the content
1354      * preview area + 2 rows of targets
1355      */
1356     private void handleLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
1357             int oldTop, int oldRight, int oldBottom) {
1358         if (mChooserMultiProfilePagerAdapter == null) {
1359             return;
1360         }
1361         RecyclerView recyclerView = mChooserMultiProfilePagerAdapter.getActiveAdapterView();
1362         ChooserGridAdapter gridAdapter = mChooserMultiProfilePagerAdapter.getCurrentRootAdapter();
1363         // Skip height calculation if recycler view was scrolled to prevent it inaccurately
1364         // calculating the height, as the logic below does not account for the scrolled offset.
1365         if (gridAdapter == null || recyclerView == null
1366                 || recyclerView.computeVerticalScrollOffset() != 0) {
1367             return;
1368         }
1369 
1370         final int availableWidth = right - left - v.getPaddingLeft() - v.getPaddingRight();
1371         boolean isLayoutUpdated =
1372                 gridAdapter.calculateChooserTargetWidth(availableWidth)
1373                 || recyclerView.getAdapter() == null
1374                 || availableWidth != mCurrAvailableWidth;
1375 
1376         boolean insetsChanged = !Objects.equals(mLastAppliedInsets, mSystemWindowInsets);
1377 
1378         if (isLayoutUpdated
1379                 || insetsChanged
1380                 || mLastNumberOfChildren != recyclerView.getChildCount()) {
1381             mCurrAvailableWidth = availableWidth;
1382             if (isLayoutUpdated) {
1383                 // It is very important we call setAdapter from here. Otherwise in some cases
1384                 // the resolver list doesn't get populated, such as b/150922090, b/150918223
1385                 // and b/150936654
1386                 recyclerView.setAdapter(gridAdapter);
1387                 ((GridLayoutManager) recyclerView.getLayoutManager()).setSpanCount(
1388                         mMaxTargetsPerRow);
1389 
1390                 updateTabPadding();
1391             }
1392 
1393             UserHandle currentUserHandle = mChooserMultiProfilePagerAdapter.getCurrentUserHandle();
1394             int currentProfile = getProfileForUser(currentUserHandle);
1395             int initialProfile = findSelectedProfile();
1396             if (currentProfile != initialProfile) {
1397                 return;
1398             }
1399 
1400             if (mLastNumberOfChildren == recyclerView.getChildCount() && !insetsChanged) {
1401                 return;
1402             }
1403 
1404             getMainThreadHandler().post(() -> {
1405                 if (mResolverDrawerLayout == null || gridAdapter == null) {
1406                     return;
1407                 }
1408                 int offset = calculateDrawerOffset(top, bottom, recyclerView, gridAdapter);
1409                 mResolverDrawerLayout.setCollapsibleHeightReserved(offset);
1410                 mEnterTransitionAnimationDelegate.markOffsetCalculated();
1411                 mLastAppliedInsets = mSystemWindowInsets;
1412             });
1413         }
1414     }
1415 
1416     private int calculateDrawerOffset(
1417             int top, int bottom, RecyclerView recyclerView, ChooserGridAdapter gridAdapter) {
1418 
1419         int offset = mSystemWindowInsets != null ? mSystemWindowInsets.bottom : 0;
1420         int rowsToShow = gridAdapter.getSystemRowCount()
1421                 + gridAdapter.getProfileRowCount()
1422                 + gridAdapter.getServiceTargetRowCount()
1423                 + gridAdapter.getCallerAndRankedTargetRowCount();
1424 
1425         // then this is most likely not a SEND_* action, so check
1426         // the app target count
1427         if (rowsToShow == 0) {
1428             rowsToShow = gridAdapter.getRowCount();
1429         }
1430 
1431         // still zero? then use a default height and leave, which
1432         // can happen when there are no targets to show
1433         if (rowsToShow == 0 && !shouldShowStickyContentPreview()) {
1434             offset += getResources().getDimensionPixelSize(
1435                     R.dimen.chooser_max_collapsed_height);
1436             return offset;
1437         }
1438 
1439         View stickyContentPreview = findViewById(com.android.internal.R.id.content_preview_container);
1440         if (shouldShowStickyContentPreview() && isStickyContentPreviewShowing()) {
1441             offset += stickyContentPreview.getHeight();
1442         }
1443 
1444         if (shouldShowTabs()) {
1445             offset += findViewById(com.android.internal.R.id.tabs).getHeight();
1446         }
1447 
1448         if (recyclerView.getVisibility() == View.VISIBLE) {
1449             rowsToShow = Math.min(4, rowsToShow);
1450             boolean shouldShowExtraRow = shouldShowExtraRow(rowsToShow);
1451             mLastNumberOfChildren = recyclerView.getChildCount();
1452             for (int i = 0, childCount = recyclerView.getChildCount();
1453                     i < childCount && rowsToShow > 0; i++) {
1454                 View child = recyclerView.getChildAt(i);
1455                 if (((GridLayoutManager.LayoutParams)
1456                         child.getLayoutParams()).getSpanIndex() != 0) {
1457                     continue;
1458                 }
1459                 int height = child.getHeight();
1460                 offset += height;
1461                 if (shouldShowExtraRow) {
1462                     offset += height;
1463                 }
1464                 rowsToShow--;
1465             }
1466         } else {
1467             ViewGroup currentEmptyStateView = getActiveEmptyStateView();
1468             if (currentEmptyStateView.getVisibility() == View.VISIBLE) {
1469                 offset += currentEmptyStateView.getHeight();
1470             }
1471         }
1472 
1473         return Math.min(offset, bottom - top);
1474     }
1475 
1476     /**
1477      * If we have a tabbed view and are showing 1 row in the current profile and an empty
1478      * state screen in the other profile, to prevent cropping of the empty state screen we show
1479      * a second row in the current profile.
1480      */
1481     private boolean shouldShowExtraRow(int rowsToShow) {
1482         return shouldShowTabs()
1483                 && rowsToShow == 1
1484                 && mChooserMultiProfilePagerAdapter.shouldShowEmptyStateScreen(
1485                         mChooserMultiProfilePagerAdapter.getInactiveListAdapter());
1486     }
1487 
1488     /**
1489      * Returns {@link #PROFILE_WORK}, if the given user handle matches work user handle.
1490      * Returns {@link #PROFILE_PERSONAL}, otherwise.
1491      **/
1492     private int getProfileForUser(UserHandle currentUserHandle) {
1493         if (currentUserHandle.equals(getWorkProfileUserHandle())) {
1494             return PROFILE_WORK;
1495         }
1496         // We return personal profile, as it is the default when there is no work profile, personal
1497         // profile represents rootUser, clonedUser & secondaryUser, covering all use cases.
1498         return PROFILE_PERSONAL;
1499     }
1500 
1501     private ViewGroup getActiveEmptyStateView() {
1502         int currentPage = mChooserMultiProfilePagerAdapter.getCurrentPage();
1503         return mChooserMultiProfilePagerAdapter.getEmptyStateView(currentPage);
1504     }
1505 
1506     @Override // ResolverListCommunicator
1507     public void onHandlePackagesChanged(ResolverListAdapter listAdapter) {
1508         mChooserMultiProfilePagerAdapter.getActiveListAdapter().notifyDataSetChanged();
1509         super.onHandlePackagesChanged(listAdapter);
1510     }
1511 
1512     @Override
1513     public void onListRebuilt(ResolverListAdapter listAdapter, boolean rebuildComplete) {
1514         setupScrollListener();
1515         maybeSetupGlobalLayoutListener();
1516 
1517         ChooserListAdapter chooserListAdapter = (ChooserListAdapter) listAdapter;
1518         if (chooserListAdapter.getUserHandle()
1519                 .equals(mChooserMultiProfilePagerAdapter.getCurrentUserHandle())) {
1520             mChooserMultiProfilePagerAdapter.getActiveAdapterView()
1521                     .setAdapter(mChooserMultiProfilePagerAdapter.getCurrentRootAdapter());
1522             mChooserMultiProfilePagerAdapter
1523                     .setupListAdapter(mChooserMultiProfilePagerAdapter.getCurrentPage());
1524         }
1525 
1526         if (chooserListAdapter.getDisplayResolveInfoCount() == 0) {
1527             chooserListAdapter.notifyDataSetChanged();
1528         } else {
1529             chooserListAdapter.updateAlphabeticalList();
1530         }
1531 
1532         if (rebuildComplete) {
1533             long duration = Tracer.INSTANCE.endAppTargetLoadingSection(listAdapter.getUserHandle());
1534             if (duration >= 0) {
1535                 Log.d(TAG, "app target loading time " + duration + " ms");
1536             }
1537             addCallerChooserTargets();
1538             getEventLog().logSharesheetAppLoadComplete();
1539             maybeQueryAdditionalPostProcessingTargets(chooserListAdapter);
1540             mLatencyTracker.onActionEnd(ACTION_LOAD_SHARE_SHEET);
1541         }
1542     }
1543 
1544     private void maybeQueryAdditionalPostProcessingTargets(ChooserListAdapter chooserListAdapter) {
1545         UserHandle userHandle = chooserListAdapter.getUserHandle();
1546         ProfileRecord record = getProfileRecord(userHandle);
1547         if (record == null || record.shortcutLoader == null) {
1548             return;
1549         }
1550         record.loadingStartTime = SystemClock.elapsedRealtime();
1551         record.shortcutLoader.updateAppTargets(chooserListAdapter.getDisplayResolveInfos());
1552     }
1553 
1554     @MainThread
1555     private void onShortcutsLoaded(UserHandle userHandle, ShortcutLoader.Result result) {
1556         if (DEBUG) {
1557             Log.d(TAG, "onShortcutsLoaded for user: " + userHandle);
1558         }
1559         mDirectShareShortcutInfoCache.putAll(result.getDirectShareShortcutInfoCache());
1560         mDirectShareAppTargetCache.putAll(result.getDirectShareAppTargetCache());
1561         ChooserListAdapter adapter =
1562                 mChooserMultiProfilePagerAdapter.getListAdapterForUserHandle(userHandle);
1563         if (adapter != null) {
1564             for (ShortcutLoader.ShortcutResultInfo resultInfo : result.getShortcutsByApp()) {
1565                 adapter.addServiceResults(
1566                         resultInfo.getAppTarget(),
1567                         resultInfo.getShortcuts(),
1568                         result.isFromAppPredictor()
1569                                 ? TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE
1570                                 : TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER,
1571                         mDirectShareShortcutInfoCache,
1572                         mDirectShareAppTargetCache);
1573             }
1574             adapter.completeServiceTargetLoading();
1575         }
1576 
1577         if (mMultiProfilePagerAdapter.getActiveListAdapter() == adapter) {
1578             long duration = Tracer.INSTANCE.endLaunchToShortcutTrace();
1579             if (duration >= 0) {
1580                 Log.d(TAG, "stat to first shortcut time: " + duration + " ms");
1581             }
1582         }
1583         logDirectShareTargetReceived(userHandle);
1584         sendVoiceChoicesIfNeeded();
1585         getEventLog().logSharesheetDirectLoadComplete();
1586     }
1587 
1588     private void setupScrollListener() {
1589         if (mResolverDrawerLayout == null) {
1590             return;
1591         }
1592         int elevatedViewResId = shouldShowTabs() ? com.android.internal.R.id.tabs : com.android.internal.R.id.chooser_header;
1593         final View elevatedView = mResolverDrawerLayout.findViewById(elevatedViewResId);
1594         final float defaultElevation = elevatedView.getElevation();
1595         final float chooserHeaderScrollElevation =
1596                 getResources().getDimensionPixelSize(R.dimen.chooser_header_scroll_elevation);
1597         mChooserMultiProfilePagerAdapter.getActiveAdapterView().addOnScrollListener(
1598                 new RecyclerView.OnScrollListener() {
1599                     public void onScrollStateChanged(RecyclerView view, int scrollState) {
1600                         if (scrollState == RecyclerView.SCROLL_STATE_IDLE) {
1601                             if (mScrollStatus == SCROLL_STATUS_SCROLLING_VERTICAL) {
1602                                 mScrollStatus = SCROLL_STATUS_IDLE;
1603                                 setHorizontalScrollingEnabled(true);
1604                             }
1605                         } else if (scrollState == RecyclerView.SCROLL_STATE_DRAGGING) {
1606                             if (mScrollStatus == SCROLL_STATUS_IDLE) {
1607                                 mScrollStatus = SCROLL_STATUS_SCROLLING_VERTICAL;
1608                                 setHorizontalScrollingEnabled(false);
1609                             }
1610                         }
1611                     }
1612 
1613                     public void onScrolled(RecyclerView view, int dx, int dy) {
1614                         if (view.getChildCount() > 0) {
1615                             View child = view.getLayoutManager().findViewByPosition(0);
1616                             if (child == null || child.getTop() < 0) {
1617                                 elevatedView.setElevation(chooserHeaderScrollElevation);
1618                                 return;
1619                             }
1620                         }
1621 
1622                         elevatedView.setElevation(defaultElevation);
1623                     }
1624                 });
1625     }
1626 
1627     private void maybeSetupGlobalLayoutListener() {
1628         if (shouldShowTabs()) {
1629             return;
1630         }
1631         final View recyclerView = mChooserMultiProfilePagerAdapter.getActiveAdapterView();
1632         recyclerView.getViewTreeObserver()
1633                 .addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
1634                     @Override
1635                     public void onGlobalLayout() {
1636                         // Fixes an issue were the accessibility border disappears on list creation.
1637                         recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
1638                         final TextView titleView = findViewById(com.android.internal.R.id.title);
1639                         if (titleView != null) {
1640                             titleView.setFocusable(true);
1641                             titleView.setFocusableInTouchMode(true);
1642                             titleView.requestFocus();
1643                             titleView.requestAccessibilityFocus();
1644                         }
1645                     }
1646                 });
1647     }
1648 
1649     /**
1650      * The sticky content preview is shown only when we have a tabbed view. It's shown above
1651      * the tabs so it is not part of the scrollable list. If we are not in tabbed view,
1652      * we instead show the content preview as a regular list item.
1653      */
1654     private boolean shouldShowStickyContentPreview() {
1655         return shouldShowStickyContentPreviewNoOrientationCheck();
1656     }
1657 
1658     private boolean shouldShowStickyContentPreviewNoOrientationCheck() {
1659         return shouldShowTabs()
1660                 && (mMultiProfilePagerAdapter.getListAdapterForUserHandle(
1661                 UserHandle.of(UserHandle.myUserId())).getCount() > 0
1662                 || shouldShowContentPreviewWhenEmpty())
1663                 && shouldShowContentPreview();
1664     }
1665 
1666     /**
1667      * This method could be used to override the default behavior when we hide the preview area
1668      * when the current tab doesn't have any items.
1669      *
1670      * @return true if we want to show the content preview area even if the tab for the current
1671      *         user is empty
1672      */
1673     protected boolean shouldShowContentPreviewWhenEmpty() {
1674         return false;
1675     }
1676 
1677     /**
1678      * @return true if we want to show the content preview area
1679      */
1680     protected boolean shouldShowContentPreview() {
1681         return (mChooserRequest != null) && mChooserRequest.isSendActionTarget();
1682     }
1683 
1684     private void updateStickyContentPreview() {
1685         if (shouldShowStickyContentPreviewNoOrientationCheck()) {
1686             // The sticky content preview is only shown when we show the work and personal tabs.
1687             // We don't show it in landscape as otherwise there is no room for scrolling.
1688             // If the sticky content preview will be shown at some point with orientation change,
1689             // then always preload it to avoid subsequent resizing of the share sheet.
1690             ViewGroup contentPreviewContainer =
1691                     findViewById(com.android.internal.R.id.content_preview_container);
1692             if (contentPreviewContainer.getChildCount() == 0) {
1693                 ViewGroup contentPreviewView = createContentPreviewView(contentPreviewContainer);
1694                 contentPreviewContainer.addView(contentPreviewView);
1695             }
1696         }
1697         if (shouldShowStickyContentPreview()) {
1698             showStickyContentPreview();
1699         } else {
1700             hideStickyContentPreview();
1701         }
1702     }
1703 
1704     private void showStickyContentPreview() {
1705         if (isStickyContentPreviewShowing()) {
1706             return;
1707         }
1708         ViewGroup contentPreviewContainer = findViewById(com.android.internal.R.id.content_preview_container);
1709         contentPreviewContainer.setVisibility(View.VISIBLE);
1710     }
1711 
1712     private boolean isStickyContentPreviewShowing() {
1713         ViewGroup contentPreviewContainer = findViewById(com.android.internal.R.id.content_preview_container);
1714         return contentPreviewContainer.getVisibility() == View.VISIBLE;
1715     }
1716 
1717     private void hideStickyContentPreview() {
1718         if (!isStickyContentPreviewShowing()) {
1719             return;
1720         }
1721         ViewGroup contentPreviewContainer = findViewById(com.android.internal.R.id.content_preview_container);
1722         contentPreviewContainer.setVisibility(View.GONE);
1723     }
1724 
1725     private View findRootView() {
1726         if (mContentView == null) {
1727             mContentView = findViewById(android.R.id.content);
1728         }
1729         return mContentView;
1730     }
1731 
1732     /**
1733      * Intentionally override the {@link ResolverActivity} implementation as we only need that
1734      * implementation for the intent resolver case.
1735      */
1736     @Override
1737     public void onButtonClick(View v) {}
1738 
1739     /**
1740      * Intentionally override the {@link ResolverActivity} implementation as we only need that
1741      * implementation for the intent resolver case.
1742      */
1743     @Override
1744     protected void resetButtonBar() {}
1745 
1746     @Override
1747     protected String getMetricsCategory() {
1748         return METRICS_CATEGORY_CHOOSER;
1749     }
1750 
1751     @Override
1752     protected void onProfileTabSelected() {
1753         // This fixes an edge case where after performing a variety of gestures, vertical scrolling
1754         // ends up disabled. That's because at some point the old tab's vertical scrolling is
1755         // disabled and the new tab's is enabled. For context, see b/159997845
1756         setVerticalScrollEnabled(true);
1757         if (mResolverDrawerLayout != null) {
1758             mResolverDrawerLayout.scrollNestedScrollableChildBackToTop();
1759         }
1760     }
1761 
1762     @Override
1763     protected WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
1764         if (shouldShowTabs()) {
1765             mChooserMultiProfilePagerAdapter
1766                     .setEmptyStateBottomOffset(insets.getSystemWindowInsetBottom());
1767             mChooserMultiProfilePagerAdapter.setupContainerPadding(
1768                     getActiveEmptyStateView().findViewById(com.android.internal.R.id.resolver_empty_state_container));
1769         }
1770 
1771         WindowInsets result = super.onApplyWindowInsets(v, insets);
1772         if (mResolverDrawerLayout != null) {
1773             mResolverDrawerLayout.requestLayout();
1774         }
1775         return result;
1776     }
1777 
1778     private void setHorizontalScrollingEnabled(boolean enabled) {
1779         ResolverViewPager viewPager = findViewById(com.android.internal.R.id.profile_pager);
1780         viewPager.setSwipingEnabled(enabled);
1781     }
1782 
1783     private void setVerticalScrollEnabled(boolean enabled) {
1784         ChooserGridLayoutManager layoutManager =
1785                 (ChooserGridLayoutManager) mChooserMultiProfilePagerAdapter.getActiveAdapterView()
1786                         .getLayoutManager();
1787         layoutManager.setVerticalScrollEnabled(enabled);
1788     }
1789 
1790     @Override
1791     void onHorizontalSwipeStateChanged(int state) {
1792         if (state == ViewPager.SCROLL_STATE_DRAGGING) {
1793             if (mScrollStatus == SCROLL_STATUS_IDLE) {
1794                 mScrollStatus = SCROLL_STATUS_SCROLLING_HORIZONTAL;
1795                 setVerticalScrollEnabled(false);
1796             }
1797         } else if (state == ViewPager.SCROLL_STATE_IDLE) {
1798             if (mScrollStatus == SCROLL_STATUS_SCROLLING_HORIZONTAL) {
1799                 mScrollStatus = SCROLL_STATUS_IDLE;
1800                 setVerticalScrollEnabled(true);
1801             }
1802         }
1803     }
1804 
1805     @Override
1806     protected void maybeLogProfileChange() {
1807         getEventLog().logSharesheetProfileChanged();
1808     }
1809 
1810     private static class ProfileRecord {
1811         /** The {@link AppPredictor} for this profile, if any. */
1812         @Nullable
1813         public final AppPredictor appPredictor;
1814         /**
1815          * null if we should not load shortcuts.
1816          */
1817         @Nullable
1818         public final ShortcutLoader shortcutLoader;
1819         public long loadingStartTime;
1820 
1821         private ProfileRecord(
1822                 @Nullable AppPredictor appPredictor,
1823                 @Nullable ShortcutLoader shortcutLoader) {
1824             this.appPredictor = appPredictor;
1825             this.shortcutLoader = shortcutLoader;
1826         }
1827 
1828         public void destroy() {
1829             if (appPredictor != null) {
1830                 appPredictor.destroy();
1831             }
1832         }
1833     }
1834 }
1835