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