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