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