• 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.internal.app;
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.content.ContentProvider.getUserIdFromUri;
25 import static android.stats.devicepolicy.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL;
26 import static android.stats.devicepolicy.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK;
27 
28 import static com.android.internal.util.LatencyTracker.ACTION_LOAD_SHARE_SHEET;
29 
30 import static java.lang.annotation.RetentionPolicy.SOURCE;
31 
32 import android.animation.Animator;
33 import android.animation.AnimatorListenerAdapter;
34 import android.animation.AnimatorSet;
35 import android.animation.ObjectAnimator;
36 import android.animation.ValueAnimator;
37 import android.annotation.IntDef;
38 import android.annotation.NonNull;
39 import android.annotation.Nullable;
40 import android.app.Activity;
41 import android.app.ActivityManager;
42 import android.app.ActivityOptions;
43 import android.app.SharedElementCallback;
44 import android.app.prediction.AppPredictionContext;
45 import android.app.prediction.AppPredictionManager;
46 import android.app.prediction.AppPredictor;
47 import android.app.prediction.AppTarget;
48 import android.app.prediction.AppTargetEvent;
49 import android.app.prediction.AppTargetId;
50 import android.compat.annotation.UnsupportedAppUsage;
51 import android.content.ClipData;
52 import android.content.ClipboardManager;
53 import android.content.ComponentName;
54 import android.content.ContentResolver;
55 import android.content.Context;
56 import android.content.Intent;
57 import android.content.IntentFilter;
58 import android.content.IntentSender;
59 import android.content.IntentSender.SendIntentException;
60 import android.content.SharedPreferences;
61 import android.content.pm.ActivityInfo;
62 import android.content.pm.ApplicationInfo;
63 import android.content.pm.PackageManager;
64 import android.content.pm.PackageManager.NameNotFoundException;
65 import android.content.pm.ResolveInfo;
66 import android.content.pm.ShortcutInfo;
67 import android.content.pm.ShortcutManager;
68 import android.content.res.Configuration;
69 import android.content.res.Resources;
70 import android.database.Cursor;
71 import android.database.DataSetObserver;
72 import android.graphics.Bitmap;
73 import android.graphics.Canvas;
74 import android.graphics.Color;
75 import android.graphics.Insets;
76 import android.graphics.Paint;
77 import android.graphics.Path;
78 import android.graphics.drawable.AnimatedVectorDrawable;
79 import android.graphics.drawable.Drawable;
80 import android.metrics.LogMaker;
81 import android.net.Uri;
82 import android.os.AsyncTask;
83 import android.os.Bundle;
84 import android.os.Environment;
85 import android.os.Handler;
86 import android.os.Message;
87 import android.os.Parcelable;
88 import android.os.PatternMatcher;
89 import android.os.ResultReceiver;
90 import android.os.UserHandle;
91 import android.os.UserManager;
92 import android.os.storage.StorageManager;
93 import android.provider.DeviceConfig;
94 import android.provider.DocumentsContract;
95 import android.provider.Downloads;
96 import android.provider.OpenableColumns;
97 import android.provider.Settings;
98 import android.service.chooser.ChooserTarget;
99 import android.text.TextUtils;
100 import android.util.AttributeSet;
101 import android.util.HashedStringCache;
102 import android.util.Log;
103 import android.util.PluralsMessageFormatter;
104 import android.util.Size;
105 import android.util.Slog;
106 import android.view.LayoutInflater;
107 import android.view.View;
108 import android.view.View.MeasureSpec;
109 import android.view.View.OnClickListener;
110 import android.view.ViewGroup;
111 import android.view.ViewGroup.LayoutParams;
112 import android.view.ViewTreeObserver;
113 import android.view.WindowInsets;
114 import android.view.animation.AccelerateInterpolator;
115 import android.view.animation.AlphaAnimation;
116 import android.view.animation.Animation;
117 import android.view.animation.DecelerateInterpolator;
118 import android.view.animation.LinearInterpolator;
119 import android.widget.Button;
120 import android.widget.ImageView;
121 import android.widget.Space;
122 import android.widget.TextView;
123 
124 import com.android.internal.R;
125 import com.android.internal.annotations.VisibleForTesting;
126 import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyState;
127 import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider;
128 import com.android.internal.app.NoCrossProfileEmptyStateProvider.DevicePolicyBlockerEmptyState;
129 import com.android.internal.app.ResolverListAdapter.ActivityInfoPresentationGetter;
130 import com.android.internal.app.ResolverListAdapter.ViewHolder;
131 import com.android.internal.app.chooser.ChooserTargetInfo;
132 import com.android.internal.app.chooser.DisplayResolveInfo;
133 import com.android.internal.app.chooser.MultiDisplayResolveInfo;
134 import com.android.internal.app.chooser.NotSelectableTargetInfo;
135 import com.android.internal.app.chooser.SelectableTargetInfo;
136 import com.android.internal.app.chooser.SelectableTargetInfo.SelectableTargetInfoCommunicator;
137 import com.android.internal.app.chooser.TargetInfo;
138 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
139 import com.android.internal.content.PackageMonitor;
140 import com.android.internal.logging.MetricsLogger;
141 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
142 import com.android.internal.util.FrameworkStatsLog;
143 import com.android.internal.widget.GridLayoutManager;
144 import com.android.internal.widget.RecyclerView;
145 import com.android.internal.widget.ResolverDrawerLayout;
146 import com.android.internal.widget.ViewPager;
147 
148 import com.google.android.collect.Lists;
149 
150 import java.io.File;
151 import java.io.IOException;
152 import java.lang.annotation.Retention;
153 import java.lang.annotation.RetentionPolicy;
154 import java.net.URISyntaxException;
155 import java.text.Collator;
156 import java.util.ArrayList;
157 import java.util.Arrays;
158 import java.util.Collections;
159 import java.util.Comparator;
160 import java.util.HashMap;
161 import java.util.List;
162 import java.util.Map;
163 import java.util.Objects;
164 import java.util.function.Supplier;
165 import java.util.stream.Collectors;
166 
167 /**
168  * The Chooser Activity handles intent resolution specifically for sharing intents -
169  * for example, those generated by @see android.content.Intent#createChooser(Intent, CharSequence).
170  *
171  */
172 public class ChooserActivity extends ResolverActivity implements
173         ChooserListAdapter.ChooserListCommunicator,
174         SelectableTargetInfoCommunicator {
175     private static final String TAG = "ChooserActivity";
176 
177     private AppPredictor mPersonalAppPredictor;
178     private AppPredictor mWorkAppPredictor;
179     private boolean mShouldDisplayLandscape;
180 
181     @UnsupportedAppUsage
ChooserActivity()182     public ChooserActivity() {
183     }
184     /**
185      * Boolean extra to change the following behavior: Normally, ChooserActivity finishes itself
186      * in onStop when launched in a new task. If this extra is set to true, we do not finish
187      * ourselves when onStop gets called.
188      */
189     public static final String EXTRA_PRIVATE_RETAIN_IN_ON_STOP
190             = "com.android.internal.app.ChooserActivity.EXTRA_PRIVATE_RETAIN_IN_ON_STOP";
191 
192 
193     /**
194      * Transition name for the first image preview.
195      * To be used for shared element transition into this activity.
196      * @hide
197      */
198     public static final String FIRST_IMAGE_PREVIEW_TRANSITION_NAME = "screenshot_preview_image";
199 
200     private static final String PREF_NUM_SHEET_EXPANSIONS = "pref_num_sheet_expansions";
201 
202     private static final String CHIP_LABEL_METADATA_KEY = "android.service.chooser.chip_label";
203     private static final String CHIP_ICON_METADATA_KEY = "android.service.chooser.chip_icon";
204 
205     private static final boolean DEBUG = true;
206 
207     private static final boolean USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES = true;
208     // TODO(b/123088566) Share these in a better way.
209     private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share";
210     public static final String LAUNCH_LOCATION_DIRECT_SHARE = "direct_share";
211     public static final String CHOOSER_TARGET = "chooser_target";
212     private static final String SHORTCUT_TARGET = "shortcut_target";
213     private static final int APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
214     public static final String APP_PREDICTION_INTENT_FILTER_KEY = "intent_filter";
215     private static final String SHARED_TEXT_KEY = "shared_text";
216 
217     private static final String PLURALS_COUNT = "count";
218     private static final String PLURALS_FILE_NAME = "file_name";
219 
220     private static final String IMAGE_EDITOR_SHARED_ELEMENT = "screenshot_preview_image";
221 
222     private boolean mIsAppPredictorComponentAvailable;
223     private Map<ChooserTarget, AppTarget> mDirectShareAppTargetCache;
224     private Map<ChooserTarget, ShortcutInfo> mDirectShareShortcutInfoCache;
225 
226     public static final int TARGET_TYPE_DEFAULT = 0;
227     public static final int TARGET_TYPE_CHOOSER_TARGET = 1;
228     public static final int TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER = 2;
229     public static final int TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE = 3;
230 
231     public static final int SELECTION_TYPE_SERVICE = 1;
232     public static final int SELECTION_TYPE_APP = 2;
233     public static final int SELECTION_TYPE_STANDARD = 3;
234     public static final int SELECTION_TYPE_COPY = 4;
235     public static final int SELECTION_TYPE_NEARBY = 5;
236     public static final int SELECTION_TYPE_EDIT = 6;
237 
238     private static final int SCROLL_STATUS_IDLE = 0;
239     private static final int SCROLL_STATUS_SCROLLING_VERTICAL = 1;
240     private static final int SCROLL_STATUS_SCROLLING_HORIZONTAL = 2;
241 
242     // statsd logger wrapper
243     protected ChooserActivityLogger mChooserActivityLogger;
244 
245     @IntDef(flag = false, prefix = { "TARGET_TYPE_" }, value = {
246             TARGET_TYPE_DEFAULT,
247             TARGET_TYPE_CHOOSER_TARGET,
248             TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER,
249             TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE
250     })
251     @Retention(RetentionPolicy.SOURCE)
252     public @interface ShareTargetType {}
253 
254     /**
255      * The transition time between placeholders for direct share to a message
256      * indicating that non are available.
257      */
258     private static final int NO_DIRECT_SHARE_ANIM_IN_MILLIS = 200;
259 
260     private static final float DIRECT_SHARE_EXPANSION_RATE = 0.78f;
261 
262     private static final int DEFAULT_SALT_EXPIRATION_DAYS = 7;
263     private int mMaxHashSaltDays = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
264             SystemUiDeviceConfigFlags.HASH_SALT_MAX_DAYS,
265             DEFAULT_SALT_EXPIRATION_DAYS);
266 
267     private static final boolean DEFAULT_IS_NEARBY_SHARE_FIRST_TARGET_IN_RANKED_APP = false;
268     private boolean mIsNearbyShareFirstTargetInRankedApp =
269             DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
270                     SystemUiDeviceConfigFlags.IS_NEARBY_SHARE_FIRST_TARGET_IN_RANKED_APP,
271                     DEFAULT_IS_NEARBY_SHARE_FIRST_TARGET_IN_RANKED_APP);
272 
273     private static final int DEFAULT_LIST_VIEW_UPDATE_DELAY_MS = 0;
274 
275     private static final int URI_PERMISSION_INTENT_FLAGS = Intent.FLAG_GRANT_READ_URI_PERMISSION
276             | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
277             | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
278             | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
279 
280     @VisibleForTesting
281     int mListViewUpdateDelayMs = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
282             SystemUiDeviceConfigFlags.SHARESHEET_LIST_VIEW_UPDATE_DELAY,
283             DEFAULT_LIST_VIEW_UPDATE_DELAY_MS);
284 
285     private Bundle mReplacementExtras;
286     private IntentSender mChosenComponentSender;
287     private IntentSender mRefinementIntentSender;
288     private RefinementResultReceiver mRefinementResultReceiver;
289     private ChooserTarget[] mCallerChooserTargets;
290     private ComponentName[] mFilteredComponentNames;
291 
292     private Intent mReferrerFillInIntent;
293 
294     private long mChooserShownTime;
295     protected boolean mIsSuccessfullySelected;
296 
297     private long mQueriedSharingShortcutsTimeMs;
298 
299     private int mCurrAvailableWidth = 0;
300     private Insets mLastAppliedInsets = null;
301     private int mLastNumberOfChildren = -1;
302     private int mMaxTargetsPerRow = 1;
303 
304     private static final String TARGET_DETAILS_FRAGMENT_TAG = "targetDetailsFragment";
305 
306     private static final int MAX_LOG_RANK_POSITION = 12;
307 
308     private static final int MAX_EXTRA_INITIAL_INTENTS = 2;
309     private static final int MAX_EXTRA_CHOOSER_TARGETS = 2;
310 
311     private SharedPreferences mPinnedSharedPrefs;
312     private static final String PINNED_SHARED_PREFS_NAME = "chooser_pin_settings";
313 
314     @Retention(SOURCE)
315     @IntDef({CONTENT_PREVIEW_FILE, CONTENT_PREVIEW_IMAGE, CONTENT_PREVIEW_TEXT})
316     private @interface ContentPreviewType {
317     }
318 
319     // Starting at 1 since 0 is considered "undefined" for some of the database transformations
320     // of tron logs.
321     protected static final int CONTENT_PREVIEW_IMAGE = 1;
322     protected static final int CONTENT_PREVIEW_FILE = 2;
323     protected static final int CONTENT_PREVIEW_TEXT = 3;
324     protected MetricsLogger mMetricsLogger;
325 
326     private ContentPreviewCoordinator mPreviewCoord;
327     private int mScrollStatus = SCROLL_STATUS_IDLE;
328 
329     @VisibleForTesting
330     protected ChooserMultiProfilePagerAdapter mChooserMultiProfilePagerAdapter;
331     private final EnterTransitionAnimationDelegate mEnterTransitionAnimationDelegate =
332             new EnterTransitionAnimationDelegate();
333 
334     private boolean mRemoveSharedElements = false;
335 
336     private View mContentView = null;
337 
338     private class ContentPreviewCoordinator {
339         private static final int IMAGE_FADE_IN_MILLIS = 150;
340         private static final int IMAGE_LOAD_TIMEOUT = 1;
341         private static final int IMAGE_LOAD_INTO_VIEW = 2;
342 
343         private final int mImageLoadTimeoutMillis =
344                 getResources().getInteger(R.integer.config_shortAnimTime);
345 
346         private final View mParentView;
347         private boolean mHideParentOnFail;
348         private boolean mAtLeastOneLoaded = false;
349 
350         class LoadUriTask {
351             public final Uri mUri;
352             public final int mImageResourceId;
353             public final int mExtraCount;
354             public final Bitmap mBmp;
355 
LoadUriTask(int imageResourceId, Uri uri, int extraCount, Bitmap bmp)356             LoadUriTask(int imageResourceId, Uri uri, int extraCount, Bitmap bmp) {
357                 this.mImageResourceId = imageResourceId;
358                 this.mUri = uri;
359                 this.mExtraCount = extraCount;
360                 this.mBmp = bmp;
361             }
362         }
363 
364         // If at least one image loads within the timeout period, allow other
365         // loads to continue. Otherwise terminate and optionally hide
366         // the parent area
367         private final Handler mHandler = new Handler() {
368             @Override
369             public void handleMessage(Message msg) {
370                 switch (msg.what) {
371                     case IMAGE_LOAD_TIMEOUT:
372                         maybeHideContentPreview();
373                         break;
374 
375                     case IMAGE_LOAD_INTO_VIEW:
376                         if (isFinishing()) break;
377 
378                         LoadUriTask task = (LoadUriTask) msg.obj;
379                         RoundedRectImageView imageView = mParentView.findViewById(
380                                 task.mImageResourceId);
381                         if (task.mBmp == null) {
382                             imageView.setVisibility(View.GONE);
383                             maybeHideContentPreview();
384                             return;
385                         }
386 
387                         mAtLeastOneLoaded = true;
388                         imageView.setVisibility(View.VISIBLE);
389                         imageView.setAlpha(0.0f);
390                         imageView.setImageBitmap(task.mBmp);
391 
392                         ValueAnimator fadeAnim = ObjectAnimator.ofFloat(imageView, "alpha", 0.0f,
393                                 1.0f);
394                         fadeAnim.setInterpolator(new DecelerateInterpolator(1.0f));
395                         fadeAnim.setDuration(IMAGE_FADE_IN_MILLIS);
396                         fadeAnim.start();
397 
398                         if (task.mExtraCount > 0) {
399                             imageView.setExtraImageCount(task.mExtraCount);
400                         }
401 
402                         setupPreDrawForSharedElementTransition(imageView);
403                 }
404             }
405         };
406 
setupPreDrawForSharedElementTransition(View v)407         private void setupPreDrawForSharedElementTransition(View v) {
408             v.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
409                 @Override
410                 public boolean onPreDraw() {
411                     v.getViewTreeObserver().removeOnPreDrawListener(this);
412 
413                     if (!mRemoveSharedElements && isActivityTransitionRunning()) {
414                         // Disable the window animations as it interferes with the
415                         // transition animation.
416                         getWindow().setWindowAnimations(0);
417                     }
418                     mEnterTransitionAnimationDelegate.markImagePreviewReady();
419                     return true;
420                 }
421             });
422         }
423 
ContentPreviewCoordinator(View parentView, boolean hideParentOnFail)424         ContentPreviewCoordinator(View parentView, boolean hideParentOnFail) {
425             super();
426 
427             this.mParentView = parentView;
428             this.mHideParentOnFail = hideParentOnFail;
429         }
430 
loadUriIntoView(final int imageResourceId, final Uri uri, final int extraImages)431         private void loadUriIntoView(final int imageResourceId, final Uri uri,
432                 final int extraImages) {
433             mHandler.sendEmptyMessageDelayed(IMAGE_LOAD_TIMEOUT, mImageLoadTimeoutMillis);
434 
435             AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
436                 int size = getResources().getDimensionPixelSize(
437                         R.dimen.chooser_preview_image_max_dimen);
438                 final Bitmap bmp = loadThumbnail(uri, new Size(size, size));
439                 final Message msg = Message.obtain();
440                 msg.what = IMAGE_LOAD_INTO_VIEW;
441                 msg.obj = new LoadUriTask(imageResourceId, uri, extraImages, bmp);
442                 mHandler.sendMessage(msg);
443             });
444         }
445 
cancelLoads()446         private void cancelLoads() {
447             mHandler.removeMessages(IMAGE_LOAD_INTO_VIEW);
448             mHandler.removeMessages(IMAGE_LOAD_TIMEOUT);
449         }
450 
maybeHideContentPreview()451         private void maybeHideContentPreview() {
452             if (!mAtLeastOneLoaded) {
453                 if (mHideParentOnFail) {
454                     Log.i(TAG, "Hiding image preview area. Timed out waiting for preview to load"
455                             + " within " + mImageLoadTimeoutMillis + "ms.");
456                     collapseParentView();
457                     if (shouldShowTabs()) {
458                         hideStickyContentPreview();
459                     } else if (mChooserMultiProfilePagerAdapter.getCurrentRootAdapter() != null) {
460                         mChooserMultiProfilePagerAdapter.getCurrentRootAdapter()
461                                 .hideContentPreview();
462                     }
463                     mHideParentOnFail = false;
464                 }
465                 mRemoveSharedElements = true;
466                 mEnterTransitionAnimationDelegate.markImagePreviewReady();
467             }
468         }
469 
collapseParentView()470         private void collapseParentView() {
471             // This will effectively hide the content preview row by forcing the height
472             // to zero. It is faster than forcing a relayout of the listview
473             final View v = mParentView;
474             int widthSpec = MeasureSpec.makeMeasureSpec(v.getWidth(), MeasureSpec.EXACTLY);
475             int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY);
476             v.measure(widthSpec, heightSpec);
477             v.getLayoutParams().height = 0;
478             v.layout(v.getLeft(), v.getTop(), v.getRight(), v.getTop());
479             v.invalidate();
480         }
481     }
482 
483     private final ChooserHandler mChooserHandler = new ChooserHandler();
484 
485     private class ChooserHandler extends Handler {
486         private static final int LIST_VIEW_UPDATE_MESSAGE = 6;
487         private static final int SHORTCUT_MANAGER_ALL_SHARE_TARGET_RESULTS = 7;
488 
removeAllMessages()489         private void removeAllMessages() {
490             removeMessages(LIST_VIEW_UPDATE_MESSAGE);
491             removeMessages(SHORTCUT_MANAGER_ALL_SHARE_TARGET_RESULTS);
492         }
493 
494         @Override
handleMessage(Message msg)495         public void handleMessage(Message msg) {
496             if (mChooserMultiProfilePagerAdapter.getActiveListAdapter() == null || isDestroyed()) {
497                 return;
498             }
499 
500             switch (msg.what) {
501                 case LIST_VIEW_UPDATE_MESSAGE:
502                     if (DEBUG) {
503                         Log.d(TAG, "LIST_VIEW_UPDATE_MESSAGE; ");
504                     }
505 
506                     UserHandle userHandle = (UserHandle) msg.obj;
507                     mChooserMultiProfilePagerAdapter.getListAdapterForUserHandle(userHandle)
508                             .refreshListView();
509                     break;
510 
511                 case SHORTCUT_MANAGER_ALL_SHARE_TARGET_RESULTS:
512                     if (DEBUG) Log.d(TAG, "SHORTCUT_MANAGER_ALL_SHARE_TARGET_RESULTS");
513                     final ServiceResultInfo[] resultInfos = (ServiceResultInfo[]) msg.obj;
514                     for (ServiceResultInfo resultInfo : resultInfos) {
515                         if (resultInfo.resultTargets != null) {
516                             ChooserListAdapter adapterForUserHandle =
517                                     mChooserMultiProfilePagerAdapter.getListAdapterForUserHandle(
518                                             resultInfo.userHandle);
519                             if (adapterForUserHandle != null) {
520                                 adapterForUserHandle.addServiceResults(
521                                         resultInfo.originalTarget,
522                                         resultInfo.resultTargets, msg.arg1,
523                                         mDirectShareShortcutInfoCache);
524                             }
525                         }
526                     }
527 
528                     logDirectShareTargetReceived(
529                             MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER);
530                     sendVoiceChoicesIfNeeded();
531                     getChooserActivityLogger().logSharesheetDirectLoadComplete();
532 
533                     mChooserMultiProfilePagerAdapter.getActiveListAdapter()
534                             .completeServiceTargetLoading();
535                     break;
536 
537                 default:
538                     super.handleMessage(msg);
539             }
540         }
541     };
542 
543     @Override
onCreate(Bundle savedInstanceState)544     protected void onCreate(Bundle savedInstanceState) {
545         final long intentReceivedTime = System.currentTimeMillis();
546         mLatencyTracker.onActionStart(ACTION_LOAD_SHARE_SHEET);
547 
548         getChooserActivityLogger().logSharesheetTriggered();
549         // This is the only place this value is being set. Effectively final.
550         mIsAppPredictorComponentAvailable = isAppPredictionServiceAvailable();
551 
552         mIsSuccessfullySelected = false;
553         Intent intent = getIntent();
554         Parcelable targetParcelable = intent.getParcelableExtra(Intent.EXTRA_INTENT);
555         if (targetParcelable instanceof Uri) {
556             try {
557                 targetParcelable = Intent.parseUri(targetParcelable.toString(),
558                         Intent.URI_INTENT_SCHEME);
559             } catch (URISyntaxException ex) {
560                 // doesn't parse as an intent; let the next test fail and error out
561             }
562         }
563 
564         if (!(targetParcelable instanceof Intent)) {
565             Log.w("ChooserActivity", "Target is not an intent: " + targetParcelable);
566             finish();
567             super.onCreate(null);
568             return;
569         }
570         Intent target = (Intent) targetParcelable;
571         if (target != null) {
572             modifyTargetIntent(target);
573         }
574         Parcelable[] targetsParcelable
575                 = intent.getParcelableArrayExtra(Intent.EXTRA_ALTERNATE_INTENTS);
576         if (targetsParcelable != null) {
577             final boolean offset = target == null;
578             Intent[] additionalTargets =
579                     new Intent[offset ? targetsParcelable.length - 1 : targetsParcelable.length];
580             for (int i = 0; i < targetsParcelable.length; i++) {
581                 if (!(targetsParcelable[i] instanceof Intent)) {
582                     Log.w(TAG, "EXTRA_ALTERNATE_INTENTS array entry #" + i + " is not an Intent: "
583                             + targetsParcelable[i]);
584                     finish();
585                     super.onCreate(null);
586                     return;
587                 }
588                 final Intent additionalTarget = (Intent) targetsParcelable[i];
589                 if (i == 0 && target == null) {
590                     target = additionalTarget;
591                     modifyTargetIntent(target);
592                 } else {
593                     additionalTargets[offset ? i - 1 : i] = additionalTarget;
594                     modifyTargetIntent(additionalTarget);
595                 }
596             }
597             setAdditionalTargets(additionalTargets);
598         }
599 
600         mReplacementExtras = intent.getBundleExtra(Intent.EXTRA_REPLACEMENT_EXTRAS);
601 
602         // Do not allow the title to be changed when sharing content
603         CharSequence title = null;
604         if (target != null) {
605             if (!isSendAction(target)) {
606                 title = intent.getCharSequenceExtra(Intent.EXTRA_TITLE);
607             } else {
608                 Log.w(TAG, "Ignoring intent's EXTRA_TITLE, deprecated in P. You may wish to set a"
609                         + " preview title by using EXTRA_TITLE property of the wrapped"
610                         + " EXTRA_INTENT.");
611             }
612         }
613 
614         int defaultTitleRes = 0;
615         if (title == null) {
616             defaultTitleRes = com.android.internal.R.string.chooseActivity;
617         }
618 
619         Parcelable[] pa = intent.getParcelableArrayExtra(Intent.EXTRA_INITIAL_INTENTS);
620         Intent[] initialIntents = null;
621         if (pa != null) {
622             int count = Math.min(pa.length, MAX_EXTRA_INITIAL_INTENTS);
623             initialIntents = new Intent[count];
624             for (int i = 0; i < count; i++) {
625                 if (!(pa[i] instanceof Intent)) {
626                     Log.w(TAG, "Initial intent #" + i + " not an Intent: " + pa[i]);
627                     finish();
628                     super.onCreate(null);
629                     return;
630                 }
631                 final Intent in = (Intent) pa[i];
632                 modifyTargetIntent(in);
633                 initialIntents[i] = in;
634             }
635         }
636 
637         mReferrerFillInIntent = new Intent().putExtra(Intent.EXTRA_REFERRER, getReferrer());
638 
639         mChosenComponentSender = intent.getParcelableExtra(
640                 Intent.EXTRA_CHOSEN_COMPONENT_INTENT_SENDER);
641         mRefinementIntentSender = intent.getParcelableExtra(
642                 Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER);
643         setSafeForwardingMode(true);
644 
645         mPinnedSharedPrefs = getPinnedSharedPrefs(this);
646 
647         pa = intent.getParcelableArrayExtra(Intent.EXTRA_EXCLUDE_COMPONENTS);
648 
649 
650         // Exclude out Nearby from main list if chip is present, to avoid duplication
651         ComponentName nearbySharingComponent = getNearbySharingComponent();
652         boolean shouldFilterNearby = !shouldNearbyShareBeFirstInRankedRow()
653                 && nearbySharingComponent != null;
654 
655         if (pa != null) {
656             ComponentName[] names = new ComponentName[pa.length + (shouldFilterNearby ? 1 : 0)];
657             for (int i = 0; i < pa.length; i++) {
658                 if (!(pa[i] instanceof ComponentName)) {
659                     Log.w(TAG, "Filtered component #" + i + " not a ComponentName: " + pa[i]);
660                     names = null;
661                     break;
662                 }
663                 names[i] = (ComponentName) pa[i];
664             }
665             if (shouldFilterNearby) {
666                 names[names.length - 1] = nearbySharingComponent;
667             }
668 
669             mFilteredComponentNames = names;
670         } else if (shouldFilterNearby) {
671             mFilteredComponentNames = new ComponentName[1];
672             mFilteredComponentNames[0] = nearbySharingComponent;
673         }
674 
675         pa = intent.getParcelableArrayExtra(Intent.EXTRA_CHOOSER_TARGETS);
676         if (pa != null) {
677             int count = Math.min(pa.length, MAX_EXTRA_CHOOSER_TARGETS);
678             ChooserTarget[] targets = new ChooserTarget[count];
679             for (int i = 0; i < count; i++) {
680                 if (!(pa[i] instanceof ChooserTarget)) {
681                     Log.w(TAG, "Chooser target #" + i + " not a ChooserTarget: " + pa[i]);
682                     targets = null;
683                     break;
684                 }
685                 targets[i] = (ChooserTarget) pa[i];
686             }
687             mCallerChooserTargets = targets;
688         }
689 
690         mMaxTargetsPerRow = getResources().getInteger(R.integer.config_chooser_max_targets_per_row);
691         mShouldDisplayLandscape =
692                 shouldDisplayLandscape(getResources().getConfiguration().orientation);
693         setRetainInOnStop(intent.getBooleanExtra(EXTRA_PRIVATE_RETAIN_IN_ON_STOP, false));
694         super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents,
695                 null, false);
696 
697         mChooserShownTime = System.currentTimeMillis();
698         final long systemCost = mChooserShownTime - intentReceivedTime;
699 
700         getMetricsLogger().write(new LogMaker(MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN)
701                 .setSubtype(isWorkProfile() ? MetricsEvent.MANAGED_PROFILE :
702                         MetricsEvent.PARENT_PROFILE)
703                 .addTaggedData(MetricsEvent.FIELD_SHARESHEET_MIMETYPE, target.getType())
704                 .addTaggedData(MetricsEvent.FIELD_TIME_TO_APP_TARGETS, systemCost));
705 
706         if (mResolverDrawerLayout != null) {
707             mResolverDrawerLayout.addOnLayoutChangeListener(this::handleLayoutChange);
708 
709             // expand/shrink direct share 4 -> 8 viewgroup
710             if (isSendAction(target)) {
711                 mResolverDrawerLayout.setOnScrollChangeListener(this::handleScroll);
712             }
713 
714             mResolverDrawerLayout.setOnCollapsedChangedListener(
715                     new ResolverDrawerLayout.OnCollapsedChangedListener() {
716 
717                         // Only consider one expansion per activity creation
718                         private boolean mWrittenOnce = false;
719 
720                         @Override
721                         public void onCollapsedChanged(boolean isCollapsed) {
722                             if (!isCollapsed && !mWrittenOnce) {
723                                 incrementNumSheetExpansions();
724                                 mWrittenOnce = true;
725                             }
726                             getChooserActivityLogger()
727                                     .logSharesheetExpansionChanged(isCollapsed);
728                         }
729                     });
730         }
731 
732         if (DEBUG) {
733             Log.d(TAG, "System Time Cost is " + systemCost);
734         }
735 
736         getChooserActivityLogger().logShareStarted(
737                 FrameworkStatsLog.SHARESHEET_STARTED,
738                 getReferrerPackageName(),
739                 target.getType(),
740                 mCallerChooserTargets == null ? 0 : mCallerChooserTargets.length,
741                 initialIntents == null ? 0 : initialIntents.length,
742                 isWorkProfile(),
743                 findPreferredContentPreview(getTargetIntent(), getContentResolver()),
744                 target.getAction()
745         );
746         mDirectShareShortcutInfoCache = new HashMap<>();
747 
748         setEnterSharedElementCallback(new SharedElementCallback() {
749             @Override
750             public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) {
751                 if (mRemoveSharedElements) {
752                     names.remove(FIRST_IMAGE_PREVIEW_TRANSITION_NAME);
753                     sharedElements.remove(FIRST_IMAGE_PREVIEW_TRANSITION_NAME);
754                 }
755                 super.onMapSharedElements(names, sharedElements);
756                 mRemoveSharedElements = false;
757             }
758         });
759         mEnterTransitionAnimationDelegate.postponeTransition();
760     }
761 
762     @Override
appliedThemeResId()763     protected int appliedThemeResId() {
764         return R.style.Theme_DeviceDefault_Chooser;
765     }
766 
setupAppPredictorForUser(UserHandle userHandle, AppPredictor.Callback appPredictorCallback)767     private AppPredictor setupAppPredictorForUser(UserHandle userHandle,
768             AppPredictor.Callback appPredictorCallback) {
769         AppPredictor appPredictor = getAppPredictorForDirectShareIfEnabled(userHandle);
770         if (appPredictor == null) {
771             return null;
772         }
773         mDirectShareAppTargetCache = new HashMap<>();
774         appPredictor.registerPredictionUpdates(this.getMainExecutor(), appPredictorCallback);
775         return appPredictor;
776     }
777 
createAppPredictorCallback( ChooserListAdapter chooserListAdapter)778     private AppPredictor.Callback createAppPredictorCallback(
779             ChooserListAdapter chooserListAdapter) {
780         return resultList -> {
781             if (isFinishing() || isDestroyed()) {
782                 return;
783             }
784             if (chooserListAdapter.getCount() == 0) {
785                 return;
786             }
787             if (resultList.isEmpty()
788                     && shouldQueryShortcutManager(chooserListAdapter.getUserHandle())) {
789                 // APS may be disabled, so try querying targets ourselves.
790                 queryDirectShareTargets(chooserListAdapter, true);
791                 return;
792             }
793             final List<ShortcutManager.ShareShortcutInfo> shareShortcutInfos =
794                     new ArrayList<>();
795 
796             List<AppTarget> shortcutResults = new ArrayList<>();
797             for (AppTarget appTarget : resultList) {
798                 if (appTarget.getShortcutInfo() == null) {
799                     continue;
800                 }
801                 shortcutResults.add(appTarget);
802             }
803             resultList = shortcutResults;
804             for (AppTarget appTarget : resultList) {
805                 shareShortcutInfos.add(new ShortcutManager.ShareShortcutInfo(
806                         appTarget.getShortcutInfo(),
807                         new ComponentName(
808                                 appTarget.getPackageName(), appTarget.getClassName())));
809             }
810             sendShareShortcutInfoList(shareShortcutInfos, chooserListAdapter, resultList,
811                     chooserListAdapter.getUserHandle());
812         };
813     }
814 
815     static SharedPreferences getPinnedSharedPrefs(Context context) {
816         // The code below is because in the android:ui process, no one can hear you scream.
817         // The package info in the context isn't initialized in the way it is for normal apps,
818         // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we
819         // build the path manually below using the same policy that appears in ContextImpl.
820         // This fails silently under the hood if there's a problem, so if we find ourselves in
821         // the case where we don't have access to credential encrypted storage we just won't
822         // have our pinned target info.
823         final File prefsFile = new File(new File(
824                 Environment.getDataUserCePackageDirectory(StorageManager.UUID_PRIVATE_INTERNAL,
825                         context.getUserId(), context.getPackageName()),
826                 "shared_prefs"),
827                 PINNED_SHARED_PREFS_NAME + ".xml");
828         return context.getSharedPreferences(prefsFile, MODE_PRIVATE);
829     }
830 
831     @Override
832     protected AbstractMultiProfilePagerAdapter createMultiProfilePagerAdapter(
833             Intent[] initialIntents,
834             List<ResolveInfo> rList,
835             boolean filterLastUsed) {
836         if (shouldShowTabs()) {
837             mChooserMultiProfilePagerAdapter = createChooserMultiProfilePagerAdapterForTwoProfiles(
838                     initialIntents, rList, filterLastUsed);
839         } else {
840             mChooserMultiProfilePagerAdapter = createChooserMultiProfilePagerAdapterForOneProfile(
841                     initialIntents, rList, filterLastUsed);
842         }
843         return mChooserMultiProfilePagerAdapter;
844     }
845 
846     @Override
847     protected EmptyStateProvider createBlockerEmptyStateProvider() {
848         final boolean isSendAction = isSendAction(getTargetIntent());
849 
850         final EmptyState noWorkToPersonalEmptyState =
851                 new DevicePolicyBlockerEmptyState(
852                 /* context= */ this,
853                 /* devicePolicyStringTitleId= */ RESOLVER_CROSS_PROFILE_BLOCKED_TITLE,
854                 /* defaultTitleResource= */ R.string.resolver_cross_profile_blocked,
855                 /* devicePolicyStringSubtitleId= */
856                 isSendAction ? RESOLVER_CANT_SHARE_WITH_PERSONAL : RESOLVER_CANT_ACCESS_PERSONAL,
857                 /* defaultSubtitleResource= */
858                 isSendAction ? R.string.resolver_cant_share_with_personal_apps_explanation
859                         : R.string.resolver_cant_access_personal_apps_explanation,
860                 /* devicePolicyEventId= */ RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL,
861                 /* devicePolicyEventCategory= */ ResolverActivity.METRICS_CATEGORY_CHOOSER);
862 
863         final EmptyState noPersonalToWorkEmptyState =
864                 new DevicePolicyBlockerEmptyState(
865                 /* context= */ this,
866                 /* devicePolicyStringTitleId= */ RESOLVER_CROSS_PROFILE_BLOCKED_TITLE,
867                 /* defaultTitleResource= */ R.string.resolver_cross_profile_blocked,
868                 /* devicePolicyStringSubtitleId= */
869                 isSendAction ? RESOLVER_CANT_SHARE_WITH_WORK : RESOLVER_CANT_ACCESS_WORK,
870                 /* defaultSubtitleResource= */
871                 isSendAction ? R.string.resolver_cant_share_with_work_apps_explanation
872                         : R.string.resolver_cant_access_work_apps_explanation,
873                 /* devicePolicyEventId= */ RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK,
874                 /* devicePolicyEventCategory= */ ResolverActivity.METRICS_CATEGORY_CHOOSER);
875 
876         return new NoCrossProfileEmptyStateProvider(getPersonalProfileUserHandle(),
877                 noWorkToPersonalEmptyState, noPersonalToWorkEmptyState,
878                 createCrossProfileIntentsChecker(), createMyUserIdProvider());
879     }
880 
881     private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForOneProfile(
882             Intent[] initialIntents,
883             List<ResolveInfo> rList,
884             boolean filterLastUsed) {
885         ChooserGridAdapter adapter = createChooserGridAdapter(
886                 /* context */ this,
887                 /* payloadIntents */ mIntents,
888                 initialIntents,
889                 rList,
890                 filterLastUsed,
891                 /* userHandle */ UserHandle.of(UserHandle.myUserId()));
892         return new ChooserMultiProfilePagerAdapter(
893                 /* context */ this,
894                 adapter,
895                 createEmptyStateProvider(/* workProfileUserHandle= */ null),
896                 mQuietModeManager,
897                 /* workProfileUserHandle= */ null,
898                 mMaxTargetsPerRow);
899     }
900 
901     private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForTwoProfiles(
902             Intent[] initialIntents,
903             List<ResolveInfo> rList,
904             boolean filterLastUsed) {
905         int selectedProfile = findSelectedProfile();
906         ChooserGridAdapter personalAdapter = createChooserGridAdapter(
907                 /* context */ this,
908                 /* payloadIntents */ mIntents,
909                 selectedProfile == PROFILE_PERSONAL ? initialIntents : null,
910                 rList,
911                 filterLastUsed,
912                 /* userHandle */ getPersonalProfileUserHandle());
913         ChooserGridAdapter workAdapter = createChooserGridAdapter(
914                 /* context */ this,
915                 /* payloadIntents */ mIntents,
916                 selectedProfile == PROFILE_WORK ? initialIntents : null,
917                 rList,
918                 filterLastUsed,
919                 /* userHandle */ getWorkProfileUserHandle());
920         return new ChooserMultiProfilePagerAdapter(
921                 /* context */ this,
922                 personalAdapter,
923                 workAdapter,
924                 createEmptyStateProvider(/* workProfileUserHandle= */ getWorkProfileUserHandle()),
925                 mQuietModeManager,
926                 selectedProfile,
927                 getWorkProfileUserHandle(),
928                 mMaxTargetsPerRow);
929     }
930 
931     private int findSelectedProfile() {
932         int selectedProfile = getSelectedProfileExtra();
933         if (selectedProfile == -1) {
934             selectedProfile = getProfileForUser(getUser());
935         }
936         return selectedProfile;
937     }
938 
939     @Override
940     protected boolean postRebuildList(boolean rebuildCompleted) {
941         updateStickyContentPreview();
942         if (shouldShowStickyContentPreview()
943                 || mChooserMultiProfilePagerAdapter
944                         .getCurrentRootAdapter().getSystemRowCount() != 0) {
945             logActionShareWithPreview();
946         }
947         return postRebuildListInternal(rebuildCompleted);
948     }
949 
950     /**
951      * Returns true if app prediction service is defined and the component exists on device.
952      */
953     private boolean isAppPredictionServiceAvailable() {
954         return getPackageManager().getAppPredictionServicePackageName() != null;
955     }
956 
957     /**
958      * Check if the profile currently used is a work profile.
959      * @return true if it is work profile, false if it is parent profile (or no work profile is
960      * set up)
961      */
962     protected boolean isWorkProfile() {
963         return getSystemService(UserManager.class)
964                 .getUserInfo(UserHandle.myUserId()).isManagedProfile();
965     }
966 
967     @Override
968     protected PackageMonitor createPackageMonitor(ResolverListAdapter listAdapter) {
969         return new PackageMonitor() {
970             @Override
971             public void onSomePackagesChanged() {
972                 handlePackagesChanged(listAdapter);
973             }
974         };
975     }
976 
977     /**
978      * Update UI to reflect changes in data.
979      */
980     public void handlePackagesChanged() {
981         handlePackagesChanged(/* listAdapter */ null);
982     }
983 
984     /**
985      * Update UI to reflect changes in data.
986      * <p>If {@code listAdapter} is {@code null}, both profile list adapters are updated if
987      * available.
988      */
989     private void handlePackagesChanged(@Nullable ResolverListAdapter listAdapter) {
990         // Refresh pinned items
991         mPinnedSharedPrefs = getPinnedSharedPrefs(this);
992         if (listAdapter == null) {
993             mChooserMultiProfilePagerAdapter.getActiveListAdapter().handlePackagesChanged();
994             if (mChooserMultiProfilePagerAdapter.getCount() > 1) {
995                 mChooserMultiProfilePagerAdapter.getInactiveListAdapter().handlePackagesChanged();
996             }
997         } else {
998             listAdapter.handlePackagesChanged();
999         }
1000         updateProfileViewButton();
1001     }
1002 
1003     private void onCopyButtonClicked(View v) {
1004         Intent targetIntent = getTargetIntent();
1005         if (targetIntent == null) {
1006             finish();
1007         } else {
1008             final String action = targetIntent.getAction();
1009 
1010             ClipData clipData = null;
1011             if (Intent.ACTION_SEND.equals(action)) {
1012                 String extraText = targetIntent.getStringExtra(Intent.EXTRA_TEXT);
1013                 Uri extraStream = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
1014 
1015                 if (extraText != null) {
1016                     clipData = ClipData.newPlainText(null, extraText);
1017                 } else if (extraStream != null) {
1018                     clipData = ClipData.newUri(getContentResolver(), null, extraStream);
1019                 } else {
1020                     Log.w(TAG, "No data available to copy to clipboard");
1021                     return;
1022                 }
1023             } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
1024                 final ArrayList<Uri> streams = targetIntent.getParcelableArrayListExtra(
1025                         Intent.EXTRA_STREAM);
1026                 clipData = ClipData.newUri(getContentResolver(), null, streams.get(0));
1027                 for (int i = 1; i < streams.size(); i++) {
1028                     clipData.addItem(getContentResolver(), new ClipData.Item(streams.get(i)));
1029                 }
1030             } else {
1031                 // expected to only be visible with ACTION_SEND or ACTION_SEND_MULTIPLE
1032                 // so warn about unexpected action
1033                 Log.w(TAG, "Action (" + action + ") not supported for copying to clipboard");
1034                 return;
1035             }
1036 
1037             ClipboardManager clipboardManager = (ClipboardManager) getSystemService(
1038                     Context.CLIPBOARD_SERVICE);
1039             clipboardManager.setPrimaryClipAsPackage(clipData, getReferrerPackageName());
1040 
1041             // Log share completion via copy
1042             LogMaker targetLogMaker = new LogMaker(
1043                     MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SYSTEM_TARGET).setSubtype(1);
1044             getMetricsLogger().write(targetLogMaker);
1045             getChooserActivityLogger().logShareTargetSelected(
1046                     SELECTION_TYPE_COPY,
1047                     "",
1048                     -1,
1049                     false);
1050 
1051             setResult(RESULT_OK);
1052             finish();
1053         }
1054     }
1055 
1056     @Override
1057     protected void onResume() {
1058         super.onResume();
1059         Log.d(TAG, "onResume: " + getComponentName().flattenToShortString());
1060         maybeCancelFinishAnimation();
1061     }
1062 
1063     @Override
1064     public void onConfigurationChanged(Configuration newConfig) {
1065         super.onConfigurationChanged(newConfig);
1066         ViewPager viewPager = findViewById(R.id.profile_pager);
1067         if (viewPager.isLayoutRtl()) {
1068             mMultiProfilePagerAdapter.setupViewPager(viewPager);
1069         }
1070 
1071         mShouldDisplayLandscape = shouldDisplayLandscape(newConfig.orientation);
1072         mMaxTargetsPerRow = getResources().getInteger(R.integer.config_chooser_max_targets_per_row);
1073         mChooserMultiProfilePagerAdapter.setMaxTargetsPerRow(mMaxTargetsPerRow);
1074         adjustPreviewWidth(newConfig.orientation, null);
1075         updateStickyContentPreview();
1076         updateTabPadding();
1077     }
1078 
1079     private boolean shouldDisplayLandscape(int orientation) {
1080         // Sharesheet fixes the # of items per row and therefore can not correctly lay out
1081         // when in the restricted size of multi-window mode. In the future, would be nice
1082         // to use minimum dp size requirements instead
1083         return orientation == Configuration.ORIENTATION_LANDSCAPE && !isInMultiWindowMode();
1084     }
1085 
1086     private void adjustPreviewWidth(int orientation, View parent) {
1087         int width = -1;
1088         if (mShouldDisplayLandscape) {
1089             width = getResources().getDimensionPixelSize(R.dimen.chooser_preview_width);
1090         }
1091 
1092         parent = parent == null ? getWindow().getDecorView() : parent;
1093 
1094         updateLayoutWidth(R.id.content_preview_text_layout, width, parent);
1095         updateLayoutWidth(R.id.content_preview_title_layout, width, parent);
1096         updateLayoutWidth(R.id.content_preview_file_layout, width, parent);
1097     }
1098 
1099     private void updateTabPadding() {
1100         if (shouldShowTabs()) {
1101             View tabs = findViewById(R.id.tabs);
1102             float iconSize = getResources().getDimension(R.dimen.chooser_icon_size);
1103             // The entire width consists of icons or padding. Divide the item padding in half to get
1104             // paddingHorizontal.
1105             float padding = (tabs.getWidth() - mMaxTargetsPerRow * iconSize)
1106                     / mMaxTargetsPerRow / 2;
1107             // Subtract the margin the buttons already have.
1108             padding -= getResources().getDimension(R.dimen.resolver_profile_tab_margin);
1109             tabs.setPadding((int) padding, 0, (int) padding, 0);
1110         }
1111     }
1112 
1113     private void updateLayoutWidth(int layoutResourceId, int width, View parent) {
1114         View view = parent.findViewById(layoutResourceId);
1115         if (view != null && view.getLayoutParams() != null) {
1116             LayoutParams params = view.getLayoutParams();
1117             params.width = width;
1118             view.setLayoutParams(params);
1119         }
1120     }
1121 
1122     /**
1123      * Create a view that will be shown in the content preview area
1124      * @param parent reference to the parent container where the view should be attached to
1125      * @return content preview view
1126      */
1127     protected ViewGroup createContentPreviewView(ViewGroup parent) {
1128         Intent targetIntent = getTargetIntent();
1129         int previewType = findPreferredContentPreview(targetIntent, getContentResolver());
1130         return displayContentPreview(previewType, targetIntent, getLayoutInflater(), parent);
1131     }
1132 
1133     @VisibleForTesting
1134     protected ComponentName getNearbySharingComponent() {
1135         String nearbyComponent = Settings.Secure.getString(
1136                 getContentResolver(),
1137                 Settings.Secure.NEARBY_SHARING_COMPONENT);
1138         if (TextUtils.isEmpty(nearbyComponent)) {
1139             nearbyComponent = getString(R.string.config_defaultNearbySharingComponent);
1140         }
1141         if (TextUtils.isEmpty(nearbyComponent)) {
1142             return null;
1143         }
1144         return ComponentName.unflattenFromString(nearbyComponent);
1145     }
1146 
1147     @VisibleForTesting
1148     protected @Nullable ComponentName getEditSharingComponent() {
1149         String editorPackage = getApplicationContext().getString(R.string.config_systemImageEditor);
1150         if (editorPackage == null || TextUtils.isEmpty(editorPackage)) {
1151             return null;
1152         }
1153         return ComponentName.unflattenFromString(editorPackage);
1154     }
1155 
1156     @VisibleForTesting
1157     protected TargetInfo getEditSharingTarget(Intent originalIntent) {
1158         final ComponentName cn = getEditSharingComponent();
1159 
1160         final Intent resolveIntent = new Intent(originalIntent);
1161         // Retain only URI permission grant flags if present. Other flags may prevent the scene
1162         // transition animation from running (i.e FLAG_ACTIVITY_NO_ANIMATION,
1163         // FLAG_ACTIVITY_NEW_TASK, FLAG_ACTIVITY_NEW_DOCUMENT) but also not needed.
1164         resolveIntent.setFlags(originalIntent.getFlags() & URI_PERMISSION_INTENT_FLAGS);
1165         resolveIntent.setComponent(cn);
1166         resolveIntent.setAction(Intent.ACTION_EDIT);
1167         String originalAction = originalIntent.getAction();
1168         if (Intent.ACTION_SEND.equals(originalAction)) {
1169             if (resolveIntent.getData() == null) {
1170                 Uri uri = resolveIntent.getParcelableExtra(Intent.EXTRA_STREAM);
1171                 if (uri != null) {
1172                     String mimeType = getContentResolver().getType(uri);
1173                     resolveIntent.setDataAndType(uri, mimeType);
1174                 }
1175             }
1176         } else {
1177             Log.e(TAG, originalAction + " is not supported.");
1178             return null;
1179         }
1180         final ResolveInfo ri = getPackageManager().resolveActivity(
1181                 resolveIntent, PackageManager.GET_META_DATA);
1182         if (ri == null || ri.activityInfo == null) {
1183             Log.e(TAG, "Device-specified image edit component (" + cn
1184                     + ") not available");
1185             return null;
1186         }
1187 
1188         final DisplayResolveInfo dri = new DisplayResolveInfo(
1189                 originalIntent, ri, getString(R.string.screenshot_edit), "", resolveIntent, null);
1190         dri.setDisplayIcon(getDrawable(R.drawable.ic_screenshot_edit));
1191         return dri;
1192     }
1193 
1194     @VisibleForTesting
1195     protected TargetInfo getNearbySharingTarget(Intent originalIntent) {
1196         final ComponentName cn = getNearbySharingComponent();
1197         if (cn == null) return null;
1198 
1199         final Intent resolveIntent = new Intent(originalIntent);
1200         resolveIntent.setComponent(cn);
1201         final ResolveInfo ri = getPackageManager().resolveActivity(
1202                 resolveIntent, PackageManager.GET_META_DATA);
1203         if (ri == null || ri.activityInfo == null) {
1204             Log.e(TAG, "Device-specified nearby sharing component (" + cn
1205                     + ") not available");
1206             return null;
1207         }
1208 
1209         // Allow the nearby sharing component to provide a more appropriate icon and label
1210         // for the chip.
1211         CharSequence name = null;
1212         Drawable icon = null;
1213         final Bundle metaData = ri.activityInfo.metaData;
1214         if (metaData != null) {
1215             try {
1216                 final Resources pkgRes = getPackageManager().getResourcesForActivity(cn);
1217                 final int nameResId = metaData.getInt(CHIP_LABEL_METADATA_KEY);
1218                 name = pkgRes.getString(nameResId);
1219                 final int resId = metaData.getInt(CHIP_ICON_METADATA_KEY);
1220                 icon = pkgRes.getDrawable(resId);
1221             } catch (Resources.NotFoundException ex) {
1222             } catch (NameNotFoundException ex) {
1223             }
1224         }
1225         if (TextUtils.isEmpty(name)) {
1226             name = ri.loadLabel(getPackageManager());
1227         }
1228         if (icon == null) {
1229             icon = ri.loadIcon(getPackageManager());
1230         }
1231 
1232         final DisplayResolveInfo dri = new DisplayResolveInfo(
1233                 originalIntent, ri, name, "", resolveIntent, null);
1234         dri.setDisplayIcon(icon);
1235         return dri;
1236     }
1237 
1238     private Button createActionButton(Drawable icon, CharSequence title, View.OnClickListener r) {
1239         Button b = (Button) LayoutInflater.from(this).inflate(R.layout.chooser_action_button, null);
1240         if (icon != null) {
1241             final int size = getResources()
1242                     .getDimensionPixelSize(R.dimen.chooser_action_button_icon_size);
1243             icon.setBounds(0, 0, size, size);
1244             b.setCompoundDrawablesRelative(icon, null, null, null);
1245         }
1246         b.setText(title);
1247         b.setOnClickListener(r);
1248         return b;
1249     }
1250 
1251     private Button createCopyButton() {
1252         final Button b = createActionButton(
1253                 getDrawable(R.drawable.ic_menu_copy_material),
1254                 getString(R.string.copy), this::onCopyButtonClicked);
1255         b.setId(R.id.chooser_copy_button);
1256         return b;
1257     }
1258 
1259     private @Nullable Button createNearbyButton(Intent originalIntent) {
1260         final TargetInfo ti = getNearbySharingTarget(originalIntent);
1261         if (ti == null) return null;
1262 
1263         final Button b = createActionButton(
1264                 ti.getDisplayIcon(this),
1265                 ti.getDisplayLabel(),
1266                 (View unused) -> {
1267                     // Log share completion via nearby
1268                     getChooserActivityLogger().logShareTargetSelected(
1269                             SELECTION_TYPE_NEARBY,
1270                             "",
1271                             -1,
1272                             false);
1273                     // Action bar is user-independent, always start as primary
1274                     safelyStartActivityAsUser(ti, getPersonalProfileUserHandle());
1275                     finish();
1276                 }
1277         );
1278         b.setId(R.id.chooser_nearby_button);
1279         return b;
1280     }
1281 
1282     private @Nullable Button createEditButton(Intent originalIntent) {
1283         final TargetInfo ti = getEditSharingTarget(originalIntent);
1284         if (ti == null) return null;
1285 
1286         final Button b = createActionButton(
1287                 ti.getDisplayIcon(this),
1288                 ti.getDisplayLabel(),
1289                 (View unused) -> {
1290                     // Log share completion via edit
1291                     getChooserActivityLogger().logShareTargetSelected(
1292                             SELECTION_TYPE_EDIT,
1293                             "",
1294                             -1,
1295                             false);
1296                     View firstImgView = getFirstVisibleImgPreviewView();
1297                     // Action bar is user-independent, always start as primary
1298                     if (firstImgView == null) {
1299                         safelyStartActivityAsUser(ti, getPersonalProfileUserHandle());
1300                         finish();
1301                     } else {
1302                         ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(
1303                                 this, firstImgView, IMAGE_EDITOR_SHARED_ELEMENT);
1304                         safelyStartActivityAsUser(
1305                                 ti, getPersonalProfileUserHandle(), options.toBundle());
1306                         startFinishAnimation();
1307                     }
1308                 }
1309         );
1310         b.setId(R.id.chooser_edit_button);
1311         return b;
1312     }
1313 
1314     @Nullable
1315     private View getFirstVisibleImgPreviewView() {
1316         View firstImage = findViewById(R.id.content_preview_image_1_large);
1317         return firstImage != null && firstImage.isVisibleToUser() ? firstImage : null;
1318     }
1319 
1320     private void addActionButton(ViewGroup parent, Button b) {
1321         if (b == null) return;
1322         final ViewGroup.MarginLayoutParams lp = new ViewGroup.MarginLayoutParams(
1323                         LayoutParams.WRAP_CONTENT,
1324                         LayoutParams.WRAP_CONTENT
1325                 );
1326         final int gap = getResources().getDimensionPixelSize(R.dimen.resolver_icon_margin) / 2;
1327         lp.setMarginsRelative(gap, 0, gap, 0);
1328         parent.addView(b, lp);
1329     }
1330 
1331     private ViewGroup displayContentPreview(@ContentPreviewType int previewType,
1332             Intent targetIntent, LayoutInflater layoutInflater, ViewGroup parent) {
1333         ViewGroup layout = null;
1334 
1335         switch (previewType) {
1336             case CONTENT_PREVIEW_TEXT:
1337                 layout = displayTextContentPreview(targetIntent, layoutInflater, parent);
1338                 break;
1339             case CONTENT_PREVIEW_IMAGE:
1340                 layout = displayImageContentPreview(targetIntent, layoutInflater, parent);
1341                 break;
1342             case CONTENT_PREVIEW_FILE:
1343                 layout = displayFileContentPreview(targetIntent, layoutInflater, parent);
1344                 break;
1345             default:
1346                 Log.e(TAG, "Unexpected content preview type: " + previewType);
1347         }
1348 
1349         if (layout != null) {
1350             adjustPreviewWidth(getResources().getConfiguration().orientation, layout);
1351         }
1352         if (previewType != CONTENT_PREVIEW_IMAGE) {
1353             mEnterTransitionAnimationDelegate.markImagePreviewReady();
1354         }
1355 
1356         return layout;
1357     }
1358 
1359     private ViewGroup displayTextContentPreview(Intent targetIntent, LayoutInflater layoutInflater,
1360             ViewGroup parent) {
1361         ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
1362                 R.layout.chooser_grid_preview_text, parent, false);
1363 
1364         final ViewGroup actionRow =
1365                 (ViewGroup) contentPreviewLayout.findViewById(R.id.chooser_action_row);
1366         addActionButton(actionRow, createCopyButton());
1367         if (shouldNearbyShareBeIncludedAsActionButton()) {
1368             addActionButton(actionRow, createNearbyButton(targetIntent));
1369         }
1370 
1371         CharSequence sharingText = targetIntent.getCharSequenceExtra(Intent.EXTRA_TEXT);
1372         if (sharingText == null) {
1373             contentPreviewLayout.findViewById(R.id.content_preview_text_layout).setVisibility(
1374                     View.GONE);
1375         } else {
1376             TextView textView = contentPreviewLayout.findViewById(R.id.content_preview_text);
1377             textView.setText(sharingText);
1378         }
1379 
1380         String previewTitle = targetIntent.getStringExtra(Intent.EXTRA_TITLE);
1381         if (TextUtils.isEmpty(previewTitle)) {
1382             contentPreviewLayout.findViewById(R.id.content_preview_title_layout).setVisibility(
1383                     View.GONE);
1384         } else {
1385             TextView previewTitleView = contentPreviewLayout.findViewById(
1386                     R.id.content_preview_title);
1387             previewTitleView.setText(previewTitle);
1388 
1389             ClipData previewData = targetIntent.getClipData();
1390             Uri previewThumbnail = null;
1391             if (previewData != null) {
1392                 if (previewData.getItemCount() > 0) {
1393                     ClipData.Item previewDataItem = previewData.getItemAt(0);
1394                     previewThumbnail = previewDataItem.getUri();
1395                 }
1396             }
1397 
1398             ImageView previewThumbnailView = contentPreviewLayout.findViewById(
1399                     R.id.content_preview_thumbnail);
1400             if (!validForContentPreview(previewThumbnail)) {
1401                 previewThumbnailView.setVisibility(View.GONE);
1402             } else {
1403                 mPreviewCoord = new ContentPreviewCoordinator(contentPreviewLayout, false);
1404                 mPreviewCoord.loadUriIntoView(R.id.content_preview_thumbnail, previewThumbnail, 0);
1405             }
1406         }
1407 
1408         return contentPreviewLayout;
1409     }
1410 
1411     private ViewGroup displayImageContentPreview(Intent targetIntent, LayoutInflater layoutInflater,
1412             ViewGroup parent) {
1413         ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
1414                 R.layout.chooser_grid_preview_image, parent, false);
1415         ViewGroup imagePreview = contentPreviewLayout.findViewById(R.id.content_preview_image_area);
1416 
1417         final ViewGroup actionRow =
1418                 (ViewGroup) contentPreviewLayout.findViewById(R.id.chooser_action_row);
1419         //TODO: addActionButton(actionRow, createCopyButton());
1420         if (shouldNearbyShareBeIncludedAsActionButton()) {
1421             addActionButton(actionRow, createNearbyButton(targetIntent));
1422         }
1423         addActionButton(actionRow, createEditButton(targetIntent));
1424 
1425         mPreviewCoord = new ContentPreviewCoordinator(contentPreviewLayout, false);
1426 
1427         String action = targetIntent.getAction();
1428         if (Intent.ACTION_SEND.equals(action)) {
1429             Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
1430             if (!validForContentPreview(uri)) {
1431                 contentPreviewLayout.setVisibility(View.GONE);
1432                 return contentPreviewLayout;
1433             }
1434             imagePreview.findViewById(R.id.content_preview_image_1_large)
1435                     .setTransitionName(ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME);
1436             mPreviewCoord.loadUriIntoView(R.id.content_preview_image_1_large, uri, 0);
1437         } else {
1438             ContentResolver resolver = getContentResolver();
1439 
1440             List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
1441             List<Uri> imageUris = new ArrayList<>();
1442             for (Uri uri : uris) {
1443                 if (validForContentPreview(uri) && isImageType(resolver.getType(uri))) {
1444                     imageUris.add(uri);
1445                 }
1446             }
1447 
1448             if (imageUris.size() == 0) {
1449                 Log.i(TAG, "Attempted to display image preview area with zero"
1450                         + " available images detected in EXTRA_STREAM list");
1451                 imagePreview.setVisibility(View.GONE);
1452                 return contentPreviewLayout;
1453             }
1454 
1455             imagePreview.findViewById(R.id.content_preview_image_1_large)
1456                     .setTransitionName(ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME);
1457             mPreviewCoord.loadUriIntoView(R.id.content_preview_image_1_large, imageUris.get(0), 0);
1458 
1459             if (imageUris.size() == 2) {
1460                 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_2_large,
1461                         imageUris.get(1), 0);
1462             } else if (imageUris.size() > 2) {
1463                 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_2_small,
1464                         imageUris.get(1), 0);
1465                 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_3_small,
1466                         imageUris.get(2), imageUris.size() - 3);
1467             }
1468         }
1469 
1470         return contentPreviewLayout;
1471     }
1472 
1473     private static class FileInfo {
1474         public final String name;
1475         public final boolean hasThumbnail;
1476 
1477         FileInfo(String name, boolean hasThumbnail) {
1478             this.name = name;
1479             this.hasThumbnail = hasThumbnail;
1480         }
1481     }
1482 
1483     /**
1484      * Wrapping the ContentResolver call to expose for easier mocking,
1485      * and to avoid mocking Android core classes.
1486      */
1487     @VisibleForTesting
1488     public Cursor queryResolver(ContentResolver resolver, Uri uri) {
1489         return resolver.query(uri, null, null, null, null);
1490     }
1491 
1492     private FileInfo extractFileInfo(Uri uri, ContentResolver resolver) {
1493         String fileName = null;
1494         boolean hasThumbnail = false;
1495 
1496         try (Cursor cursor = queryResolver(resolver, uri)) {
1497             if (cursor != null && cursor.getCount() > 0) {
1498                 int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
1499                 int titleIndex = cursor.getColumnIndex(Downloads.Impl.COLUMN_TITLE);
1500                 int flagsIndex = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_FLAGS);
1501 
1502                 cursor.moveToFirst();
1503                 if (nameIndex != -1) {
1504                     fileName = cursor.getString(nameIndex);
1505                 } else if (titleIndex != -1) {
1506                     fileName = cursor.getString(titleIndex);
1507                 }
1508 
1509                 if (flagsIndex != -1) {
1510                     hasThumbnail = (cursor.getInt(flagsIndex)
1511                             & DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL) != 0;
1512                 }
1513             }
1514         } catch (SecurityException | NullPointerException e) {
1515             logContentPreviewWarning(uri);
1516         }
1517 
1518         if (TextUtils.isEmpty(fileName)) {
1519             fileName = uri.getPath();
1520             int index = fileName.lastIndexOf('/');
1521             if (index != -1) {
1522                 fileName = fileName.substring(index + 1);
1523             }
1524         }
1525 
1526         return new FileInfo(fileName, hasThumbnail);
1527     }
1528 
1529     private void logContentPreviewWarning(Uri uri) {
1530         // The ContentResolver already logs the exception. Log something more informative.
1531         Log.w(TAG, "Could not load (" + uri.toString() + ") thumbnail/name for preview. If "
1532                 + "desired, consider using Intent#createChooser to launch the ChooserActivity, "
1533                 + "and set your Intent's clipData and flags in accordance with that method's "
1534                 + "documentation");
1535     }
1536 
1537     private ViewGroup displayFileContentPreview(Intent targetIntent, LayoutInflater layoutInflater,
1538             ViewGroup parent) {
1539 
1540         ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
1541                 R.layout.chooser_grid_preview_file, parent, false);
1542 
1543         final ViewGroup actionRow =
1544                 (ViewGroup) contentPreviewLayout.findViewById(R.id.chooser_action_row);
1545         //TODO(b/120417119): addActionButton(actionRow, createCopyButton());
1546         if (shouldNearbyShareBeIncludedAsActionButton()) {
1547             addActionButton(actionRow, createNearbyButton(targetIntent));
1548         }
1549 
1550         String action = targetIntent.getAction();
1551         if (Intent.ACTION_SEND.equals(action)) {
1552             Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
1553             if (!validForContentPreview(uri)) {
1554                 contentPreviewLayout.setVisibility(View.GONE);
1555                 return contentPreviewLayout;
1556             }
1557             loadFileUriIntoView(uri, contentPreviewLayout);
1558         } else {
1559             List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
1560             uris = uris.stream()
1561                     .filter(ChooserActivity::validForContentPreview)
1562                     .collect(Collectors.toList());
1563             int uriCount = uris.size();
1564 
1565             if (uriCount == 0) {
1566                 contentPreviewLayout.setVisibility(View.GONE);
1567                 Log.i(TAG,
1568                         "Appears to be no uris available in EXTRA_STREAM, removing "
1569                                 + "preview area");
1570                 return contentPreviewLayout;
1571             } else if (uriCount == 1) {
1572                 loadFileUriIntoView(uris.get(0), contentPreviewLayout);
1573             } else {
1574                 FileInfo fileInfo = extractFileInfo(uris.get(0), getContentResolver());
1575                 int remUriCount = uriCount - 1;
1576                 Map<String, Object> arguments = new HashMap<>();
1577                 arguments.put(PLURALS_COUNT, remUriCount);
1578                 arguments.put(PLURALS_FILE_NAME, fileInfo.name);
1579                 String fileName = PluralsMessageFormatter.format(
1580                         getResources(),
1581                         arguments,
1582                         R.string.file_count);
1583 
1584                 TextView fileNameView = contentPreviewLayout.findViewById(
1585                         R.id.content_preview_filename);
1586                 fileNameView.setText(fileName);
1587 
1588                 View thumbnailView = contentPreviewLayout.findViewById(
1589                         R.id.content_preview_file_thumbnail);
1590                 thumbnailView.setVisibility(View.GONE);
1591 
1592                 ImageView fileIconView = contentPreviewLayout.findViewById(
1593                         R.id.content_preview_file_icon);
1594                 fileIconView.setVisibility(View.VISIBLE);
1595                 fileIconView.setImageResource(R.drawable.ic_file_copy);
1596             }
1597         }
1598 
1599         return contentPreviewLayout;
1600     }
1601 
1602     private void loadFileUriIntoView(final Uri uri, final View parent) {
1603         FileInfo fileInfo = extractFileInfo(uri, getContentResolver());
1604 
1605         TextView fileNameView = parent.findViewById(R.id.content_preview_filename);
1606         fileNameView.setText(fileInfo.name);
1607 
1608         if (fileInfo.hasThumbnail) {
1609             mPreviewCoord = new ContentPreviewCoordinator(parent, false);
1610             mPreviewCoord.loadUriIntoView(R.id.content_preview_file_thumbnail, uri, 0);
1611         } else {
1612             View thumbnailView = parent.findViewById(R.id.content_preview_file_thumbnail);
1613             thumbnailView.setVisibility(View.GONE);
1614 
1615             ImageView fileIconView = parent.findViewById(R.id.content_preview_file_icon);
1616             fileIconView.setVisibility(View.VISIBLE);
1617             fileIconView.setImageResource(R.drawable.chooser_file_generic);
1618         }
1619     }
1620 
1621     /**
1622      * Indicate if the incoming content URI should be allowed.
1623      *
1624      * @param uri the uri to test
1625      * @return true if the URI is allowed for content preview
1626      */
1627     private static boolean validForContentPreview(Uri uri) throws SecurityException {
1628         if (uri == null) {
1629             return false;
1630         }
1631         int userId = getUserIdFromUri(uri, UserHandle.USER_CURRENT);
1632         if (userId != UserHandle.USER_CURRENT && userId != UserHandle.myUserId()) {
1633             Log.e(TAG, "dropped invalid content URI belonging to user " + userId);
1634             return false;
1635         }
1636         return true;
1637     }
1638 
1639     @VisibleForTesting
1640     protected boolean isImageType(String mimeType) {
1641         return mimeType != null && mimeType.startsWith("image/");
1642     }
1643 
1644     @ContentPreviewType
1645     private int findPreferredContentPreview(Uri uri, ContentResolver resolver) {
1646         if (uri == null) {
1647             return CONTENT_PREVIEW_TEXT;
1648         }
1649 
1650         String mimeType = resolver.getType(uri);
1651         return isImageType(mimeType) ? CONTENT_PREVIEW_IMAGE : CONTENT_PREVIEW_FILE;
1652     }
1653 
1654     /**
1655      * In {@link android.content.Intent#getType}, the app may specify a very general
1656      * mime-type that broadly covers all data being shared, such as {@literal *}/*
1657      * when sending an image and text. We therefore should inspect each item for the
1658      * the preferred type, in order of IMAGE, FILE, TEXT.
1659      */
1660     @ContentPreviewType
1661     private int findPreferredContentPreview(Intent targetIntent, ContentResolver resolver) {
1662         String action = targetIntent.getAction();
1663         if (Intent.ACTION_SEND.equals(action)) {
1664             Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
1665             return findPreferredContentPreview(uri, resolver);
1666         } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
1667             List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
1668             if (uris == null || uris.isEmpty()) {
1669                 return CONTENT_PREVIEW_TEXT;
1670             }
1671 
1672             for (Uri uri : uris) {
1673                 // Defaulting to file preview when there are mixed image/file types is
1674                 // preferable, as it shows the user the correct number of items being shared
1675                 if (findPreferredContentPreview(uri, resolver) == CONTENT_PREVIEW_FILE) {
1676                     return CONTENT_PREVIEW_FILE;
1677                 }
1678             }
1679 
1680             return CONTENT_PREVIEW_IMAGE;
1681         }
1682 
1683         return CONTENT_PREVIEW_TEXT;
1684     }
1685 
1686     private int getNumSheetExpansions() {
1687         return getPreferences(Context.MODE_PRIVATE).getInt(PREF_NUM_SHEET_EXPANSIONS, 0);
1688     }
1689 
1690     private void incrementNumSheetExpansions() {
1691         getPreferences(Context.MODE_PRIVATE).edit().putInt(PREF_NUM_SHEET_EXPANSIONS,
1692                 getNumSheetExpansions() + 1).apply();
1693     }
1694 
1695     @Override
1696     protected void onStop() {
1697         super.onStop();
1698         if (maybeCancelFinishAnimation()) {
1699             finish();
1700         }
1701     }
1702 
1703     @Override
1704     protected void onDestroy() {
1705         super.onDestroy();
1706 
1707         if (isFinishing()) {
1708             mLatencyTracker.onActionCancel(ACTION_LOAD_SHARE_SHEET);
1709         }
1710 
1711         if (mRefinementResultReceiver != null) {
1712             mRefinementResultReceiver.destroy();
1713             mRefinementResultReceiver = null;
1714         }
1715         mChooserHandler.removeAllMessages();
1716 
1717         if (mPreviewCoord != null) mPreviewCoord.cancelLoads();
1718 
1719         mChooserMultiProfilePagerAdapter.getActiveListAdapter().destroyAppPredictor();
1720         if (mChooserMultiProfilePagerAdapter.getInactiveListAdapter() != null) {
1721             mChooserMultiProfilePagerAdapter.getInactiveListAdapter().destroyAppPredictor();
1722         }
1723         mPersonalAppPredictor = null;
1724         mWorkAppPredictor = null;
1725     }
1726 
1727     @Override // ResolverListCommunicator
1728     public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) {
1729         Intent result = defIntent;
1730         if (mReplacementExtras != null) {
1731             final Bundle replExtras = mReplacementExtras.getBundle(aInfo.packageName);
1732             if (replExtras != null) {
1733                 result = new Intent(defIntent);
1734                 result.putExtras(replExtras);
1735             }
1736         }
1737         if (aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_PARENT)
1738                 || aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE)) {
1739             result = Intent.createChooser(result,
1740                     getIntent().getCharSequenceExtra(Intent.EXTRA_TITLE));
1741 
1742             // Don't auto-launch single intents if the intent is being forwarded. This is done
1743             // because automatically launching a resolving application as a response to the user
1744             // action of switching accounts is pretty unexpected.
1745             result.putExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, false);
1746         }
1747         return result;
1748     }
1749 
1750     @Override
1751     public void onActivityStarted(TargetInfo cti) {
1752         if (mChosenComponentSender != null) {
1753             final ComponentName target = cti.getResolvedComponentName();
1754             if (target != null) {
1755                 final Intent fillIn = new Intent().putExtra(Intent.EXTRA_CHOSEN_COMPONENT, target);
1756                 try {
1757                     mChosenComponentSender.sendIntent(this, Activity.RESULT_OK, fillIn, null, null);
1758                 } catch (IntentSender.SendIntentException e) {
1759                     Slog.e(TAG, "Unable to launch supplied IntentSender to report "
1760                             + "the chosen component: " + e);
1761                 }
1762             }
1763         }
1764     }
1765 
1766     @Override
1767     public void addUseDifferentAppLabelIfNecessary(ResolverListAdapter adapter) {
1768         if (mCallerChooserTargets != null && mCallerChooserTargets.length > 0) {
1769             mChooserMultiProfilePagerAdapter.getActiveListAdapter().addServiceResults(
1770                     /* origTarget */ null,
1771                     Lists.newArrayList(mCallerChooserTargets),
1772                     TARGET_TYPE_DEFAULT,
1773                     /* directShareShortcutInfoCache */ null);
1774         }
1775     }
1776 
1777     @Override
1778     public int getLayoutResource() {
1779         return R.layout.chooser_grid;
1780     }
1781 
1782     @Override // ResolverListCommunicator
1783     public boolean shouldGetActivityMetadata() {
1784         return true;
1785     }
1786 
1787     @Override
1788     public boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
1789         // Note that this is only safe because the Intent handled by the ChooserActivity is
1790         // guaranteed to contain no extras unknown to the local ClassLoader. That is why this
1791         // method can not be replaced in the ResolverActivity whole hog.
1792         if (!super.shouldAutoLaunchSingleChoice(target)) {
1793             return false;
1794         }
1795 
1796         return getIntent().getBooleanExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, true);
1797     }
1798 
1799     private void showTargetDetails(TargetInfo targetInfo) {
1800         if (targetInfo == null) return;
1801 
1802         ArrayList<DisplayResolveInfo> targetList;
1803         ChooserTargetActionsDialogFragment fragment = new ChooserTargetActionsDialogFragment();
1804         Bundle bundle = new Bundle();
1805 
1806         if (targetInfo instanceof SelectableTargetInfo) {
1807             SelectableTargetInfo selectableTargetInfo = (SelectableTargetInfo) targetInfo;
1808             if (selectableTargetInfo.getDisplayResolveInfo() == null
1809                     || selectableTargetInfo.getChooserTarget() == null) {
1810                 Log.e(TAG, "displayResolveInfo or chooserTarget in selectableTargetInfo are null");
1811                 return;
1812             }
1813             targetList = new ArrayList<>();
1814             targetList.add(selectableTargetInfo.getDisplayResolveInfo());
1815             bundle.putString(ChooserTargetActionsDialogFragment.SHORTCUT_ID_KEY,
1816                     selectableTargetInfo.getChooserTarget().getIntentExtras().getString(
1817                             Intent.EXTRA_SHORTCUT_ID));
1818             bundle.putBoolean(ChooserTargetActionsDialogFragment.IS_SHORTCUT_PINNED_KEY,
1819                     selectableTargetInfo.isPinned());
1820             bundle.putParcelable(ChooserTargetActionsDialogFragment.INTENT_FILTER_KEY,
1821                     getTargetIntentFilter());
1822             if (selectableTargetInfo.getDisplayLabel() != null) {
1823                 bundle.putString(ChooserTargetActionsDialogFragment.SHORTCUT_TITLE_KEY,
1824                         selectableTargetInfo.getDisplayLabel().toString());
1825             }
1826         } else if (targetInfo instanceof MultiDisplayResolveInfo) {
1827             // For multiple targets, include info on all targets
1828             MultiDisplayResolveInfo mti = (MultiDisplayResolveInfo) targetInfo;
1829             targetList = mti.getTargets();
1830         } else {
1831             targetList = new ArrayList<DisplayResolveInfo>();
1832             targetList.add((DisplayResolveInfo) targetInfo);
1833         }
1834         bundle.putParcelable(ChooserTargetActionsDialogFragment.USER_HANDLE_KEY,
1835                 mChooserMultiProfilePagerAdapter.getCurrentUserHandle());
1836         bundle.putParcelableArrayList(ChooserTargetActionsDialogFragment.TARGET_INFOS_KEY,
1837                 targetList);
1838         fragment.setArguments(bundle);
1839 
1840         fragment.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
1841     }
1842 
1843     private void modifyTargetIntent(Intent in) {
1844         if (isSendAction(in)) {
1845             in.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT |
1846                     Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
1847         }
1848     }
1849 
1850     @Override
1851     protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) {
1852         if (mRefinementIntentSender != null) {
1853             final Intent fillIn = new Intent();
1854             final List<Intent> sourceIntents = target.getAllSourceIntents();
1855             if (!sourceIntents.isEmpty()) {
1856                 fillIn.putExtra(Intent.EXTRA_INTENT, sourceIntents.get(0));
1857                 if (sourceIntents.size() > 1) {
1858                     final Intent[] alts = new Intent[sourceIntents.size() - 1];
1859                     for (int i = 1, N = sourceIntents.size(); i < N; i++) {
1860                         alts[i - 1] = sourceIntents.get(i);
1861                     }
1862                     fillIn.putExtra(Intent.EXTRA_ALTERNATE_INTENTS, alts);
1863                 }
1864                 if (mRefinementResultReceiver != null) {
1865                     mRefinementResultReceiver.destroy();
1866                 }
1867                 mRefinementResultReceiver = new RefinementResultReceiver(this, target, null);
1868                 fillIn.putExtra(Intent.EXTRA_RESULT_RECEIVER,
1869                         mRefinementResultReceiver);
1870                 try {
1871                     mRefinementIntentSender.sendIntent(this, 0, fillIn, null, null);
1872                     return false;
1873                 } catch (SendIntentException e) {
1874                     Log.e(TAG, "Refinement IntentSender failed to send", e);
1875                 }
1876             }
1877         }
1878         updateModelAndChooserCounts(target);
1879         return super.onTargetSelected(target, alwaysCheck);
1880     }
1881 
1882     @Override
1883     public void startSelected(int which, boolean always, boolean filtered) {
1884         ChooserListAdapter currentListAdapter =
1885                 mChooserMultiProfilePagerAdapter.getActiveListAdapter();
1886         TargetInfo targetInfo = currentListAdapter
1887                 .targetInfoForPosition(which, filtered);
1888         if (targetInfo != null && targetInfo instanceof NotSelectableTargetInfo) {
1889             return;
1890         }
1891 
1892         final long selectionCost = System.currentTimeMillis() - mChooserShownTime;
1893 
1894         if (targetInfo instanceof MultiDisplayResolveInfo) {
1895             MultiDisplayResolveInfo mti = (MultiDisplayResolveInfo) targetInfo;
1896             if (!mti.hasSelected()) {
1897                 ChooserStackedAppDialogFragment f = new ChooserStackedAppDialogFragment();
1898                 Bundle b = new Bundle();
1899                 b.putParcelable(ChooserTargetActionsDialogFragment.USER_HANDLE_KEY,
1900                         mChooserMultiProfilePagerAdapter.getCurrentUserHandle());
1901                 b.putObject(ChooserStackedAppDialogFragment.MULTI_DRI_KEY,
1902                         mti);
1903                 b.putInt(ChooserStackedAppDialogFragment.WHICH_KEY, which);
1904                 f.setArguments(b);
1905 
1906                 f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
1907                 return;
1908             }
1909         }
1910 
1911         super.startSelected(which, always, filtered);
1912 
1913         if (currentListAdapter.getCount() > 0) {
1914             // Log the index of which type of target the user picked.
1915             // Lower values mean the ranking was better.
1916             int cat = 0;
1917             int value = which;
1918             int directTargetAlsoRanked = -1;
1919             int numCallerProvided = 0;
1920             HashedStringCache.HashResult directTargetHashed = null;
1921             switch (currentListAdapter.getPositionTargetType(which)) {
1922                 case ChooserListAdapter.TARGET_SERVICE:
1923                     cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET;
1924                     // Log the package name + target name to answer the question if most users
1925                     // share to mostly the same person or to a bunch of different people.
1926                     ChooserTarget target = currentListAdapter.getChooserTargetForValue(value);
1927                     directTargetHashed = HashedStringCache.getInstance().hashString(
1928                             this,
1929                             TAG,
1930                             target.getComponentName().getPackageName()
1931                                     + target.getTitle().toString(),
1932                             mMaxHashSaltDays);
1933                     SelectableTargetInfo selectableTargetInfo = (SelectableTargetInfo) targetInfo;
1934                     directTargetAlsoRanked = getRankedPosition(selectableTargetInfo);
1935 
1936                     if (mCallerChooserTargets != null) {
1937                         numCallerProvided = mCallerChooserTargets.length;
1938                     }
1939                     getChooserActivityLogger().logShareTargetSelected(
1940                             SELECTION_TYPE_SERVICE,
1941                             targetInfo.getResolveInfo().activityInfo.processName,
1942                             value,
1943                             selectableTargetInfo.isPinned()
1944                     );
1945                     break;
1946                 case ChooserListAdapter.TARGET_CALLER:
1947                 case ChooserListAdapter.TARGET_STANDARD:
1948                     cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET;
1949                     value -= currentListAdapter.getSurfacedTargetInfo().size();
1950                     numCallerProvided = currentListAdapter.getCallerTargetCount();
1951                     getChooserActivityLogger().logShareTargetSelected(
1952                             SELECTION_TYPE_APP,
1953                             targetInfo.getResolveInfo().activityInfo.processName,
1954                             value,
1955                             targetInfo.isPinned()
1956                     );
1957                     break;
1958                 case ChooserListAdapter.TARGET_STANDARD_AZ:
1959                     // A-Z targets are unranked standard targets; we use -1 to mark that they
1960                     // are from the alphabetical pool.
1961                     value = -1;
1962                     cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_STANDARD_TARGET;
1963                     getChooserActivityLogger().logShareTargetSelected(
1964                             SELECTION_TYPE_STANDARD,
1965                             targetInfo.getResolveInfo().activityInfo.processName,
1966                             value,
1967                             false
1968                     );
1969                     break;
1970             }
1971 
1972             if (cat != 0) {
1973                 LogMaker targetLogMaker = new LogMaker(cat).setSubtype(value);
1974                 if (directTargetHashed != null) {
1975                     targetLogMaker.addTaggedData(
1976                             MetricsEvent.FIELD_HASHED_TARGET_NAME, directTargetHashed.hashedString);
1977                     targetLogMaker.addTaggedData(
1978                                     MetricsEvent.FIELD_HASHED_TARGET_SALT_GEN,
1979                                     directTargetHashed.saltGeneration);
1980                     targetLogMaker.addTaggedData(MetricsEvent.FIELD_RANKED_POSITION,
1981                                     directTargetAlsoRanked);
1982                 }
1983                 targetLogMaker.addTaggedData(MetricsEvent.FIELD_IS_CATEGORY_USED,
1984                         numCallerProvided);
1985                 getMetricsLogger().write(targetLogMaker);
1986             }
1987 
1988             if (mIsSuccessfullySelected) {
1989                 if (DEBUG) {
1990                     Log.d(TAG, "User Selection Time Cost is " + selectionCost);
1991                     Log.d(TAG, "position of selected app/service/caller is " +
1992                             Integer.toString(value));
1993                 }
1994                 MetricsLogger.histogram(null, "user_selection_cost_for_smart_sharing",
1995                         (int) selectionCost);
1996                 MetricsLogger.histogram(null, "app_position_for_smart_sharing", value);
1997             }
1998         }
1999     }
2000 
2001     private int getRankedPosition(SelectableTargetInfo targetInfo) {
2002         String targetPackageName =
2003                 targetInfo.getChooserTarget().getComponentName().getPackageName();
2004         ChooserListAdapter currentListAdapter =
2005                 mChooserMultiProfilePagerAdapter.getActiveListAdapter();
2006         int maxRankedResults = Math.min(currentListAdapter.mDisplayList.size(),
2007                 MAX_LOG_RANK_POSITION);
2008 
2009         for (int i = 0; i < maxRankedResults; i++) {
2010             if (currentListAdapter.mDisplayList.get(i)
2011                     .getResolveInfo().activityInfo.packageName.equals(targetPackageName)) {
2012                 return i;
2013             }
2014         }
2015         return -1;
2016     }
2017 
2018     @Override
2019     protected boolean shouldAddFooterView() {
2020         // To accommodate for window insets
2021         return true;
2022     }
2023 
2024     @Override
2025     protected void applyFooterView(int height) {
2026         int count = mChooserMultiProfilePagerAdapter.getItemCount();
2027 
2028         for (int i = 0; i < count; i++) {
2029             mChooserMultiProfilePagerAdapter.getAdapterForIndex(i).setFooterHeight(height);
2030         }
2031     }
2032 
2033     private IntentFilter getTargetIntentFilter() {
2034         try {
2035             final Intent intent = getTargetIntent();
2036             String dataString = intent.getDataString();
2037             if (intent.getType() == null) {
2038                 if (!TextUtils.isEmpty(dataString)) {
2039                     return new IntentFilter(intent.getAction(), dataString);
2040                 }
2041                 Log.e(TAG, "Failed to get target intent filter: intent data and type are null");
2042                 return null;
2043             }
2044             IntentFilter intentFilter = new IntentFilter(intent.getAction(), intent.getType());
2045             List<Uri> contentUris = new ArrayList<>();
2046             if (Intent.ACTION_SEND.equals(intent.getAction())) {
2047                 Uri uri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);
2048                 if (uri != null) {
2049                     contentUris.add(uri);
2050                 }
2051             } else {
2052                 List<Uri> uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
2053                 if (uris != null) {
2054                     contentUris.addAll(uris);
2055                 }
2056             }
2057             for (Uri uri : contentUris) {
2058                 intentFilter.addDataScheme(uri.getScheme());
2059                 intentFilter.addDataAuthority(uri.getAuthority(), null);
2060                 intentFilter.addDataPath(uri.getPath(), PatternMatcher.PATTERN_LITERAL);
2061             }
2062             return intentFilter;
2063         } catch (Exception e) {
2064             Log.e(TAG, "Failed to get target intent filter", e);
2065             return null;
2066         }
2067     }
2068 
2069     @VisibleForTesting
2070     protected void queryDirectShareTargets(
2071                 ChooserListAdapter adapter, boolean skipAppPredictionService) {
2072         mQueriedSharingShortcutsTimeMs = System.currentTimeMillis();
2073         UserHandle userHandle = adapter.getUserHandle();
2074         if (!skipAppPredictionService) {
2075             AppPredictor appPredictor = getAppPredictorForDirectShareIfEnabled(userHandle);
2076             if (appPredictor != null) {
2077                 appPredictor.requestPredictionUpdate();
2078                 return;
2079             }
2080         }
2081         // Default to just querying ShortcutManager if AppPredictor not present.
2082         final IntentFilter filter = getTargetIntentFilter();
2083         if (filter == null) {
2084             return;
2085         }
2086 
2087         AsyncTask.execute(() -> {
2088             Context selectedProfileContext = createContextAsUser(userHandle, 0 /* flags */);
2089             ShortcutManager sm = (ShortcutManager) selectedProfileContext
2090                     .getSystemService(Context.SHORTCUT_SERVICE);
2091             List<ShortcutManager.ShareShortcutInfo> resultList = sm.getShareTargets(filter);
2092             sendShareShortcutInfoList(resultList, adapter, null, userHandle);
2093         });
2094     }
2095 
2096     /**
2097      * Returns {@code false} if {@code userHandle} is the work profile and it's either
2098      * in quiet mode or not running.
2099      */
2100     private boolean shouldQueryShortcutManager(UserHandle userHandle) {
2101         if (!shouldShowTabs()) {
2102             return true;
2103         }
2104         if (!getWorkProfileUserHandle().equals(userHandle)) {
2105             return true;
2106         }
2107         if (!isUserRunning(userHandle)) {
2108             return false;
2109         }
2110         if (!isUserUnlocked(userHandle)) {
2111             return false;
2112         }
2113         if (isQuietModeEnabled(userHandle)) {
2114             return false;
2115         }
2116         return true;
2117     }
2118 
2119     private void sendShareShortcutInfoList(
2120                 List<ShortcutManager.ShareShortcutInfo> resultList,
2121                 ChooserListAdapter chooserListAdapter,
2122                 @Nullable List<AppTarget> appTargets, UserHandle userHandle) {
2123         if (appTargets != null && appTargets.size() != resultList.size()) {
2124             throw new RuntimeException("resultList and appTargets must have the same size."
2125                     + " resultList.size()=" + resultList.size()
2126                     + " appTargets.size()=" + appTargets.size());
2127         }
2128         Context selectedProfileContext = createContextAsUser(userHandle, 0 /* flags */);
2129         for (int i = resultList.size() - 1; i >= 0; i--) {
2130             final String packageName = resultList.get(i).getTargetComponent().getPackageName();
2131             if (!isPackageEnabled(selectedProfileContext, packageName)) {
2132                 resultList.remove(i);
2133                 if (appTargets != null) {
2134                     appTargets.remove(i);
2135                 }
2136             }
2137         }
2138 
2139         // If |appTargets| is not null, results are from AppPredictionService and already sorted.
2140         final int shortcutType = (appTargets == null ? TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER :
2141                 TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE);
2142 
2143         // Match ShareShortcutInfos with DisplayResolveInfos to be able to use the old code path
2144         // for direct share targets. After ShareSheet is refactored we should use the
2145         // ShareShortcutInfos directly.
2146         List<ServiceResultInfo> resultRecords = new ArrayList<>();
2147         for (int i = 0; i < chooserListAdapter.getDisplayResolveInfoCount(); i++) {
2148             DisplayResolveInfo displayResolveInfo = chooserListAdapter.getDisplayResolveInfo(i);
2149             List<ShortcutManager.ShareShortcutInfo> matchingShortcuts =
2150                     filterShortcutsByTargetComponentName(
2151                             resultList, displayResolveInfo.getResolvedComponentName());
2152             if (matchingShortcuts.isEmpty()) {
2153                 continue;
2154             }
2155             List<ChooserTarget> chooserTargets = convertToChooserTarget(
2156                     matchingShortcuts, resultList, appTargets, shortcutType);
2157 
2158             ServiceResultInfo resultRecord = new ServiceResultInfo(
2159                     displayResolveInfo, chooserTargets, userHandle);
2160             resultRecords.add(resultRecord);
2161         }
2162 
2163         sendShortcutManagerShareTargetResults(
2164                 shortcutType, resultRecords.toArray(new ServiceResultInfo[0]));
2165     }
2166 
2167     private List<ShortcutManager.ShareShortcutInfo> filterShortcutsByTargetComponentName(
2168             List<ShortcutManager.ShareShortcutInfo> allShortcuts, ComponentName requiredTarget) {
2169         List<ShortcutManager.ShareShortcutInfo> matchingShortcuts = new ArrayList<>();
2170         for (ShortcutManager.ShareShortcutInfo shortcut : allShortcuts) {
2171             if (requiredTarget.equals(shortcut.getTargetComponent())) {
2172                 matchingShortcuts.add(shortcut);
2173             }
2174         }
2175         return matchingShortcuts;
2176     }
2177 
2178     @VisibleForTesting
2179     protected void sendShortcutManagerShareTargetResults(
2180             int shortcutType, ServiceResultInfo[] results) {
2181         final Message msg = Message.obtain();
2182         msg.what = ChooserHandler.SHORTCUT_MANAGER_ALL_SHARE_TARGET_RESULTS;
2183         msg.obj = results;
2184         msg.arg1 = shortcutType;
2185         mChooserHandler.sendMessage(msg);
2186     }
2187 
2188     private boolean isPackageEnabled(Context context, String packageName) {
2189         if (TextUtils.isEmpty(packageName)) {
2190             return false;
2191         }
2192         ApplicationInfo appInfo;
2193         try {
2194             appInfo = context.getPackageManager().getApplicationInfo(packageName, 0);
2195         } catch (NameNotFoundException e) {
2196             return false;
2197         }
2198 
2199         if (appInfo != null && appInfo.enabled
2200                 && (appInfo.flags & ApplicationInfo.FLAG_SUSPENDED) == 0) {
2201             return true;
2202         }
2203         return false;
2204     }
2205 
2206     /**
2207      * Converts a list of ShareShortcutInfos to ChooserTargets.
2208      * @param matchingShortcuts List of shortcuts, all from the same package, that match the current
2209      *                         share intent filter.
2210      * @param allShortcuts List of all the shortcuts from all the packages on the device that are
2211      *                    returned for the current sharing action.
2212      * @param allAppTargets List of AppTargets. Null if the results are not from prediction service.
2213      * @param shortcutType One of the values TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER or
2214      *                    TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE
2215      * @return A list of ChooserTargets sorted by score in descending order.
2216      */
2217     @VisibleForTesting
2218     @NonNull
2219     public List<ChooserTarget> convertToChooserTarget(
2220             @NonNull List<ShortcutManager.ShareShortcutInfo> matchingShortcuts,
2221             @NonNull List<ShortcutManager.ShareShortcutInfo> allShortcuts,
2222             @Nullable List<AppTarget> allAppTargets, @ShareTargetType int shortcutType) {
2223         // A set of distinct scores for the matched shortcuts. We use index of a rank in the sorted
2224         // list instead of the actual rank value when converting a rank to a score.
2225         List<Integer> scoreList = new ArrayList<>();
2226         if (shortcutType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER) {
2227             for (int i = 0; i < matchingShortcuts.size(); i++) {
2228                 int shortcutRank = matchingShortcuts.get(i).getShortcutInfo().getRank();
2229                 if (!scoreList.contains(shortcutRank)) {
2230                     scoreList.add(shortcutRank);
2231                 }
2232             }
2233             Collections.sort(scoreList);
2234         }
2235 
2236         List<ChooserTarget> chooserTargetList = new ArrayList<>(matchingShortcuts.size());
2237         for (int i = 0; i < matchingShortcuts.size(); i++) {
2238             ShortcutInfo shortcutInfo = matchingShortcuts.get(i).getShortcutInfo();
2239             int indexInAllShortcuts = allShortcuts.indexOf(matchingShortcuts.get(i));
2240 
2241             float score;
2242             if (shortcutType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE) {
2243                 // Incoming results are ordered. Create a score based on index in the original list.
2244                 score = Math.max(1.0f - (0.01f * indexInAllShortcuts), 0.0f);
2245             } else {
2246                 // Create a score based on the rank of the shortcut.
2247                 int rankIndex = scoreList.indexOf(shortcutInfo.getRank());
2248                 score = Math.max(1.0f - (0.01f * rankIndex), 0.0f);
2249             }
2250 
2251             Bundle extras = new Bundle();
2252             extras.putString(Intent.EXTRA_SHORTCUT_ID, shortcutInfo.getId());
2253 
2254             ChooserTarget chooserTarget = new ChooserTarget(
2255                     shortcutInfo.getLabel(),
2256                     null, // Icon will be loaded later if this target is selected to be shown.
2257                     score, matchingShortcuts.get(i).getTargetComponent().clone(), extras);
2258 
2259             chooserTargetList.add(chooserTarget);
2260             if (mDirectShareAppTargetCache != null && allAppTargets != null) {
2261                 mDirectShareAppTargetCache.put(chooserTarget,
2262                         allAppTargets.get(indexInAllShortcuts));
2263             }
2264             if (mDirectShareShortcutInfoCache != null) {
2265                 mDirectShareShortcutInfoCache.put(chooserTarget, shortcutInfo);
2266             }
2267         }
2268         // Sort ChooserTargets by score in descending order
2269         Comparator<ChooserTarget> byScore =
2270                 (ChooserTarget a, ChooserTarget b) -> -Float.compare(a.getScore(), b.getScore());
2271         Collections.sort(chooserTargetList, byScore);
2272         return chooserTargetList;
2273     }
2274 
2275     private void logDirectShareTargetReceived(int logCategory) {
2276         final int apiLatency = (int) (System.currentTimeMillis() - mQueriedSharingShortcutsTimeMs);
2277         getMetricsLogger().write(new LogMaker(logCategory).setSubtype(apiLatency));
2278     }
2279 
2280     void updateModelAndChooserCounts(TargetInfo info) {
2281         if (info != null && info instanceof MultiDisplayResolveInfo) {
2282             info = ((MultiDisplayResolveInfo) info).getSelectedTarget();
2283         }
2284         if (info != null) {
2285             sendClickToAppPredictor(info);
2286             final ResolveInfo ri = info.getResolveInfo();
2287             Intent targetIntent = getTargetIntent();
2288             if (ri != null && ri.activityInfo != null && targetIntent != null) {
2289                 ChooserListAdapter currentListAdapter =
2290                         mChooserMultiProfilePagerAdapter.getActiveListAdapter();
2291                 if (currentListAdapter != null) {
2292                     sendImpressionToAppPredictor(info, currentListAdapter);
2293                     currentListAdapter.updateModel(info.getResolvedComponentName());
2294                     currentListAdapter.updateChooserCounts(ri.activityInfo.packageName,
2295                             targetIntent.getAction());
2296                 }
2297                 if (DEBUG) {
2298                     Log.d(TAG, "ResolveInfo Package is " + ri.activityInfo.packageName);
2299                     Log.d(TAG, "Action to be updated is " + targetIntent.getAction());
2300                 }
2301             } else if (DEBUG) {
2302                 Log.d(TAG, "Can not log Chooser Counts of null ResovleInfo");
2303             }
2304         }
2305         mIsSuccessfullySelected = true;
2306     }
2307 
2308     private void sendImpressionToAppPredictor(TargetInfo targetInfo, ChooserListAdapter adapter) {
2309         AppPredictor directShareAppPredictor = getAppPredictorForDirectShareIfEnabled(
2310                 mChooserMultiProfilePagerAdapter.getCurrentUserHandle());
2311         if (directShareAppPredictor == null) {
2312             return;
2313         }
2314         // Send DS target impression info to AppPredictor, only when user chooses app share.
2315         if (targetInfo instanceof ChooserTargetInfo) {
2316             return;
2317         }
2318         List<ChooserTargetInfo> surfacedTargetInfo = adapter.getSurfacedTargetInfo();
2319         List<AppTargetId> targetIds = new ArrayList<>();
2320         for (ChooserTargetInfo chooserTargetInfo : surfacedTargetInfo) {
2321             ChooserTarget chooserTarget = chooserTargetInfo.getChooserTarget();
2322             ComponentName componentName = chooserTarget.getComponentName();
2323             if (mDirectShareShortcutInfoCache.containsKey(chooserTarget)) {
2324                 String shortcutId = mDirectShareShortcutInfoCache.get(chooserTarget).getId();
2325                 targetIds.add(new AppTargetId(
2326                         String.format("%s/%s/%s", shortcutId, componentName.flattenToString(),
2327                                 SHORTCUT_TARGET)));
2328             }
2329         }
2330         directShareAppPredictor.notifyLaunchLocationShown(LAUNCH_LOCATION_DIRECT_SHARE, targetIds);
2331     }
2332 
2333     private void sendClickToAppPredictor(TargetInfo targetInfo) {
2334         AppPredictor directShareAppPredictor = getAppPredictorForDirectShareIfEnabled(
2335                 mChooserMultiProfilePagerAdapter.getCurrentUserHandle());
2336         if (directShareAppPredictor == null) {
2337             return;
2338         }
2339         if (!(targetInfo instanceof ChooserTargetInfo)) {
2340             return;
2341         }
2342         ChooserTarget chooserTarget = ((ChooserTargetInfo) targetInfo).getChooserTarget();
2343         AppTarget appTarget = null;
2344         if (mDirectShareAppTargetCache != null) {
2345             appTarget = mDirectShareAppTargetCache.get(chooserTarget);
2346         }
2347         // This is a direct share click that was provided by the APS
2348         if (appTarget != null) {
2349             directShareAppPredictor.notifyAppTargetEvent(
2350                     new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_LAUNCH)
2351                         .setLaunchLocation(LAUNCH_LOCATION_DIRECT_SHARE)
2352                         .build());
2353         }
2354     }
2355 
2356     @Nullable
2357     private AppPredictor createAppPredictor(UserHandle userHandle) {
2358         if (!mIsAppPredictorComponentAvailable) {
2359             return null;
2360         }
2361 
2362         if (getPersonalProfileUserHandle().equals(userHandle)) {
2363             if (mPersonalAppPredictor != null) {
2364                 return mPersonalAppPredictor;
2365             }
2366         } else {
2367             if (mWorkAppPredictor != null) {
2368                 return mWorkAppPredictor;
2369             }
2370         }
2371 
2372         // TODO(b/148230574): Currently AppPredictor fetches only the same-profile app targets.
2373         // Make AppPredictor work cross-profile.
2374         Context contextAsUser = createContextAsUser(userHandle, 0 /* flags */);
2375         final IntentFilter filter = getTargetIntentFilter();
2376         Bundle extras = new Bundle();
2377         extras.putParcelable(APP_PREDICTION_INTENT_FILTER_KEY, filter);
2378         populateTextContent(extras);
2379         AppPredictionContext appPredictionContext = new AppPredictionContext.Builder(contextAsUser)
2380             .setUiSurface(APP_PREDICTION_SHARE_UI_SURFACE)
2381             .setPredictedTargetCount(APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT)
2382             .setExtras(extras)
2383             .build();
2384         AppPredictionManager appPredictionManager =
2385                 contextAsUser
2386                         .getSystemService(AppPredictionManager.class);
2387         AppPredictor appPredictionSession = appPredictionManager.createAppPredictionSession(
2388                 appPredictionContext);
2389         if (getPersonalProfileUserHandle().equals(userHandle)) {
2390             mPersonalAppPredictor = appPredictionSession;
2391         } else {
2392             mWorkAppPredictor = appPredictionSession;
2393         }
2394         return appPredictionSession;
2395     }
2396 
2397     private void populateTextContent(Bundle extras) {
2398         final Intent intent = getTargetIntent();
2399         String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
2400         extras.putString(SHARED_TEXT_KEY, sharedText);
2401     }
2402 
2403     /**
2404      * This will return an app predictor if it is enabled for direct share sorting
2405      * and if one exists. Otherwise, it returns null.
2406      * @param userHandle
2407      */
2408     @Nullable
2409     private AppPredictor getAppPredictorForDirectShareIfEnabled(UserHandle userHandle) {
2410         return ChooserFlags.USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS
2411                 && !ActivityManager.isLowRamDeviceStatic() ? createAppPredictor(userHandle) : null;
2412     }
2413 
2414     /**
2415      * This will return an app predictor if it is enabled for share activity sorting
2416      * and if one exists. Otherwise, it returns null.
2417      */
2418     @Nullable
2419     private AppPredictor getAppPredictorForShareActivitiesIfEnabled(UserHandle userHandle) {
2420         return USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES ? createAppPredictor(userHandle) : null;
2421     }
2422 
2423     void onRefinementResult(TargetInfo selectedTarget, Intent matchingIntent) {
2424         if (mRefinementResultReceiver != null) {
2425             mRefinementResultReceiver.destroy();
2426             mRefinementResultReceiver = null;
2427         }
2428         if (selectedTarget == null) {
2429             Log.e(TAG, "Refinement result intent did not match any known targets; canceling");
2430         } else if (!checkTargetSourceIntent(selectedTarget, matchingIntent)) {
2431             Log.e(TAG, "onRefinementResult: Selected target " + selectedTarget
2432                     + " cannot match refined source intent " + matchingIntent);
2433         } else {
2434             TargetInfo clonedTarget = selectedTarget.cloneFilledIn(matchingIntent, 0);
2435             if (super.onTargetSelected(clonedTarget, false)) {
2436                 updateModelAndChooserCounts(clonedTarget);
2437                 finish();
2438                 return;
2439             }
2440         }
2441         onRefinementCanceled();
2442     }
2443 
2444     void onRefinementCanceled() {
2445         if (mRefinementResultReceiver != null) {
2446             mRefinementResultReceiver.destroy();
2447             mRefinementResultReceiver = null;
2448         }
2449         finish();
2450     }
2451 
2452     boolean checkTargetSourceIntent(TargetInfo target, Intent matchingIntent) {
2453         final List<Intent> targetIntents = target.getAllSourceIntents();
2454         for (int i = 0, N = targetIntents.size(); i < N; i++) {
2455             final Intent targetIntent = targetIntents.get(i);
2456             if (targetIntent.filterEquals(matchingIntent)) {
2457                 return true;
2458             }
2459         }
2460         return false;
2461     }
2462 
2463     /**
2464      * Sort intents alphabetically based on display label.
2465      */
2466     static class AzInfoComparator implements Comparator<DisplayResolveInfo> {
2467         Collator mCollator;
2468         AzInfoComparator(Context context) {
2469             mCollator = Collator.getInstance(context.getResources().getConfiguration().locale);
2470         }
2471 
2472         @Override
2473         public int compare(
2474                 DisplayResolveInfo lhsp, DisplayResolveInfo rhsp) {
2475             return mCollator.compare(lhsp.getDisplayLabel(), rhsp.getDisplayLabel());
2476         }
2477     }
2478 
2479     protected MetricsLogger getMetricsLogger() {
2480         if (mMetricsLogger == null) {
2481             mMetricsLogger = new MetricsLogger();
2482         }
2483         return mMetricsLogger;
2484     }
2485 
2486     protected ChooserActivityLogger getChooserActivityLogger() {
2487         if (mChooserActivityLogger == null) {
2488             mChooserActivityLogger = new ChooserActivityLoggerImpl();
2489         }
2490         return mChooserActivityLogger;
2491     }
2492 
2493     public class ChooserListController extends ResolverListController {
2494         public ChooserListController(Context context,
2495                 PackageManager pm,
2496                 Intent targetIntent,
2497                 String referrerPackageName,
2498                 int launchedFromUid,
2499                 UserHandle userId,
2500                 AbstractResolverComparator resolverComparator) {
2501             super(context, pm, targetIntent, referrerPackageName, launchedFromUid, userId,
2502                     resolverComparator);
2503         }
2504 
2505         @Override
2506         boolean isComponentFiltered(ComponentName name) {
2507             if (mFilteredComponentNames == null) {
2508                 return false;
2509             }
2510             for (ComponentName filteredComponentName : mFilteredComponentNames) {
2511                 if (name.equals(filteredComponentName)) {
2512                     return true;
2513                 }
2514             }
2515             return false;
2516         }
2517 
2518         @Override
2519         public boolean isComponentPinned(ComponentName name) {
2520             return mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
2521         }
2522 
2523         @Override
2524         public boolean isFixedAtTop(ComponentName name) {
2525             return name != null && name.equals(getNearbySharingComponent())
2526                     && shouldNearbyShareBeFirstInRankedRow();
2527         }
2528     }
2529 
2530     @VisibleForTesting
2531     public ChooserGridAdapter createChooserGridAdapter(Context context,
2532             List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList,
2533             boolean filterLastUsed, UserHandle userHandle) {
2534         ChooserListAdapter chooserListAdapter = createChooserListAdapter(context, payloadIntents,
2535                 initialIntents, rList, filterLastUsed,
2536                 createListController(userHandle));
2537         AppPredictor.Callback appPredictorCallback = createAppPredictorCallback(chooserListAdapter);
2538         AppPredictor appPredictor = setupAppPredictorForUser(userHandle, appPredictorCallback);
2539         chooserListAdapter.setAppPredictor(appPredictor);
2540         chooserListAdapter.setAppPredictorCallback(appPredictorCallback);
2541         return new ChooserGridAdapter(chooserListAdapter);
2542     }
2543 
2544     @VisibleForTesting
2545     public ChooserListAdapter createChooserListAdapter(Context context,
2546             List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList,
2547             boolean filterLastUsed, ResolverListController resolverListController) {
2548         return new ChooserListAdapter(context, payloadIntents, initialIntents, rList,
2549                 filterLastUsed, resolverListController, this,
2550                 this, context.getPackageManager(),
2551                 getChooserActivityLogger());
2552     }
2553 
2554     @VisibleForTesting
2555     protected ResolverListController createListController(UserHandle userHandle) {
2556         AppPredictor appPredictor = getAppPredictorForShareActivitiesIfEnabled(userHandle);
2557         AbstractResolverComparator resolverComparator;
2558         if (appPredictor != null) {
2559             resolverComparator = new AppPredictionServiceResolverComparator(this, getTargetIntent(),
2560                     getReferrerPackageName(), appPredictor, userHandle, getChooserActivityLogger());
2561         } else {
2562             resolverComparator =
2563                     new ResolverRankerServiceResolverComparator(this, getTargetIntent(),
2564                         getReferrerPackageName(), null, getChooserActivityLogger());
2565         }
2566 
2567         return new ChooserListController(
2568                 this,
2569                 mPm,
2570                 getTargetIntent(),
2571                 getReferrerPackageName(),
2572                 mLaunchedFromUid,
2573                 userHandle,
2574                 resolverComparator);
2575     }
2576 
2577     @VisibleForTesting
2578     protected Bitmap loadThumbnail(Uri uri, Size size) {
2579         if (uri == null || size == null) {
2580             return null;
2581         }
2582 
2583         try {
2584             return getContentResolver().loadThumbnail(uri, size, null);
2585         } catch (IOException | NullPointerException | SecurityException ex) {
2586             logContentPreviewWarning(uri);
2587         }
2588         return null;
2589     }
2590 
2591     static final class PlaceHolderTargetInfo extends NotSelectableTargetInfo {
2592         public Drawable getDisplayIcon(Context context) {
2593             AnimatedVectorDrawable avd = (AnimatedVectorDrawable)
2594                     context.getDrawable(R.drawable.chooser_direct_share_icon_placeholder);
2595             avd.start(); // Start animation after generation
2596             return avd;
2597         }
2598     }
2599 
2600     protected static final class EmptyTargetInfo extends NotSelectableTargetInfo {
2601         public EmptyTargetInfo() {}
2602 
2603         public Drawable getDisplayIcon(Context context) {
2604             return null;
2605         }
2606     }
2607 
2608     private void handleScroll(View view, int x, int y, int oldx, int oldy) {
2609         if (mChooserMultiProfilePagerAdapter.getCurrentRootAdapter() != null) {
2610             mChooserMultiProfilePagerAdapter.getCurrentRootAdapter().handleScroll(view, y, oldy);
2611         }
2612     }
2613 
2614     /*
2615      * Need to dynamically adjust how many icons can fit per row before we add them,
2616      * which also means setting the correct offset to initially show the content
2617      * preview area + 2 rows of targets
2618      */
2619     private void handleLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
2620             int oldTop, int oldRight, int oldBottom) {
2621         if (mChooserMultiProfilePagerAdapter == null) {
2622             return;
2623         }
2624         RecyclerView recyclerView = mChooserMultiProfilePagerAdapter.getActiveAdapterView();
2625         ChooserGridAdapter gridAdapter = mChooserMultiProfilePagerAdapter.getCurrentRootAdapter();
2626         // Skip height calculation if recycler view was scrolled to prevent it inaccurately
2627         // calculating the height, as the logic below does not account for the scrolled offset.
2628         if (gridAdapter == null || recyclerView == null
2629                 || recyclerView.computeVerticalScrollOffset() != 0) {
2630             return;
2631         }
2632 
2633         final int availableWidth = right - left - v.getPaddingLeft() - v.getPaddingRight();
2634         boolean isLayoutUpdated = gridAdapter.consumeLayoutRequest()
2635                 || gridAdapter.calculateChooserTargetWidth(availableWidth)
2636                 || recyclerView.getAdapter() == null
2637                 || availableWidth != mCurrAvailableWidth;
2638 
2639         boolean insetsChanged = !Objects.equals(mLastAppliedInsets, mSystemWindowInsets);
2640 
2641         if (isLayoutUpdated
2642                 || insetsChanged
2643                 || mLastNumberOfChildren != recyclerView.getChildCount()) {
2644             mCurrAvailableWidth = availableWidth;
2645             if (isLayoutUpdated) {
2646                 // It is very important we call setAdapter from here. Otherwise in some cases
2647                 // the resolver list doesn't get populated, such as b/150922090, b/150918223
2648                 // and b/150936654
2649                 recyclerView.setAdapter(gridAdapter);
2650                 ((GridLayoutManager) recyclerView.getLayoutManager()).setSpanCount(
2651                         mMaxTargetsPerRow);
2652 
2653                 updateTabPadding();
2654             }
2655 
2656             UserHandle currentUserHandle = mChooserMultiProfilePagerAdapter.getCurrentUserHandle();
2657             int currentProfile = getProfileForUser(currentUserHandle);
2658             int initialProfile = findSelectedProfile();
2659             if (currentProfile != initialProfile) {
2660                 return;
2661             }
2662 
2663             if (mLastNumberOfChildren == recyclerView.getChildCount() && !insetsChanged) {
2664                 return;
2665             }
2666 
2667             getMainThreadHandler().post(() -> {
2668                 if (mResolverDrawerLayout == null || gridAdapter == null) {
2669                     return;
2670                 }
2671                 int offset = calculateDrawerOffset(top, bottom, recyclerView, gridAdapter);
2672                 mResolverDrawerLayout.setCollapsibleHeightReserved(offset);
2673                 mEnterTransitionAnimationDelegate.markOffsetCalculated();
2674                 mLastAppliedInsets = mSystemWindowInsets;
2675             });
2676         }
2677     }
2678 
2679     private int calculateDrawerOffset(
2680             int top, int bottom, RecyclerView recyclerView, ChooserGridAdapter gridAdapter) {
2681 
2682         final int bottomInset = mSystemWindowInsets != null
2683                 ? mSystemWindowInsets.bottom : 0;
2684         int offset = bottomInset;
2685         int rowsToShow = gridAdapter.getSystemRowCount()
2686                 + gridAdapter.getProfileRowCount()
2687                 + gridAdapter.getServiceTargetRowCount()
2688                 + gridAdapter.getCallerAndRankedTargetRowCount();
2689 
2690         // then this is most likely not a SEND_* action, so check
2691         // the app target count
2692         if (rowsToShow == 0) {
2693             rowsToShow = gridAdapter.getRowCount();
2694         }
2695 
2696         // still zero? then use a default height and leave, which
2697         // can happen when there are no targets to show
2698         if (rowsToShow == 0 && !shouldShowStickyContentPreview()) {
2699             offset += getResources().getDimensionPixelSize(
2700                     R.dimen.chooser_max_collapsed_height);
2701             return offset;
2702         }
2703 
2704         View stickyContentPreview = findViewById(R.id.content_preview_container);
2705         if (shouldShowStickyContentPreview() && isStickyContentPreviewShowing()) {
2706             offset += stickyContentPreview.getHeight();
2707         }
2708 
2709         if (shouldShowTabs()) {
2710             offset += findViewById(R.id.tabs).getHeight();
2711         }
2712 
2713         if (recyclerView.getVisibility() == View.VISIBLE) {
2714             int directShareHeight = 0;
2715             rowsToShow = Math.min(4, rowsToShow);
2716             boolean shouldShowExtraRow = shouldShowExtraRow(rowsToShow);
2717             mLastNumberOfChildren = recyclerView.getChildCount();
2718             for (int i = 0, childCount = recyclerView.getChildCount();
2719                     i < childCount && rowsToShow > 0; i++) {
2720                 View child = recyclerView.getChildAt(i);
2721                 if (((GridLayoutManager.LayoutParams)
2722                         child.getLayoutParams()).getSpanIndex() != 0) {
2723                     continue;
2724                 }
2725                 int height = child.getHeight();
2726                 offset += height;
2727                 if (shouldShowExtraRow) {
2728                     offset += height;
2729                 }
2730 
2731                 if (gridAdapter.getTargetType(
2732                         recyclerView.getChildAdapterPosition(child))
2733                         == ChooserListAdapter.TARGET_SERVICE) {
2734                     directShareHeight = height;
2735                 }
2736                 rowsToShow--;
2737             }
2738 
2739             boolean isExpandable = getResources().getConfiguration().orientation
2740                     == Configuration.ORIENTATION_PORTRAIT && !isInMultiWindowMode();
2741             if (directShareHeight != 0 && shouldShowContentPreview()
2742                     && isExpandable) {
2743                 // make sure to leave room for direct share 4->8 expansion
2744                 int requiredExpansionHeight =
2745                         (int) (directShareHeight / DIRECT_SHARE_EXPANSION_RATE);
2746                 int topInset = mSystemWindowInsets != null ? mSystemWindowInsets.top : 0;
2747                 int minHeight = bottom - top - mResolverDrawerLayout.getAlwaysShowHeight()
2748                         - requiredExpansionHeight - topInset - bottomInset;
2749 
2750                 offset = Math.min(offset, minHeight);
2751             }
2752         } else {
2753             ViewGroup currentEmptyStateView = getActiveEmptyStateView();
2754             if (currentEmptyStateView.getVisibility() == View.VISIBLE) {
2755                 offset += currentEmptyStateView.getHeight();
2756             }
2757         }
2758 
2759         return Math.min(offset, bottom - top);
2760     }
2761 
2762     /**
2763      * If we have a tabbed view and are showing 1 row in the current profile and an empty
2764      * state screen in the other profile, to prevent cropping of the empty state screen we show
2765      * a second row in the current profile.
2766      */
2767     private boolean shouldShowExtraRow(int rowsToShow) {
2768         return shouldShowTabs()
2769                 && rowsToShow == 1
2770                 && mChooserMultiProfilePagerAdapter.shouldShowEmptyStateScreen(
2771                         mChooserMultiProfilePagerAdapter.getInactiveListAdapter());
2772     }
2773 
2774     /**
2775      * Returns {@link #PROFILE_PERSONAL}, {@link #PROFILE_WORK}, or -1 if the given user handle
2776      * does not match either the personal or work user handle.
2777      **/
2778     private int getProfileForUser(UserHandle currentUserHandle) {
2779         if (currentUserHandle.equals(getPersonalProfileUserHandle())) {
2780             return PROFILE_PERSONAL;
2781         } else if (currentUserHandle.equals(getWorkProfileUserHandle())) {
2782             return PROFILE_WORK;
2783         }
2784         Log.e(TAG, "User " + currentUserHandle + " does not belong to a personal or work profile.");
2785         return -1;
2786     }
2787 
2788     private ViewGroup getActiveEmptyStateView() {
2789         int currentPage = mChooserMultiProfilePagerAdapter.getCurrentPage();
2790         return mChooserMultiProfilePagerAdapter.getItem(currentPage).getEmptyStateView();
2791     }
2792 
2793     static class BaseChooserTargetComparator implements Comparator<ChooserTarget> {
2794         @Override
2795         public int compare(ChooserTarget lhs, ChooserTarget rhs) {
2796             // Descending order
2797             return (int) Math.signum(rhs.getScore() - lhs.getScore());
2798         }
2799     }
2800 
2801     @Override // ResolverListCommunicator
2802     public void onHandlePackagesChanged(ResolverListAdapter listAdapter) {
2803         mChooserMultiProfilePagerAdapter.getActiveListAdapter().notifyDataSetChanged();
2804         super.onHandlePackagesChanged(listAdapter);
2805     }
2806 
2807     @Override // SelectableTargetInfoCommunicator
2808     public ActivityInfoPresentationGetter makePresentationGetter(ActivityInfo info) {
2809         return mChooserMultiProfilePagerAdapter.getActiveListAdapter().makePresentationGetter(info);
2810     }
2811 
2812     @Override // SelectableTargetInfoCommunicator
2813     public Intent getReferrerFillInIntent() {
2814         return mReferrerFillInIntent;
2815     }
2816 
2817     @Override // ChooserListCommunicator
2818     public int getMaxRankedTargets() {
2819         return mMaxTargetsPerRow;
2820     }
2821 
2822     @Override // ChooserListCommunicator
2823     public void sendListViewUpdateMessage(UserHandle userHandle) {
2824         Message msg = Message.obtain();
2825         msg.what = ChooserHandler.LIST_VIEW_UPDATE_MESSAGE;
2826         msg.obj = userHandle;
2827         mChooserHandler.sendMessageDelayed(msg, mListViewUpdateDelayMs);
2828     }
2829 
2830     @Override
2831     public void onListRebuilt(ResolverListAdapter listAdapter, boolean rebuildComplete) {
2832         setupScrollListener();
2833         maybeSetupGlobalLayoutListener();
2834 
2835         ChooserListAdapter chooserListAdapter = (ChooserListAdapter) listAdapter;
2836         if (chooserListAdapter.getUserHandle()
2837                 .equals(mChooserMultiProfilePagerAdapter.getCurrentUserHandle())) {
2838             mChooserMultiProfilePagerAdapter.getActiveAdapterView()
2839                     .setAdapter(mChooserMultiProfilePagerAdapter.getCurrentRootAdapter());
2840             mChooserMultiProfilePagerAdapter
2841                     .setupListAdapter(mChooserMultiProfilePagerAdapter.getCurrentPage());
2842         }
2843 
2844         if (chooserListAdapter.mDisplayList == null
2845                 || chooserListAdapter.mDisplayList.isEmpty()) {
2846             chooserListAdapter.notifyDataSetChanged();
2847         } else {
2848             chooserListAdapter.updateAlphabeticalList();
2849         }
2850 
2851         if (rebuildComplete) {
2852             getChooserActivityLogger().logSharesheetAppLoadComplete();
2853             maybeQueryAdditionalPostProcessingTargets(chooserListAdapter);
2854             mLatencyTracker.onActionEnd(ACTION_LOAD_SHARE_SHEET);
2855         }
2856     }
2857 
2858     private void maybeQueryAdditionalPostProcessingTargets(ChooserListAdapter chooserListAdapter) {
2859         // don't support direct share on low ram devices
2860         if (ActivityManager.isLowRamDeviceStatic()) {
2861             return;
2862         }
2863 
2864         // no need to query direct share for work profile when its locked or disabled
2865         if (!shouldQueryShortcutManager(chooserListAdapter.getUserHandle())) {
2866             return;
2867         }
2868 
2869         if (ChooserFlags.USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
2870             if (DEBUG) {
2871                 Log.d(TAG, "querying direct share targets from ShortcutManager");
2872             }
2873 
2874             queryDirectShareTargets(chooserListAdapter, false);
2875         }
2876     }
2877 
2878     @VisibleForTesting
2879     protected boolean isUserRunning(UserHandle userHandle) {
2880         UserManager userManager = getSystemService(UserManager.class);
2881         return userManager.isUserRunning(userHandle);
2882     }
2883 
2884     @VisibleForTesting
2885     protected boolean isUserUnlocked(UserHandle userHandle) {
2886         UserManager userManager = getSystemService(UserManager.class);
2887         return userManager.isUserUnlocked(userHandle);
2888     }
2889 
2890     @VisibleForTesting
2891     protected boolean isQuietModeEnabled(UserHandle userHandle) {
2892         UserManager userManager = getSystemService(UserManager.class);
2893         return userManager.isQuietModeEnabled(userHandle);
2894     }
2895 
2896     private void setupScrollListener() {
2897         if (mResolverDrawerLayout == null) {
2898             return;
2899         }
2900         int elevatedViewResId = shouldShowTabs() ? R.id.tabs : R.id.chooser_header;
2901         final View elevatedView = mResolverDrawerLayout.findViewById(elevatedViewResId);
2902         final float defaultElevation = elevatedView.getElevation();
2903         final float chooserHeaderScrollElevation =
2904                 getResources().getDimensionPixelSize(R.dimen.chooser_header_scroll_elevation);
2905         mChooserMultiProfilePagerAdapter.getActiveAdapterView().addOnScrollListener(
2906                 new RecyclerView.OnScrollListener() {
2907                     public void onScrollStateChanged(RecyclerView view, int scrollState) {
2908                         if (scrollState == RecyclerView.SCROLL_STATE_IDLE) {
2909                             if (mScrollStatus == SCROLL_STATUS_SCROLLING_VERTICAL) {
2910                                 mScrollStatus = SCROLL_STATUS_IDLE;
2911                                 setHorizontalScrollingEnabled(true);
2912                             }
2913                         } else if (scrollState == RecyclerView.SCROLL_STATE_DRAGGING) {
2914                             if (mScrollStatus == SCROLL_STATUS_IDLE) {
2915                                 mScrollStatus = SCROLL_STATUS_SCROLLING_VERTICAL;
2916                                 setHorizontalScrollingEnabled(false);
2917                             }
2918                         }
2919                     }
2920 
2921                     public void onScrolled(RecyclerView view, int dx, int dy) {
2922                         if (view.getChildCount() > 0) {
2923                             View child = view.getLayoutManager().findViewByPosition(0);
2924                             if (child == null || child.getTop() < 0) {
2925                                 elevatedView.setElevation(chooserHeaderScrollElevation);
2926                                 return;
2927                             }
2928                         }
2929 
2930                         elevatedView.setElevation(defaultElevation);
2931                     }
2932                 });
2933     }
2934 
2935     private void maybeSetupGlobalLayoutListener() {
2936         if (shouldShowTabs()) {
2937             return;
2938         }
2939         final View recyclerView = mChooserMultiProfilePagerAdapter.getActiveAdapterView();
2940         recyclerView.getViewTreeObserver()
2941                 .addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
2942                     @Override
2943                     public void onGlobalLayout() {
2944                         // Fixes an issue were the accessibility border disappears on list creation.
2945                         recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
2946                         final TextView titleView = findViewById(R.id.title);
2947                         if (titleView != null) {
2948                             titleView.setFocusable(true);
2949                             titleView.setFocusableInTouchMode(true);
2950                             titleView.requestFocus();
2951                             titleView.requestAccessibilityFocus();
2952                         }
2953                     }
2954                 });
2955     }
2956 
2957     @Override // ChooserListCommunicator
2958     public boolean isSendAction(Intent targetIntent) {
2959         if (targetIntent == null) {
2960             return false;
2961         }
2962 
2963         String action = targetIntent.getAction();
2964         if (action == null) {
2965             return false;
2966         }
2967 
2968         if (Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action)) {
2969             return true;
2970         }
2971 
2972         return false;
2973     }
2974 
2975     /**
2976      * The sticky content preview is shown only when we have a tabbed view. It's shown above
2977      * the tabs so it is not part of the scrollable list. If we are not in tabbed view,
2978      * we instead show the content preview as a regular list item.
2979      */
2980     private boolean shouldShowStickyContentPreview() {
2981         return shouldShowStickyContentPreviewNoOrientationCheck()
2982                 && !getResources().getBoolean(R.bool.resolver_landscape_phone);
2983     }
2984 
2985     private boolean shouldShowStickyContentPreviewNoOrientationCheck() {
2986         return shouldShowTabs()
2987                 && (mMultiProfilePagerAdapter.getListAdapterForUserHandle(
2988                         UserHandle.of(UserHandle.myUserId())).getCount() > 0
2989                     || shouldShowContentPreviewWhenEmpty())
2990                 && shouldShowContentPreview();
2991     }
2992 
2993     /**
2994      * This method could be used to override the default behavior when we hide the preview area
2995      * when the current tab doesn't have any items.
2996      *
2997      * @return true if we want to show the content preview area even if the tab for the current
2998      *         user is empty
2999      */
3000     protected boolean shouldShowContentPreviewWhenEmpty() {
3001         return false;
3002     }
3003 
3004     /**
3005      * @return true if we want to show the content preview area
3006      */
3007     protected boolean shouldShowContentPreview() {
3008         return isSendAction(getTargetIntent());
3009     }
3010 
3011     private void updateStickyContentPreview() {
3012         if (shouldShowStickyContentPreviewNoOrientationCheck()) {
3013             // The sticky content preview is only shown when we show the work and personal tabs.
3014             // We don't show it in landscape as otherwise there is no room for scrolling.
3015             // If the sticky content preview will be shown at some point with orientation change,
3016             // then always preload it to avoid subsequent resizing of the share sheet.
3017             ViewGroup contentPreviewContainer = findViewById(R.id.content_preview_container);
3018             if (contentPreviewContainer.getChildCount() == 0) {
3019                 ViewGroup contentPreviewView = createContentPreviewView(contentPreviewContainer);
3020                 contentPreviewContainer.addView(contentPreviewView);
3021             }
3022         }
3023         if (shouldShowStickyContentPreview()) {
3024             showStickyContentPreview();
3025         } else {
3026             hideStickyContentPreview();
3027         }
3028     }
3029 
3030     private void showStickyContentPreview() {
3031         if (isStickyContentPreviewShowing()) {
3032             return;
3033         }
3034         ViewGroup contentPreviewContainer = findViewById(R.id.content_preview_container);
3035         contentPreviewContainer.setVisibility(View.VISIBLE);
3036     }
3037 
3038     private boolean isStickyContentPreviewShowing() {
3039         ViewGroup contentPreviewContainer = findViewById(R.id.content_preview_container);
3040         return contentPreviewContainer.getVisibility() == View.VISIBLE;
3041     }
3042 
3043     private void hideStickyContentPreview() {
3044         if (!isStickyContentPreviewShowing()) {
3045             return;
3046         }
3047         ViewGroup contentPreviewContainer = findViewById(R.id.content_preview_container);
3048         contentPreviewContainer.setVisibility(View.GONE);
3049     }
3050 
3051     private void logActionShareWithPreview() {
3052         Intent targetIntent = getTargetIntent();
3053         int previewType = findPreferredContentPreview(targetIntent, getContentResolver());
3054         getMetricsLogger().write(new LogMaker(MetricsEvent.ACTION_SHARE_WITH_PREVIEW)
3055                 .setSubtype(previewType));
3056     }
3057 
3058     private void startFinishAnimation() {
3059         View rootView = findRootView();
3060         if (rootView != null) {
3061             rootView.startAnimation(new FinishAnimation(this, rootView));
3062         }
3063     }
3064 
3065     private boolean maybeCancelFinishAnimation() {
3066         View rootView = findRootView();
3067         Animation animation = rootView == null ? null : rootView.getAnimation();
3068         if (animation instanceof FinishAnimation) {
3069             boolean hasEnded = animation.hasEnded();
3070             animation.cancel();
3071             rootView.clearAnimation();
3072             return !hasEnded;
3073         }
3074         return false;
3075     }
3076 
3077     private View findRootView() {
3078         if (mContentView == null) {
3079             mContentView = findViewById(android.R.id.content);
3080         }
3081         return mContentView;
3082     }
3083 
3084     abstract static class ViewHolderBase extends RecyclerView.ViewHolder {
3085         private int mViewType;
3086 
3087         ViewHolderBase(View itemView, int viewType) {
3088             super(itemView);
3089             this.mViewType = viewType;
3090         }
3091 
3092         int getViewType() {
3093             return mViewType;
3094         }
3095     }
3096 
3097     /**
3098      * Used to bind types of individual item including
3099      * {@link ChooserGridAdapter#VIEW_TYPE_NORMAL},
3100      * {@link ChooserGridAdapter#VIEW_TYPE_CONTENT_PREVIEW},
3101      * {@link ChooserGridAdapter#VIEW_TYPE_PROFILE},
3102      * and {@link ChooserGridAdapter#VIEW_TYPE_AZ_LABEL}.
3103      */
3104     final class ItemViewHolder extends ViewHolderBase {
3105         ResolverListAdapter.ViewHolder mWrappedViewHolder;
3106         int mListPosition = ChooserListAdapter.NO_POSITION;
3107 
3108         ItemViewHolder(View itemView, boolean isClickable, int viewType) {
3109             super(itemView, viewType);
3110             mWrappedViewHolder = new ResolverListAdapter.ViewHolder(itemView);
3111             if (isClickable) {
3112                 itemView.setOnClickListener(v -> startSelected(mListPosition,
3113                         false/* always */, true/* filterd */));
3114 
3115                 itemView.setOnLongClickListener(v -> {
3116                     final TargetInfo ti = mChooserMultiProfilePagerAdapter.getActiveListAdapter()
3117                             .targetInfoForPosition(mListPosition, /* filtered */ true);
3118 
3119                     // This should always be the case for ItemViewHolder, check for validity
3120                     if (ti instanceof DisplayResolveInfo && shouldShowTargetDetails(ti)) {
3121                         showTargetDetails((DisplayResolveInfo) ti);
3122                     }
3123                     return true;
3124                 });
3125             }
3126         }
3127     }
3128 
3129     private boolean shouldShowTargetDetails(TargetInfo ti) {
3130         ComponentName nearbyShare = getNearbySharingComponent();
3131         //  Suppress target details for nearby share to hide pin/unpin action
3132         boolean isNearbyShare = nearbyShare != null && nearbyShare.equals(
3133                 ti.getResolvedComponentName()) && shouldNearbyShareBeFirstInRankedRow();
3134         return ti instanceof SelectableTargetInfo
3135                 || (ti instanceof DisplayResolveInfo && !isNearbyShare);
3136     }
3137 
3138     /**
3139      * Add a footer to the list, to support scrolling behavior below the navbar.
3140      */
3141     static final class FooterViewHolder extends ViewHolderBase {
3142         FooterViewHolder(View itemView, int viewType) {
3143             super(itemView, viewType);
3144         }
3145     }
3146 
3147     /**
3148      * Intentionally override the {@link ResolverActivity} implementation as we only need that
3149      * implementation for the intent resolver case.
3150      */
3151     @Override
3152     public void onButtonClick(View v) {}
3153 
3154     /**
3155      * Intentionally override the {@link ResolverActivity} implementation as we only need that
3156      * implementation for the intent resolver case.
3157      */
3158     @Override
3159     protected void resetButtonBar() {}
3160 
3161     @Override
3162     protected String getMetricsCategory() {
3163         return METRICS_CATEGORY_CHOOSER;
3164     }
3165 
3166     @Override
3167     protected void onProfileTabSelected() {
3168         ChooserGridAdapter currentRootAdapter =
3169                 mChooserMultiProfilePagerAdapter.getCurrentRootAdapter();
3170         currentRootAdapter.updateDirectShareExpansion();
3171         // This fixes an edge case where after performing a variety of gestures, vertical scrolling
3172         // ends up disabled. That's because at some point the old tab's vertical scrolling is
3173         // disabled and the new tab's is enabled. For context, see b/159997845
3174         setVerticalScrollEnabled(true);
3175         if (mResolverDrawerLayout != null) {
3176             mResolverDrawerLayout.scrollNestedScrollableChildBackToTop();
3177         }
3178     }
3179 
3180     @Override
3181     protected WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
3182         if (shouldShowTabs()) {
3183             mChooserMultiProfilePagerAdapter
3184                     .setEmptyStateBottomOffset(insets.getSystemWindowInsetBottom());
3185             mChooserMultiProfilePagerAdapter.setupContainerPadding(
3186                     getActiveEmptyStateView().findViewById(R.id.resolver_empty_state_container));
3187         }
3188 
3189         WindowInsets result = super.onApplyWindowInsets(v, insets);
3190         if (mResolverDrawerLayout != null) {
3191             mResolverDrawerLayout.requestLayout();
3192         }
3193         return result;
3194     }
3195 
3196     private void setHorizontalScrollingEnabled(boolean enabled) {
3197         ResolverViewPager viewPager = findViewById(R.id.profile_pager);
3198         viewPager.setSwipingEnabled(enabled);
3199     }
3200 
3201     private void setVerticalScrollEnabled(boolean enabled) {
3202         ChooserGridLayoutManager layoutManager =
3203                 (ChooserGridLayoutManager) mChooserMultiProfilePagerAdapter.getActiveAdapterView()
3204                         .getLayoutManager();
3205         layoutManager.setVerticalScrollEnabled(enabled);
3206     }
3207 
3208     @Override
3209     void onHorizontalSwipeStateChanged(int state) {
3210         if (state == ViewPager.SCROLL_STATE_DRAGGING) {
3211             if (mScrollStatus == SCROLL_STATUS_IDLE) {
3212                 mScrollStatus = SCROLL_STATUS_SCROLLING_HORIZONTAL;
3213                 setVerticalScrollEnabled(false);
3214             }
3215         } else if (state == ViewPager.SCROLL_STATE_IDLE) {
3216             if (mScrollStatus == SCROLL_STATUS_SCROLLING_HORIZONTAL) {
3217                 mScrollStatus = SCROLL_STATUS_IDLE;
3218                 setVerticalScrollEnabled(true);
3219             }
3220         }
3221     }
3222 
3223     /**
3224      * Adapter for all types of items and targets in ShareSheet.
3225      * Note that ranked sections like Direct Share - while appearing grid-like - are handled on the
3226      * row level by this adapter but not on the item level. Individual targets within the row are
3227      * handled by {@link ChooserListAdapter}
3228      */
3229     @VisibleForTesting
3230     public final class ChooserGridAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
3231         private ChooserListAdapter mChooserListAdapter;
3232         private final LayoutInflater mLayoutInflater;
3233 
3234         private DirectShareViewHolder mDirectShareViewHolder;
3235         private int mChooserTargetWidth = 0;
3236         private boolean mShowAzLabelIfPoss;
3237         private boolean mLayoutRequested = false;
3238 
3239         private int mFooterHeight = 0;
3240 
3241         private static final int VIEW_TYPE_DIRECT_SHARE = 0;
3242         private static final int VIEW_TYPE_NORMAL = 1;
3243         private static final int VIEW_TYPE_CONTENT_PREVIEW = 2;
3244         private static final int VIEW_TYPE_PROFILE = 3;
3245         private static final int VIEW_TYPE_AZ_LABEL = 4;
3246         private static final int VIEW_TYPE_CALLER_AND_RANK = 5;
3247         private static final int VIEW_TYPE_FOOTER = 6;
3248 
3249         private static final int NUM_EXPANSIONS_TO_HIDE_AZ_LABEL = 20;
3250 
3251         ChooserGridAdapter(ChooserListAdapter wrappedAdapter) {
3252             super();
3253             mChooserListAdapter = wrappedAdapter;
3254             mLayoutInflater = LayoutInflater.from(ChooserActivity.this);
3255 
3256             mShowAzLabelIfPoss = getNumSheetExpansions() < NUM_EXPANSIONS_TO_HIDE_AZ_LABEL;
3257 
3258             wrappedAdapter.registerDataSetObserver(new DataSetObserver() {
3259                 @Override
3260                 public void onChanged() {
3261                     super.onChanged();
3262                     notifyDataSetChanged();
3263                 }
3264 
3265                 @Override
3266                 public void onInvalidated() {
3267                     super.onInvalidated();
3268                     notifyDataSetChanged();
3269                 }
3270             });
3271         }
3272 
3273         public void setFooterHeight(int height) {
3274             mFooterHeight = height;
3275         }
3276 
3277         /**
3278          * Calculate the chooser target width to maximize space per item
3279          *
3280          * @param width The new row width to use for recalculation
3281          * @return true if the view width has changed
3282          */
3283         public boolean calculateChooserTargetWidth(int width) {
3284             if (width == 0) {
3285                 return false;
3286             }
3287 
3288             // Limit width to the maximum width of the chooser activity
3289             int maxWidth = getResources().getDimensionPixelSize(R.dimen.chooser_width);
3290             width = Math.min(maxWidth, width);
3291 
3292             int newWidth = width / mMaxTargetsPerRow;
3293             if (newWidth != mChooserTargetWidth) {
3294                 mChooserTargetWidth = newWidth;
3295                 return true;
3296             }
3297 
3298             return false;
3299         }
3300 
3301         /**
3302          * Hides the list item content preview.
3303          * <p>Not to be confused with the sticky content preview which is above the
3304          * personal and work tabs.
3305          */
3306         public void hideContentPreview() {
3307             mLayoutRequested = true;
3308             notifyDataSetChanged();
3309         }
3310 
3311         public boolean consumeLayoutRequest() {
3312             boolean oldValue = mLayoutRequested;
3313             mLayoutRequested = false;
3314             return oldValue;
3315         }
3316 
3317         public int getRowCount() {
3318             return (int) (
3319                     getSystemRowCount()
3320                             + getProfileRowCount()
3321                             + getServiceTargetRowCount()
3322                             + getCallerAndRankedTargetRowCount()
3323                             + getAzLabelRowCount()
3324                             + Math.ceil(
3325                             (float) mChooserListAdapter.getAlphaTargetCount()
3326                                     / mMaxTargetsPerRow)
3327             );
3328         }
3329 
3330         /**
3331          * Whether the "system" row of targets is displayed.
3332          * This area includes the content preview (if present) and action row.
3333          */
3334         public int getSystemRowCount() {
3335             // For the tabbed case we show the sticky content preview above the tabs,
3336             // please refer to shouldShowStickyContentPreview
3337             if (shouldShowTabs()) {
3338                 return 0;
3339             }
3340 
3341             if (!shouldShowContentPreview()) {
3342                 return 0;
3343             }
3344 
3345             if (mChooserListAdapter == null || mChooserListAdapter.getCount() == 0) {
3346                 return 0;
3347             }
3348 
3349             return 1;
3350         }
3351 
3352         public int getProfileRowCount() {
3353             if (shouldShowTabs()) {
3354                 return 0;
3355             }
3356             return mChooserListAdapter.getOtherProfile() == null ? 0 : 1;
3357         }
3358 
3359         public int getFooterRowCount() {
3360             return 1;
3361         }
3362 
3363         public int getCallerAndRankedTargetRowCount() {
3364             return (int) Math.ceil(
3365                     ((float) mChooserListAdapter.getCallerTargetCount()
3366                             + mChooserListAdapter.getRankedTargetCount()) / mMaxTargetsPerRow);
3367         }
3368 
3369         // There can be at most one row in the listview, that is internally
3370         // a ViewGroup with 2 rows
3371         public int getServiceTargetRowCount() {
3372             if (shouldShowContentPreview()
3373                     && !ActivityManager.isLowRamDeviceStatic()) {
3374                 return 1;
3375             }
3376             return 0;
3377         }
3378 
3379         public int getAzLabelRowCount() {
3380             // Only show a label if the a-z list is showing
3381             return (mShowAzLabelIfPoss && mChooserListAdapter.getAlphaTargetCount() > 0) ? 1 : 0;
3382         }
3383 
3384         @Override
3385         public int getItemCount() {
3386             return (int) (
3387                     getSystemRowCount()
3388                             + getProfileRowCount()
3389                             + getServiceTargetRowCount()
3390                             + getCallerAndRankedTargetRowCount()
3391                             + getAzLabelRowCount()
3392                             + mChooserListAdapter.getAlphaTargetCount()
3393                             + getFooterRowCount()
3394             );
3395         }
3396 
3397         @Override
3398         public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
3399             switch (viewType) {
3400                 case VIEW_TYPE_CONTENT_PREVIEW:
3401                     return new ItemViewHolder(createContentPreviewView(parent), false, viewType);
3402                 case VIEW_TYPE_PROFILE:
3403                     return new ItemViewHolder(createProfileView(parent), false, viewType);
3404                 case VIEW_TYPE_AZ_LABEL:
3405                     return new ItemViewHolder(createAzLabelView(parent), false, viewType);
3406                 case VIEW_TYPE_NORMAL:
3407                     return new ItemViewHolder(
3408                             mChooserListAdapter.createView(parent), true, viewType);
3409                 case VIEW_TYPE_DIRECT_SHARE:
3410                 case VIEW_TYPE_CALLER_AND_RANK:
3411                     return createItemGroupViewHolder(viewType, parent);
3412                 case VIEW_TYPE_FOOTER:
3413                     Space sp = new Space(parent.getContext());
3414                     sp.setLayoutParams(new RecyclerView.LayoutParams(
3415                             LayoutParams.MATCH_PARENT, mFooterHeight));
3416                     return new FooterViewHolder(sp, viewType);
3417                 default:
3418                     // Since we catch all possible viewTypes above, no chance this is being called.
3419                     return null;
3420             }
3421         }
3422 
3423         @Override
3424         public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
3425             int viewType = ((ViewHolderBase) holder).getViewType();
3426             switch (viewType) {
3427                 case VIEW_TYPE_DIRECT_SHARE:
3428                 case VIEW_TYPE_CALLER_AND_RANK:
3429                     bindItemGroupViewHolder(position, (ItemGroupViewHolder) holder);
3430                     break;
3431                 case VIEW_TYPE_NORMAL:
3432                     bindItemViewHolder(position, (ItemViewHolder) holder);
3433                     break;
3434                 default:
3435             }
3436         }
3437 
3438         @Override
3439         public int getItemViewType(int position) {
3440             int count;
3441 
3442             int countSum = (count = getSystemRowCount());
3443             if (count > 0 && position < countSum) return VIEW_TYPE_CONTENT_PREVIEW;
3444 
3445             countSum += (count = getProfileRowCount());
3446             if (count > 0 && position < countSum) return VIEW_TYPE_PROFILE;
3447 
3448             countSum += (count = getServiceTargetRowCount());
3449             if (count > 0 && position < countSum) return VIEW_TYPE_DIRECT_SHARE;
3450 
3451             countSum += (count = getCallerAndRankedTargetRowCount());
3452             if (count > 0 && position < countSum) return VIEW_TYPE_CALLER_AND_RANK;
3453 
3454             countSum += (count = getAzLabelRowCount());
3455             if (count > 0 && position < countSum) return VIEW_TYPE_AZ_LABEL;
3456 
3457             if (position == getItemCount() - 1) return VIEW_TYPE_FOOTER;
3458 
3459             return VIEW_TYPE_NORMAL;
3460         }
3461 
3462         public int getTargetType(int position) {
3463             return mChooserListAdapter.getPositionTargetType(getListPosition(position));
3464         }
3465 
3466         private View createProfileView(ViewGroup parent) {
3467             View profileRow = mLayoutInflater.inflate(R.layout.chooser_profile_row, parent, false);
3468             mProfileView = profileRow.findViewById(R.id.profile_button);
3469             mProfileView.setOnClickListener(ChooserActivity.this::onProfileClick);
3470             updateProfileViewButton();
3471             return profileRow;
3472         }
3473 
3474         private View createAzLabelView(ViewGroup parent) {
3475             return mLayoutInflater.inflate(R.layout.chooser_az_label_row, parent, false);
3476         }
3477 
3478         private ItemGroupViewHolder loadViewsIntoGroup(ItemGroupViewHolder holder) {
3479             final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
3480             final int exactSpec = MeasureSpec.makeMeasureSpec(mChooserTargetWidth,
3481                     MeasureSpec.EXACTLY);
3482             int columnCount = holder.getColumnCount();
3483 
3484             final boolean isDirectShare = holder instanceof DirectShareViewHolder;
3485 
3486             for (int i = 0; i < columnCount; i++) {
3487                 final View v = mChooserListAdapter.createView(holder.getRowByIndex(i));
3488                 final int column = i;
3489                 v.setOnClickListener(new OnClickListener() {
3490                     @Override
3491                     public void onClick(View v) {
3492                         startSelected(holder.getItemIndex(column), false, true);
3493                     }
3494                 });
3495 
3496                 // Show menu for both direct share and app share targets after long click.
3497                 v.setOnLongClickListener(v1 -> {
3498                     TargetInfo ti = mChooserListAdapter.targetInfoForPosition(
3499                             holder.getItemIndex(column), true);
3500                     if (shouldShowTargetDetails(ti)) {
3501                         showTargetDetails(ti);
3502                     }
3503                     return true;
3504                 });
3505 
3506                 holder.addView(i, v);
3507 
3508                 // Force Direct Share to be 2 lines and auto-wrap to second line via hoz scroll =
3509                 // false. TextView#setHorizontallyScrolling must be reset after #setLines. Must be
3510                 // done before measuring.
3511                 if (isDirectShare) {
3512                     final ViewHolder vh = (ViewHolder) v.getTag();
3513                     vh.text.setLines(2);
3514                     vh.text.setHorizontallyScrolling(false);
3515                     vh.text2.setVisibility(View.GONE);
3516                 }
3517 
3518                 // Force height to be a given so we don't have visual disruption during scaling.
3519                 v.measure(exactSpec, spec);
3520                 setViewBounds(v, v.getMeasuredWidth(), v.getMeasuredHeight());
3521             }
3522 
3523             final ViewGroup viewGroup = holder.getViewGroup();
3524 
3525             // Pre-measure and fix height so we can scale later.
3526             holder.measure();
3527             setViewBounds(viewGroup, LayoutParams.MATCH_PARENT, holder.getMeasuredRowHeight());
3528 
3529             if (isDirectShare) {
3530                 DirectShareViewHolder dsvh = (DirectShareViewHolder) holder;
3531                 setViewBounds(dsvh.getRow(0), LayoutParams.MATCH_PARENT, dsvh.getMinRowHeight());
3532                 setViewBounds(dsvh.getRow(1), LayoutParams.MATCH_PARENT, dsvh.getMinRowHeight());
3533             }
3534 
3535             viewGroup.setTag(holder);
3536             return holder;
3537         }
3538 
3539         private void setViewBounds(View view, int widthPx, int heightPx) {
3540             LayoutParams lp = view.getLayoutParams();
3541             if (lp == null) {
3542                 lp = new LayoutParams(widthPx, heightPx);
3543                 view.setLayoutParams(lp);
3544             } else {
3545                 lp.height = heightPx;
3546                 lp.width = widthPx;
3547             }
3548         }
3549 
3550         ItemGroupViewHolder createItemGroupViewHolder(int viewType, ViewGroup parent) {
3551             if (viewType == VIEW_TYPE_DIRECT_SHARE) {
3552                 ViewGroup parentGroup = (ViewGroup) mLayoutInflater.inflate(
3553                         R.layout.chooser_row_direct_share, parent, false);
3554                 ViewGroup row1 = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
3555                         parentGroup, false);
3556                 ViewGroup row2 = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
3557                         parentGroup, false);
3558                 parentGroup.addView(row1);
3559                 parentGroup.addView(row2);
3560 
3561                 mDirectShareViewHolder = new DirectShareViewHolder(parentGroup,
3562                         Lists.newArrayList(row1, row2), mMaxTargetsPerRow, viewType,
3563                         mChooserMultiProfilePagerAdapter::getActiveListAdapter);
3564                 loadViewsIntoGroup(mDirectShareViewHolder);
3565 
3566                 return mDirectShareViewHolder;
3567             } else {
3568                 ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row, parent,
3569                         false);
3570                 ItemGroupViewHolder holder =
3571                         new SingleRowViewHolder(row, mMaxTargetsPerRow, viewType);
3572                 loadViewsIntoGroup(holder);
3573 
3574                 return holder;
3575             }
3576         }
3577 
3578         /**
3579          * Need to merge CALLER + ranked STANDARD into a single row and prevent a separator from
3580          * showing on top of the AZ list if the AZ label is visible. All other types are placed into
3581          * their own row as determined by their target type, and dividers are added in the list to
3582          * separate each type.
3583          */
3584         int getRowType(int rowPosition) {
3585             // Merge caller and ranked standard into a single row
3586             int positionType = mChooserListAdapter.getPositionTargetType(rowPosition);
3587             if (positionType == ChooserListAdapter.TARGET_CALLER) {
3588                 return ChooserListAdapter.TARGET_STANDARD;
3589             }
3590 
3591             // If an the A-Z label is shown, prevent a separator from appearing by making the A-Z
3592             // row type the same as the suggestion row type
3593             if (getAzLabelRowCount() > 0 && positionType == ChooserListAdapter.TARGET_STANDARD_AZ) {
3594                 return ChooserListAdapter.TARGET_STANDARD;
3595             }
3596 
3597             return positionType;
3598         }
3599 
3600         void bindItemViewHolder(int position, ItemViewHolder holder) {
3601             View v = holder.itemView;
3602             int listPosition = getListPosition(position);
3603             holder.mListPosition = listPosition;
3604             mChooserListAdapter.bindView(listPosition, v);
3605         }
3606 
3607         void bindItemGroupViewHolder(int position, ItemGroupViewHolder holder) {
3608             final ViewGroup viewGroup = (ViewGroup) holder.itemView;
3609             int start = getListPosition(position);
3610             int startType = getRowType(start);
3611 
3612             int columnCount = holder.getColumnCount();
3613             int end = start + columnCount - 1;
3614             while (getRowType(end) != startType && end >= start) {
3615                 end--;
3616             }
3617 
3618             if (end == start && mChooserListAdapter.getItem(start) instanceof EmptyTargetInfo) {
3619                 final TextView textView = viewGroup.findViewById(R.id.chooser_row_text_option);
3620 
3621                 if (textView.getVisibility() != View.VISIBLE) {
3622                     textView.setAlpha(0.0f);
3623                     textView.setVisibility(View.VISIBLE);
3624                     textView.setText(R.string.chooser_no_direct_share_targets);
3625 
3626                     ValueAnimator fadeAnim = ObjectAnimator.ofFloat(textView, "alpha", 0.0f, 1.0f);
3627                     fadeAnim.setInterpolator(new DecelerateInterpolator(1.0f));
3628 
3629                     float translationInPx = getResources().getDimensionPixelSize(
3630                             R.dimen.chooser_row_text_option_translate);
3631                     textView.setTranslationY(translationInPx);
3632                     ValueAnimator translateAnim = ObjectAnimator.ofFloat(textView, "translationY",
3633                             0.0f);
3634                     translateAnim.setInterpolator(new DecelerateInterpolator(1.0f));
3635 
3636                     AnimatorSet animSet = new AnimatorSet();
3637                     animSet.setDuration(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
3638                     animSet.setStartDelay(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
3639                     animSet.playTogether(fadeAnim, translateAnim);
3640                     animSet.start();
3641                 }
3642             }
3643 
3644             for (int i = 0; i < columnCount; i++) {
3645                 final View v = holder.getView(i);
3646 
3647                 if (start + i <= end) {
3648                     holder.setViewVisibility(i, View.VISIBLE);
3649                     holder.setItemIndex(i, start + i);
3650                     mChooserListAdapter.bindView(holder.getItemIndex(i), v);
3651                 } else {
3652                     holder.setViewVisibility(i, View.INVISIBLE);
3653                 }
3654             }
3655         }
3656 
3657         int getListPosition(int position) {
3658             position -= getSystemRowCount() + getProfileRowCount();
3659 
3660             final int serviceCount = mChooserListAdapter.getServiceTargetCount();
3661             final int serviceRows = (int) Math.ceil((float) serviceCount / getMaxRankedTargets());
3662             if (position < serviceRows) {
3663                 return position * mMaxTargetsPerRow;
3664             }
3665 
3666             position -= serviceRows;
3667 
3668             final int callerAndRankedCount = mChooserListAdapter.getCallerTargetCount()
3669                                                  + mChooserListAdapter.getRankedTargetCount();
3670             final int callerAndRankedRows = getCallerAndRankedTargetRowCount();
3671             if (position < callerAndRankedRows) {
3672                 return serviceCount + position * mMaxTargetsPerRow;
3673             }
3674 
3675             position -= getAzLabelRowCount() + callerAndRankedRows;
3676 
3677             return callerAndRankedCount + serviceCount + position;
3678         }
3679 
3680         public void handleScroll(View v, int y, int oldy) {
3681             boolean canExpandDirectShare = canExpandDirectShare();
3682             if (mDirectShareViewHolder != null && canExpandDirectShare) {
3683                 mDirectShareViewHolder.handleScroll(
3684                         mChooserMultiProfilePagerAdapter.getActiveAdapterView(), y, oldy,
3685                         mMaxTargetsPerRow);
3686             }
3687         }
3688 
3689         /**
3690          * Only expand direct share area if there is a minimum number of targets.
3691          */
3692         private boolean canExpandDirectShare() {
3693             // Do not enable until we have confirmed more apps are using sharing shortcuts
3694             // Check git history for enablement logic
3695             return false;
3696         }
3697 
3698         public ChooserListAdapter getListAdapter() {
3699             return mChooserListAdapter;
3700         }
3701 
3702         boolean shouldCellSpan(int position) {
3703             return getItemViewType(position) == VIEW_TYPE_NORMAL;
3704         }
3705 
3706         void updateDirectShareExpansion() {
3707             if (mDirectShareViewHolder == null || !canExpandDirectShare()) {
3708                 return;
3709             }
3710             RecyclerView activeAdapterView =
3711                     mChooserMultiProfilePagerAdapter.getActiveAdapterView();
3712             if (mResolverDrawerLayout.isCollapsed()) {
3713                 mDirectShareViewHolder.collapse(activeAdapterView);
3714             } else {
3715                 mDirectShareViewHolder.expand(activeAdapterView);
3716             }
3717         }
3718     }
3719 
3720     /**
3721      * Used to bind types for group of items including:
3722      * {@link ChooserGridAdapter#VIEW_TYPE_DIRECT_SHARE},
3723      * and {@link ChooserGridAdapter#VIEW_TYPE_CALLER_AND_RANK}.
3724      */
3725     abstract static class ItemGroupViewHolder extends ViewHolderBase {
3726         protected int mMeasuredRowHeight;
3727         private int[] mItemIndices;
3728         protected final View[] mCells;
3729         private final int mColumnCount;
3730 
3731         ItemGroupViewHolder(int cellCount, View itemView, int viewType) {
3732             super(itemView, viewType);
3733             this.mCells = new View[cellCount];
3734             this.mItemIndices = new int[cellCount];
3735             this.mColumnCount = cellCount;
3736         }
3737 
3738         abstract ViewGroup addView(int index, View v);
3739 
3740         abstract ViewGroup getViewGroup();
3741 
3742         abstract ViewGroup getRowByIndex(int index);
3743 
3744         abstract ViewGroup getRow(int rowNumber);
3745 
3746         abstract void setViewVisibility(int i, int visibility);
3747 
3748         public int getColumnCount() {
3749             return mColumnCount;
3750         }
3751 
3752         public void measure() {
3753             final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
3754             getViewGroup().measure(spec, spec);
3755             mMeasuredRowHeight = getViewGroup().getMeasuredHeight();
3756         }
3757 
3758         public int getMeasuredRowHeight() {
3759             return mMeasuredRowHeight;
3760         }
3761 
3762         public void setItemIndex(int itemIndex, int listIndex) {
3763             mItemIndices[itemIndex] = listIndex;
3764         }
3765 
3766         public int getItemIndex(int itemIndex) {
3767             return mItemIndices[itemIndex];
3768         }
3769 
3770         public View getView(int index) {
3771             return mCells[index];
3772         }
3773     }
3774 
3775     static class SingleRowViewHolder extends ItemGroupViewHolder {
3776         private final ViewGroup mRow;
3777 
3778         SingleRowViewHolder(ViewGroup row, int cellCount, int viewType) {
3779             super(cellCount, row, viewType);
3780 
3781             this.mRow = row;
3782         }
3783 
3784         public ViewGroup getViewGroup() {
3785             return mRow;
3786         }
3787 
3788         public ViewGroup getRowByIndex(int index) {
3789             return mRow;
3790         }
3791 
3792         public ViewGroup getRow(int rowNumber) {
3793             if (rowNumber == 0) return mRow;
3794             return null;
3795         }
3796 
3797         public ViewGroup addView(int index, View v) {
3798             mRow.addView(v);
3799             mCells[index] = v;
3800 
3801             return mRow;
3802         }
3803 
3804         public void setViewVisibility(int i, int visibility) {
3805             getView(i).setVisibility(visibility);
3806         }
3807     }
3808 
3809     static class DirectShareViewHolder extends ItemGroupViewHolder {
3810         private final ViewGroup mParent;
3811         private final List<ViewGroup> mRows;
3812         private int mCellCountPerRow;
3813 
3814         private boolean mHideDirectShareExpansion = false;
3815         private int mDirectShareMinHeight = 0;
3816         private int mDirectShareCurrHeight = 0;
3817         private int mDirectShareMaxHeight = 0;
3818 
3819         private final boolean[] mCellVisibility;
3820 
3821         private final Supplier<ChooserListAdapter> mListAdapterSupplier;
3822 
3823         DirectShareViewHolder(ViewGroup parent, List<ViewGroup> rows, int cellCountPerRow,
3824                 int viewType, Supplier<ChooserListAdapter> listAdapterSupplier) {
3825             super(rows.size() * cellCountPerRow, parent, viewType);
3826 
3827             this.mParent = parent;
3828             this.mRows = rows;
3829             this.mCellCountPerRow = cellCountPerRow;
3830             this.mCellVisibility = new boolean[rows.size() * cellCountPerRow];
3831             Arrays.fill(mCellVisibility, true);
3832             this.mListAdapterSupplier = listAdapterSupplier;
3833         }
3834 
3835         public ViewGroup addView(int index, View v) {
3836             ViewGroup row = getRowByIndex(index);
3837             row.addView(v);
3838             mCells[index] = v;
3839 
3840             return row;
3841         }
3842 
3843         public ViewGroup getViewGroup() {
3844             return mParent;
3845         }
3846 
3847         public ViewGroup getRowByIndex(int index) {
3848             return mRows.get(index / mCellCountPerRow);
3849         }
3850 
3851         public ViewGroup getRow(int rowNumber) {
3852             return mRows.get(rowNumber);
3853         }
3854 
3855         public void measure() {
3856             final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
3857             getRow(0).measure(spec, spec);
3858             getRow(1).measure(spec, spec);
3859 
3860             mDirectShareMinHeight = getRow(0).getMeasuredHeight();
3861             mDirectShareCurrHeight = mDirectShareCurrHeight > 0
3862                     ? mDirectShareCurrHeight : mDirectShareMinHeight;
3863             mDirectShareMaxHeight = 2 * mDirectShareMinHeight;
3864         }
3865 
3866         public int getMeasuredRowHeight() {
3867             return mDirectShareCurrHeight;
3868         }
3869 
3870         public int getMinRowHeight() {
3871             return mDirectShareMinHeight;
3872         }
3873 
3874         public void setViewVisibility(int i, int visibility) {
3875             final View v = getView(i);
3876             if (visibility == View.VISIBLE) {
3877                 mCellVisibility[i] = true;
3878                 v.setVisibility(visibility);
3879                 v.setAlpha(1.0f);
3880             } else if (visibility == View.INVISIBLE && mCellVisibility[i]) {
3881                 mCellVisibility[i] = false;
3882 
3883                 ValueAnimator fadeAnim = ObjectAnimator.ofFloat(v, "alpha", 1.0f, 0f);
3884                 fadeAnim.setDuration(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
3885                 fadeAnim.setInterpolator(new AccelerateInterpolator(1.0f));
3886                 fadeAnim.addListener(new AnimatorListenerAdapter() {
3887                     public void onAnimationEnd(Animator animation) {
3888                         v.setVisibility(View.INVISIBLE);
3889                     }
3890                 });
3891                 fadeAnim.start();
3892             }
3893         }
3894 
3895         public void handleScroll(RecyclerView view, int y, int oldy, int maxTargetsPerRow) {
3896             // only exit early if fully collapsed, otherwise onListRebuilt() with shifting
3897             // targets can lock us into an expanded mode
3898             boolean notExpanded = mDirectShareCurrHeight == mDirectShareMinHeight;
3899             if (notExpanded) {
3900                 if (mHideDirectShareExpansion) {
3901                     return;
3902                 }
3903 
3904                 // only expand if we have more than maxTargetsPerRow, and delay that decision
3905                 // until they start to scroll
3906                 ChooserListAdapter adapter = mListAdapterSupplier.get();
3907                 int validTargets = adapter.getSelectableServiceTargetCount();
3908                 if (validTargets <= maxTargetsPerRow) {
3909                     mHideDirectShareExpansion = true;
3910                     return;
3911                 }
3912             }
3913 
3914             int yDiff = (int) ((oldy - y) * DIRECT_SHARE_EXPANSION_RATE);
3915 
3916             int prevHeight = mDirectShareCurrHeight;
3917             int newHeight = Math.min(prevHeight + yDiff, mDirectShareMaxHeight);
3918             newHeight = Math.max(newHeight, mDirectShareMinHeight);
3919             yDiff = newHeight - prevHeight;
3920 
3921             updateDirectShareRowHeight(view, yDiff, newHeight);
3922         }
3923 
3924         void expand(RecyclerView view) {
3925             updateDirectShareRowHeight(view, mDirectShareMaxHeight - mDirectShareCurrHeight,
3926                     mDirectShareMaxHeight);
3927         }
3928 
3929         void collapse(RecyclerView view) {
3930             updateDirectShareRowHeight(view, mDirectShareMinHeight - mDirectShareCurrHeight,
3931                     mDirectShareMinHeight);
3932         }
3933 
3934         private void updateDirectShareRowHeight(RecyclerView view, int yDiff, int newHeight) {
3935             if (view == null || view.getChildCount() == 0 || yDiff == 0) {
3936                 return;
3937             }
3938 
3939             // locate the item to expand, and offset the rows below that one
3940             boolean foundExpansion = false;
3941             for (int i = 0; i < view.getChildCount(); i++) {
3942                 View child = view.getChildAt(i);
3943 
3944                 if (foundExpansion) {
3945                     child.offsetTopAndBottom(yDiff);
3946                 } else {
3947                     if (child.getTag() != null && child.getTag() instanceof DirectShareViewHolder) {
3948                         int widthSpec = MeasureSpec.makeMeasureSpec(child.getWidth(),
3949                                 MeasureSpec.EXACTLY);
3950                         int heightSpec = MeasureSpec.makeMeasureSpec(newHeight,
3951                                 MeasureSpec.EXACTLY);
3952                         child.measure(widthSpec, heightSpec);
3953                         child.getLayoutParams().height = child.getMeasuredHeight();
3954                         child.layout(child.getLeft(), child.getTop(), child.getRight(),
3955                                 child.getTop() + child.getMeasuredHeight());
3956 
3957                         foundExpansion = true;
3958                     }
3959                 }
3960             }
3961 
3962             if (foundExpansion) {
3963                 mDirectShareCurrHeight = newHeight;
3964             }
3965         }
3966     }
3967 
3968     /**
3969      * Shortcuts grouped by application.
3970      */
3971     @VisibleForTesting
3972     public static class ServiceResultInfo {
3973         public final DisplayResolveInfo originalTarget;
3974         public final List<ChooserTarget> resultTargets;
3975         public final UserHandle userHandle;
3976 
3977         public ServiceResultInfo(DisplayResolveInfo ot, List<ChooserTarget> rt,
3978                 UserHandle userHandle) {
3979             originalTarget = ot;
3980             resultTargets = rt;
3981             this.userHandle = userHandle;
3982         }
3983     }
3984 
3985     static class ChooserTargetRankingInfo {
3986         public final List<AppTarget> scores;
3987         public final UserHandle userHandle;
3988 
3989         ChooserTargetRankingInfo(List<AppTarget> chooserTargetScores,
3990                 UserHandle userHandle) {
3991             this.scores = chooserTargetScores;
3992             this.userHandle = userHandle;
3993         }
3994     }
3995 
3996     static class RefinementResultReceiver extends ResultReceiver {
3997         private ChooserActivity mChooserActivity;
3998         private TargetInfo mSelectedTarget;
3999 
4000         public RefinementResultReceiver(ChooserActivity host, TargetInfo target,
4001                 Handler handler) {
4002             super(handler);
4003             mChooserActivity = host;
4004             mSelectedTarget = target;
4005         }
4006 
4007         @Override
4008         protected void onReceiveResult(int resultCode, Bundle resultData) {
4009             if (mChooserActivity == null) {
4010                 Log.e(TAG, "Destroyed RefinementResultReceiver received a result");
4011                 return;
4012             }
4013             if (resultData == null) {
4014                 Log.e(TAG, "RefinementResultReceiver received null resultData");
4015                 return;
4016             }
4017 
4018             switch (resultCode) {
4019                 case RESULT_CANCELED:
4020                     mChooserActivity.onRefinementCanceled();
4021                     break;
4022                 case RESULT_OK:
4023                     Parcelable intentParcelable = resultData.getParcelable(Intent.EXTRA_INTENT);
4024                     if (intentParcelable instanceof Intent) {
4025                         mChooserActivity.onRefinementResult(mSelectedTarget,
4026                                 (Intent) intentParcelable);
4027                     } else {
4028                         Log.e(TAG, "RefinementResultReceiver received RESULT_OK but no Intent"
4029                                 + " in resultData with key Intent.EXTRA_INTENT");
4030                     }
4031                     break;
4032                 default:
4033                     Log.w(TAG, "Unknown result code " + resultCode
4034                             + " sent to RefinementResultReceiver");
4035                     break;
4036             }
4037         }
4038 
4039         public void destroy() {
4040             mChooserActivity = null;
4041             mSelectedTarget = null;
4042         }
4043     }
4044 
4045     /**
4046      * Used internally to round image corners while obeying view padding.
4047      */
4048     public static class RoundedRectImageView extends ImageView {
4049         private int mRadius = 0;
4050         private Path mPath = new Path();
4051         private Paint mOverlayPaint = new Paint(0);
4052         private Paint mRoundRectPaint = new Paint(0);
4053         private Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
4054         private String mExtraImageCount = null;
4055 
4056         public RoundedRectImageView(Context context) {
4057             super(context);
4058         }
4059 
4060         public RoundedRectImageView(Context context, AttributeSet attrs) {
4061             this(context, attrs, 0);
4062         }
4063 
4064         public RoundedRectImageView(Context context, AttributeSet attrs, int defStyleAttr) {
4065             this(context, attrs, defStyleAttr, 0);
4066         }
4067 
4068         public RoundedRectImageView(Context context, AttributeSet attrs, int defStyleAttr,
4069                 int defStyleRes) {
4070             super(context, attrs, defStyleAttr, defStyleRes);
4071             mRadius = context.getResources().getDimensionPixelSize(R.dimen.chooser_corner_radius);
4072 
4073             mOverlayPaint.setColor(0x99000000);
4074             mOverlayPaint.setStyle(Paint.Style.FILL);
4075 
4076             mRoundRectPaint.setColor(context.getResources().getColor(R.color.chooser_row_divider));
4077             mRoundRectPaint.setStyle(Paint.Style.STROKE);
4078             mRoundRectPaint.setStrokeWidth(context.getResources()
4079                     .getDimensionPixelSize(R.dimen.chooser_preview_image_border));
4080 
4081             mTextPaint.setColor(Color.WHITE);
4082             mTextPaint.setTextSize(context.getResources()
4083                     .getDimensionPixelSize(R.dimen.chooser_preview_image_font_size));
4084             mTextPaint.setTextAlign(Paint.Align.CENTER);
4085         }
4086 
4087         private void updatePath(int width, int height) {
4088             mPath.reset();
4089 
4090             int imageWidth = width - getPaddingRight() - getPaddingLeft();
4091             int imageHeight = height - getPaddingBottom() - getPaddingTop();
4092             mPath.addRoundRect(getPaddingLeft(), getPaddingTop(), imageWidth, imageHeight, mRadius,
4093                     mRadius, Path.Direction.CW);
4094         }
4095 
4096         /**
4097           * Sets the corner radius on all corners
4098           *
4099           * param radius 0 for no radius, &gt; 0 for a visible corner radius
4100           */
4101         public void setRadius(int radius) {
4102             mRadius = radius;
4103             updatePath(getWidth(), getHeight());
4104         }
4105 
4106         /**
4107           * Display an overlay with extra image count on 3rd image
4108           */
4109         public void setExtraImageCount(int count) {
4110             if (count > 0) {
4111                 this.mExtraImageCount = "+" + count;
4112             } else {
4113                 this.mExtraImageCount = null;
4114             }
4115         }
4116 
4117         @Override
4118         protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
4119             super.onSizeChanged(width, height, oldWidth, oldHeight);
4120             updatePath(width, height);
4121         }
4122 
4123         @Override
4124         protected void onDraw(Canvas canvas) {
4125             if (mRadius != 0) {
4126                 canvas.clipPath(mPath);
4127             }
4128 
4129             super.onDraw(canvas);
4130 
4131             int x = getPaddingLeft();
4132             int y = getPaddingRight();
4133             int width = getWidth() - getPaddingRight() - getPaddingLeft();
4134             int height = getHeight() - getPaddingBottom() - getPaddingTop();
4135             if (mExtraImageCount != null) {
4136                 canvas.drawRect(x, y, width, height, mOverlayPaint);
4137 
4138                 int xPos = canvas.getWidth() / 2;
4139                 int yPos = (int) ((canvas.getHeight() / 2.0f)
4140                         - ((mTextPaint.descent() + mTextPaint.ascent()) / 2.0f));
4141 
4142                 canvas.drawText(mExtraImageCount, xPos, yPos, mTextPaint);
4143             }
4144 
4145             canvas.drawRoundRect(x, y, width, height, mRadius, mRadius, mRoundRectPaint);
4146         }
4147     }
4148 
4149     /**
4150      * A helper class to track app's readiness for the scene transition animation.
4151      * The app is ready when both the image is laid out and the drawer offset is calculated.
4152      */
4153     private class EnterTransitionAnimationDelegate implements View.OnLayoutChangeListener {
4154         private boolean mPreviewReady = false;
4155         private boolean mOffsetCalculated = false;
4156 
4157         void postponeTransition() {
4158             postponeEnterTransition();
4159         }
4160 
4161         void markImagePreviewReady() {
4162             if (!mPreviewReady) {
4163                 mPreviewReady = true;
4164                 maybeStartListenForLayout();
4165             }
4166         }
4167 
4168         void markOffsetCalculated() {
4169             if (!mOffsetCalculated) {
4170                 mOffsetCalculated = true;
4171                 maybeStartListenForLayout();
4172             }
4173         }
4174 
4175         private void maybeStartListenForLayout() {
4176             if (mPreviewReady && mOffsetCalculated && mResolverDrawerLayout != null) {
4177                 if (mResolverDrawerLayout.isInLayout()) {
4178                     startPostponedEnterTransition();
4179                 } else {
4180                     mResolverDrawerLayout.addOnLayoutChangeListener(this);
4181                     mResolverDrawerLayout.requestLayout();
4182                 }
4183             }
4184         }
4185 
4186         @Override
4187         public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
4188                 int oldTop, int oldRight, int oldBottom) {
4189             v.removeOnLayoutChangeListener(this);
4190             startPostponedEnterTransition();
4191         }
4192     }
4193 
4194     /**
4195      * Used in combination with the scene transition when launching the image editor
4196      */
4197     private static class FinishAnimation extends AlphaAnimation implements
4198             Animation.AnimationListener {
4199         @Nullable
4200         private Activity mActivity;
4201         @Nullable
4202         private View mRootView;
4203         private final float mFromAlpha;
4204 
4205         FinishAnimation(@NonNull Activity activity, @NonNull View rootView) {
4206             super(rootView.getAlpha(), 0.0f);
4207             mActivity = activity;
4208             mRootView = rootView;
4209             mFromAlpha = rootView.getAlpha();
4210             setInterpolator(new LinearInterpolator());
4211             long duration = activity.getWindow().getTransitionBackgroundFadeDuration();
4212             setDuration(duration);
4213             // The scene transition animation looks better when it's not overlapped with this
4214             // fade-out animation thus the delay.
4215             // It is most likely that the image editor will cause this activity to stop and this
4216             // animation will be cancelled in the background without running (i.e. we'll animate
4217             // only when this activity remains partially visible after the image editor launch).
4218             setStartOffset(duration);
4219             super.setAnimationListener(this);
4220         }
4221 
4222         @Override
4223         public void setAnimationListener(AnimationListener listener) {
4224             throw new UnsupportedOperationException();
4225         }
4226 
4227         @Override
4228         public void cancel() {
4229             if (mRootView != null) {
4230                 mRootView.setAlpha(mFromAlpha);
4231             }
4232             cleanup();
4233             super.cancel();
4234         }
4235 
4236         @Override
4237         public void onAnimationStart(Animation animation) {
4238         }
4239 
4240         @Override
4241         public void onAnimationEnd(Animation animation) {
4242             Activity activity = mActivity;
4243             cleanup();
4244             if (activity != null) {
4245                 activity.finish();
4246             }
4247         }
4248 
4249         @Override
4250         public void onAnimationRepeat(Animation animation) {
4251         }
4252 
4253         private void cleanup() {
4254             mActivity = null;
4255             mRootView = null;
4256         }
4257     }
4258 
4259     @Override
4260     protected void maybeLogProfileChange() {
4261         getChooserActivityLogger().logShareheetProfileChanged();
4262     }
4263 
4264     private boolean shouldNearbyShareBeFirstInRankedRow() {
4265         return ActivityManager.isLowRamDeviceStatic() && mIsNearbyShareFirstTargetInRankedApp;
4266     }
4267 
4268     private boolean shouldNearbyShareBeIncludedAsActionButton() {
4269         return !shouldNearbyShareBeFirstInRankedRow();
4270     }
4271 }
4272