• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.app;
18 
19 import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_PERSONAL;
20 import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_WORK;
21 import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_PERSONAL;
22 import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_WORK;
23 import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE;
24 import static android.content.ContentProvider.getUserIdFromUri;
25 import static android.stats.devicepolicy.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL;
26 import static android.stats.devicepolicy.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK;
27 
28 import static com.android.internal.util.LatencyTracker.ACTION_LOAD_SHARE_SHEET;
29 
30 import static java.lang.annotation.RetentionPolicy.SOURCE;
31 
32 import android.animation.Animator;
33 import android.animation.AnimatorListenerAdapter;
34 import android.animation.AnimatorSet;
35 import android.animation.ObjectAnimator;
36 import android.animation.ValueAnimator;
37 import android.annotation.IntDef;
38 import android.annotation.NonNull;
39 import android.annotation.Nullable;
40 import android.app.Activity;
41 import android.app.ActivityManager;
42 import android.app.ActivityOptions;
43 import android.app.SharedElementCallback;
44 import android.app.prediction.AppPredictionContext;
45 import android.app.prediction.AppPredictionManager;
46 import android.app.prediction.AppPredictor;
47 import android.app.prediction.AppTarget;
48 import android.app.prediction.AppTargetEvent;
49 import android.app.prediction.AppTargetId;
50 import android.compat.annotation.UnsupportedAppUsage;
51 import android.content.ClipData;
52 import android.content.ClipboardManager;
53 import android.content.ComponentName;
54 import android.content.ContentResolver;
55 import android.content.Context;
56 import android.content.Intent;
57 import android.content.IntentFilter;
58 import android.content.IntentSender;
59 import android.content.IntentSender.SendIntentException;
60 import android.content.SharedPreferences;
61 import android.content.pm.ActivityInfo;
62 import android.content.pm.ApplicationInfo;
63 import android.content.pm.PackageManager;
64 import android.content.pm.PackageManager.NameNotFoundException;
65 import android.content.pm.ResolveInfo;
66 import android.content.pm.ShortcutInfo;
67 import android.content.pm.ShortcutManager;
68 import android.content.res.Configuration;
69 import android.content.res.Resources;
70 import android.database.Cursor;
71 import android.database.DataSetObserver;
72 import android.graphics.Bitmap;
73 import android.graphics.Canvas;
74 import android.graphics.Color;
75 import android.graphics.Insets;
76 import android.graphics.Paint;
77 import android.graphics.Path;
78 import android.graphics.drawable.AnimatedVectorDrawable;
79 import android.graphics.drawable.Drawable;
80 import android.metrics.LogMaker;
81 import android.net.Uri;
82 import android.os.AsyncTask;
83 import android.os.Bundle;
84 import android.os.Environment;
85 import android.os.Handler;
86 import android.os.Message;
87 import android.os.Parcelable;
88 import android.os.PatternMatcher;
89 import android.os.ResultReceiver;
90 import android.os.UserHandle;
91 import android.os.UserManager;
92 import android.os.storage.StorageManager;
93 import android.provider.DeviceConfig;
94 import android.provider.DocumentsContract;
95 import android.provider.Downloads;
96 import android.provider.OpenableColumns;
97 import android.provider.Settings;
98 import android.service.chooser.ChooserTarget;
99 import android.text.TextUtils;
100 import android.util.AttributeSet;
101 import android.util.HashedStringCache;
102 import android.util.Log;
103 import android.util.PluralsMessageFormatter;
104 import android.util.Size;
105 import android.util.Slog;
106 import android.view.LayoutInflater;
107 import android.view.View;
108 import android.view.View.MeasureSpec;
109 import android.view.View.OnClickListener;
110 import android.view.ViewGroup;
111 import android.view.ViewGroup.LayoutParams;
112 import android.view.ViewTreeObserver;
113 import android.view.WindowInsets;
114 import android.view.animation.AccelerateInterpolator;
115 import android.view.animation.AlphaAnimation;
116 import android.view.animation.Animation;
117 import android.view.animation.DecelerateInterpolator;
118 import android.view.animation.LinearInterpolator;
119 import android.widget.Button;
120 import android.widget.ImageView;
121 import android.widget.Space;
122 import android.widget.TextView;
123 
124 import com.android.internal.R;
125 import com.android.internal.annotations.VisibleForTesting;
126 import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyState;
127 import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider;
128 import com.android.internal.app.NoCrossProfileEmptyStateProvider.DevicePolicyBlockerEmptyState;
129 import com.android.internal.app.ResolverListAdapter.ActivityInfoPresentationGetter;
130 import com.android.internal.app.ResolverListAdapter.ViewHolder;
131 import com.android.internal.app.chooser.ChooserTargetInfo;
132 import com.android.internal.app.chooser.DisplayResolveInfo;
133 import com.android.internal.app.chooser.MultiDisplayResolveInfo;
134 import com.android.internal.app.chooser.NotSelectableTargetInfo;
135 import com.android.internal.app.chooser.SelectableTargetInfo;
136 import com.android.internal.app.chooser.SelectableTargetInfo.SelectableTargetInfoCommunicator;
137 import com.android.internal.app.chooser.TargetInfo;
138 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
139 import com.android.internal.content.PackageMonitor;
140 import com.android.internal.logging.MetricsLogger;
141 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
142 import com.android.internal.util.FrameworkStatsLog;
143 import com.android.internal.widget.GridLayoutManager;
144 import com.android.internal.widget.RecyclerView;
145 import com.android.internal.widget.ResolverDrawerLayout;
146 import com.android.internal.widget.ViewPager;
147 
148 import com.google.android.collect.Lists;
149 
150 import java.io.File;
151 import java.io.IOException;
152 import java.lang.annotation.Retention;
153 import java.lang.annotation.RetentionPolicy;
154 import java.net.URISyntaxException;
155 import java.text.Collator;
156 import java.util.ArrayList;
157 import java.util.Arrays;
158 import java.util.Collections;
159 import java.util.Comparator;
160 import java.util.HashMap;
161 import java.util.List;
162 import java.util.Map;
163 import java.util.Objects;
164 import java.util.function.Supplier;
165 import java.util.stream.Collectors;
166 
167 /**
168  * 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 ResolverAppPredictorCallback createAppPredictorCallback(
781             ChooserListAdapter chooserListAdapter) {
782         return new ResolverAppPredictorCallback(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 modifyTargetIntent(Intent in) {
1804         if (isSendAction(in)) {
1805             in.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT |
1806                     Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
1807         }
1808     }
1809 
1810     @Override
1811     protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) {
1812         if (mRefinementIntentSender != null) {
1813             final Intent fillIn = new Intent();
1814             final List<Intent> sourceIntents = target.getAllSourceIntents();
1815             if (!sourceIntents.isEmpty()) {
1816                 fillIn.putExtra(Intent.EXTRA_INTENT, sourceIntents.get(0));
1817                 if (sourceIntents.size() > 1) {
1818                     final Intent[] alts = new Intent[sourceIntents.size() - 1];
1819                     for (int i = 1, N = sourceIntents.size(); i < N; i++) {
1820                         alts[i - 1] = sourceIntents.get(i);
1821                     }
1822                     fillIn.putExtra(Intent.EXTRA_ALTERNATE_INTENTS, alts);
1823                 }
1824                 if (mRefinementResultReceiver != null) {
1825                     mRefinementResultReceiver.destroy();
1826                 }
1827                 mRefinementResultReceiver = new RefinementResultReceiver(this, target, null);
1828                 fillIn.putExtra(Intent.EXTRA_RESULT_RECEIVER,
1829                         mRefinementResultReceiver);
1830                 try {
1831                     mRefinementIntentSender.sendIntent(this, 0, fillIn, null, null);
1832                     return false;
1833                 } catch (SendIntentException e) {
1834                     Log.e(TAG, "Refinement IntentSender failed to send", e);
1835                 }
1836             }
1837         }
1838         updateModelAndChooserCounts(target);
1839         return super.onTargetSelected(target, alwaysCheck);
1840     }
1841 
1842     @Override
1843     public void startSelected(int which, boolean always, boolean filtered) {
1844         ChooserListAdapter currentListAdapter =
1845                 mChooserMultiProfilePagerAdapter.getActiveListAdapter();
1846         TargetInfo targetInfo = currentListAdapter
1847                 .targetInfoForPosition(which, filtered);
1848         if (targetInfo != null && targetInfo instanceof NotSelectableTargetInfo) {
1849             return;
1850         }
1851 
1852         final long selectionCost = System.currentTimeMillis() - mChooserShownTime;
1853 
1854         if (targetInfo instanceof MultiDisplayResolveInfo) {
1855             MultiDisplayResolveInfo mti = (MultiDisplayResolveInfo) targetInfo;
1856             if (!mti.hasSelected()) {
1857                 ChooserStackedAppDialogFragment f = new ChooserStackedAppDialogFragment();
1858                 Bundle b = new Bundle();
1859                 // Add userHandle based badge to the stackedAppDialogBox.
1860                 b.putParcelable(ChooserTargetActionsDialogFragment.USER_HANDLE_KEY,
1861                         getResolveInfoUserHandle(
1862                                 targetInfo.getResolveInfo(),
1863                                 mChooserMultiProfilePagerAdapter.getCurrentUserHandle()));
1864                 b.putObject(ChooserStackedAppDialogFragment.MULTI_DRI_KEY,
1865                         mti);
1866                 b.putInt(ChooserStackedAppDialogFragment.WHICH_KEY, which);
1867                 f.setArguments(b);
1868 
1869                 f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
1870                 return;
1871             }
1872         }
1873 
1874         super.startSelected(which, always, filtered);
1875 
1876         if (currentListAdapter.getCount() > 0) {
1877             // Log the index of which type of target the user picked.
1878             // Lower values mean the ranking was better.
1879             int cat = 0;
1880             int value = which;
1881             int directTargetAlsoRanked = -1;
1882             int numCallerProvided = 0;
1883             HashedStringCache.HashResult directTargetHashed = null;
1884             switch (currentListAdapter.getPositionTargetType(which)) {
1885                 case ChooserListAdapter.TARGET_SERVICE:
1886                     cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET;
1887                     // Log the package name + target name to answer the question if most users
1888                     // share to mostly the same person or to a bunch of different people.
1889                     ChooserTarget target = currentListAdapter.getChooserTargetForValue(value);
1890                     directTargetHashed = HashedStringCache.getInstance().hashString(
1891                             this,
1892                             TAG,
1893                             target.getComponentName().getPackageName()
1894                                     + target.getTitle().toString(),
1895                             mMaxHashSaltDays);
1896                     SelectableTargetInfo selectableTargetInfo = (SelectableTargetInfo) targetInfo;
1897                     directTargetAlsoRanked = getRankedPosition(selectableTargetInfo);
1898 
1899                     if (mCallerChooserTargets != null) {
1900                         numCallerProvided = mCallerChooserTargets.length;
1901                     }
1902                     getChooserActivityLogger().logShareTargetSelected(
1903                             SELECTION_TYPE_SERVICE,
1904                             targetInfo.getResolveInfo().activityInfo.processName,
1905                             value,
1906                             selectableTargetInfo.isPinned()
1907                     );
1908                     break;
1909                 case ChooserListAdapter.TARGET_CALLER:
1910                 case ChooserListAdapter.TARGET_STANDARD:
1911                     cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET;
1912                     value -= currentListAdapter.getSurfacedTargetInfo().size();
1913                     numCallerProvided = currentListAdapter.getCallerTargetCount();
1914                     getChooserActivityLogger().logShareTargetSelected(
1915                             SELECTION_TYPE_APP,
1916                             targetInfo.getResolveInfo().activityInfo.processName,
1917                             value,
1918                             targetInfo.isPinned()
1919                     );
1920                     break;
1921                 case ChooserListAdapter.TARGET_STANDARD_AZ:
1922                     // A-Z targets are unranked standard targets; we use -1 to mark that they
1923                     // are from the alphabetical pool.
1924                     value = -1;
1925                     cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_STANDARD_TARGET;
1926                     getChooserActivityLogger().logShareTargetSelected(
1927                             SELECTION_TYPE_STANDARD,
1928                             targetInfo.getResolveInfo().activityInfo.processName,
1929                             value,
1930                             false
1931                     );
1932                     break;
1933             }
1934 
1935             if (cat != 0) {
1936                 LogMaker targetLogMaker = new LogMaker(cat).setSubtype(value);
1937                 if (directTargetHashed != null) {
1938                     targetLogMaker.addTaggedData(
1939                             MetricsEvent.FIELD_HASHED_TARGET_NAME, directTargetHashed.hashedString);
1940                     targetLogMaker.addTaggedData(
1941                                     MetricsEvent.FIELD_HASHED_TARGET_SALT_GEN,
1942                                     directTargetHashed.saltGeneration);
1943                     targetLogMaker.addTaggedData(MetricsEvent.FIELD_RANKED_POSITION,
1944                                     directTargetAlsoRanked);
1945                 }
1946                 targetLogMaker.addTaggedData(MetricsEvent.FIELD_IS_CATEGORY_USED,
1947                         numCallerProvided);
1948                 getMetricsLogger().write(targetLogMaker);
1949             }
1950 
1951             if (mIsSuccessfullySelected) {
1952                 if (DEBUG) {
1953                     Log.d(TAG, "User Selection Time Cost is " + selectionCost);
1954                     Log.d(TAG, "position of selected app/service/caller is " +
1955                             Integer.toString(value));
1956                 }
1957                 MetricsLogger.histogram(null, "user_selection_cost_for_smart_sharing",
1958                         (int) selectionCost);
1959                 MetricsLogger.histogram(null, "app_position_for_smart_sharing", value);
1960             }
1961         }
1962     }
1963 
1964     private int getRankedPosition(SelectableTargetInfo targetInfo) {
1965         String targetPackageName =
1966                 targetInfo.getChooserTarget().getComponentName().getPackageName();
1967         ChooserListAdapter currentListAdapter =
1968                 mChooserMultiProfilePagerAdapter.getActiveListAdapter();
1969         int maxRankedResults = Math.min(currentListAdapter.mDisplayList.size(),
1970                 MAX_LOG_RANK_POSITION);
1971 
1972         for (int i = 0; i < maxRankedResults; i++) {
1973             if (currentListAdapter.mDisplayList.get(i)
1974                     .getResolveInfo().activityInfo.packageName.equals(targetPackageName)) {
1975                 return i;
1976             }
1977         }
1978         return -1;
1979     }
1980 
1981     @Override
1982     protected boolean shouldAddFooterView() {
1983         // To accommodate for window insets
1984         return true;
1985     }
1986 
1987     @Override
1988     protected void applyFooterView(int height) {
1989         int count = mChooserMultiProfilePagerAdapter.getItemCount();
1990 
1991         for (int i = 0; i < count; i++) {
1992             mChooserMultiProfilePagerAdapter.getAdapterForIndex(i).setFooterHeight(height);
1993         }
1994     }
1995 
1996     private IntentFilter getTargetIntentFilter() {
1997         try {
1998             final Intent intent = getTargetIntent();
1999             String dataString = intent.getDataString();
2000             if (intent.getType() == null) {
2001                 if (!TextUtils.isEmpty(dataString)) {
2002                     return new IntentFilter(intent.getAction(), dataString);
2003                 }
2004                 Log.e(TAG, "Failed to get target intent filter: intent data and type are null");
2005                 return null;
2006             }
2007             IntentFilter intentFilter = new IntentFilter(intent.getAction(), intent.getType());
2008             List<Uri> contentUris = new ArrayList<>();
2009             if (Intent.ACTION_SEND.equals(intent.getAction())) {
2010                 Uri uri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM, android.net.Uri.class);
2011                 if (uri != null) {
2012                     contentUris.add(uri);
2013                 }
2014             } else {
2015                 List<Uri> uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM, android.net.Uri.class);
2016                 if (uris != null) {
2017                     contentUris.addAll(uris);
2018                 }
2019             }
2020             for (Uri uri : contentUris) {
2021                 intentFilter.addDataScheme(uri.getScheme());
2022                 intentFilter.addDataAuthority(uri.getAuthority(), null);
2023                 intentFilter.addDataPath(uri.getPath(), PatternMatcher.PATTERN_LITERAL);
2024             }
2025             return intentFilter;
2026         } catch (Exception e) {
2027             Log.e(TAG, "Failed to get target intent filter", e);
2028             return null;
2029         }
2030     }
2031 
2032     @VisibleForTesting
2033     protected void queryDirectShareTargets(
2034                 ChooserListAdapter adapter, boolean skipAppPredictionService) {
2035         mQueriedSharingShortcutsTimeMs = System.currentTimeMillis();
2036         UserHandle userHandle = adapter.getUserHandle();
2037         if (!skipAppPredictionService) {
2038             AppPredictor appPredictor = getAppPredictorForDirectShareIfEnabled(userHandle);
2039             if (appPredictor != null) {
2040                 appPredictor.requestPredictionUpdate();
2041                 return;
2042             }
2043         }
2044         // Default to just querying ShortcutManager if AppPredictor not present.
2045         final IntentFilter filter = getTargetIntentFilter();
2046         if (filter == null) {
2047             return;
2048         }
2049 
2050         AsyncTask.execute(() -> {
2051             Context selectedProfileContext = createContextAsUser(userHandle, 0 /* flags */);
2052             ShortcutManager sm = (ShortcutManager) selectedProfileContext
2053                     .getSystemService(Context.SHORTCUT_SERVICE);
2054             List<ShortcutManager.ShareShortcutInfo> resultList = sm.getShareTargets(filter);
2055             sendShareShortcutInfoList(resultList, adapter, null, userHandle);
2056         });
2057     }
2058 
2059     /**
2060      * Returns {@code false} if {@code userHandle} is the work profile and it's either
2061      * in quiet mode or not running.
2062      */
2063     private boolean shouldQueryShortcutManager(UserHandle userHandle) {
2064         if (!shouldShowTabs()) {
2065             return true;
2066         }
2067         if (!getWorkProfileUserHandle().equals(userHandle)) {
2068             return true;
2069         }
2070         if (!isUserRunning(userHandle)) {
2071             return false;
2072         }
2073         if (!isUserUnlocked(userHandle)) {
2074             return false;
2075         }
2076         if (isQuietModeEnabled(userHandle)) {
2077             return false;
2078         }
2079         return true;
2080     }
2081 
2082     private void sendShareShortcutInfoList(
2083                 List<ShortcutManager.ShareShortcutInfo> resultList,
2084                 ChooserListAdapter chooserListAdapter,
2085                 @Nullable List<AppTarget> appTargets, UserHandle userHandle) {
2086         if (appTargets != null && appTargets.size() != resultList.size()) {
2087             throw new RuntimeException("resultList and appTargets must have the same size."
2088                     + " resultList.size()=" + resultList.size()
2089                     + " appTargets.size()=" + appTargets.size());
2090         }
2091         Context selectedProfileContext = createContextAsUser(userHandle, 0 /* flags */);
2092         for (int i = resultList.size() - 1; i >= 0; i--) {
2093             final String packageName = resultList.get(i).getTargetComponent().getPackageName();
2094             if (!isPackageEnabled(selectedProfileContext, packageName)) {
2095                 resultList.remove(i);
2096                 if (appTargets != null) {
2097                     appTargets.remove(i);
2098                 }
2099             }
2100         }
2101 
2102         // If |appTargets| is not null, results are from AppPredictionService and already sorted.
2103         final int shortcutType = (appTargets == null ? TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER :
2104                 TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE);
2105 
2106         // Match ShareShortcutInfos with DisplayResolveInfos to be able to use the old code path
2107         // for direct share targets. After ShareSheet is refactored we should use the
2108         // ShareShortcutInfos directly.
2109         List<ServiceResultInfo> resultRecords = new ArrayList<>();
2110         for (int i = 0; i < chooserListAdapter.getDisplayResolveInfoCount(); i++) {
2111             DisplayResolveInfo displayResolveInfo = chooserListAdapter.getDisplayResolveInfo(i);
2112             List<ShortcutManager.ShareShortcutInfo> matchingShortcuts =
2113                     filterShortcutsByTargetComponentName(
2114                             resultList, displayResolveInfo.getResolvedComponentName());
2115             if (matchingShortcuts.isEmpty()) {
2116                 continue;
2117             }
2118             List<ChooserTarget> chooserTargets = convertToChooserTarget(
2119                     matchingShortcuts, resultList, appTargets, shortcutType);
2120 
2121             ServiceResultInfo resultRecord = new ServiceResultInfo(
2122                     displayResolveInfo, chooserTargets, userHandle);
2123             resultRecords.add(resultRecord);
2124         }
2125 
2126         sendShortcutManagerShareTargetResults(
2127                 shortcutType, resultRecords.toArray(new ServiceResultInfo[0]));
2128     }
2129 
2130     private List<ShortcutManager.ShareShortcutInfo> filterShortcutsByTargetComponentName(
2131             List<ShortcutManager.ShareShortcutInfo> allShortcuts, ComponentName requiredTarget) {
2132         List<ShortcutManager.ShareShortcutInfo> matchingShortcuts = new ArrayList<>();
2133         for (ShortcutManager.ShareShortcutInfo shortcut : allShortcuts) {
2134             if (requiredTarget.equals(shortcut.getTargetComponent())) {
2135                 matchingShortcuts.add(shortcut);
2136             }
2137         }
2138         return matchingShortcuts;
2139     }
2140 
2141     @VisibleForTesting
2142     protected void sendShortcutManagerShareTargetResults(
2143             int shortcutType, ServiceResultInfo[] results) {
2144         final Message msg = Message.obtain();
2145         msg.what = ChooserHandler.SHORTCUT_MANAGER_ALL_SHARE_TARGET_RESULTS;
2146         msg.obj = results;
2147         msg.arg1 = shortcutType;
2148         mChooserHandler.sendMessage(msg);
2149     }
2150 
2151     private boolean isPackageEnabled(Context context, String packageName) {
2152         if (TextUtils.isEmpty(packageName)) {
2153             return false;
2154         }
2155         ApplicationInfo appInfo;
2156         try {
2157             appInfo = context.getPackageManager().getApplicationInfo(packageName, 0);
2158         } catch (NameNotFoundException e) {
2159             return false;
2160         }
2161 
2162         if (appInfo != null && appInfo.enabled
2163                 && (appInfo.flags & ApplicationInfo.FLAG_SUSPENDED) == 0) {
2164             return true;
2165         }
2166         return false;
2167     }
2168 
2169     /**
2170      * Converts a list of ShareShortcutInfos to ChooserTargets.
2171      * @param matchingShortcuts List of shortcuts, all from the same package, that match the current
2172      *                         share intent filter.
2173      * @param allShortcuts List of all the shortcuts from all the packages on the device that are
2174      *                    returned for the current sharing action.
2175      * @param allAppTargets List of AppTargets. Null if the results are not from prediction service.
2176      * @param shortcutType One of the values TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER or
2177      *                    TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE
2178      * @return A list of ChooserTargets sorted by score in descending order.
2179      */
2180     @VisibleForTesting
2181     @NonNull
2182     public List<ChooserTarget> convertToChooserTarget(
2183             @NonNull List<ShortcutManager.ShareShortcutInfo> matchingShortcuts,
2184             @NonNull List<ShortcutManager.ShareShortcutInfo> allShortcuts,
2185             @Nullable List<AppTarget> allAppTargets, @ShareTargetType int shortcutType) {
2186         // A set of distinct scores for the matched shortcuts. We use index of a rank in the sorted
2187         // list instead of the actual rank value when converting a rank to a score.
2188         List<Integer> scoreList = new ArrayList<>();
2189         if (shortcutType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER) {
2190             for (int i = 0; i < matchingShortcuts.size(); i++) {
2191                 int shortcutRank = matchingShortcuts.get(i).getShortcutInfo().getRank();
2192                 if (!scoreList.contains(shortcutRank)) {
2193                     scoreList.add(shortcutRank);
2194                 }
2195             }
2196             Collections.sort(scoreList);
2197         }
2198 
2199         List<ChooserTarget> chooserTargetList = new ArrayList<>(matchingShortcuts.size());
2200         for (int i = 0; i < matchingShortcuts.size(); i++) {
2201             ShortcutInfo shortcutInfo = matchingShortcuts.get(i).getShortcutInfo();
2202             int indexInAllShortcuts = allShortcuts.indexOf(matchingShortcuts.get(i));
2203 
2204             float score;
2205             if (shortcutType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE) {
2206                 // Incoming results are ordered. Create a score based on index in the original list.
2207                 score = Math.max(1.0f - (0.01f * indexInAllShortcuts), 0.0f);
2208             } else {
2209                 // Create a score based on the rank of the shortcut.
2210                 int rankIndex = scoreList.indexOf(shortcutInfo.getRank());
2211                 score = Math.max(1.0f - (0.01f * rankIndex), 0.0f);
2212             }
2213 
2214             Bundle extras = new Bundle();
2215             extras.putString(Intent.EXTRA_SHORTCUT_ID, shortcutInfo.getId());
2216 
2217             ChooserTarget chooserTarget = new ChooserTarget(
2218                     shortcutInfo.getLabel(),
2219                     null, // Icon will be loaded later if this target is selected to be shown.
2220                     score, matchingShortcuts.get(i).getTargetComponent().clone(), extras);
2221 
2222             chooserTargetList.add(chooserTarget);
2223             if (mDirectShareAppTargetCache != null && allAppTargets != null) {
2224                 mDirectShareAppTargetCache.put(chooserTarget,
2225                         allAppTargets.get(indexInAllShortcuts));
2226             }
2227             if (mDirectShareShortcutInfoCache != null) {
2228                 mDirectShareShortcutInfoCache.put(chooserTarget, shortcutInfo);
2229             }
2230         }
2231         // Sort ChooserTargets by score in descending order
2232         Comparator<ChooserTarget> byScore =
2233                 (ChooserTarget a, ChooserTarget b) -> -Float.compare(a.getScore(), b.getScore());
2234         Collections.sort(chooserTargetList, byScore);
2235         return chooserTargetList;
2236     }
2237 
2238     private void logDirectShareTargetReceived(int logCategory) {
2239         final int apiLatency = (int) (System.currentTimeMillis() - mQueriedSharingShortcutsTimeMs);
2240         getMetricsLogger().write(new LogMaker(logCategory).setSubtype(apiLatency));
2241     }
2242 
2243     void updateModelAndChooserCounts(TargetInfo info) {
2244         if (info != null && info instanceof MultiDisplayResolveInfo) {
2245             info = ((MultiDisplayResolveInfo) info).getSelectedTarget();
2246         }
2247         if (info != null) {
2248             sendClickToAppPredictor(info);
2249             final ResolveInfo ri = info.getResolveInfo();
2250             Intent targetIntent = getTargetIntent();
2251             if (ri != null && ri.activityInfo != null && targetIntent != null) {
2252                 ChooserListAdapter currentListAdapter =
2253                         mChooserMultiProfilePagerAdapter.getActiveListAdapter();
2254                 if (currentListAdapter != null) {
2255                     sendImpressionToAppPredictor(info, currentListAdapter);
2256                     currentListAdapter.updateModel(info);
2257                     currentListAdapter.updateChooserCounts(
2258                             ri.activityInfo.packageName,
2259                             targetIntent.getAction(),
2260                             ri.userHandle);
2261                 }
2262                 if (DEBUG) {
2263                     Log.d(TAG, "ResolveInfo Package is " + ri.activityInfo.packageName);
2264                     Log.d(TAG, "Action to be updated is " + targetIntent.getAction());
2265                 }
2266             } else if (DEBUG) {
2267                 Log.d(TAG, "Can not log Chooser Counts of null ResovleInfo");
2268             }
2269         }
2270         mIsSuccessfullySelected = true;
2271     }
2272 
2273     private void sendImpressionToAppPredictor(TargetInfo targetInfo, ChooserListAdapter adapter) {
2274         AppPredictor directShareAppPredictor = getAppPredictorForDirectShareIfEnabled(
2275                 mChooserMultiProfilePagerAdapter.getCurrentUserHandle());
2276         if (directShareAppPredictor == null) {
2277             return;
2278         }
2279         // Send DS target impression info to AppPredictor, only when user chooses app share.
2280         if (targetInfo instanceof ChooserTargetInfo) {
2281             return;
2282         }
2283         List<ChooserTargetInfo> surfacedTargetInfo = adapter.getSurfacedTargetInfo();
2284         List<AppTargetId> targetIds = new ArrayList<>();
2285         for (ChooserTargetInfo chooserTargetInfo : surfacedTargetInfo) {
2286             ChooserTarget chooserTarget = chooserTargetInfo.getChooserTarget();
2287             ComponentName componentName = chooserTarget.getComponentName();
2288             if (mDirectShareShortcutInfoCache.containsKey(chooserTarget)) {
2289                 String shortcutId = mDirectShareShortcutInfoCache.get(chooserTarget).getId();
2290                 targetIds.add(new AppTargetId(
2291                         String.format("%s/%s/%s", shortcutId, componentName.flattenToString(),
2292                                 SHORTCUT_TARGET)));
2293             }
2294         }
2295         directShareAppPredictor.notifyLaunchLocationShown(LAUNCH_LOCATION_DIRECT_SHARE, targetIds);
2296     }
2297 
2298     private void sendClickToAppPredictor(TargetInfo targetInfo) {
2299         AppPredictor directShareAppPredictor = getAppPredictorForDirectShareIfEnabled(
2300                 mChooserMultiProfilePagerAdapter.getCurrentUserHandle());
2301         if (directShareAppPredictor == null) {
2302             return;
2303         }
2304         if (!(targetInfo instanceof ChooserTargetInfo)) {
2305             return;
2306         }
2307         ChooserTarget chooserTarget = ((ChooserTargetInfo) targetInfo).getChooserTarget();
2308         AppTarget appTarget = null;
2309         if (mDirectShareAppTargetCache != null) {
2310             appTarget = mDirectShareAppTargetCache.get(chooserTarget);
2311         }
2312         // This is a direct share click that was provided by the APS
2313         if (appTarget != null) {
2314             directShareAppPredictor.notifyAppTargetEvent(
2315                     new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_LAUNCH)
2316                         .setLaunchLocation(LAUNCH_LOCATION_DIRECT_SHARE)
2317                         .build());
2318         }
2319     }
2320 
2321     @Nullable
2322     private AppPredictor createAppPredictor(UserHandle userHandle) {
2323         if (!mIsAppPredictorComponentAvailable) {
2324             return null;
2325         }
2326 
2327         if (getPersonalProfileUserHandle().equals(userHandle)) {
2328             if (mPersonalAppPredictor != null) {
2329                 return mPersonalAppPredictor;
2330             }
2331         } else {
2332             if (mWorkAppPredictor != null) {
2333                 return mWorkAppPredictor;
2334             }
2335         }
2336 
2337         // TODO(b/148230574): Currently AppPredictor fetches only the same-profile app targets.
2338         // Make AppPredictor work cross-profile.
2339         Context contextAsUser = createContextAsUser(userHandle, 0 /* flags */);
2340         final IntentFilter filter = getTargetIntentFilter();
2341         Bundle extras = new Bundle();
2342         extras.putParcelable(APP_PREDICTION_INTENT_FILTER_KEY, filter);
2343         populateTextContent(extras);
2344         AppPredictionContext appPredictionContext = new AppPredictionContext.Builder(contextAsUser)
2345             .setUiSurface(APP_PREDICTION_SHARE_UI_SURFACE)
2346             .setPredictedTargetCount(APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT)
2347             .setExtras(extras)
2348             .build();
2349         AppPredictionManager appPredictionManager =
2350                 contextAsUser
2351                         .getSystemService(AppPredictionManager.class);
2352         AppPredictor appPredictionSession = appPredictionManager.createAppPredictionSession(
2353                 appPredictionContext);
2354         if (getPersonalProfileUserHandle().equals(userHandle)) {
2355             mPersonalAppPredictor = appPredictionSession;
2356         } else {
2357             mWorkAppPredictor = appPredictionSession;
2358         }
2359         return appPredictionSession;
2360     }
2361 
2362     private void populateTextContent(Bundle extras) {
2363         final Intent intent = getTargetIntent();
2364         String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
2365         extras.putString(SHARED_TEXT_KEY, sharedText);
2366     }
2367 
2368     /**
2369      * This will return an app predictor if it is enabled for direct share sorting
2370      * and if one exists. Otherwise, it returns null.
2371      * @param userHandle
2372      */
2373     @Nullable
2374     private AppPredictor getAppPredictorForDirectShareIfEnabled(UserHandle userHandle) {
2375         return ChooserFlags.USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS
2376                 && !ActivityManager.isLowRamDeviceStatic() ? createAppPredictor(userHandle) : null;
2377     }
2378 
2379     /**
2380      * This will return an app predictor if it is enabled for share activity sorting
2381      * and if one exists. Otherwise, it returns null.
2382      */
2383     @Nullable
2384     private AppPredictor getAppPredictorForShareActivitiesIfEnabled(UserHandle userHandle) {
2385         // We cannot use APS service when clone profile is present as APS service cannot sort
2386         // cross profile targets as of now.
2387         return USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES && getCloneProfileUserHandle() == null
2388                 ? createAppPredictor(userHandle) : null;
2389     }
2390 
2391     void onRefinementResult(TargetInfo selectedTarget, Intent matchingIntent) {
2392         if (mRefinementResultReceiver != null) {
2393             mRefinementResultReceiver.destroy();
2394             mRefinementResultReceiver = null;
2395         }
2396         if (selectedTarget == null) {
2397             Log.e(TAG, "Refinement result intent did not match any known targets; canceling");
2398         } else if (!checkTargetSourceIntent(selectedTarget, matchingIntent)) {
2399             Log.e(TAG, "onRefinementResult: Selected target " + selectedTarget
2400                     + " cannot match refined source intent " + matchingIntent);
2401         } else {
2402             TargetInfo clonedTarget = selectedTarget.cloneFilledIn(matchingIntent, 0);
2403             if (super.onTargetSelected(clonedTarget, false)) {
2404                 updateModelAndChooserCounts(clonedTarget);
2405                 finish();
2406                 return;
2407             }
2408         }
2409         onRefinementCanceled();
2410     }
2411 
2412     void onRefinementCanceled() {
2413         if (mRefinementResultReceiver != null) {
2414             mRefinementResultReceiver.destroy();
2415             mRefinementResultReceiver = null;
2416         }
2417         finish();
2418     }
2419 
2420     boolean checkTargetSourceIntent(TargetInfo target, Intent matchingIntent) {
2421         final List<Intent> targetIntents = target.getAllSourceIntents();
2422         for (int i = 0, N = targetIntents.size(); i < N; i++) {
2423             final Intent targetIntent = targetIntents.get(i);
2424             if (targetIntent.filterEquals(matchingIntent)) {
2425                 return true;
2426             }
2427         }
2428         return false;
2429     }
2430 
2431     /**
2432      * Sort intents alphabetically based on display label.
2433      */
2434     static class AzInfoComparator implements Comparator<DisplayResolveInfo> {
2435         Comparator<DisplayResolveInfo> mComparator;
2436         AzInfoComparator(Context context) {
2437             Collator collator = Collator
2438                     .getInstance(context.getResources().getConfiguration().locale);
2439             // Adding two stage comparator, first stage compares using displayLabel, next stage
2440             //  compares using resolveInfo.userHandle
2441             mComparator = Comparator.comparing(DisplayResolveInfo::getDisplayLabel, collator)
2442                     .thenComparingInt(displayResolveInfo ->
2443                             getResolveInfoUserHandle(
2444                                     displayResolveInfo.getResolveInfo(),
2445                                     // TODO: User resolveInfo.userHandle, once its available.
2446                                     UserHandle.SYSTEM).getIdentifier());
2447         }
2448 
2449         @Override
2450         public int compare(
2451                 DisplayResolveInfo lhsp, DisplayResolveInfo rhsp) {
2452             return mComparator.compare(lhsp, rhsp);
2453         }
2454     }
2455 
2456     protected MetricsLogger getMetricsLogger() {
2457         if (mMetricsLogger == null) {
2458             mMetricsLogger = new MetricsLogger();
2459         }
2460         return mMetricsLogger;
2461     }
2462 
2463     protected ChooserActivityLogger getChooserActivityLogger() {
2464         if (mChooserActivityLogger == null) {
2465             mChooserActivityLogger = new ChooserActivityLoggerImpl();
2466         }
2467         return mChooserActivityLogger;
2468     }
2469 
2470     public class ChooserListController extends ResolverListController {
2471         public ChooserListController(Context context,
2472                 PackageManager pm,
2473                 Intent targetIntent,
2474                 String referrerPackageName,
2475                 int launchedFromUid,
2476                 UserHandle userId,
2477                 AbstractResolverComparator resolverComparator,
2478                 UserHandle queryIntentsAsUser) {
2479             super(context, pm, targetIntent, referrerPackageName, launchedFromUid, userId,
2480                     resolverComparator, queryIntentsAsUser);
2481         }
2482 
2483         @Override
2484         boolean isComponentFiltered(ComponentName name) {
2485             if (mFilteredComponentNames == null) {
2486                 return false;
2487             }
2488             for (ComponentName filteredComponentName : mFilteredComponentNames) {
2489                 if (name.equals(filteredComponentName)) {
2490                     return true;
2491                 }
2492             }
2493             return false;
2494         }
2495 
2496         @Override
2497         public boolean isComponentPinned(ComponentName name) {
2498             return false;
2499         }
2500 
2501         @Override
2502         public boolean isFixedAtTop(ComponentName name) {
2503             return name != null && name.equals(getNearbySharingComponent())
2504                     && shouldNearbyShareBeFirstInRankedRow();
2505         }
2506     }
2507 
2508     @VisibleForTesting
2509     public ChooserGridAdapter createChooserGridAdapter(Context context,
2510             List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList,
2511             boolean filterLastUsed, UserHandle userHandle) {
2512         ChooserListAdapter chooserListAdapter = createChooserListAdapter(context, payloadIntents,
2513                 initialIntents, rList, filterLastUsed, userHandle);
2514         ResolverAppPredictorCallback appPredictorCallbackWrapper =
2515                 createAppPredictorCallback(chooserListAdapter);
2516         AppPredictor.Callback appPredictorCallback = appPredictorCallbackWrapper.asCallback();
2517         AppPredictor appPredictor = setupAppPredictorForUser(userHandle, appPredictorCallback);
2518         chooserListAdapter.setAppPredictor(appPredictor);
2519         chooserListAdapter.setAppPredictorCallback(
2520                 appPredictorCallback, appPredictorCallbackWrapper);
2521         return new ChooserGridAdapter(chooserListAdapter);
2522     }
2523 
2524     @VisibleForTesting
2525     public ChooserListAdapter createChooserListAdapter(Context context,
2526             List<Intent> payloadIntents,
2527             Intent[] initialIntents,
2528             List<ResolveInfo> rList,
2529             boolean filterLastUsed,
2530             UserHandle userHandle) {
2531         UserHandle initialIntentsUserSpace = isLaunchedAsCloneProfile()
2532                 && userHandle.equals(getPersonalProfileUserHandle())
2533                 ? getCloneProfileUserHandle() : userHandle;
2534         return new ChooserListAdapter(context, payloadIntents, initialIntents, rList,
2535                 filterLastUsed, createListController(userHandle), this,
2536                 this, context.getPackageManager(),
2537                 getChooserActivityLogger(), initialIntentsUserSpace);
2538     }
2539 
2540     @VisibleForTesting
2541     protected ResolverListController createListController(UserHandle userHandle) {
2542         AppPredictor appPredictor = getAppPredictorForShareActivitiesIfEnabled(userHandle);
2543         AbstractResolverComparator resolverComparator;
2544         if (appPredictor != null) {
2545             resolverComparator = new AppPredictionServiceResolverComparator(this, getTargetIntent(),
2546                     getReferrerPackageName(), appPredictor, userHandle, getChooserActivityLogger());
2547         } else {
2548             resolverComparator =
2549                     new ResolverRankerServiceResolverComparator(
2550                             this,
2551                             getTargetIntent(),
2552                             getReferrerPackageName(),
2553                             null,
2554                             getChooserActivityLogger(),
2555                             getResolverRankerServiceUserHandleList(userHandle));
2556         }
2557 
2558         UserHandle queryIntentsUser = getQueryIntentsUser(userHandle);
2559         return new ChooserListController(
2560                 this,
2561                 mPm,
2562                 getTargetIntent(),
2563                 getReferrerPackageName(),
2564                 mLaunchedFromUid,
2565                 userHandle,
2566                 resolverComparator,
2567                 queryIntentsUser == null ? userHandle : queryIntentsUser);
2568     }
2569 
2570     @VisibleForTesting
2571     protected Bitmap loadThumbnail(Uri uri, Size size) {
2572         if (uri == null || size == null) {
2573             return null;
2574         }
2575 
2576         try {
2577             return getContentResolver().loadThumbnail(uri, size, null);
2578         } catch (IOException | NullPointerException | SecurityException ex) {
2579             logContentPreviewWarning(uri);
2580         }
2581         return null;
2582     }
2583 
2584     static final class PlaceHolderTargetInfo extends NotSelectableTargetInfo {
2585         public Drawable getDisplayIcon(Context context) {
2586             AnimatedVectorDrawable avd = (AnimatedVectorDrawable)
2587                     context.getDrawable(R.drawable.chooser_direct_share_icon_placeholder);
2588             avd.start(); // Start animation after generation
2589             return avd;
2590         }
2591     }
2592 
2593     protected static final class EmptyTargetInfo extends NotSelectableTargetInfo {
2594         public EmptyTargetInfo() {}
2595 
2596         public Drawable getDisplayIcon(Context context) {
2597             return null;
2598         }
2599     }
2600 
2601     private void handleScroll(View view, int x, int y, int oldx, int oldy) {
2602         if (mChooserMultiProfilePagerAdapter.getCurrentRootAdapter() != null) {
2603             mChooserMultiProfilePagerAdapter.getCurrentRootAdapter().handleScroll(view, y, oldy);
2604         }
2605     }
2606 
2607     /*
2608      * Need to dynamically adjust how many icons can fit per row before we add them,
2609      * which also means setting the correct offset to initially show the content
2610      * preview area + 2 rows of targets
2611      */
2612     private void handleLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
2613             int oldTop, int oldRight, int oldBottom) {
2614         if (mChooserMultiProfilePagerAdapter == null) {
2615             return;
2616         }
2617         RecyclerView recyclerView = mChooserMultiProfilePagerAdapter.getActiveAdapterView();
2618         ChooserGridAdapter gridAdapter = mChooserMultiProfilePagerAdapter.getCurrentRootAdapter();
2619         // Skip height calculation if recycler view was scrolled to prevent it inaccurately
2620         // calculating the height, as the logic below does not account for the scrolled offset.
2621         if (gridAdapter == null || recyclerView == null
2622                 || recyclerView.computeVerticalScrollOffset() != 0) {
2623             return;
2624         }
2625 
2626         final int availableWidth = right - left - v.getPaddingLeft() - v.getPaddingRight();
2627         boolean isLayoutUpdated = gridAdapter.consumeLayoutRequest()
2628                 || gridAdapter.calculateChooserTargetWidth(availableWidth)
2629                 || recyclerView.getAdapter() == null
2630                 || availableWidth != mCurrAvailableWidth;
2631 
2632         boolean insetsChanged = !Objects.equals(mLastAppliedInsets, mSystemWindowInsets);
2633 
2634         if (isLayoutUpdated
2635                 || insetsChanged
2636                 || mLastNumberOfChildren != recyclerView.getChildCount()) {
2637             mCurrAvailableWidth = availableWidth;
2638             if (isLayoutUpdated) {
2639                 // It is very important we call setAdapter from here. Otherwise in some cases
2640                 // the resolver list doesn't get populated, such as b/150922090, b/150918223
2641                 // and b/150936654
2642                 recyclerView.setAdapter(gridAdapter);
2643                 ((GridLayoutManager) recyclerView.getLayoutManager()).setSpanCount(
2644                         mMaxTargetsPerRow);
2645 
2646                 updateTabPadding();
2647             }
2648 
2649             UserHandle currentUserHandle = mChooserMultiProfilePagerAdapter.getCurrentUserHandle();
2650             int currentProfile = getProfileForUser(currentUserHandle);
2651             int initialProfile = findSelectedProfile();
2652             if (currentProfile != initialProfile) {
2653                 return;
2654             }
2655 
2656             if (mLastNumberOfChildren == recyclerView.getChildCount() && !insetsChanged) {
2657                 return;
2658             }
2659 
2660             getMainThreadHandler().post(() -> {
2661                 if (mResolverDrawerLayout == null || gridAdapter == null) {
2662                     return;
2663                 }
2664                 int offset = calculateDrawerOffset(top, bottom, recyclerView, gridAdapter);
2665                 mResolverDrawerLayout.setCollapsibleHeightReserved(offset);
2666                 mEnterTransitionAnimationDelegate.markOffsetCalculated();
2667                 mLastAppliedInsets = mSystemWindowInsets;
2668             });
2669         }
2670     }
2671 
2672     private int calculateDrawerOffset(
2673             int top, int bottom, RecyclerView recyclerView, ChooserGridAdapter gridAdapter) {
2674 
2675         final int bottomInset = mSystemWindowInsets != null
2676                 ? mSystemWindowInsets.bottom : 0;
2677         int offset = bottomInset;
2678         int rowsToShow = gridAdapter.getSystemRowCount()
2679                 + gridAdapter.getProfileRowCount()
2680                 + gridAdapter.getServiceTargetRowCount()
2681                 + gridAdapter.getCallerAndRankedTargetRowCount();
2682 
2683         // then this is most likely not a SEND_* action, so check
2684         // the app target count
2685         if (rowsToShow == 0) {
2686             rowsToShow = gridAdapter.getRowCount();
2687         }
2688 
2689         // still zero? then use a default height and leave, which
2690         // can happen when there are no targets to show
2691         if (rowsToShow == 0 && !shouldShowStickyContentPreview()) {
2692             offset += getResources().getDimensionPixelSize(
2693                     R.dimen.chooser_max_collapsed_height);
2694             return offset;
2695         }
2696 
2697         View stickyContentPreview = findViewById(R.id.content_preview_container);
2698         if (shouldShowStickyContentPreview() && isStickyContentPreviewShowing()) {
2699             offset += stickyContentPreview.getHeight();
2700         }
2701 
2702         if (shouldShowTabs()) {
2703             offset += findViewById(R.id.tabs).getHeight();
2704         }
2705 
2706         if (recyclerView.getVisibility() == View.VISIBLE) {
2707             int directShareHeight = 0;
2708             rowsToShow = Math.min(4, rowsToShow);
2709             boolean shouldShowExtraRow = shouldShowExtraRow(rowsToShow);
2710             mLastNumberOfChildren = recyclerView.getChildCount();
2711             for (int i = 0, childCount = recyclerView.getChildCount();
2712                     i < childCount && rowsToShow > 0; i++) {
2713                 View child = recyclerView.getChildAt(i);
2714                 if (((GridLayoutManager.LayoutParams)
2715                         child.getLayoutParams()).getSpanIndex() != 0) {
2716                     continue;
2717                 }
2718                 int height = child.getHeight();
2719                 offset += height;
2720                 if (shouldShowExtraRow) {
2721                     offset += height;
2722                 }
2723 
2724                 if (gridAdapter.getTargetType(
2725                         recyclerView.getChildAdapterPosition(child))
2726                         == ChooserListAdapter.TARGET_SERVICE) {
2727                     directShareHeight = height;
2728                 }
2729                 rowsToShow--;
2730             }
2731 
2732             boolean isExpandable = getResources().getConfiguration().orientation
2733                     == Configuration.ORIENTATION_PORTRAIT && !isInMultiWindowMode();
2734             if (directShareHeight != 0 && shouldShowContentPreview()
2735                     && isExpandable) {
2736                 // make sure to leave room for direct share 4->8 expansion
2737                 int requiredExpansionHeight =
2738                         (int) (directShareHeight / DIRECT_SHARE_EXPANSION_RATE);
2739                 int topInset = mSystemWindowInsets != null ? mSystemWindowInsets.top : 0;
2740                 int minHeight = bottom - top - mResolverDrawerLayout.getAlwaysShowHeight()
2741                         - requiredExpansionHeight - topInset - bottomInset;
2742 
2743                 offset = Math.min(offset, minHeight);
2744             }
2745         } else {
2746             ViewGroup currentEmptyStateView = getActiveEmptyStateView();
2747             if (currentEmptyStateView.getVisibility() == View.VISIBLE) {
2748                 offset += currentEmptyStateView.getHeight();
2749             }
2750         }
2751 
2752         return Math.min(offset, bottom - top);
2753     }
2754 
2755     /**
2756      * If we have a tabbed view and are showing 1 row in the current profile and an empty
2757      * state screen in the other profile, to prevent cropping of the empty state screen we show
2758      * a second row in the current profile.
2759      */
2760     private boolean shouldShowExtraRow(int rowsToShow) {
2761         return shouldShowTabs()
2762                 && rowsToShow == 1
2763                 && mChooserMultiProfilePagerAdapter.shouldShowEmptyStateScreen(
2764                         mChooserMultiProfilePagerAdapter.getInactiveListAdapter());
2765     }
2766 
2767     /**
2768      * Returns {@link #PROFILE_WORK}, if the given user handle matches work user handle.
2769      * Returns {@link #PROFILE_PERSONAL}, otherwise.
2770      **/
2771     private int getProfileForUser(UserHandle currentUserHandle) {
2772         if (currentUserHandle.equals(getWorkProfileUserHandle())) {
2773             return PROFILE_WORK;
2774         }
2775         // We return personal profile, as it is the default when there is no work profile, personal
2776         // profile represents rootUser, clonedUser & secondaryUser, covering all use cases.
2777         return PROFILE_PERSONAL;
2778     }
2779 
2780     private ViewGroup getActiveEmptyStateView() {
2781         int currentPage = mChooserMultiProfilePagerAdapter.getCurrentPage();
2782         return mChooserMultiProfilePagerAdapter.getItem(currentPage).getEmptyStateView();
2783     }
2784 
2785     static class BaseChooserTargetComparator implements Comparator<ChooserTarget> {
2786         @Override
2787         public int compare(ChooserTarget lhs, ChooserTarget rhs) {
2788             // Descending order
2789             return (int) Math.signum(rhs.getScore() - lhs.getScore());
2790         }
2791     }
2792 
2793     @Override // ResolverListCommunicator
2794     public void onHandlePackagesChanged(ResolverListAdapter listAdapter) {
2795         mChooserMultiProfilePagerAdapter.getActiveListAdapter().notifyDataSetChanged();
2796         super.onHandlePackagesChanged(listAdapter);
2797     }
2798 
2799     @Override // SelectableTargetInfoCommunicator
2800     public ActivityInfoPresentationGetter makePresentationGetter(ActivityInfo info) {
2801         return mChooserMultiProfilePagerAdapter.getActiveListAdapter().makePresentationGetter(info);
2802     }
2803 
2804     @Override // SelectableTargetInfoCommunicator
2805     public Intent getReferrerFillInIntent() {
2806         return mReferrerFillInIntent;
2807     }
2808 
2809     @Override // ChooserListCommunicator
2810     public int getMaxRankedTargets() {
2811         return mMaxTargetsPerRow;
2812     }
2813 
2814     @Override // ChooserListCommunicator
2815     public void sendListViewUpdateMessage(UserHandle userHandle) {
2816         Message msg = Message.obtain();
2817         msg.what = ChooserHandler.LIST_VIEW_UPDATE_MESSAGE;
2818         msg.obj = userHandle;
2819         mChooserHandler.sendMessageDelayed(msg, mListViewUpdateDelayMs);
2820     }
2821 
2822     @Override
2823     public void onListRebuilt(ResolverListAdapter listAdapter, boolean rebuildComplete) {
2824         setupScrollListener();
2825 
2826         ChooserListAdapter chooserListAdapter = (ChooserListAdapter) listAdapter;
2827         if (chooserListAdapter.getUserHandle()
2828                 .equals(mChooserMultiProfilePagerAdapter.getCurrentUserHandle())) {
2829             mChooserMultiProfilePagerAdapter.getActiveAdapterView()
2830                     .setAdapter(mChooserMultiProfilePagerAdapter.getCurrentRootAdapter());
2831             mChooserMultiProfilePagerAdapter
2832                     .setupListAdapter(mChooserMultiProfilePagerAdapter.getCurrentPage());
2833         }
2834 
2835         if (chooserListAdapter.mDisplayList == null
2836                 || chooserListAdapter.mDisplayList.isEmpty()) {
2837             chooserListAdapter.notifyDataSetChanged();
2838         } else {
2839             chooserListAdapter.updateAlphabeticalList();
2840         }
2841 
2842         if (rebuildComplete) {
2843             getChooserActivityLogger().logSharesheetAppLoadComplete();
2844             maybeQueryAdditionalPostProcessingTargets(chooserListAdapter);
2845             mLatencyTracker.onActionEnd(ACTION_LOAD_SHARE_SHEET);
2846         }
2847     }
2848 
2849     private void maybeQueryAdditionalPostProcessingTargets(ChooserListAdapter chooserListAdapter) {
2850         // don't support direct share on low ram devices
2851         if (ActivityManager.isLowRamDeviceStatic()) {
2852             return;
2853         }
2854 
2855         // no need to query direct share for work profile when its locked or disabled
2856         if (!shouldQueryShortcutManager(chooserListAdapter.getUserHandle())) {
2857             return;
2858         }
2859 
2860         if (ChooserFlags.USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
2861             if (DEBUG) {
2862                 Log.d(TAG, "querying direct share targets from ShortcutManager");
2863             }
2864 
2865             queryDirectShareTargets(chooserListAdapter, false);
2866         }
2867     }
2868 
2869     @VisibleForTesting
2870     protected boolean isUserRunning(UserHandle userHandle) {
2871         UserManager userManager = getSystemService(UserManager.class);
2872         return userManager.isUserRunning(userHandle);
2873     }
2874 
2875     @VisibleForTesting
2876     protected boolean isUserUnlocked(UserHandle userHandle) {
2877         UserManager userManager = getSystemService(UserManager.class);
2878         return userManager.isUserUnlocked(userHandle);
2879     }
2880 
2881     @VisibleForTesting
2882     protected boolean isQuietModeEnabled(UserHandle userHandle) {
2883         UserManager userManager = getSystemService(UserManager.class);
2884         return userManager.isQuietModeEnabled(userHandle);
2885     }
2886 
2887     private void setupScrollListener() {
2888         if (mResolverDrawerLayout == null) {
2889             return;
2890         }
2891         int elevatedViewResId = shouldShowTabs() ? R.id.tabs : R.id.chooser_header;
2892         final View elevatedView = mResolverDrawerLayout.findViewById(elevatedViewResId);
2893         final float defaultElevation = elevatedView.getElevation();
2894         final float chooserHeaderScrollElevation =
2895                 getResources().getDimensionPixelSize(R.dimen.chooser_header_scroll_elevation);
2896         mChooserMultiProfilePagerAdapter.getActiveAdapterView().addOnScrollListener(
2897                 new RecyclerView.OnScrollListener() {
2898                     public void onScrollStateChanged(RecyclerView view, int scrollState) {
2899                         if (scrollState == RecyclerView.SCROLL_STATE_IDLE) {
2900                             if (mScrollStatus == SCROLL_STATUS_SCROLLING_VERTICAL) {
2901                                 mScrollStatus = SCROLL_STATUS_IDLE;
2902                                 setHorizontalScrollingEnabled(true);
2903                             }
2904                         } else if (scrollState == RecyclerView.SCROLL_STATE_DRAGGING) {
2905                             if (mScrollStatus == SCROLL_STATUS_IDLE) {
2906                                 mScrollStatus = SCROLL_STATUS_SCROLLING_VERTICAL;
2907                                 setHorizontalScrollingEnabled(false);
2908                             }
2909                         }
2910                     }
2911 
2912                     public void onScrolled(RecyclerView view, int dx, int dy) {
2913                         if (view.getChildCount() > 0) {
2914                             View child = view.getLayoutManager().findViewByPosition(0);
2915                             if (child == null || child.getTop() < 0) {
2916                                 elevatedView.setElevation(chooserHeaderScrollElevation);
2917                                 return;
2918                             }
2919                         }
2920 
2921                         elevatedView.setElevation(defaultElevation);
2922                     }
2923                 });
2924     }
2925 
2926     @Override // ChooserListCommunicator
2927     public boolean isSendAction(Intent targetIntent) {
2928         if (targetIntent == null) {
2929             return false;
2930         }
2931 
2932         String action = targetIntent.getAction();
2933         if (action == null) {
2934             return false;
2935         }
2936 
2937         if (Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action)) {
2938             return true;
2939         }
2940 
2941         return false;
2942     }
2943 
2944     /**
2945      * The sticky content preview is shown only when we have a tabbed view. It's shown above
2946      * the tabs so it is not part of the scrollable list. If we are not in tabbed view,
2947      * we instead show the content preview as a regular list item.
2948      */
2949     private boolean shouldShowStickyContentPreview() {
2950         return shouldShowStickyContentPreviewNoOrientationCheck()
2951                 && !getResources().getBoolean(R.bool.resolver_landscape_phone);
2952     }
2953 
2954     private boolean shouldShowStickyContentPreviewNoOrientationCheck() {
2955         return shouldShowTabs()
2956                 && (mMultiProfilePagerAdapter.getListAdapterForUserHandle(
2957                         UserHandle.of(UserHandle.myUserId())).getCount() > 0
2958                     || shouldShowStickyContentPreviewWhenEmpty())
2959                 && shouldShowContentPreview();
2960     }
2961 
2962     /**
2963      * This method could be used to override the default behavior when we hide the sticky preview
2964      * area when the current tab doesn't have any items.
2965      *
2966      * @return {@code true} if we want to show the sticky content preview area even if the tab for
2967      *         the current user is empty
2968      */
2969     protected boolean shouldShowStickyContentPreviewWhenEmpty() {
2970         return false;
2971     }
2972 
2973     @Override
2974     public boolean shouldShowContentPreview() {
2975         return isSendAction(getTargetIntent());
2976     }
2977 
2978     @Override
2979     public boolean shouldShowServiceTargets() {
2980         return shouldShowContentPreview() && !ActivityManager.isLowRamDeviceStatic();
2981     }
2982 
2983     private void updateStickyContentPreview() {
2984         if (shouldShowStickyContentPreviewNoOrientationCheck()) {
2985             // The sticky content preview is only shown when we show the work and personal tabs.
2986             // We don't show it in landscape as otherwise there is no room for scrolling.
2987             // If the sticky content preview will be shown at some point with orientation change,
2988             // then always preload it to avoid subsequent resizing of the share sheet.
2989             ViewGroup contentPreviewContainer = findViewById(R.id.content_preview_container);
2990             if (contentPreviewContainer.getChildCount() == 0) {
2991                 ViewGroup contentPreviewView = createContentPreviewView(contentPreviewContainer);
2992                 contentPreviewContainer.addView(contentPreviewView);
2993             }
2994         }
2995         if (shouldShowStickyContentPreview()) {
2996             showStickyContentPreview();
2997         } else {
2998             hideStickyContentPreview();
2999         }
3000     }
3001 
3002     private void showStickyContentPreview() {
3003         if (isStickyContentPreviewShowing()) {
3004             return;
3005         }
3006         ViewGroup contentPreviewContainer = findViewById(R.id.content_preview_container);
3007         contentPreviewContainer.setVisibility(View.VISIBLE);
3008     }
3009 
3010     private boolean isStickyContentPreviewShowing() {
3011         ViewGroup contentPreviewContainer = findViewById(R.id.content_preview_container);
3012         return contentPreviewContainer.getVisibility() == View.VISIBLE;
3013     }
3014 
3015     private void hideStickyContentPreview() {
3016         if (!isStickyContentPreviewShowing()) {
3017             return;
3018         }
3019         ViewGroup contentPreviewContainer = findViewById(R.id.content_preview_container);
3020         contentPreviewContainer.setVisibility(View.GONE);
3021     }
3022 
3023     private void logActionShareWithPreview() {
3024         Intent targetIntent = getTargetIntent();
3025         int previewType = findPreferredContentPreview(targetIntent, getContentResolver());
3026         getMetricsLogger().write(new LogMaker(MetricsEvent.ACTION_SHARE_WITH_PREVIEW)
3027                 .setSubtype(previewType));
3028     }
3029 
3030     private void startFinishAnimation() {
3031         View rootView = findRootView();
3032         if (rootView != null) {
3033             rootView.startAnimation(new FinishAnimation(this, rootView));
3034         }
3035     }
3036 
3037     private boolean maybeCancelFinishAnimation() {
3038         View rootView = findRootView();
3039         Animation animation = rootView == null ? null : rootView.getAnimation();
3040         if (animation instanceof FinishAnimation) {
3041             boolean hasEnded = animation.hasEnded();
3042             animation.cancel();
3043             rootView.clearAnimation();
3044             return !hasEnded;
3045         }
3046         return false;
3047     }
3048 
3049     private View findRootView() {
3050         if (mContentView == null) {
3051             mContentView = findViewById(android.R.id.content);
3052         }
3053         return mContentView;
3054     }
3055 
3056     abstract static class ViewHolderBase extends RecyclerView.ViewHolder {
3057         private int mViewType;
3058 
3059         ViewHolderBase(View itemView, int viewType) {
3060             super(itemView);
3061             this.mViewType = viewType;
3062         }
3063 
3064         int getViewType() {
3065             return mViewType;
3066         }
3067     }
3068 
3069     /**
3070      * Used to bind types of individual item including
3071      * {@link ChooserGridAdapter#VIEW_TYPE_NORMAL},
3072      * {@link ChooserGridAdapter#VIEW_TYPE_CONTENT_PREVIEW},
3073      * {@link ChooserGridAdapter#VIEW_TYPE_PROFILE},
3074      * and {@link ChooserGridAdapter#VIEW_TYPE_AZ_LABEL}.
3075      */
3076     final class ItemViewHolder extends ViewHolderBase {
3077         ResolverListAdapter.ViewHolder mWrappedViewHolder;
3078         int mListPosition = ChooserListAdapter.NO_POSITION;
3079 
3080         ItemViewHolder(View itemView, boolean isClickable, int viewType) {
3081             super(itemView, viewType);
3082             mWrappedViewHolder = new ResolverListAdapter.ViewHolder(itemView);
3083             if (isClickable) {
3084                 itemView.setOnClickListener(v -> startSelected(mListPosition,
3085                         false/* always */, true/* filterd */));
3086             }
3087         }
3088     }
3089 
3090     /**
3091      * Add a footer to the list, to support scrolling behavior below the navbar.
3092      */
3093     static final class FooterViewHolder extends ViewHolderBase {
3094         FooterViewHolder(View itemView, int viewType) {
3095             super(itemView, viewType);
3096         }
3097     }
3098 
3099     /**
3100      * Intentionally override the {@link ResolverActivity} implementation as we only need that
3101      * implementation for the intent resolver case.
3102      */
3103     @Override
3104     public void onButtonClick(View v) {}
3105 
3106     /**
3107      * Intentionally override the {@link ResolverActivity} implementation as we only need that
3108      * implementation for the intent resolver case.
3109      */
3110     @Override
3111     protected void resetButtonBar() {}
3112 
3113     @Override
3114     protected String getMetricsCategory() {
3115         return METRICS_CATEGORY_CHOOSER;
3116     }
3117 
3118     @Override
3119     protected void onProfileTabSelected() {
3120         ChooserGridAdapter currentRootAdapter =
3121                 mChooserMultiProfilePagerAdapter.getCurrentRootAdapter();
3122         currentRootAdapter.updateDirectShareExpansion();
3123         // This fixes an edge case where after performing a variety of gestures, vertical scrolling
3124         // ends up disabled. That's because at some point the old tab's vertical scrolling is
3125         // disabled and the new tab's is enabled. For context, see b/159997845
3126         setVerticalScrollEnabled(true);
3127         if (mResolverDrawerLayout != null) {
3128             mResolverDrawerLayout.scrollNestedScrollableChildBackToTop();
3129         }
3130     }
3131 
3132     @Override
3133     protected WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
3134         if (shouldShowTabs()) {
3135             mChooserMultiProfilePagerAdapter
3136                     .setEmptyStateBottomOffset(insets.getSystemWindowInsetBottom());
3137             mChooserMultiProfilePagerAdapter.setupContainerPadding(
3138                     getActiveEmptyStateView().findViewById(R.id.resolver_empty_state_container));
3139         }
3140 
3141         WindowInsets result = super.onApplyWindowInsets(v, insets);
3142         if (mResolverDrawerLayout != null) {
3143             mResolverDrawerLayout.requestLayout();
3144         }
3145         return result;
3146     }
3147 
3148     private void setHorizontalScrollingEnabled(boolean enabled) {
3149         ResolverViewPager viewPager = findViewById(R.id.profile_pager);
3150         viewPager.setSwipingEnabled(enabled);
3151     }
3152 
3153     private void setVerticalScrollEnabled(boolean enabled) {
3154         ChooserGridLayoutManager layoutManager =
3155                 (ChooserGridLayoutManager) mChooserMultiProfilePagerAdapter.getActiveAdapterView()
3156                         .getLayoutManager();
3157         layoutManager.setVerticalScrollEnabled(enabled);
3158     }
3159 
3160     @Override
3161     void onHorizontalSwipeStateChanged(int state) {
3162         if (state == ViewPager.SCROLL_STATE_DRAGGING) {
3163             if (mScrollStatus == SCROLL_STATUS_IDLE) {
3164                 mScrollStatus = SCROLL_STATUS_SCROLLING_HORIZONTAL;
3165                 setVerticalScrollEnabled(false);
3166             }
3167         } else if (state == ViewPager.SCROLL_STATE_IDLE) {
3168             if (mScrollStatus == SCROLL_STATUS_SCROLLING_HORIZONTAL) {
3169                 mScrollStatus = SCROLL_STATUS_IDLE;
3170                 setVerticalScrollEnabled(true);
3171             }
3172         }
3173     }
3174 
3175     /**
3176      * Adapter for all types of items and targets in ShareSheet.
3177      * Note that ranked sections like Direct Share - while appearing grid-like - are handled on the
3178      * row level by this adapter but not on the item level. Individual targets within the row are
3179      * handled by {@link ChooserListAdapter}
3180      */
3181     @VisibleForTesting
3182     public final class ChooserGridAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
3183         private ChooserListAdapter mChooserListAdapter;
3184         private final LayoutInflater mLayoutInflater;
3185 
3186         private DirectShareViewHolder mDirectShareViewHolder;
3187         private int mChooserTargetWidth = 0;
3188         private boolean mShowAzLabelIfPoss;
3189         private boolean mLayoutRequested = false;
3190 
3191         private int mFooterHeight = 0;
3192 
3193         private static final int VIEW_TYPE_DIRECT_SHARE = 0;
3194         private static final int VIEW_TYPE_NORMAL = 1;
3195         private static final int VIEW_TYPE_CONTENT_PREVIEW = 2;
3196         private static final int VIEW_TYPE_PROFILE = 3;
3197         private static final int VIEW_TYPE_AZ_LABEL = 4;
3198         private static final int VIEW_TYPE_CALLER_AND_RANK = 5;
3199         private static final int VIEW_TYPE_FOOTER = 6;
3200 
3201         private static final int NUM_EXPANSIONS_TO_HIDE_AZ_LABEL = 20;
3202 
3203         ChooserGridAdapter(ChooserListAdapter wrappedAdapter) {
3204             super();
3205             mChooserListAdapter = wrappedAdapter;
3206             mLayoutInflater = LayoutInflater.from(ChooserActivity.this);
3207 
3208             mShowAzLabelIfPoss = getNumSheetExpansions() < NUM_EXPANSIONS_TO_HIDE_AZ_LABEL;
3209 
3210             wrappedAdapter.registerDataSetObserver(new DataSetObserver() {
3211                 @Override
3212                 public void onChanged() {
3213                     super.onChanged();
3214                     notifyDataSetChanged();
3215                 }
3216 
3217                 @Override
3218                 public void onInvalidated() {
3219                     super.onInvalidated();
3220                     notifyDataSetChanged();
3221                 }
3222             });
3223         }
3224 
3225         public void setFooterHeight(int height) {
3226             mFooterHeight = height;
3227         }
3228 
3229         /**
3230          * Calculate the chooser target width to maximize space per item
3231          *
3232          * @param width The new row width to use for recalculation
3233          * @return true if the view width has changed
3234          */
3235         public boolean calculateChooserTargetWidth(int width) {
3236             if (width == 0) {
3237                 return false;
3238             }
3239 
3240             // Limit width to the maximum width of the chooser activity
3241             int maxWidth = getResources().getDimensionPixelSize(R.dimen.chooser_width);
3242             width = Math.min(maxWidth, width);
3243 
3244             int newWidth = width / mMaxTargetsPerRow;
3245             if (newWidth != mChooserTargetWidth) {
3246                 mChooserTargetWidth = newWidth;
3247                 return true;
3248             }
3249 
3250             return false;
3251         }
3252 
3253         /**
3254          * Hides the list item content preview.
3255          * <p>Not to be confused with the sticky content preview which is above the
3256          * personal and work tabs.
3257          */
3258         public void hideContentPreview() {
3259             mLayoutRequested = true;
3260             notifyDataSetChanged();
3261         }
3262 
3263         public boolean consumeLayoutRequest() {
3264             boolean oldValue = mLayoutRequested;
3265             mLayoutRequested = false;
3266             return oldValue;
3267         }
3268 
3269         public int getRowCount() {
3270             return (int) (
3271                     getSystemRowCount()
3272                             + getProfileRowCount()
3273                             + getServiceTargetRowCount()
3274                             + getCallerAndRankedTargetRowCount()
3275                             + getAzLabelRowCount()
3276                             + Math.ceil(
3277                             (float) mChooserListAdapter.getAlphaTargetCount()
3278                                     / mMaxTargetsPerRow)
3279             );
3280         }
3281 
3282         /**
3283          * Whether the "system" row of targets is displayed.
3284          * This area includes the content preview (if present) and action row.
3285          */
3286         public int getSystemRowCount() {
3287             // For the tabbed case we show the sticky content preview above the tabs,
3288             // please refer to shouldShowStickyContentPreview
3289             if (shouldShowTabs()) {
3290                 return 0;
3291             }
3292 
3293             if (!shouldShowContentPreview()) {
3294                 return 0;
3295             }
3296 
3297             if (mChooserListAdapter == null || mChooserListAdapter.getCount() == 0) {
3298                 return 0;
3299             }
3300 
3301             return 1;
3302         }
3303 
3304         public int getProfileRowCount() {
3305             if (shouldShowTabs()) {
3306                 return 0;
3307             }
3308             return mChooserListAdapter.getOtherProfile() == null ? 0 : 1;
3309         }
3310 
3311         public int getFooterRowCount() {
3312             return 1;
3313         }
3314 
3315         public int getCallerAndRankedTargetRowCount() {
3316             return (int) Math.ceil(
3317                     ((float) mChooserListAdapter.getCallerTargetCount()
3318                             + mChooserListAdapter.getRankedTargetCount()) / mMaxTargetsPerRow);
3319         }
3320 
3321         // There can be at most one row in the listview, that is internally
3322         // a ViewGroup with 2 rows
3323         public int getServiceTargetRowCount() {
3324             return shouldShowServiceTargets() ? 1 : 0;
3325         }
3326 
3327         public int getAzLabelRowCount() {
3328             // Only show a label if the a-z list is showing
3329             return (mShowAzLabelIfPoss && mChooserListAdapter.getAlphaTargetCount() > 0) ? 1 : 0;
3330         }
3331 
3332         @Override
3333         public int getItemCount() {
3334             return (int) (
3335                     getSystemRowCount()
3336                             + getProfileRowCount()
3337                             + getServiceTargetRowCount()
3338                             + getCallerAndRankedTargetRowCount()
3339                             + getAzLabelRowCount()
3340                             + mChooserListAdapter.getAlphaTargetCount()
3341                             + getFooterRowCount()
3342             );
3343         }
3344 
3345         @Override
3346         public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
3347             switch (viewType) {
3348                 case VIEW_TYPE_CONTENT_PREVIEW:
3349                     return new ItemViewHolder(createContentPreviewView(parent), false, viewType);
3350                 case VIEW_TYPE_PROFILE:
3351                     return new ItemViewHolder(createProfileView(parent), false, viewType);
3352                 case VIEW_TYPE_AZ_LABEL:
3353                     return new ItemViewHolder(createAzLabelView(parent), false, viewType);
3354                 case VIEW_TYPE_NORMAL:
3355                     return new ItemViewHolder(
3356                             mChooserListAdapter.createView(parent), true, viewType);
3357                 case VIEW_TYPE_DIRECT_SHARE:
3358                 case VIEW_TYPE_CALLER_AND_RANK:
3359                     return createItemGroupViewHolder(viewType, parent);
3360                 case VIEW_TYPE_FOOTER:
3361                     Space sp = new Space(parent.getContext());
3362                     sp.setLayoutParams(new RecyclerView.LayoutParams(
3363                             LayoutParams.MATCH_PARENT, mFooterHeight));
3364                     return new FooterViewHolder(sp, viewType);
3365                 default:
3366                     // Since we catch all possible viewTypes above, no chance this is being called.
3367                     return null;
3368             }
3369         }
3370 
3371         @Override
3372         public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
3373             int viewType = ((ViewHolderBase) holder).getViewType();
3374             switch (viewType) {
3375                 case VIEW_TYPE_DIRECT_SHARE:
3376                 case VIEW_TYPE_CALLER_AND_RANK:
3377                     bindItemGroupViewHolder(position, (ItemGroupViewHolder) holder);
3378                     break;
3379                 case VIEW_TYPE_NORMAL:
3380                     bindItemViewHolder(position, (ItemViewHolder) holder);
3381                     break;
3382                 default:
3383             }
3384         }
3385 
3386         @Override
3387         public int getItemViewType(int position) {
3388             int count;
3389 
3390             int countSum = (count = getSystemRowCount());
3391             if (count > 0 && position < countSum) return VIEW_TYPE_CONTENT_PREVIEW;
3392 
3393             countSum += (count = getProfileRowCount());
3394             if (count > 0 && position < countSum) return VIEW_TYPE_PROFILE;
3395 
3396             countSum += (count = getServiceTargetRowCount());
3397             if (count > 0 && position < countSum) return VIEW_TYPE_DIRECT_SHARE;
3398 
3399             countSum += (count = getCallerAndRankedTargetRowCount());
3400             if (count > 0 && position < countSum) return VIEW_TYPE_CALLER_AND_RANK;
3401 
3402             countSum += (count = getAzLabelRowCount());
3403             if (count > 0 && position < countSum) return VIEW_TYPE_AZ_LABEL;
3404 
3405             if (position == getItemCount() - 1) return VIEW_TYPE_FOOTER;
3406 
3407             return VIEW_TYPE_NORMAL;
3408         }
3409 
3410         public int getTargetType(int position) {
3411             return mChooserListAdapter.getPositionTargetType(getListPosition(position));
3412         }
3413 
3414         private View createProfileView(ViewGroup parent) {
3415             View profileRow = mLayoutInflater.inflate(R.layout.chooser_profile_row, parent, false);
3416             mProfileView = profileRow.findViewById(R.id.profile_button);
3417             mProfileView.setOnClickListener(ChooserActivity.this::onProfileClick);
3418             updateProfileViewButton();
3419             return profileRow;
3420         }
3421 
3422         private View createAzLabelView(ViewGroup parent) {
3423             return mLayoutInflater.inflate(R.layout.chooser_az_label_row, parent, false);
3424         }
3425 
3426         private ItemGroupViewHolder loadViewsIntoGroup(ItemGroupViewHolder holder) {
3427             final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
3428             final int exactSpec = MeasureSpec.makeMeasureSpec(mChooserTargetWidth,
3429                     MeasureSpec.EXACTLY);
3430             int columnCount = holder.getColumnCount();
3431 
3432             final boolean isDirectShare = holder instanceof DirectShareViewHolder;
3433 
3434             for (int i = 0; i < columnCount; i++) {
3435                 final View v = mChooserListAdapter.createView(holder.getRowByIndex(i));
3436                 final int column = i;
3437                 v.setOnClickListener(new OnClickListener() {
3438                     @Override
3439                     public void onClick(View v) {
3440                         startSelected(holder.getItemIndex(column), false, true);
3441                     }
3442                 });
3443 
3444                 holder.addView(i, v);
3445 
3446                 // Force Direct Share to be 2 lines and auto-wrap to second line via hoz scroll =
3447                 // false. TextView#setHorizontallyScrolling must be reset after #setLines. Must be
3448                 // done before measuring.
3449                 if (isDirectShare) {
3450                     final ViewHolder vh = (ViewHolder) v.getTag();
3451                     vh.text.setLines(2);
3452                     vh.text.setHorizontallyScrolling(false);
3453                     vh.text2.setVisibility(View.GONE);
3454                 }
3455 
3456                 // Force height to be a given so we don't have visual disruption during scaling.
3457                 v.measure(exactSpec, spec);
3458                 setViewBounds(v, v.getMeasuredWidth(), v.getMeasuredHeight());
3459             }
3460 
3461             final ViewGroup viewGroup = holder.getViewGroup();
3462 
3463             // Pre-measure and fix height so we can scale later.
3464             holder.measure();
3465             setViewBounds(viewGroup, LayoutParams.MATCH_PARENT, holder.getMeasuredRowHeight());
3466 
3467             if (isDirectShare) {
3468                 DirectShareViewHolder dsvh = (DirectShareViewHolder) holder;
3469                 setViewBounds(dsvh.getRow(0), LayoutParams.MATCH_PARENT, dsvh.getMinRowHeight());
3470                 setViewBounds(dsvh.getRow(1), LayoutParams.MATCH_PARENT, dsvh.getMinRowHeight());
3471             }
3472 
3473             viewGroup.setTag(holder);
3474             return holder;
3475         }
3476 
3477         private void setViewBounds(View view, int widthPx, int heightPx) {
3478             LayoutParams lp = view.getLayoutParams();
3479             if (lp == null) {
3480                 lp = new LayoutParams(widthPx, heightPx);
3481                 view.setLayoutParams(lp);
3482             } else {
3483                 lp.height = heightPx;
3484                 lp.width = widthPx;
3485             }
3486         }
3487 
3488         ItemGroupViewHolder createItemGroupViewHolder(int viewType, ViewGroup parent) {
3489             if (viewType == VIEW_TYPE_DIRECT_SHARE) {
3490                 ViewGroup parentGroup = (ViewGroup) mLayoutInflater.inflate(
3491                         R.layout.chooser_row_direct_share, parent, false);
3492                 ViewGroup row1 = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
3493                         parentGroup, false);
3494                 ViewGroup row2 = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
3495                         parentGroup, false);
3496                 parentGroup.addView(row1);
3497                 parentGroup.addView(row2);
3498 
3499                 mDirectShareViewHolder = new DirectShareViewHolder(parentGroup,
3500                         Lists.newArrayList(row1, row2), mMaxTargetsPerRow, viewType,
3501                         mChooserMultiProfilePagerAdapter::getActiveListAdapter);
3502                 loadViewsIntoGroup(mDirectShareViewHolder);
3503 
3504                 return mDirectShareViewHolder;
3505             } else {
3506                 ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row, parent,
3507                         false);
3508                 ItemGroupViewHolder holder =
3509                         new SingleRowViewHolder(row, mMaxTargetsPerRow, viewType);
3510                 loadViewsIntoGroup(holder);
3511 
3512                 return holder;
3513             }
3514         }
3515 
3516         /**
3517          * Need to merge CALLER + ranked STANDARD into a single row and prevent a separator from
3518          * showing on top of the AZ list if the AZ label is visible. All other types are placed into
3519          * their own row as determined by their target type, and dividers are added in the list to
3520          * separate each type.
3521          */
3522         int getRowType(int rowPosition) {
3523             // Merge caller and ranked standard into a single row
3524             int positionType = mChooserListAdapter.getPositionTargetType(rowPosition);
3525             if (positionType == ChooserListAdapter.TARGET_CALLER) {
3526                 return ChooserListAdapter.TARGET_STANDARD;
3527             }
3528 
3529             // If an the A-Z label is shown, prevent a separator from appearing by making the A-Z
3530             // row type the same as the suggestion row type
3531             if (getAzLabelRowCount() > 0 && positionType == ChooserListAdapter.TARGET_STANDARD_AZ) {
3532                 return ChooserListAdapter.TARGET_STANDARD;
3533             }
3534 
3535             return positionType;
3536         }
3537 
3538         void bindItemViewHolder(int position, ItemViewHolder holder) {
3539             View v = holder.itemView;
3540             int listPosition = getListPosition(position);
3541             holder.mListPosition = listPosition;
3542             mChooserListAdapter.bindView(listPosition, v);
3543         }
3544 
3545         void bindItemGroupViewHolder(int position, ItemGroupViewHolder holder) {
3546             final ViewGroup viewGroup = (ViewGroup) holder.itemView;
3547             int start = getListPosition(position);
3548             int startType = getRowType(start);
3549 
3550             int columnCount = holder.getColumnCount();
3551             int end = start + columnCount - 1;
3552             while (getRowType(end) != startType && end >= start) {
3553                 end--;
3554             }
3555 
3556             if (end == start && mChooserListAdapter.getItem(start) instanceof EmptyTargetInfo) {
3557                 final TextView textView = viewGroup.findViewById(R.id.chooser_row_text_option);
3558 
3559                 if (textView.getVisibility() != View.VISIBLE) {
3560                     textView.setAlpha(0.0f);
3561                     textView.setVisibility(View.VISIBLE);
3562                     textView.setText(R.string.chooser_no_direct_share_targets);
3563 
3564                     ValueAnimator fadeAnim = ObjectAnimator.ofFloat(textView, "alpha", 0.0f, 1.0f);
3565                     fadeAnim.setInterpolator(new DecelerateInterpolator(1.0f));
3566 
3567                     float translationInPx = getResources().getDimensionPixelSize(
3568                             R.dimen.chooser_row_text_option_translate);
3569                     textView.setTranslationY(translationInPx);
3570                     ValueAnimator translateAnim = ObjectAnimator.ofFloat(textView, "translationY",
3571                             0.0f);
3572                     translateAnim.setInterpolator(new DecelerateInterpolator(1.0f));
3573 
3574                     AnimatorSet animSet = new AnimatorSet();
3575                     animSet.setDuration(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
3576                     animSet.setStartDelay(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
3577                     animSet.playTogether(fadeAnim, translateAnim);
3578                     animSet.start();
3579                 }
3580             }
3581 
3582             for (int i = 0; i < columnCount; i++) {
3583                 final View v = holder.getView(i);
3584 
3585                 if (start + i <= end) {
3586                     holder.setViewVisibility(i, View.VISIBLE);
3587                     holder.setItemIndex(i, start + i);
3588                     mChooserListAdapter.bindView(holder.getItemIndex(i), v);
3589                 } else {
3590                     holder.setViewVisibility(i, View.INVISIBLE);
3591                 }
3592             }
3593         }
3594 
3595         int getListPosition(int position) {
3596             position -= getSystemRowCount() + getProfileRowCount();
3597 
3598             final int serviceCount = mChooserListAdapter.getServiceTargetCount();
3599             final int serviceRows = (int) Math.ceil((float) serviceCount / getMaxRankedTargets());
3600             if (position < serviceRows) {
3601                 return position * mMaxTargetsPerRow;
3602             }
3603 
3604             position -= serviceRows;
3605 
3606             final int callerAndRankedCount = mChooserListAdapter.getCallerTargetCount()
3607                                                  + mChooserListAdapter.getRankedTargetCount();
3608             final int callerAndRankedRows = getCallerAndRankedTargetRowCount();
3609             if (position < callerAndRankedRows) {
3610                 return serviceCount + position * mMaxTargetsPerRow;
3611             }
3612 
3613             position -= getAzLabelRowCount() + callerAndRankedRows;
3614 
3615             return callerAndRankedCount + serviceCount + position;
3616         }
3617 
3618         public void handleScroll(View v, int y, int oldy) {
3619             boolean canExpandDirectShare = canExpandDirectShare();
3620             if (mDirectShareViewHolder != null && canExpandDirectShare) {
3621                 mDirectShareViewHolder.handleScroll(
3622                         mChooserMultiProfilePagerAdapter.getActiveAdapterView(), y, oldy,
3623                         mMaxTargetsPerRow);
3624             }
3625         }
3626 
3627         /**
3628          * Only expand direct share area if there is a minimum number of targets.
3629          */
3630         private boolean canExpandDirectShare() {
3631             // Do not enable until we have confirmed more apps are using sharing shortcuts
3632             // Check git history for enablement logic
3633             return false;
3634         }
3635 
3636         public ChooserListAdapter getListAdapter() {
3637             return mChooserListAdapter;
3638         }
3639 
3640         boolean shouldCellSpan(int position) {
3641             return getItemViewType(position) == VIEW_TYPE_NORMAL;
3642         }
3643 
3644         void updateDirectShareExpansion() {
3645             if (mDirectShareViewHolder == null || !canExpandDirectShare()) {
3646                 return;
3647             }
3648             RecyclerView activeAdapterView =
3649                     mChooserMultiProfilePagerAdapter.getActiveAdapterView();
3650             if (mResolverDrawerLayout.isCollapsed()) {
3651                 mDirectShareViewHolder.collapse(activeAdapterView);
3652             } else {
3653                 mDirectShareViewHolder.expand(activeAdapterView);
3654             }
3655         }
3656     }
3657 
3658     /**
3659      * Used to bind types for group of items including:
3660      * {@link ChooserGridAdapter#VIEW_TYPE_DIRECT_SHARE},
3661      * and {@link ChooserGridAdapter#VIEW_TYPE_CALLER_AND_RANK}.
3662      */
3663     abstract static class ItemGroupViewHolder extends ViewHolderBase {
3664         protected int mMeasuredRowHeight;
3665         private int[] mItemIndices;
3666         protected final View[] mCells;
3667         private final int mColumnCount;
3668 
3669         ItemGroupViewHolder(int cellCount, View itemView, int viewType) {
3670             super(itemView, viewType);
3671             this.mCells = new View[cellCount];
3672             this.mItemIndices = new int[cellCount];
3673             this.mColumnCount = cellCount;
3674         }
3675 
3676         abstract ViewGroup addView(int index, View v);
3677 
3678         abstract ViewGroup getViewGroup();
3679 
3680         abstract ViewGroup getRowByIndex(int index);
3681 
3682         abstract ViewGroup getRow(int rowNumber);
3683 
3684         abstract void setViewVisibility(int i, int visibility);
3685 
3686         public int getColumnCount() {
3687             return mColumnCount;
3688         }
3689 
3690         public void measure() {
3691             final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
3692             getViewGroup().measure(spec, spec);
3693             mMeasuredRowHeight = getViewGroup().getMeasuredHeight();
3694         }
3695 
3696         public int getMeasuredRowHeight() {
3697             return mMeasuredRowHeight;
3698         }
3699 
3700         public void setItemIndex(int itemIndex, int listIndex) {
3701             mItemIndices[itemIndex] = listIndex;
3702         }
3703 
3704         public int getItemIndex(int itemIndex) {
3705             return mItemIndices[itemIndex];
3706         }
3707 
3708         public View getView(int index) {
3709             return mCells[index];
3710         }
3711     }
3712 
3713     static class SingleRowViewHolder extends ItemGroupViewHolder {
3714         private final ViewGroup mRow;
3715 
3716         SingleRowViewHolder(ViewGroup row, int cellCount, int viewType) {
3717             super(cellCount, row, viewType);
3718 
3719             this.mRow = row;
3720         }
3721 
3722         public ViewGroup getViewGroup() {
3723             return mRow;
3724         }
3725 
3726         public ViewGroup getRowByIndex(int index) {
3727             return mRow;
3728         }
3729 
3730         public ViewGroup getRow(int rowNumber) {
3731             if (rowNumber == 0) return mRow;
3732             return null;
3733         }
3734 
3735         public ViewGroup addView(int index, View v) {
3736             mRow.addView(v);
3737             mCells[index] = v;
3738 
3739             return mRow;
3740         }
3741 
3742         public void setViewVisibility(int i, int visibility) {
3743             getView(i).setVisibility(visibility);
3744         }
3745     }
3746 
3747     static class DirectShareViewHolder extends ItemGroupViewHolder {
3748         private final ViewGroup mParent;
3749         private final List<ViewGroup> mRows;
3750         private int mCellCountPerRow;
3751 
3752         private boolean mHideDirectShareExpansion = false;
3753         private int mDirectShareMinHeight = 0;
3754         private int mDirectShareCurrHeight = 0;
3755         private int mDirectShareMaxHeight = 0;
3756 
3757         private final boolean[] mCellVisibility;
3758 
3759         private final Supplier<ChooserListAdapter> mListAdapterSupplier;
3760 
3761         DirectShareViewHolder(ViewGroup parent, List<ViewGroup> rows, int cellCountPerRow,
3762                 int viewType, Supplier<ChooserListAdapter> listAdapterSupplier) {
3763             super(rows.size() * cellCountPerRow, parent, viewType);
3764 
3765             this.mParent = parent;
3766             this.mRows = rows;
3767             this.mCellCountPerRow = cellCountPerRow;
3768             this.mCellVisibility = new boolean[rows.size() * cellCountPerRow];
3769             Arrays.fill(mCellVisibility, true);
3770             this.mListAdapterSupplier = listAdapterSupplier;
3771         }
3772 
3773         public ViewGroup addView(int index, View v) {
3774             ViewGroup row = getRowByIndex(index);
3775             row.addView(v);
3776             mCells[index] = v;
3777 
3778             return row;
3779         }
3780 
3781         public ViewGroup getViewGroup() {
3782             return mParent;
3783         }
3784 
3785         public ViewGroup getRowByIndex(int index) {
3786             return mRows.get(index / mCellCountPerRow);
3787         }
3788 
3789         public ViewGroup getRow(int rowNumber) {
3790             return mRows.get(rowNumber);
3791         }
3792 
3793         public void measure() {
3794             final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
3795             getRow(0).measure(spec, spec);
3796             getRow(1).measure(spec, spec);
3797 
3798             mDirectShareMinHeight = getRow(0).getMeasuredHeight();
3799             mDirectShareCurrHeight = mDirectShareCurrHeight > 0
3800                     ? mDirectShareCurrHeight : mDirectShareMinHeight;
3801             mDirectShareMaxHeight = 2 * mDirectShareMinHeight;
3802         }
3803 
3804         public int getMeasuredRowHeight() {
3805             return mDirectShareCurrHeight;
3806         }
3807 
3808         public int getMinRowHeight() {
3809             return mDirectShareMinHeight;
3810         }
3811 
3812         public void setViewVisibility(int i, int visibility) {
3813             final View v = getView(i);
3814             if (visibility == View.VISIBLE) {
3815                 mCellVisibility[i] = true;
3816                 v.setVisibility(visibility);
3817                 v.setAlpha(1.0f);
3818             } else if (visibility == View.INVISIBLE && mCellVisibility[i]) {
3819                 mCellVisibility[i] = false;
3820 
3821                 ValueAnimator fadeAnim = ObjectAnimator.ofFloat(v, "alpha", 1.0f, 0f);
3822                 fadeAnim.setDuration(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
3823                 fadeAnim.setInterpolator(new AccelerateInterpolator(1.0f));
3824                 fadeAnim.addListener(new AnimatorListenerAdapter() {
3825                     public void onAnimationEnd(Animator animation) {
3826                         v.setVisibility(View.INVISIBLE);
3827                     }
3828                 });
3829                 fadeAnim.start();
3830             }
3831         }
3832 
3833         public void handleScroll(RecyclerView view, int y, int oldy, int maxTargetsPerRow) {
3834             // only exit early if fully collapsed, otherwise onListRebuilt() with shifting
3835             // targets can lock us into an expanded mode
3836             boolean notExpanded = mDirectShareCurrHeight == mDirectShareMinHeight;
3837             if (notExpanded) {
3838                 if (mHideDirectShareExpansion) {
3839                     return;
3840                 }
3841 
3842                 // only expand if we have more than maxTargetsPerRow, and delay that decision
3843                 // until they start to scroll
3844                 ChooserListAdapter adapter = mListAdapterSupplier.get();
3845                 int validTargets = adapter.getSelectableServiceTargetCount();
3846                 if (validTargets <= maxTargetsPerRow) {
3847                     mHideDirectShareExpansion = true;
3848                     return;
3849                 }
3850             }
3851 
3852             int yDiff = (int) ((oldy - y) * DIRECT_SHARE_EXPANSION_RATE);
3853 
3854             int prevHeight = mDirectShareCurrHeight;
3855             int newHeight = Math.min(prevHeight + yDiff, mDirectShareMaxHeight);
3856             newHeight = Math.max(newHeight, mDirectShareMinHeight);
3857             yDiff = newHeight - prevHeight;
3858 
3859             updateDirectShareRowHeight(view, yDiff, newHeight);
3860         }
3861 
3862         void expand(RecyclerView view) {
3863             updateDirectShareRowHeight(view, mDirectShareMaxHeight - mDirectShareCurrHeight,
3864                     mDirectShareMaxHeight);
3865         }
3866 
3867         void collapse(RecyclerView view) {
3868             updateDirectShareRowHeight(view, mDirectShareMinHeight - mDirectShareCurrHeight,
3869                     mDirectShareMinHeight);
3870         }
3871 
3872         private void updateDirectShareRowHeight(RecyclerView view, int yDiff, int newHeight) {
3873             if (view == null || view.getChildCount() == 0 || yDiff == 0) {
3874                 return;
3875             }
3876 
3877             // locate the item to expand, and offset the rows below that one
3878             boolean foundExpansion = false;
3879             for (int i = 0; i < view.getChildCount(); i++) {
3880                 View child = view.getChildAt(i);
3881 
3882                 if (foundExpansion) {
3883                     child.offsetTopAndBottom(yDiff);
3884                 } else {
3885                     if (child.getTag() != null && child.getTag() instanceof DirectShareViewHolder) {
3886                         int widthSpec = MeasureSpec.makeMeasureSpec(child.getWidth(),
3887                                 MeasureSpec.EXACTLY);
3888                         int heightSpec = MeasureSpec.makeMeasureSpec(newHeight,
3889                                 MeasureSpec.EXACTLY);
3890                         child.measure(widthSpec, heightSpec);
3891                         child.getLayoutParams().height = child.getMeasuredHeight();
3892                         child.layout(child.getLeft(), child.getTop(), child.getRight(),
3893                                 child.getTop() + child.getMeasuredHeight());
3894 
3895                         foundExpansion = true;
3896                     }
3897                 }
3898             }
3899 
3900             if (foundExpansion) {
3901                 mDirectShareCurrHeight = newHeight;
3902             }
3903         }
3904     }
3905 
3906     /**
3907      * Shortcuts grouped by application.
3908      */
3909     @VisibleForTesting
3910     public static class ServiceResultInfo {
3911         public final DisplayResolveInfo originalTarget;
3912         public final List<ChooserTarget> resultTargets;
3913         public final UserHandle userHandle;
3914 
3915         public ServiceResultInfo(DisplayResolveInfo ot, List<ChooserTarget> rt,
3916                 UserHandle userHandle) {
3917             originalTarget = ot;
3918             resultTargets = rt;
3919             this.userHandle = userHandle;
3920         }
3921     }
3922 
3923     static class ChooserTargetRankingInfo {
3924         public final List<AppTarget> scores;
3925         public final UserHandle userHandle;
3926 
3927         ChooserTargetRankingInfo(List<AppTarget> chooserTargetScores,
3928                 UserHandle userHandle) {
3929             this.scores = chooserTargetScores;
3930             this.userHandle = userHandle;
3931         }
3932     }
3933 
3934     static class RefinementResultReceiver extends ResultReceiver {
3935         private ChooserActivity mChooserActivity;
3936         private TargetInfo mSelectedTarget;
3937 
3938         public RefinementResultReceiver(ChooserActivity host, TargetInfo target,
3939                 Handler handler) {
3940             super(handler);
3941             mChooserActivity = host;
3942             mSelectedTarget = target;
3943         }
3944 
3945         @Override
3946         protected void onReceiveResult(int resultCode, Bundle resultData) {
3947             if (mChooserActivity == null) {
3948                 Log.e(TAG, "Destroyed RefinementResultReceiver received a result");
3949                 return;
3950             }
3951             if (resultData == null) {
3952                 Log.e(TAG, "RefinementResultReceiver received null resultData");
3953                 return;
3954             }
3955 
3956             switch (resultCode) {
3957                 case RESULT_CANCELED:
3958                     mChooserActivity.onRefinementCanceled();
3959                     break;
3960                 case RESULT_OK:
3961                     Parcelable intentParcelable = resultData.getParcelable(Intent.EXTRA_INTENT);
3962                     if (intentParcelable instanceof Intent) {
3963                         mChooserActivity.onRefinementResult(mSelectedTarget,
3964                                 (Intent) intentParcelable);
3965                     } else {
3966                         Log.e(TAG, "RefinementResultReceiver received RESULT_OK but no Intent"
3967                                 + " in resultData with key Intent.EXTRA_INTENT");
3968                     }
3969                     break;
3970                 default:
3971                     Log.w(TAG, "Unknown result code " + resultCode
3972                             + " sent to RefinementResultReceiver");
3973                     break;
3974             }
3975         }
3976 
3977         public void destroy() {
3978             mChooserActivity = null;
3979             mSelectedTarget = null;
3980         }
3981     }
3982 
3983     /**
3984      * Used internally to round image corners while obeying view padding.
3985      */
3986     public static class RoundedRectImageView extends ImageView {
3987         private int mRadius = 0;
3988         private Path mPath = new Path();
3989         private Paint mOverlayPaint = new Paint(0);
3990         private Paint mRoundRectPaint = new Paint(0);
3991         private Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
3992         private String mExtraImageCount = null;
3993 
3994         public RoundedRectImageView(Context context) {
3995             super(context);
3996         }
3997 
3998         public RoundedRectImageView(Context context, AttributeSet attrs) {
3999             this(context, attrs, 0);
4000         }
4001 
4002         public RoundedRectImageView(Context context, AttributeSet attrs, int defStyleAttr) {
4003             this(context, attrs, defStyleAttr, 0);
4004         }
4005 
4006         public RoundedRectImageView(Context context, AttributeSet attrs, int defStyleAttr,
4007                 int defStyleRes) {
4008             super(context, attrs, defStyleAttr, defStyleRes);
4009             mRadius = context.getResources().getDimensionPixelSize(R.dimen.chooser_corner_radius);
4010 
4011             mOverlayPaint.setColor(0x99000000);
4012             mOverlayPaint.setStyle(Paint.Style.FILL);
4013 
4014             mRoundRectPaint.setColor(context.getResources().getColor(R.color.chooser_row_divider));
4015             mRoundRectPaint.setStyle(Paint.Style.STROKE);
4016             mRoundRectPaint.setStrokeWidth(context.getResources()
4017                     .getDimensionPixelSize(R.dimen.chooser_preview_image_border));
4018 
4019             mTextPaint.setColor(Color.WHITE);
4020             mTextPaint.setTextSize(context.getResources()
4021                     .getDimensionPixelSize(R.dimen.chooser_preview_image_font_size));
4022             mTextPaint.setTextAlign(Paint.Align.CENTER);
4023         }
4024 
4025         private void updatePath(int width, int height) {
4026             mPath.reset();
4027 
4028             int imageWidth = width - getPaddingRight() - getPaddingLeft();
4029             int imageHeight = height - getPaddingBottom() - getPaddingTop();
4030             mPath.addRoundRect(getPaddingLeft(), getPaddingTop(), imageWidth, imageHeight, mRadius,
4031                     mRadius, Path.Direction.CW);
4032         }
4033 
4034         /**
4035           * Sets the corner radius on all corners
4036           *
4037           * param radius 0 for no radius, &gt; 0 for a visible corner radius
4038           */
4039         public void setRadius(int radius) {
4040             mRadius = radius;
4041             updatePath(getWidth(), getHeight());
4042         }
4043 
4044         /**
4045           * Display an overlay with extra image count on 3rd image
4046           */
4047         public void setExtraImageCount(int count) {
4048             if (count > 0) {
4049                 this.mExtraImageCount = "+" + count;
4050             } else {
4051                 this.mExtraImageCount = null;
4052             }
4053         }
4054 
4055         @Override
4056         protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
4057             super.onSizeChanged(width, height, oldWidth, oldHeight);
4058             updatePath(width, height);
4059         }
4060 
4061         @Override
4062         protected void onDraw(Canvas canvas) {
4063             if (mRadius != 0) {
4064                 canvas.clipPath(mPath);
4065             }
4066 
4067             super.onDraw(canvas);
4068 
4069             int x = getPaddingLeft();
4070             int y = getPaddingRight();
4071             int width = getWidth() - getPaddingRight() - getPaddingLeft();
4072             int height = getHeight() - getPaddingBottom() - getPaddingTop();
4073             if (mExtraImageCount != null) {
4074                 canvas.drawRect(x, y, width, height, mOverlayPaint);
4075 
4076                 int xPos = canvas.getWidth() / 2;
4077                 int yPos = (int) ((canvas.getHeight() / 2.0f)
4078                         - ((mTextPaint.descent() + mTextPaint.ascent()) / 2.0f));
4079 
4080                 canvas.drawText(mExtraImageCount, xPos, yPos, mTextPaint);
4081             }
4082 
4083             canvas.drawRoundRect(x, y, width, height, mRadius, mRadius, mRoundRectPaint);
4084         }
4085     }
4086 
4087     /**
4088      * A helper class to track app's readiness for the scene transition animation.
4089      * The app is ready when both the image is laid out and the drawer offset is calculated.
4090      */
4091     private class EnterTransitionAnimationDelegate implements View.OnLayoutChangeListener {
4092         private boolean mPreviewReady = false;
4093         private boolean mOffsetCalculated = false;
4094 
4095         void postponeTransition() {
4096             postponeEnterTransition();
4097         }
4098 
4099         void markImagePreviewReady() {
4100             if (!mPreviewReady) {
4101                 mPreviewReady = true;
4102                 maybeStartListenForLayout();
4103             }
4104         }
4105 
4106         void markOffsetCalculated() {
4107             if (!mOffsetCalculated) {
4108                 mOffsetCalculated = true;
4109                 maybeStartListenForLayout();
4110             }
4111         }
4112 
4113         private void maybeStartListenForLayout() {
4114             if (mPreviewReady && mOffsetCalculated && mResolverDrawerLayout != null) {
4115                 if (mResolverDrawerLayout.isInLayout()) {
4116                     startPostponedEnterTransition();
4117                 } else {
4118                     mResolverDrawerLayout.addOnLayoutChangeListener(this);
4119                     mResolverDrawerLayout.requestLayout();
4120                 }
4121             }
4122         }
4123 
4124         @Override
4125         public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
4126                 int oldTop, int oldRight, int oldBottom) {
4127             v.removeOnLayoutChangeListener(this);
4128             startPostponedEnterTransition();
4129         }
4130     }
4131 
4132     /**
4133      * Used in combination with the scene transition when launching the image editor
4134      */
4135     private static class FinishAnimation extends AlphaAnimation implements
4136             Animation.AnimationListener {
4137         @Nullable
4138         private Activity mActivity;
4139         @Nullable
4140         private View mRootView;
4141         private final float mFromAlpha;
4142 
4143         FinishAnimation(@NonNull Activity activity, @NonNull View rootView) {
4144             super(rootView.getAlpha(), 0.0f);
4145             mActivity = activity;
4146             mRootView = rootView;
4147             mFromAlpha = rootView.getAlpha();
4148             setInterpolator(new LinearInterpolator());
4149             long duration = activity.getWindow().getTransitionBackgroundFadeDuration();
4150             setDuration(duration);
4151             // The scene transition animation looks better when it's not overlapped with this
4152             // fade-out animation thus the delay.
4153             // It is most likely that the image editor will cause this activity to stop and this
4154             // animation will be cancelled in the background without running (i.e. we'll animate
4155             // only when this activity remains partially visible after the image editor launch).
4156             setStartOffset(duration);
4157             super.setAnimationListener(this);
4158         }
4159 
4160         @Override
4161         public void setAnimationListener(AnimationListener listener) {
4162             throw new UnsupportedOperationException();
4163         }
4164 
4165         @Override
4166         public void cancel() {
4167             if (mRootView != null) {
4168                 mRootView.setAlpha(mFromAlpha);
4169             }
4170             cleanup();
4171             super.cancel();
4172         }
4173 
4174         @Override
4175         public void onAnimationStart(Animation animation) {
4176         }
4177 
4178         @Override
4179         public void onAnimationEnd(Animation animation) {
4180             Activity activity = mActivity;
4181             cleanup();
4182             if (activity != null) {
4183                 activity.finish();
4184             }
4185         }
4186 
4187         @Override
4188         public void onAnimationRepeat(Animation animation) {
4189         }
4190 
4191         private void cleanup() {
4192             mActivity = null;
4193             mRootView = null;
4194         }
4195     }
4196 
4197     @Override
4198     protected void maybeLogProfileChange() {
4199         getChooserActivityLogger().logShareheetProfileChanged();
4200     }
4201 
4202     private boolean shouldNearbyShareBeFirstInRankedRow() {
4203         return ActivityManager.isLowRamDeviceStatic() && mIsNearbyShareFirstTargetInRankedApp;
4204     }
4205 
4206     private boolean shouldNearbyShareBeIncludedAsActionButton() {
4207         return !shouldNearbyShareBeFirstInRankedRow();
4208     }
4209 }
4210