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