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