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