• 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.Nullable;
28 import android.app.Activity;
29 import android.app.ActivityManager;
30 import android.app.prediction.AppPredictionContext;
31 import android.app.prediction.AppPredictionManager;
32 import android.app.prediction.AppPredictor;
33 import android.app.prediction.AppTarget;
34 import android.app.prediction.AppTargetEvent;
35 import android.content.ClipData;
36 import android.content.ClipboardManager;
37 import android.content.ComponentName;
38 import android.content.ContentResolver;
39 import android.content.Context;
40 import android.content.Intent;
41 import android.content.IntentFilter;
42 import android.content.IntentSender;
43 import android.content.IntentSender.SendIntentException;
44 import android.content.ServiceConnection;
45 import android.content.pm.ActivityInfo;
46 import android.content.pm.ApplicationInfo;
47 import android.content.pm.LabeledIntent;
48 import android.content.pm.LauncherApps;
49 import android.content.pm.PackageManager;
50 import android.content.pm.PackageManager.NameNotFoundException;
51 import android.content.pm.ResolveInfo;
52 import android.content.pm.ShortcutInfo;
53 import android.content.pm.ShortcutManager;
54 import android.content.res.Configuration;
55 import android.database.Cursor;
56 import android.database.DataSetObserver;
57 import android.graphics.Bitmap;
58 import android.graphics.Canvas;
59 import android.graphics.Color;
60 import android.graphics.Paint;
61 import android.graphics.Path;
62 import android.graphics.drawable.AnimatedVectorDrawable;
63 import android.graphics.drawable.BitmapDrawable;
64 import android.graphics.drawable.Drawable;
65 import android.graphics.drawable.Icon;
66 import android.metrics.LogMaker;
67 import android.net.Uri;
68 import android.os.AsyncTask;
69 import android.os.Bundle;
70 import android.os.Handler;
71 import android.os.IBinder;
72 import android.os.Message;
73 import android.os.Parcelable;
74 import android.os.Process;
75 import android.os.RemoteException;
76 import android.os.ResultReceiver;
77 import android.os.UserHandle;
78 import android.os.UserManager;
79 import android.provider.DeviceConfig;
80 import android.provider.DocumentsContract;
81 import android.provider.Downloads;
82 import android.provider.OpenableColumns;
83 import android.service.chooser.ChooserTarget;
84 import android.service.chooser.ChooserTargetService;
85 import android.service.chooser.IChooserTargetResult;
86 import android.service.chooser.IChooserTargetService;
87 import android.text.SpannableStringBuilder;
88 import android.text.TextUtils;
89 import android.util.AttributeSet;
90 import android.util.HashedStringCache;
91 import android.util.Log;
92 import android.util.Size;
93 import android.util.Slog;
94 import android.view.LayoutInflater;
95 import android.view.View;
96 import android.view.View.MeasureSpec;
97 import android.view.View.OnClickListener;
98 import android.view.View.OnLongClickListener;
99 import android.view.ViewGroup;
100 import android.view.ViewGroup.LayoutParams;
101 import android.view.animation.AccelerateInterpolator;
102 import android.view.animation.DecelerateInterpolator;
103 import android.widget.AbsListView;
104 import android.widget.BaseAdapter;
105 import android.widget.ImageView;
106 import android.widget.ListView;
107 import android.widget.TextView;
108 import android.widget.Toast;
109 
110 import com.android.internal.R;
111 import com.android.internal.annotations.VisibleForTesting;
112 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
113 import com.android.internal.content.PackageMonitor;
114 import com.android.internal.logging.MetricsLogger;
115 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
116 import com.android.internal.util.ImageUtils;
117 import com.android.internal.widget.ResolverDrawerLayout;
118 
119 import com.google.android.collect.Lists;
120 
121 import java.io.IOException;
122 import java.lang.annotation.Retention;
123 import java.text.Collator;
124 import java.util.ArrayList;
125 import java.util.Arrays;
126 import java.util.Collections;
127 import java.util.Comparator;
128 import java.util.HashMap;
129 import java.util.HashSet;
130 import java.util.List;
131 import java.util.Map;
132 import java.util.Set;
133 
134 /**
135  * The Chooser Activity handles intent resolution specifically for sharing intents -
136  * for example, those generated by @see android.content.Intent#createChooser(Intent, CharSequence).
137  *
138  */
139 public class ChooserActivity extends ResolverActivity {
140     private static final String TAG = "ChooserActivity";
141 
142 
143     /**
144      * Boolean extra to change the following behavior: Normally, ChooserActivity finishes itself
145      * in onStop when launched in a new task. If this extra is set to true, we do not finish
146      * ourselves when onStop gets called.
147      */
148     public static final String EXTRA_PRIVATE_RETAIN_IN_ON_STOP
149             = "com.android.internal.app.ChooserActivity.EXTRA_PRIVATE_RETAIN_IN_ON_STOP";
150 
151     private static final String PREF_NUM_SHEET_EXPANSIONS = "pref_num_sheet_expansions";
152 
153     private static final boolean DEBUG = false;
154 
155     /**
156      * If {@link #USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS} and this is set to true,
157      * {@link AppPredictionManager} will be queried for direct share targets.
158      */
159     // TODO(b/123089490): Replace with system flag
160     private static final boolean USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS = true;
161     private static final boolean USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES = true;
162     // TODO(b/123088566) Share these in a better way.
163     private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share";
164     public static final String LAUNCH_LOCATON_DIRECT_SHARE = "direct_share";
165     private static final int APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
166     public static final String APP_PREDICTION_INTENT_FILTER_KEY = "intent_filter";
167     private AppPredictor mAppPredictor;
168     private AppPredictor.Callback mAppPredictorCallback;
169     private Map<ChooserTarget, AppTarget> mDirectShareAppTargetCache;
170 
171     /**
172      * If set to true, use ShortcutManager to retrieve the matching direct share targets, instead of
173      * binding to every ChooserTargetService implementation.
174      */
175     // TODO(b/121287573): Replace with a system flag (setprop?)
176     private static final boolean USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS = true;
177     private static final boolean USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS = true;
178 
179     /**
180      * The transition time between placeholders for direct share to a message
181      * indicating that non are available.
182      */
183     private static final int NO_DIRECT_SHARE_ANIM_IN_MILLIS = 200;
184 
185     private static final float DIRECT_SHARE_EXPANSION_RATE = 0.78f;
186 
187     // TODO(b/121287224): Re-evaluate this limit
188     private static final int SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
189 
190     private static final int QUERY_TARGET_SERVICE_LIMIT = 5;
191 
192     private static final int DEFAULT_SALT_EXPIRATION_DAYS = 7;
193     private int mMaxHashSaltDays = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
194             SystemUiDeviceConfigFlags.HASH_SALT_MAX_DAYS,
195             DEFAULT_SALT_EXPIRATION_DAYS);
196 
197     private Bundle mReplacementExtras;
198     private IntentSender mChosenComponentSender;
199     private IntentSender mRefinementIntentSender;
200     private RefinementResultReceiver mRefinementResultReceiver;
201     private ChooserTarget[] mCallerChooserTargets;
202     private ComponentName[] mFilteredComponentNames;
203 
204     private Intent mReferrerFillInIntent;
205 
206     private long mChooserShownTime;
207     protected boolean mIsSuccessfullySelected;
208 
209     private long mQueriedTargetServicesTimeMs;
210     private long mQueriedSharingShortcutsTimeMs;
211 
212     private ChooserListAdapter mChooserListAdapter;
213     private ChooserRowAdapter mChooserRowAdapter;
214     private int mChooserRowServiceSpacing;
215 
216     private int mCurrAvailableWidth = 0;
217 
218     /** {@link ChooserActivity#getBaseScore} */
219     private static final float CALLER_TARGET_SCORE_BOOST = 900.f;
220     /** {@link ChooserActivity#getBaseScore} */
221     private static final float SHORTCUT_TARGET_SCORE_BOOST = 90.f;
222     private static final String TARGET_DETAILS_FRAGMENT_TAG = "targetDetailsFragment";
223     // TODO: Update to handle landscape instead of using static value
224     private static final int MAX_RANKED_TARGETS = 4;
225 
226     private final List<ChooserTargetServiceConnection> mServiceConnections = new ArrayList<>();
227     private final Set<ComponentName> mServicesRequested = new HashSet<>();
228 
229     private static final int MAX_LOG_RANK_POSITION = 12;
230 
231     @VisibleForTesting
232     public static final int LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS = 250;
233 
234     private static final int MAX_EXTRA_INITIAL_INTENTS = 2;
235     private static final int MAX_EXTRA_CHOOSER_TARGETS = 2;
236 
237     private boolean mListViewDataChanged = false;
238 
239     @Retention(SOURCE)
240     @IntDef({CONTENT_PREVIEW_FILE, CONTENT_PREVIEW_IMAGE, CONTENT_PREVIEW_TEXT})
241     private @interface ContentPreviewType {
242     }
243 
244     // Starting at 1 since 0 is considered "undefined" for some of the database transformations
245     // of tron logs.
246     private static final int CONTENT_PREVIEW_IMAGE = 1;
247     private static final int CONTENT_PREVIEW_FILE = 2;
248     private static final int CONTENT_PREVIEW_TEXT = 3;
249     protected MetricsLogger mMetricsLogger;
250 
251     // Sorted list of DisplayResolveInfos for the alphabetical app section.
252     private List<ResolverActivity.DisplayResolveInfo> mSortedList = new ArrayList<>();
253 
254     private ContentPreviewCoordinator mPreviewCoord;
255 
256     private class ContentPreviewCoordinator {
257         private static final int IMAGE_FADE_IN_MILLIS = 150;
258         private static final int IMAGE_LOAD_TIMEOUT = 1;
259         private static final int IMAGE_LOAD_INTO_VIEW = 2;
260 
261         private final int mImageLoadTimeoutMillis =
262                 getResources().getInteger(R.integer.config_shortAnimTime);
263 
264         private final View mParentView;
265         private boolean mHideParentOnFail;
266         private boolean mAtLeastOneLoaded = false;
267 
268         class LoadUriTask {
269             public final Uri mUri;
270             public final int mImageResourceId;
271             public final int mExtraCount;
272             public final Bitmap mBmp;
273 
LoadUriTask(int imageResourceId, Uri uri, int extraCount, Bitmap bmp)274             LoadUriTask(int imageResourceId, Uri uri, int extraCount, Bitmap bmp) {
275                 this.mImageResourceId = imageResourceId;
276                 this.mUri = uri;
277                 this.mExtraCount = extraCount;
278                 this.mBmp = bmp;
279             }
280         }
281 
282         // If at least one image loads within the timeout period, allow other
283         // loads to continue. Otherwise terminate and optionally hide
284         // the parent area
285         private final Handler mHandler = new Handler() {
286             @Override
287             public void handleMessage(Message msg) {
288                 switch (msg.what) {
289                     case IMAGE_LOAD_TIMEOUT:
290                         maybeHideContentPreview();
291                         break;
292 
293                     case IMAGE_LOAD_INTO_VIEW:
294                         if (isFinishing()) break;
295 
296                         LoadUriTask task = (LoadUriTask) msg.obj;
297                         RoundedRectImageView imageView = mParentView.findViewById(
298                                 task.mImageResourceId);
299                         if (task.mBmp == null) {
300                             imageView.setVisibility(View.GONE);
301                             maybeHideContentPreview();
302                             return;
303                         }
304 
305                         mAtLeastOneLoaded = true;
306                         imageView.setVisibility(View.VISIBLE);
307                         imageView.setAlpha(0.0f);
308                         imageView.setImageBitmap(task.mBmp);
309 
310                         ValueAnimator fadeAnim = ObjectAnimator.ofFloat(imageView, "alpha", 0.0f,
311                                 1.0f);
312                         fadeAnim.setInterpolator(new DecelerateInterpolator(1.0f));
313                         fadeAnim.setDuration(IMAGE_FADE_IN_MILLIS);
314                         fadeAnim.start();
315 
316                         if (task.mExtraCount > 0) {
317                             imageView.setExtraImageCount(task.mExtraCount);
318                         }
319                 }
320             }
321         };
322 
ContentPreviewCoordinator(View parentView, boolean hideParentOnFail)323         ContentPreviewCoordinator(View parentView, boolean hideParentOnFail) {
324             super();
325 
326             this.mParentView = parentView;
327             this.mHideParentOnFail = hideParentOnFail;
328         }
329 
loadUriIntoView(final int imageResourceId, final Uri uri, final int extraImages)330         private void loadUriIntoView(final int imageResourceId, final Uri uri,
331                 final int extraImages) {
332             mHandler.sendEmptyMessageDelayed(IMAGE_LOAD_TIMEOUT, mImageLoadTimeoutMillis);
333 
334             AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
335                 final Bitmap bmp = loadThumbnail(uri, new Size(200, 200));
336                 final Message msg = Message.obtain();
337                 msg.what = IMAGE_LOAD_INTO_VIEW;
338                 msg.obj = new LoadUriTask(imageResourceId, uri, extraImages, bmp);
339                 mHandler.sendMessage(msg);
340             });
341         }
342 
cancelLoads()343         private void cancelLoads() {
344             mHandler.removeMessages(IMAGE_LOAD_INTO_VIEW);
345             mHandler.removeMessages(IMAGE_LOAD_TIMEOUT);
346         }
347 
maybeHideContentPreview()348         private void maybeHideContentPreview() {
349             if (!mAtLeastOneLoaded && mHideParentOnFail) {
350                 Log.i(TAG, "Hiding image preview area. Timed out waiting for preview to load"
351                         + " within " + mImageLoadTimeoutMillis + "ms.");
352                 collapseParentView();
353                 if (mChooserRowAdapter != null) {
354                     mChooserRowAdapter.hideContentPreview();
355                 }
356                 mHideParentOnFail = false;
357             }
358         }
359 
collapseParentView()360         private void collapseParentView() {
361             // This will effectively hide the content preview row by forcing the height
362             // to zero. It is faster than forcing a relayout of the listview
363             final View v = mParentView;
364             int widthSpec = MeasureSpec.makeMeasureSpec(v.getWidth(), MeasureSpec.EXACTLY);
365             int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY);
366             v.measure(widthSpec, heightSpec);
367             v.getLayoutParams().height = 0;
368             v.layout(v.getLeft(), v.getTop(), v.getRight(), v.getTop());
369             v.invalidate();
370         }
371     }
372 
373     private final ChooserHandler mChooserHandler = new ChooserHandler();
374 
375     private class ChooserHandler extends Handler {
376         private static final int CHOOSER_TARGET_SERVICE_RESULT = 1;
377         private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT = 2;
378         private static final int CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT = 3;
379         private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT = 4;
380         private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED = 5;
381         private static final int LIST_VIEW_UPDATE_MESSAGE = 6;
382 
383         private static final int WATCHDOG_TIMEOUT_MAX_MILLIS = 10000;
384         private static final int WATCHDOG_TIMEOUT_MIN_MILLIS = 3000;
385 
386         private boolean mMinTimeoutPassed = false;
387 
removeAllMessages()388         private void removeAllMessages() {
389             removeMessages(LIST_VIEW_UPDATE_MESSAGE);
390             removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT);
391             removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT);
392             removeMessages(CHOOSER_TARGET_SERVICE_RESULT);
393             removeMessages(SHORTCUT_MANAGER_SHARE_TARGET_RESULT);
394             removeMessages(SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED);
395         }
396 
restartServiceRequestTimer()397         private void restartServiceRequestTimer() {
398             mMinTimeoutPassed = false;
399             removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT);
400             removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT);
401 
402             if (DEBUG) {
403                 Log.d(TAG, "queryTargets setting watchdog timer for "
404                         + WATCHDOG_TIMEOUT_MIN_MILLIS + "-"
405                         + WATCHDOG_TIMEOUT_MAX_MILLIS + "ms");
406             }
407 
408             sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT,
409                     WATCHDOG_TIMEOUT_MIN_MILLIS);
410             sendEmptyMessageDelayed(CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT,
411                     WATCHDOG_TIMEOUT_MAX_MILLIS);
412         }
413 
maybeStopServiceRequestTimer()414         private void maybeStopServiceRequestTimer() {
415             // Set a minimum timeout threshold, to ensure both apis, sharing shortcuts
416             // and older-style direct share services, have had time to load, otherwise
417             // just checking mServiceConnections could force us to end prematurely
418             if (mMinTimeoutPassed && mServiceConnections.isEmpty()) {
419                 logDirectShareTargetReceived(
420                         MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_CHOOSER_SERVICE);
421                 sendVoiceChoicesIfNeeded();
422                 mChooserListAdapter.completeServiceTargetLoading();
423             }
424         }
425 
426         @Override
handleMessage(Message msg)427         public void handleMessage(Message msg) {
428             if (mChooserListAdapter == null || isDestroyed()) {
429                 return;
430             }
431 
432             switch (msg.what) {
433                 case CHOOSER_TARGET_SERVICE_RESULT:
434                     if (DEBUG) Log.d(TAG, "CHOOSER_TARGET_SERVICE_RESULT");
435                     final ServiceResultInfo sri = (ServiceResultInfo) msg.obj;
436                     if (!mServiceConnections.contains(sri.connection)) {
437                         Log.w(TAG, "ChooserTargetServiceConnection " + sri.connection
438                                 + " returned after being removed from active connections."
439                                 + " Have you considered returning results faster?");
440                         break;
441                     }
442                     if (sri.resultTargets != null) {
443                         mChooserListAdapter.addServiceResults(sri.originalTarget,
444                                 sri.resultTargets, false);
445                     }
446                     unbindService(sri.connection);
447                     sri.connection.destroy();
448                     mServiceConnections.remove(sri.connection);
449                     maybeStopServiceRequestTimer();
450                     break;
451 
452                 case CHOOSER_TARGET_SERVICE_WATCHDOG_MIN_TIMEOUT:
453                     mMinTimeoutPassed = true;
454                     maybeStopServiceRequestTimer();
455                     break;
456 
457                 case CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT:
458                     unbindRemainingServices();
459                     maybeStopServiceRequestTimer();
460                     break;
461 
462                 case LIST_VIEW_UPDATE_MESSAGE:
463                     if (DEBUG) {
464                         Log.d(TAG, "LIST_VIEW_UPDATE_MESSAGE; ");
465                     }
466 
467                     mChooserListAdapter.refreshListView();
468                     break;
469 
470                 case SHORTCUT_MANAGER_SHARE_TARGET_RESULT:
471                     if (DEBUG) Log.d(TAG, "SHORTCUT_MANAGER_SHARE_TARGET_RESULT");
472                     final ServiceResultInfo resultInfo = (ServiceResultInfo) msg.obj;
473                     if (resultInfo.resultTargets != null) {
474                         mChooserListAdapter.addServiceResults(resultInfo.originalTarget,
475                                 resultInfo.resultTargets, true);
476                     }
477                     break;
478 
479                 case SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED:
480                     logDirectShareTargetReceived(
481                             MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER);
482                     sendVoiceChoicesIfNeeded();
483                     break;
484 
485                 default:
486                     super.handleMessage(msg);
487             }
488         }
489     };
490 
491     @Override
onCreate(Bundle savedInstanceState)492     protected void onCreate(Bundle savedInstanceState) {
493         final long intentReceivedTime = System.currentTimeMillis();
494         mIsSuccessfullySelected = false;
495         Intent intent = getIntent();
496         Parcelable targetParcelable = intent.getParcelableExtra(Intent.EXTRA_INTENT);
497         if (!(targetParcelable instanceof Intent)) {
498             Log.w("ChooserActivity", "Target is not an intent: " + targetParcelable);
499             finish();
500             super.onCreate(null);
501             return;
502         }
503         Intent target = (Intent) targetParcelable;
504         if (target != null) {
505             modifyTargetIntent(target);
506         }
507         Parcelable[] targetsParcelable
508                 = intent.getParcelableArrayExtra(Intent.EXTRA_ALTERNATE_INTENTS);
509         if (targetsParcelable != null) {
510             final boolean offset = target == null;
511             Intent[] additionalTargets =
512                     new Intent[offset ? targetsParcelable.length - 1 : targetsParcelable.length];
513             for (int i = 0; i < targetsParcelable.length; i++) {
514                 if (!(targetsParcelable[i] instanceof Intent)) {
515                     Log.w(TAG, "EXTRA_ALTERNATE_INTENTS array entry #" + i + " is not an Intent: "
516                             + targetsParcelable[i]);
517                     finish();
518                     super.onCreate(null);
519                     return;
520                 }
521                 final Intent additionalTarget = (Intent) targetsParcelable[i];
522                 if (i == 0 && target == null) {
523                     target = additionalTarget;
524                     modifyTargetIntent(target);
525                 } else {
526                     additionalTargets[offset ? i - 1 : i] = additionalTarget;
527                     modifyTargetIntent(additionalTarget);
528                 }
529             }
530             setAdditionalTargets(additionalTargets);
531         }
532 
533         mReplacementExtras = intent.getBundleExtra(Intent.EXTRA_REPLACEMENT_EXTRAS);
534 
535         // Do not allow the title to be changed when sharing content
536         CharSequence title = null;
537         if (target != null) {
538             if (!isSendAction(target)) {
539                 title = intent.getCharSequenceExtra(Intent.EXTRA_TITLE);
540             } else {
541                 Log.w(TAG, "Ignoring intent's EXTRA_TITLE, deprecated in P. You may wish to set a"
542                         + " preview title by using EXTRA_TITLE property of the wrapped"
543                         + " EXTRA_INTENT.");
544             }
545         }
546 
547         int defaultTitleRes = 0;
548         if (title == null) {
549             defaultTitleRes = com.android.internal.R.string.chooseActivity;
550         }
551 
552         Parcelable[] pa = intent.getParcelableArrayExtra(Intent.EXTRA_INITIAL_INTENTS);
553         Intent[] initialIntents = null;
554         if (pa != null) {
555             int count = Math.min(pa.length, MAX_EXTRA_INITIAL_INTENTS);
556             initialIntents = new Intent[count];
557             for (int i = 0; i < count; i++) {
558                 if (!(pa[i] instanceof Intent)) {
559                     Log.w(TAG, "Initial intent #" + i + " not an Intent: " + pa[i]);
560                     finish();
561                     super.onCreate(null);
562                     return;
563                 }
564                 final Intent in = (Intent) pa[i];
565                 modifyTargetIntent(in);
566                 initialIntents[i] = in;
567             }
568         }
569 
570         mReferrerFillInIntent = new Intent().putExtra(Intent.EXTRA_REFERRER, getReferrer());
571 
572         mChosenComponentSender = intent.getParcelableExtra(
573                 Intent.EXTRA_CHOSEN_COMPONENT_INTENT_SENDER);
574         mRefinementIntentSender = intent.getParcelableExtra(
575                 Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER);
576         setSafeForwardingMode(true);
577 
578         pa = intent.getParcelableArrayExtra(Intent.EXTRA_EXCLUDE_COMPONENTS);
579         if (pa != null) {
580             ComponentName[] names = new ComponentName[pa.length];
581             for (int i = 0; i < pa.length; i++) {
582                 if (!(pa[i] instanceof ComponentName)) {
583                     Log.w(TAG, "Filtered component #" + i + " not a ComponentName: " + pa[i]);
584                     names = null;
585                     break;
586                 }
587                 names[i] = (ComponentName) pa[i];
588             }
589             mFilteredComponentNames = names;
590         }
591 
592         pa = intent.getParcelableArrayExtra(Intent.EXTRA_CHOOSER_TARGETS);
593         if (pa != null) {
594             int count = Math.min(pa.length, MAX_EXTRA_CHOOSER_TARGETS);
595             ChooserTarget[] targets = new ChooserTarget[count];
596             for (int i = 0; i < count; i++) {
597                 if (!(pa[i] instanceof ChooserTarget)) {
598                     Log.w(TAG, "Chooser target #" + i + " not a ChooserTarget: " + pa[i]);
599                     targets = null;
600                     break;
601                 }
602                 targets[i] = (ChooserTarget) pa[i];
603             }
604             mCallerChooserTargets = targets;
605         }
606 
607         setRetainInOnStop(intent.getBooleanExtra(EXTRA_PRIVATE_RETAIN_IN_ON_STOP, false));
608         super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents,
609                 null, false);
610 
611         mChooserShownTime = System.currentTimeMillis();
612         final long systemCost = mChooserShownTime - intentReceivedTime;
613 
614         getMetricsLogger().write(new LogMaker(MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN)
615                 .setSubtype(isWorkProfile() ? MetricsEvent.MANAGED_PROFILE :
616                         MetricsEvent.PARENT_PROFILE)
617                 .addTaggedData(MetricsEvent.FIELD_SHARESHEET_MIMETYPE, target.getType())
618                 .addTaggedData(MetricsEvent.FIELD_TIME_TO_APP_TARGETS, systemCost));
619 
620         AppPredictor appPredictor = getAppPredictorForDirectShareIfEnabled();
621         if (appPredictor != null) {
622             mDirectShareAppTargetCache = new HashMap<>();
623             mAppPredictorCallback = resultList -> {
624                 if (isFinishing() || isDestroyed()) {
625                     return;
626                 }
627                 // May be null if there are no apps to perform share/open action.
628                 if (mChooserListAdapter == null) {
629                     return;
630                 }
631                 if (resultList.isEmpty()) {
632                     // APS may be disabled, so try querying targets ourselves.
633                     queryDirectShareTargets(mChooserListAdapter, true);
634                     return;
635                 }
636                 final List<DisplayResolveInfo> driList =
637                         getDisplayResolveInfos(mChooserListAdapter);
638                 final List<ShortcutManager.ShareShortcutInfo> shareShortcutInfos =
639                         new ArrayList<>();
640                 for (AppTarget appTarget : resultList) {
641                     if (appTarget.getShortcutInfo() == null) {
642                         continue;
643                     }
644                     shareShortcutInfos.add(new ShortcutManager.ShareShortcutInfo(
645                             appTarget.getShortcutInfo(),
646                             new ComponentName(
647                                 appTarget.getPackageName(), appTarget.getClassName())));
648                 }
649                 sendShareShortcutInfoList(shareShortcutInfos, driList, resultList);
650             };
651             appPredictor
652                 .registerPredictionUpdates(this.getMainExecutor(), mAppPredictorCallback);
653         }
654 
655         mChooserRowServiceSpacing = getResources()
656                                         .getDimensionPixelSize(R.dimen.chooser_service_spacing);
657 
658         if (mResolverDrawerLayout != null) {
659             mResolverDrawerLayout.addOnLayoutChangeListener(this::handleLayoutChange);
660 
661             // expand/shrink direct share 4 -> 8 viewgroup
662             if (isSendAction(target)) {
663                 mResolverDrawerLayout.setOnScrollChangeListener(this::handleScroll);
664             }
665 
666             final View chooserHeader = mResolverDrawerLayout.findViewById(R.id.chooser_header);
667             final float defaultElevation = chooserHeader.getElevation();
668             final float chooserHeaderScrollElevation =
669                     getResources().getDimensionPixelSize(R.dimen.chooser_header_scroll_elevation);
670 
671             mAdapterView.setOnScrollListener(new AbsListView.OnScrollListener() {
672                 public void onScrollStateChanged(AbsListView view, int scrollState) {
673                 }
674 
675                 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
676                         int totalItemCount) {
677                     if (view.getChildCount() > 0) {
678                         if (firstVisibleItem > 0 || view.getChildAt(0).getTop() < 0) {
679                             chooserHeader.setElevation(chooserHeaderScrollElevation);
680                             return;
681                         }
682                     }
683 
684                     chooserHeader.setElevation(defaultElevation);
685                 }
686             });
687 
688             mResolverDrawerLayout.setOnCollapsedChangedListener(
689                     new ResolverDrawerLayout.OnCollapsedChangedListener() {
690 
691                         // Only consider one expansion per activity creation
692                         private boolean mWrittenOnce = false;
693 
694                         @Override
695                         public void onCollapsedChanged(boolean isCollapsed) {
696                             if (!isCollapsed && !mWrittenOnce) {
697                                 incrementNumSheetExpansions();
698                                 mWrittenOnce = true;
699                             }
700                         }
701                     });
702         }
703 
704         if (DEBUG) {
705             Log.d(TAG, "System Time Cost is " + systemCost);
706         }
707     }
708 
709     /**
710      * Check if the profile currently used is a work profile.
711      * @return true if it is work profile, false if it is parent profile (or no work profile is
712      * set up)
713      */
isWorkProfile()714     protected boolean isWorkProfile() {
715         return ((UserManager) getSystemService(Context.USER_SERVICE))
716                 .getUserInfo(UserHandle.myUserId()).isManagedProfile();
717     }
718 
719     @Override
createPackageMonitor()720     protected PackageMonitor createPackageMonitor() {
721         return new PackageMonitor() {
722             @Override
723             public void onSomePackagesChanged() {
724                 mAdapter.handlePackagesChanged();
725                 bindProfileView();
726             }
727         };
728     }
729 
730     private void onCopyButtonClicked(View v) {
731         Intent targetIntent = getTargetIntent();
732         if (targetIntent == null) {
733             finish();
734         } else {
735             final String action = targetIntent.getAction();
736 
737             ClipData clipData = null;
738             if (Intent.ACTION_SEND.equals(action)) {
739                 String extraText = targetIntent.getStringExtra(Intent.EXTRA_TEXT);
740                 Uri extraStream = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
741 
742                 if (extraText != null) {
743                     clipData = ClipData.newPlainText(null, extraText);
744                 } else if (extraStream != null) {
745                     clipData = ClipData.newUri(getContentResolver(), null, extraStream);
746                 } else {
747                     Log.w(TAG, "No data available to copy to clipboard");
748                     return;
749                 }
750             } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
751                 final ArrayList<Uri> streams = targetIntent.getParcelableArrayListExtra(
752                         Intent.EXTRA_STREAM);
753                 clipData = ClipData.newUri(getContentResolver(), null, streams.get(0));
754                 for (int i = 1; i < streams.size(); i++) {
755                     clipData.addItem(getContentResolver(), new ClipData.Item(streams.get(i)));
756                 }
757             } else {
758                 // expected to only be visible with ACTION_SEND or ACTION_SEND_MULTIPLE
759                 // so warn about unexpected action
760                 Log.w(TAG, "Action (" + action + ") not supported for copying to clipboard");
761                 return;
762             }
763 
764             ClipboardManager clipboardManager = (ClipboardManager) getSystemService(
765                     Context.CLIPBOARD_SERVICE);
766             clipboardManager.setPrimaryClip(clipData);
767             Toast.makeText(getApplicationContext(), R.string.copied, Toast.LENGTH_SHORT).show();
768 
769             finish();
770         }
771     }
772 
773     @Override
774     public void onConfigurationChanged(Configuration newConfig) {
775         super.onConfigurationChanged(newConfig);
776 
777         adjustPreviewWidth(newConfig.orientation, null);
778     }
779 
780     private boolean shouldDisplayLandscape(int orientation) {
781         // Sharesheet fixes the # of items per row and therefore can not correctly lay out
782         // when in the restricted size of multi-window mode. In the future, would be nice
783         // to use minimum dp size requirements instead
784         return orientation == Configuration.ORIENTATION_LANDSCAPE && !isInMultiWindowMode();
785     }
786 
787     private void adjustPreviewWidth(int orientation, View parent) {
788         int width = -1;
789         if (shouldDisplayLandscape(orientation)) {
790             width = getResources().getDimensionPixelSize(R.dimen.chooser_preview_width);
791         }
792 
793         parent = parent == null ? getWindow().getDecorView() : parent;
794 
795         updateLayoutWidth(R.id.content_preview_text_layout, width, parent);
796         updateLayoutWidth(R.id.content_preview_title_layout, width, parent);
797         updateLayoutWidth(R.id.content_preview_file_layout, width, parent);
798     }
799 
800     private void updateLayoutWidth(int layoutResourceId, int width, View parent) {
801         View view = parent.findViewById(layoutResourceId);
802         if (view != null && view.getLayoutParams() != null) {
803             LayoutParams params = view.getLayoutParams();
804             params.width = width;
805             view.setLayoutParams(params);
806         }
807     }
808 
809     private ViewGroup displayContentPreview(@ContentPreviewType int previewType,
810             Intent targetIntent, LayoutInflater layoutInflater, ViewGroup convertView,
811             ViewGroup parent) {
812         if (convertView != null) return convertView;
813 
814         ViewGroup layout = null;
815 
816         switch (previewType) {
817             case CONTENT_PREVIEW_TEXT:
818                 layout = displayTextContentPreview(targetIntent, layoutInflater, parent);
819                 break;
820             case CONTENT_PREVIEW_IMAGE:
821                 layout = displayImageContentPreview(targetIntent, layoutInflater, parent);
822                 break;
823             case CONTENT_PREVIEW_FILE:
824                 layout = displayFileContentPreview(targetIntent, layoutInflater, parent);
825                 break;
826             default:
827                 Log.e(TAG, "Unexpected content preview type: " + previewType);
828         }
829 
830         if (layout != null) {
831             adjustPreviewWidth(getResources().getConfiguration().orientation, layout);
832         }
833 
834         return layout;
835     }
836 
837     private ViewGroup displayTextContentPreview(Intent targetIntent, LayoutInflater layoutInflater,
838             ViewGroup parent) {
839         ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
840                 R.layout.chooser_grid_preview_text, parent, false);
841 
842         contentPreviewLayout.findViewById(R.id.copy_button).setOnClickListener(
843                 this::onCopyButtonClicked);
844 
845         CharSequence sharingText = targetIntent.getCharSequenceExtra(Intent.EXTRA_TEXT);
846         if (sharingText == null) {
847             contentPreviewLayout.findViewById(R.id.content_preview_text_layout).setVisibility(
848                     View.GONE);
849         } else {
850             TextView textView = contentPreviewLayout.findViewById(R.id.content_preview_text);
851             textView.setText(sharingText);
852         }
853 
854         String previewTitle = targetIntent.getStringExtra(Intent.EXTRA_TITLE);
855         if (TextUtils.isEmpty(previewTitle)) {
856             contentPreviewLayout.findViewById(R.id.content_preview_title_layout).setVisibility(
857                     View.GONE);
858         } else {
859             TextView previewTitleView = contentPreviewLayout.findViewById(
860                     R.id.content_preview_title);
861             previewTitleView.setText(previewTitle);
862 
863             ClipData previewData = targetIntent.getClipData();
864             Uri previewThumbnail = null;
865             if (previewData != null) {
866                 if (previewData.getItemCount() > 0) {
867                     ClipData.Item previewDataItem = previewData.getItemAt(0);
868                     previewThumbnail = previewDataItem.getUri();
869                 }
870             }
871 
872             ImageView previewThumbnailView = contentPreviewLayout.findViewById(
873                     R.id.content_preview_thumbnail);
874             if (previewThumbnail == null) {
875                 previewThumbnailView.setVisibility(View.GONE);
876             } else {
877                 mPreviewCoord = new ContentPreviewCoordinator(contentPreviewLayout, false);
878                 mPreviewCoord.loadUriIntoView(R.id.content_preview_thumbnail, previewThumbnail, 0);
879             }
880         }
881 
882         return contentPreviewLayout;
883     }
884 
885     private ViewGroup displayImageContentPreview(Intent targetIntent, LayoutInflater layoutInflater,
886             ViewGroup parent) {
887         ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
888                 R.layout.chooser_grid_preview_image, parent, false);
889         mPreviewCoord = new ContentPreviewCoordinator(contentPreviewLayout, true);
890 
891         String action = targetIntent.getAction();
892         if (Intent.ACTION_SEND.equals(action)) {
893             Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
894             mPreviewCoord.loadUriIntoView(R.id.content_preview_image_1_large, uri, 0);
895         } else {
896             ContentResolver resolver = getContentResolver();
897 
898             List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
899             List<Uri> imageUris = new ArrayList<>();
900             for (Uri uri : uris) {
901                 if (isImageType(resolver.getType(uri))) {
902                     imageUris.add(uri);
903                 }
904             }
905 
906             if (imageUris.size() == 0) {
907                 Log.i(TAG, "Attempted to display image preview area with zero"
908                         + " available images detected in EXTRA_STREAM list");
909                 contentPreviewLayout.setVisibility(View.GONE);
910                 return contentPreviewLayout;
911             }
912 
913             mPreviewCoord.loadUriIntoView(R.id.content_preview_image_1_large, imageUris.get(0), 0);
914 
915             if (imageUris.size() == 2) {
916                 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_2_large,
917                         imageUris.get(1), 0);
918             } else if (imageUris.size() > 2) {
919                 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_2_small,
920                         imageUris.get(1), 0);
921                 mPreviewCoord.loadUriIntoView(R.id.content_preview_image_3_small,
922                         imageUris.get(2), imageUris.size() - 3);
923             }
924         }
925 
926         return contentPreviewLayout;
927     }
928 
929     private static class FileInfo {
930         public final String name;
931         public final boolean hasThumbnail;
932 
933         FileInfo(String name, boolean hasThumbnail) {
934             this.name = name;
935             this.hasThumbnail = hasThumbnail;
936         }
937     }
938 
939     /**
940      * Wrapping the ContentResolver call to expose for easier mocking,
941      * and to avoid mocking Android core classes.
942      */
943     @VisibleForTesting
944     public Cursor queryResolver(ContentResolver resolver, Uri uri) {
945         return resolver.query(uri, null, null, null, null);
946     }
947 
948     private FileInfo extractFileInfo(Uri uri, ContentResolver resolver) {
949         String fileName = null;
950         boolean hasThumbnail = false;
951 
952         try (Cursor cursor = queryResolver(resolver, uri)) {
953             if (cursor != null && cursor.getCount() > 0) {
954                 int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
955                 int titleIndex = cursor.getColumnIndex(Downloads.Impl.COLUMN_TITLE);
956                 int flagsIndex = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_FLAGS);
957 
958                 cursor.moveToFirst();
959                 if (nameIndex != -1) {
960                     fileName = cursor.getString(nameIndex);
961                 } else if (titleIndex != -1) {
962                     fileName = cursor.getString(titleIndex);
963                 }
964 
965                 if (flagsIndex != -1) {
966                     hasThumbnail = (cursor.getInt(flagsIndex)
967                             & DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL) != 0;
968                 }
969             }
970         } catch (SecurityException | NullPointerException e) {
971             logContentPreviewWarning(uri);
972         }
973 
974         if (TextUtils.isEmpty(fileName)) {
975             fileName = uri.getPath();
976             int index = fileName.lastIndexOf('/');
977             if (index != -1) {
978                 fileName = fileName.substring(index + 1);
979             }
980         }
981 
982         return new FileInfo(fileName, hasThumbnail);
983     }
984 
985     private void logContentPreviewWarning(Uri uri) {
986         // The ContentResolver already logs the exception. Log something more informative.
987         Log.w(TAG, "Could not load (" + uri.toString() + ") thumbnail/name for preview. If "
988                 + "desired, consider using Intent#createChooser to launch the ChooserActivity, "
989                 + "and set your Intent's clipData and flags in accordance with that method's "
990                 + "documentation");
991     }
992 
993     private ViewGroup displayFileContentPreview(Intent targetIntent, LayoutInflater layoutInflater,
994             ViewGroup parent) {
995 
996         ViewGroup contentPreviewLayout = (ViewGroup) layoutInflater.inflate(
997                 R.layout.chooser_grid_preview_file, parent, false);
998 
999         // TODO(b/120417119): Disable file copy until after moving to sysui,
1000         // due to permissions issues
1001         contentPreviewLayout.findViewById(R.id.file_copy_button).setVisibility(View.GONE);
1002 
1003         String action = targetIntent.getAction();
1004         if (Intent.ACTION_SEND.equals(action)) {
1005             Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
1006             loadFileUriIntoView(uri, contentPreviewLayout);
1007         } else {
1008             List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
1009             int uriCount = uris.size();
1010 
1011             if (uriCount == 0) {
1012                 contentPreviewLayout.setVisibility(View.GONE);
1013                 Log.i(TAG,
1014                         "Appears to be no uris available in EXTRA_STREAM, removing "
1015                                 + "preview area");
1016                 return contentPreviewLayout;
1017             } else if (uriCount == 1) {
1018                 loadFileUriIntoView(uris.get(0), contentPreviewLayout);
1019             } else {
1020                 FileInfo fileInfo = extractFileInfo(uris.get(0), getContentResolver());
1021                 int remUriCount = uriCount - 1;
1022                 String fileName = getResources().getQuantityString(R.plurals.file_count,
1023                         remUriCount, fileInfo.name, remUriCount);
1024 
1025                 TextView fileNameView = contentPreviewLayout.findViewById(
1026                         R.id.content_preview_filename);
1027                 fileNameView.setText(fileName);
1028 
1029                 View thumbnailView = contentPreviewLayout.findViewById(
1030                         R.id.content_preview_file_thumbnail);
1031                 thumbnailView.setVisibility(View.GONE);
1032 
1033                 ImageView fileIconView = contentPreviewLayout.findViewById(
1034                         R.id.content_preview_file_icon);
1035                 fileIconView.setVisibility(View.VISIBLE);
1036                 fileIconView.setImageResource(R.drawable.ic_file_copy);
1037             }
1038         }
1039 
1040         return contentPreviewLayout;
1041     }
1042 
1043     private void loadFileUriIntoView(final Uri uri, final View parent) {
1044         FileInfo fileInfo = extractFileInfo(uri, getContentResolver());
1045 
1046         TextView fileNameView = parent.findViewById(R.id.content_preview_filename);
1047         fileNameView.setText(fileInfo.name);
1048 
1049         if (fileInfo.hasThumbnail) {
1050             mPreviewCoord = new ContentPreviewCoordinator(parent, false);
1051             mPreviewCoord.loadUriIntoView(R.id.content_preview_file_thumbnail, uri, 0);
1052         } else {
1053             View thumbnailView = parent.findViewById(R.id.content_preview_file_thumbnail);
1054             thumbnailView.setVisibility(View.GONE);
1055 
1056             ImageView fileIconView = parent.findViewById(R.id.content_preview_file_icon);
1057             fileIconView.setVisibility(View.VISIBLE);
1058             fileIconView.setImageResource(R.drawable.chooser_file_generic);
1059         }
1060     }
1061 
1062     @VisibleForTesting
1063     protected boolean isImageType(String mimeType) {
1064         return mimeType != null && mimeType.startsWith("image/");
1065     }
1066 
1067     @ContentPreviewType
1068     private int findPreferredContentPreview(Uri uri, ContentResolver resolver) {
1069         if (uri == null) {
1070             return CONTENT_PREVIEW_TEXT;
1071         }
1072 
1073         String mimeType = resolver.getType(uri);
1074         return isImageType(mimeType) ? CONTENT_PREVIEW_IMAGE : CONTENT_PREVIEW_FILE;
1075     }
1076 
1077     /**
1078      * In {@link android.content.Intent#getType}, the app may specify a very general
1079      * mime-type that broadly covers all data being shared, such as {@literal *}/*
1080      * when sending an image and text. We therefore should inspect each item for the
1081      * the preferred type, in order of IMAGE, FILE, TEXT.
1082      */
1083     @ContentPreviewType
1084     private int findPreferredContentPreview(Intent targetIntent, ContentResolver resolver) {
1085         String action = targetIntent.getAction();
1086         if (Intent.ACTION_SEND.equals(action)) {
1087             Uri uri = targetIntent.getParcelableExtra(Intent.EXTRA_STREAM);
1088             return findPreferredContentPreview(uri, resolver);
1089         } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
1090             List<Uri> uris = targetIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
1091             if (uris == null || uris.isEmpty()) {
1092                 return CONTENT_PREVIEW_TEXT;
1093             }
1094 
1095             for (Uri uri : uris) {
1096                 // Defaulting to file preview when there are mixed image/file types is
1097                 // preferable, as it shows the user the correct number of items being shared
1098                 if (findPreferredContentPreview(uri, resolver) == CONTENT_PREVIEW_FILE) {
1099                     return CONTENT_PREVIEW_FILE;
1100                 }
1101             }
1102 
1103             return CONTENT_PREVIEW_IMAGE;
1104         }
1105 
1106         return CONTENT_PREVIEW_TEXT;
1107     }
1108 
1109     private int getNumSheetExpansions() {
1110         return getPreferences(Context.MODE_PRIVATE).getInt(PREF_NUM_SHEET_EXPANSIONS, 0);
1111     }
1112 
1113     private void incrementNumSheetExpansions() {
1114         getPreferences(Context.MODE_PRIVATE).edit().putInt(PREF_NUM_SHEET_EXPANSIONS,
1115                 getNumSheetExpansions() + 1).apply();
1116     }
1117 
1118     @Override
1119     protected void onDestroy() {
1120         super.onDestroy();
1121         if (mRefinementResultReceiver != null) {
1122             mRefinementResultReceiver.destroy();
1123             mRefinementResultReceiver = null;
1124         }
1125         unbindRemainingServices();
1126         mChooserHandler.removeAllMessages();
1127 
1128         if (mPreviewCoord != null) mPreviewCoord.cancelLoads();
1129 
1130         if (mAppPredictor != null) {
1131             mAppPredictor.unregisterPredictionUpdates(mAppPredictorCallback);
1132             mAppPredictor.destroy();
1133         }
1134     }
1135 
1136     @Override
1137     public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) {
1138         Intent result = defIntent;
1139         if (mReplacementExtras != null) {
1140             final Bundle replExtras = mReplacementExtras.getBundle(aInfo.packageName);
1141             if (replExtras != null) {
1142                 result = new Intent(defIntent);
1143                 result.putExtras(replExtras);
1144             }
1145         }
1146         if (aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_PARENT)
1147                 || aInfo.name.equals(IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE)) {
1148             result = Intent.createChooser(result,
1149                     getIntent().getCharSequenceExtra(Intent.EXTRA_TITLE));
1150 
1151             // Don't auto-launch single intents if the intent is being forwarded. This is done
1152             // because automatically launching a resolving application as a response to the user
1153             // action of switching accounts is pretty unexpected.
1154             result.putExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, false);
1155         }
1156         return result;
1157     }
1158 
1159     @Override
1160     public void onActivityStarted(TargetInfo cti) {
1161         if (mChosenComponentSender != null) {
1162             final ComponentName target = cti.getResolvedComponentName();
1163             if (target != null) {
1164                 final Intent fillIn = new Intent().putExtra(Intent.EXTRA_CHOSEN_COMPONENT, target);
1165                 try {
1166                     mChosenComponentSender.sendIntent(this, Activity.RESULT_OK, fillIn, null, null);
1167                 } catch (IntentSender.SendIntentException e) {
1168                     Slog.e(TAG, "Unable to launch supplied IntentSender to report "
1169                             + "the chosen component: " + e);
1170                 }
1171             }
1172         }
1173     }
1174 
1175     @Override
1176     public void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter) {
1177         final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null;
1178         mChooserListAdapter = (ChooserListAdapter) adapter;
1179         if (mCallerChooserTargets != null && mCallerChooserTargets.length > 0) {
1180             mChooserListAdapter.addServiceResults(null, Lists.newArrayList(mCallerChooserTargets),
1181                     false);
1182         }
1183         mChooserRowAdapter = new ChooserRowAdapter(mChooserListAdapter);
1184         if (listView != null) {
1185             listView.setItemsCanFocus(true);
1186         }
1187     }
1188 
1189     @Override
1190     public int getLayoutResource() {
1191         return R.layout.chooser_grid;
1192     }
1193 
1194     @Override
1195     public boolean shouldGetActivityMetadata() {
1196         return true;
1197     }
1198 
1199     @Override
1200     public boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
1201         // Note that this is only safe because the Intent handled by the ChooserActivity is
1202         // guaranteed to contain no extras unknown to the local ClassLoader. That is why this
1203         // method can not be replaced in the ResolverActivity whole hog.
1204         if (!super.shouldAutoLaunchSingleChoice(target)) {
1205             return false;
1206         }
1207 
1208         return getIntent().getBooleanExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE, true);
1209     }
1210 
1211     @Override
1212     public void showTargetDetails(ResolveInfo ri) {
1213         if (ri == null) {
1214             return;
1215         }
1216 
1217         ComponentName name = ri.activityInfo.getComponentName();
1218         ResolverTargetActionsDialogFragment f =
1219                 new ResolverTargetActionsDialogFragment(ri.loadLabel(getPackageManager()),
1220                         name);
1221         f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
1222     }
1223 
1224     private void modifyTargetIntent(Intent in) {
1225         if (isSendAction(in)) {
1226             in.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT |
1227                     Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
1228         }
1229     }
1230 
1231     @Override
1232     protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) {
1233         if (mRefinementIntentSender != null) {
1234             final Intent fillIn = new Intent();
1235             final List<Intent> sourceIntents = target.getAllSourceIntents();
1236             if (!sourceIntents.isEmpty()) {
1237                 fillIn.putExtra(Intent.EXTRA_INTENT, sourceIntents.get(0));
1238                 if (sourceIntents.size() > 1) {
1239                     final Intent[] alts = new Intent[sourceIntents.size() - 1];
1240                     for (int i = 1, N = sourceIntents.size(); i < N; i++) {
1241                         alts[i - 1] = sourceIntents.get(i);
1242                     }
1243                     fillIn.putExtra(Intent.EXTRA_ALTERNATE_INTENTS, alts);
1244                 }
1245                 if (mRefinementResultReceiver != null) {
1246                     mRefinementResultReceiver.destroy();
1247                 }
1248                 mRefinementResultReceiver = new RefinementResultReceiver(this, target, null);
1249                 fillIn.putExtra(Intent.EXTRA_RESULT_RECEIVER,
1250                         mRefinementResultReceiver);
1251                 try {
1252                     mRefinementIntentSender.sendIntent(this, 0, fillIn, null, null);
1253                     return false;
1254                 } catch (SendIntentException e) {
1255                     Log.e(TAG, "Refinement IntentSender failed to send", e);
1256                 }
1257             }
1258         }
1259         updateModelAndChooserCounts(target);
1260         return super.onTargetSelected(target, alwaysCheck);
1261     }
1262 
1263     @Override
1264     public void startSelected(int which, boolean always, boolean filtered) {
1265         TargetInfo targetInfo = mChooserListAdapter.targetInfoForPosition(which, filtered);
1266         if (targetInfo != null && targetInfo instanceof NotSelectableTargetInfo) {
1267             return;
1268         }
1269 
1270         final long selectionCost = System.currentTimeMillis() - mChooserShownTime;
1271         super.startSelected(which, always, filtered);
1272 
1273         if (mChooserListAdapter != null) {
1274             // Log the index of which type of target the user picked.
1275             // Lower values mean the ranking was better.
1276             int cat = 0;
1277             int value = which;
1278             int directTargetAlsoRanked = -1;
1279             int numCallerProvided = 0;
1280             HashedStringCache.HashResult directTargetHashed = null;
1281             switch (mChooserListAdapter.getPositionTargetType(which)) {
1282                 case ChooserListAdapter.TARGET_SERVICE:
1283                     cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET;
1284                     // Log the package name + target name to answer the question if most users
1285                     // share to mostly the same person or to a bunch of different people.
1286                     ChooserTarget target =
1287                             mChooserListAdapter.mServiceTargets.get(value).getChooserTarget();
1288                     directTargetHashed = HashedStringCache.getInstance().hashString(
1289                             this,
1290                             TAG,
1291                             target.getComponentName().getPackageName()
1292                                     + target.getTitle().toString(),
1293                             mMaxHashSaltDays);
1294                     directTargetAlsoRanked = getRankedPosition((SelectableTargetInfo) targetInfo);
1295 
1296                     if (mCallerChooserTargets != null) {
1297                         numCallerProvided = mCallerChooserTargets.length;
1298                     }
1299                     break;
1300                 case ChooserListAdapter.TARGET_CALLER:
1301                 case ChooserListAdapter.TARGET_STANDARD:
1302                     cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET;
1303                     value -= mChooserListAdapter.getSelectableServiceTargetCount();
1304                     numCallerProvided = mChooserListAdapter.getCallerTargetCount();
1305                     break;
1306                 case ChooserListAdapter.TARGET_STANDARD_AZ:
1307                     // A-Z targets are unranked standard targets; we use -1 to mark that they
1308                     // are from the alphabetical pool.
1309                     value = -1;
1310                     cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_STANDARD_TARGET;
1311                     break;
1312             }
1313 
1314             if (cat != 0) {
1315                 LogMaker targetLogMaker = new LogMaker(cat).setSubtype(value);
1316                 if (directTargetHashed != null) {
1317                     targetLogMaker.addTaggedData(
1318                             MetricsEvent.FIELD_HASHED_TARGET_NAME, directTargetHashed.hashedString);
1319                     targetLogMaker.addTaggedData(
1320                                     MetricsEvent.FIELD_HASHED_TARGET_SALT_GEN,
1321                                     directTargetHashed.saltGeneration);
1322                     targetLogMaker.addTaggedData(MetricsEvent.FIELD_RANKED_POSITION,
1323                                     directTargetAlsoRanked);
1324                 }
1325                 targetLogMaker.addTaggedData(MetricsEvent.FIELD_IS_CATEGORY_USED,
1326                         numCallerProvided);
1327                 getMetricsLogger().write(targetLogMaker);
1328             }
1329 
1330             if (mIsSuccessfullySelected) {
1331                 if (DEBUG) {
1332                     Log.d(TAG, "User Selection Time Cost is " + selectionCost);
1333                     Log.d(TAG, "position of selected app/service/caller is " +
1334                             Integer.toString(value));
1335                 }
1336                 MetricsLogger.histogram(null, "user_selection_cost_for_smart_sharing",
1337                         (int) selectionCost);
1338                 MetricsLogger.histogram(null, "app_position_for_smart_sharing", value);
1339             }
1340         }
1341     }
1342 
1343     private int getRankedPosition(SelectableTargetInfo targetInfo) {
1344         String targetPackageName =
1345                 targetInfo.getChooserTarget().getComponentName().getPackageName();
1346         int maxRankedResults = Math.min(mChooserListAdapter.mDisplayList.size(),
1347                         MAX_LOG_RANK_POSITION);
1348 
1349         for (int i = 0; i < maxRankedResults; i++) {
1350             if (mChooserListAdapter.mDisplayList.get(i)
1351                     .getResolveInfo().activityInfo.packageName.equals(targetPackageName)) {
1352                 return i;
1353             }
1354         }
1355         return -1;
1356     }
1357 
1358     void queryTargetServices(ChooserListAdapter adapter) {
1359         mQueriedTargetServicesTimeMs = System.currentTimeMillis();
1360 
1361         final PackageManager pm = getPackageManager();
1362         ShortcutManager sm = (ShortcutManager) getSystemService(ShortcutManager.class);
1363         int targetsToQuery = 0;
1364 
1365         for (int i = 0, N = adapter.getDisplayResolveInfoCount(); i < N; i++) {
1366             final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
1367             if (adapter.getScore(dri) == 0) {
1368                 // A score of 0 means the app hasn't been used in some time;
1369                 // don't query it as it's not likely to be relevant.
1370                 continue;
1371             }
1372             final ActivityInfo ai = dri.getResolveInfo().activityInfo;
1373             if (USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS
1374                     && sm.hasShareTargets(ai.packageName)) {
1375                 // Share targets will be queried from ShortcutManager
1376                 continue;
1377             }
1378             final Bundle md = ai.metaData;
1379             final String serviceName = md != null ? convertServiceName(ai.packageName,
1380                     md.getString(ChooserTargetService.META_DATA_NAME)) : null;
1381             if (serviceName != null) {
1382                 final ComponentName serviceComponent = new ComponentName(
1383                         ai.packageName, serviceName);
1384 
1385                 if (mServicesRequested.contains(serviceComponent)) {
1386                     continue;
1387                 }
1388                 mServicesRequested.add(serviceComponent);
1389 
1390                 final Intent serviceIntent = new Intent(ChooserTargetService.SERVICE_INTERFACE)
1391                         .setComponent(serviceComponent);
1392 
1393                 if (DEBUG) {
1394                     Log.d(TAG, "queryTargets found target with service " + serviceComponent);
1395                 }
1396 
1397                 try {
1398                     final String perm = pm.getServiceInfo(serviceComponent, 0).permission;
1399                     if (!ChooserTargetService.BIND_PERMISSION.equals(perm)) {
1400                         Log.w(TAG, "ChooserTargetService " + serviceComponent + " does not require"
1401                                 + " permission " + ChooserTargetService.BIND_PERMISSION
1402                                 + " - this service will not be queried for ChooserTargets."
1403                                 + " add android:permission=\""
1404                                 + ChooserTargetService.BIND_PERMISSION + "\""
1405                                 + " to the <service> tag for " + serviceComponent
1406                                 + " in the manifest.");
1407                         continue;
1408                     }
1409                 } catch (NameNotFoundException e) {
1410                     Log.e(TAG, "Could not look up service " + serviceComponent
1411                             + "; component name not found");
1412                     continue;
1413                 }
1414 
1415                 final ChooserTargetServiceConnection conn =
1416                         new ChooserTargetServiceConnection(this, dri);
1417 
1418                 // Explicitly specify Process.myUserHandle instead of calling bindService
1419                 // to avoid the warning from calling from the system process without an explicit
1420                 // user handle
1421                 if (bindServiceAsUser(serviceIntent, conn, BIND_AUTO_CREATE | BIND_NOT_FOREGROUND,
1422                         Process.myUserHandle())) {
1423                     if (DEBUG) {
1424                         Log.d(TAG, "Binding service connection for target " + dri
1425                                 + " intent " + serviceIntent);
1426                     }
1427                     mServiceConnections.add(conn);
1428                     targetsToQuery++;
1429                 }
1430             }
1431             if (targetsToQuery >= QUERY_TARGET_SERVICE_LIMIT) {
1432                 if (DEBUG) {
1433                     Log.d(TAG, "queryTargets hit query target limit "
1434                             + QUERY_TARGET_SERVICE_LIMIT);
1435                 }
1436                 break;
1437             }
1438         }
1439 
1440         mChooserHandler.restartServiceRequestTimer();
1441     }
1442 
1443     private IntentFilter getTargetIntentFilter() {
1444         try {
1445             final Intent intent = getTargetIntent();
1446             String dataString = intent.getDataString();
1447             if (TextUtils.isEmpty(dataString)) {
1448                 dataString = intent.getType();
1449             }
1450             return new IntentFilter(intent.getAction(), dataString);
1451         } catch (Exception e) {
1452             Log.e(TAG, "failed to get target intent filter " + e);
1453             return null;
1454         }
1455     }
1456 
1457     private List<DisplayResolveInfo> getDisplayResolveInfos(ChooserListAdapter adapter) {
1458         // Need to keep the original DisplayResolveInfos to be able to reconstruct ServiceResultInfo
1459         // and use the old code path. This Ugliness should go away when Sharesheet is refactored.
1460         List<DisplayResolveInfo> driList = new ArrayList<>();
1461         int targetsToQuery = 0;
1462         for (int i = 0, n = adapter.getDisplayResolveInfoCount(); i < n; i++) {
1463             final DisplayResolveInfo dri = adapter.getDisplayResolveInfo(i);
1464             if (adapter.getScore(dri) == 0) {
1465                 // A score of 0 means the app hasn't been used in some time;
1466                 // don't query it as it's not likely to be relevant.
1467                 continue;
1468             }
1469             driList.add(dri);
1470             targetsToQuery++;
1471             // TODO(b/121287224): Do we need this here? (similar to queryTargetServices)
1472             if (targetsToQuery >= SHARE_TARGET_QUERY_PACKAGE_LIMIT) {
1473                 if (DEBUG) {
1474                     Log.d(TAG, "queryTargets hit query target limit "
1475                             + SHARE_TARGET_QUERY_PACKAGE_LIMIT);
1476                 }
1477                 break;
1478             }
1479         }
1480         return driList;
1481     }
1482 
1483     private void queryDirectShareTargets(
1484                 ChooserListAdapter adapter, boolean skipAppPredictionService) {
1485         mQueriedSharingShortcutsTimeMs = System.currentTimeMillis();
1486         if (!skipAppPredictionService) {
1487             AppPredictor appPredictor = getAppPredictorForDirectShareIfEnabled();
1488             if (appPredictor != null) {
1489                 appPredictor.requestPredictionUpdate();
1490                 return;
1491             }
1492         }
1493         // Default to just querying ShortcutManager if AppPredictor not present.
1494         final IntentFilter filter = getTargetIntentFilter();
1495         if (filter == null) {
1496             return;
1497         }
1498         final List<DisplayResolveInfo> driList = getDisplayResolveInfos(adapter);
1499 
1500         AsyncTask.execute(() -> {
1501             ShortcutManager sm = (ShortcutManager) getSystemService(Context.SHORTCUT_SERVICE);
1502             List<ShortcutManager.ShareShortcutInfo> resultList = sm.getShareTargets(filter);
1503             sendShareShortcutInfoList(resultList, driList, null);
1504         });
1505     }
1506 
1507     private void sendShareShortcutInfoList(
1508                 List<ShortcutManager.ShareShortcutInfo> resultList,
1509                 List<DisplayResolveInfo> driList,
1510                 @Nullable List<AppTarget> appTargets) {
1511         if (appTargets != null && appTargets.size() != resultList.size()) {
1512             throw new RuntimeException("resultList and appTargets must have the same size."
1513                     + " resultList.size()=" + resultList.size()
1514                     + " appTargets.size()=" + appTargets.size());
1515         }
1516 
1517         for (int i = resultList.size() - 1; i >= 0; i--) {
1518             final String packageName = resultList.get(i).getTargetComponent().getPackageName();
1519             if (!isPackageEnabled(packageName)) {
1520                 resultList.remove(i);
1521                 if (appTargets != null) {
1522                     appTargets.remove(i);
1523                 }
1524             }
1525         }
1526 
1527         // Match ShareShortcutInfos with DisplayResolveInfos to be able to use the old code path
1528         // for direct share targets. After ShareSheet is refactored we should use the
1529         // ShareShortcutInfos directly.
1530         boolean resultMessageSent = false;
1531         for (int i = 0; i < driList.size(); i++) {
1532             List<ChooserTarget> chooserTargets = new ArrayList<>();
1533             for (int j = 0; j < resultList.size(); j++) {
1534                 if (driList.get(i).getResolvedComponentName().equals(
1535                             resultList.get(j).getTargetComponent())) {
1536                     ShortcutManager.ShareShortcutInfo shareShortcutInfo = resultList.get(j);
1537                     // Incoming results are ordered but without a score. Create a score
1538                     // based on the index in order to be sorted appropriately when joined
1539                     // with legacy direct share api results.
1540                     float score = Math.max(1.0f - (0.05f * j), 0.0f);
1541                     ChooserTarget chooserTarget = convertToChooserTarget(shareShortcutInfo, score);
1542                     chooserTargets.add(chooserTarget);
1543                     if (mDirectShareAppTargetCache != null && appTargets != null) {
1544                         mDirectShareAppTargetCache.put(chooserTarget, appTargets.get(j));
1545                     }
1546                 }
1547             }
1548             if (chooserTargets.isEmpty()) {
1549                 continue;
1550             }
1551             final Message msg = Message.obtain();
1552             msg.what = ChooserHandler.SHORTCUT_MANAGER_SHARE_TARGET_RESULT;
1553             msg.obj = new ServiceResultInfo(driList.get(i), chooserTargets, null);
1554             mChooserHandler.sendMessage(msg);
1555             resultMessageSent = true;
1556         }
1557 
1558         if (resultMessageSent) {
1559             sendShortcutManagerShareTargetResultCompleted();
1560         }
1561     }
1562 
1563     private void sendShortcutManagerShareTargetResultCompleted() {
1564         final Message msg = Message.obtain();
1565         msg.what = ChooserHandler.SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED;
1566         mChooserHandler.sendMessage(msg);
1567     }
1568 
1569     private boolean isPackageEnabled(String packageName) {
1570         if (TextUtils.isEmpty(packageName)) {
1571             return false;
1572         }
1573         ApplicationInfo appInfo;
1574         try {
1575             appInfo = getPackageManager().getApplicationInfo(packageName, 0);
1576         } catch (NameNotFoundException e) {
1577             return false;
1578         }
1579 
1580         if (appInfo != null && appInfo.enabled
1581                 && (appInfo.flags & ApplicationInfo.FLAG_SUSPENDED) == 0) {
1582             return true;
1583         }
1584         return false;
1585     }
1586 
1587     private ChooserTarget convertToChooserTarget(ShortcutManager.ShareShortcutInfo shareShortcut,
1588                                                  float score) {
1589         ShortcutInfo shortcutInfo = shareShortcut.getShortcutInfo();
1590         Bundle extras = new Bundle();
1591         extras.putString(Intent.EXTRA_SHORTCUT_ID, shortcutInfo.getId());
1592         return new ChooserTarget(
1593                 // The name of this target.
1594                 shortcutInfo.getShortLabel(),
1595                 // Don't load the icon until it is selected to be shown
1596                 null,
1597                 // The ranking score for this target (0.0-1.0); the system will omit items with low
1598                 // scores when there are too many Direct Share items.
1599                 score,
1600                 // The name of the component to be launched if this target is chosen.
1601                 shareShortcut.getTargetComponent().clone(),
1602                 // The extra values here will be merged into the Intent when this target is chosen.
1603                 extras);
1604     }
1605 
1606     private String convertServiceName(String packageName, String serviceName) {
1607         if (TextUtils.isEmpty(serviceName)) {
1608             return null;
1609         }
1610 
1611         final String fullName;
1612         if (serviceName.startsWith(".")) {
1613             // Relative to the app package. Prepend the app package name.
1614             fullName = packageName + serviceName;
1615         } else if (serviceName.indexOf('.') >= 0) {
1616             // Fully qualified package name.
1617             fullName = serviceName;
1618         } else {
1619             fullName = null;
1620         }
1621         return fullName;
1622     }
1623 
1624     void unbindRemainingServices() {
1625         if (DEBUG) {
1626             Log.d(TAG, "unbindRemainingServices, " + mServiceConnections.size() + " left");
1627         }
1628         for (int i = 0, N = mServiceConnections.size(); i < N; i++) {
1629             final ChooserTargetServiceConnection conn = mServiceConnections.get(i);
1630             if (DEBUG) Log.d(TAG, "unbinding " + conn);
1631             unbindService(conn);
1632             conn.destroy();
1633         }
1634         mServicesRequested.clear();
1635         mServiceConnections.clear();
1636     }
1637 
1638     public void onSetupVoiceInteraction() {
1639         // Do nothing. We'll send the voice stuff ourselves.
1640     }
1641 
1642     private void logDirectShareTargetReceived(int logCategory) {
1643         final long queryTime =
1644                 logCategory == MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER
1645                         ? mQueriedSharingShortcutsTimeMs : mQueriedTargetServicesTimeMs;
1646         final int apiLatency = (int) (System.currentTimeMillis() - queryTime);
1647         getMetricsLogger().write(new LogMaker(logCategory).setSubtype(apiLatency));
1648     }
1649 
1650     void updateModelAndChooserCounts(TargetInfo info) {
1651         if (info != null) {
1652             sendClickToAppPredictor(info);
1653             final ResolveInfo ri = info.getResolveInfo();
1654             Intent targetIntent = getTargetIntent();
1655             if (ri != null && ri.activityInfo != null && targetIntent != null) {
1656                 if (mAdapter != null) {
1657                     mAdapter.updateModel(info.getResolvedComponentName());
1658                     mAdapter.updateChooserCounts(ri.activityInfo.packageName, getUserId(),
1659                             targetIntent.getAction());
1660                 }
1661                 if (DEBUG) {
1662                     Log.d(TAG, "ResolveInfo Package is " + ri.activityInfo.packageName);
1663                     Log.d(TAG, "Action to be updated is " + targetIntent.getAction());
1664                 }
1665             } else if (DEBUG) {
1666                 Log.d(TAG, "Can not log Chooser Counts of null ResovleInfo");
1667             }
1668         }
1669         mIsSuccessfullySelected = true;
1670     }
1671 
1672     private void sendClickToAppPredictor(TargetInfo targetInfo) {
1673         AppPredictor directShareAppPredictor = getAppPredictorForDirectShareIfEnabled();
1674         if (directShareAppPredictor == null) {
1675             return;
1676         }
1677         if (!(targetInfo instanceof ChooserTargetInfo)) {
1678             return;
1679         }
1680         ChooserTarget chooserTarget = ((ChooserTargetInfo) targetInfo).getChooserTarget();
1681         AppTarget appTarget = null;
1682         if (mDirectShareAppTargetCache != null) {
1683             appTarget = mDirectShareAppTargetCache.get(chooserTarget);
1684         }
1685         // This is a direct share click that was provided by the APS
1686         if (appTarget != null) {
1687             directShareAppPredictor.notifyAppTargetEvent(
1688                     new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_LAUNCH)
1689                         .setLaunchLocation(LAUNCH_LOCATON_DIRECT_SHARE)
1690                         .build());
1691         }
1692     }
1693 
1694     @Nullable
1695     private AppPredictor getAppPredictor() {
1696         if (mAppPredictor == null
1697                     && getPackageManager().getAppPredictionServicePackageName() != null) {
1698             final IntentFilter filter = getTargetIntentFilter();
1699             Bundle extras = new Bundle();
1700             extras.putParcelable(APP_PREDICTION_INTENT_FILTER_KEY, filter);
1701             AppPredictionContext appPredictionContext = new AppPredictionContext.Builder(this)
1702                 .setUiSurface(APP_PREDICTION_SHARE_UI_SURFACE)
1703                 .setPredictedTargetCount(APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT)
1704                 .setExtras(extras)
1705                 .build();
1706             AppPredictionManager appPredictionManager
1707                     = getSystemService(AppPredictionManager.class);
1708             mAppPredictor = appPredictionManager.createAppPredictionSession(appPredictionContext);
1709         }
1710         return mAppPredictor;
1711     }
1712 
1713     /**
1714      * This will return an app predictor if it is enabled for direct share sorting
1715      * and if one exists. Otherwise, it returns null.
1716      */
1717     @Nullable
1718     private AppPredictor getAppPredictorForDirectShareIfEnabled() {
1719         return USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS && !ActivityManager.isLowRamDeviceStatic()
1720                 ? getAppPredictor() : null;
1721     }
1722 
1723     /**
1724      * This will return an app predictor if it is enabled for share activity sorting
1725      * and if one exists. Otherwise, it returns null.
1726      */
1727     @Nullable
1728     private AppPredictor getAppPredictorForShareActivitesIfEnabled() {
1729         return USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES ? getAppPredictor() : null;
1730     }
1731 
1732     void onRefinementResult(TargetInfo selectedTarget, Intent matchingIntent) {
1733         if (mRefinementResultReceiver != null) {
1734             mRefinementResultReceiver.destroy();
1735             mRefinementResultReceiver = null;
1736         }
1737         if (selectedTarget == null) {
1738             Log.e(TAG, "Refinement result intent did not match any known targets; canceling");
1739         } else if (!checkTargetSourceIntent(selectedTarget, matchingIntent)) {
1740             Log.e(TAG, "onRefinementResult: Selected target " + selectedTarget
1741                     + " cannot match refined source intent " + matchingIntent);
1742         } else {
1743             TargetInfo clonedTarget = selectedTarget.cloneFilledIn(matchingIntent, 0);
1744             if (super.onTargetSelected(clonedTarget, false)) {
1745                 updateModelAndChooserCounts(clonedTarget);
1746                 finish();
1747                 return;
1748             }
1749         }
1750         onRefinementCanceled();
1751     }
1752 
1753     void onRefinementCanceled() {
1754         if (mRefinementResultReceiver != null) {
1755             mRefinementResultReceiver.destroy();
1756             mRefinementResultReceiver = null;
1757         }
1758         finish();
1759     }
1760 
1761     boolean checkTargetSourceIntent(TargetInfo target, Intent matchingIntent) {
1762         final List<Intent> targetIntents = target.getAllSourceIntents();
1763         for (int i = 0, N = targetIntents.size(); i < N; i++) {
1764             final Intent targetIntent = targetIntents.get(i);
1765             if (targetIntent.filterEquals(matchingIntent)) {
1766                 return true;
1767             }
1768         }
1769         return false;
1770     }
1771 
1772     void filterServiceTargets(String packageName, List<ChooserTarget> targets) {
1773         if (targets == null) {
1774             return;
1775         }
1776 
1777         final PackageManager pm = getPackageManager();
1778         for (int i = targets.size() - 1; i >= 0; i--) {
1779             final ChooserTarget target = targets.get(i);
1780             final ComponentName targetName = target.getComponentName();
1781             if (packageName != null && packageName.equals(targetName.getPackageName())) {
1782                 // Anything from the original target's package is fine.
1783                 continue;
1784             }
1785 
1786             boolean remove;
1787             try {
1788                 final ActivityInfo ai = pm.getActivityInfo(targetName, 0);
1789                 remove = !ai.exported || ai.permission != null;
1790             } catch (NameNotFoundException e) {
1791                 Log.e(TAG, "Target " + target + " returned by " + packageName
1792                         + " component not found");
1793                 remove = true;
1794             }
1795 
1796             if (remove) {
1797                 targets.remove(i);
1798             }
1799         }
1800     }
1801 
1802     private void updateAlphabeticalList() {
1803         mSortedList.clear();
1804         mSortedList.addAll(getDisplayList());
1805         Collections.sort(mSortedList, new AzInfoComparator(ChooserActivity.this));
1806     }
1807 
1808     /**
1809      * Sort intents alphabetically based on display label.
1810      */
1811     class AzInfoComparator implements Comparator<ResolverActivity.DisplayResolveInfo> {
1812         Collator mCollator;
1813         AzInfoComparator(Context context) {
1814             mCollator = Collator.getInstance(context.getResources().getConfiguration().locale);
1815         }
1816 
1817         @Override
1818         public int compare(ResolverActivity.DisplayResolveInfo lhsp,
1819                 ResolverActivity.DisplayResolveInfo rhsp) {
1820             return mCollator.compare(lhsp.getDisplayLabel(), rhsp.getDisplayLabel());
1821         }
1822     }
1823 
1824     protected MetricsLogger getMetricsLogger() {
1825         if (mMetricsLogger == null) {
1826             mMetricsLogger = new MetricsLogger();
1827         }
1828         return mMetricsLogger;
1829     }
1830 
1831     public class ChooserListController extends ResolverListController {
1832         public ChooserListController(Context context,
1833                 PackageManager pm,
1834                 Intent targetIntent,
1835                 String referrerPackageName,
1836                 int launchedFromUid,
1837                 AbstractResolverComparator resolverComparator) {
1838             super(context, pm, targetIntent, referrerPackageName, launchedFromUid,
1839                     resolverComparator);
1840         }
1841 
1842         @Override
1843         boolean isComponentFiltered(ComponentName name) {
1844             if (mFilteredComponentNames == null) {
1845                 return false;
1846             }
1847             for (ComponentName filteredComponentName : mFilteredComponentNames) {
1848                 if (name.equals(filteredComponentName)) {
1849                     return true;
1850                 }
1851             }
1852             return false;
1853         }
1854     }
1855 
1856     @Override
1857     public ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
1858             Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
1859             boolean filterLastUsed) {
1860         final ChooserListAdapter adapter = new ChooserListAdapter(context, payloadIntents,
1861                 initialIntents, rList, launchedFromUid, filterLastUsed, createListController());
1862         return adapter;
1863     }
1864 
1865     @VisibleForTesting
1866     protected ResolverListController createListController() {
1867         AppPredictor appPredictor = getAppPredictorForShareActivitesIfEnabled();
1868         AbstractResolverComparator resolverComparator;
1869         if (appPredictor != null) {
1870             resolverComparator = new AppPredictionServiceResolverComparator(this, getTargetIntent(),
1871                     getReferrerPackageName(), appPredictor, getUser());
1872         } else {
1873             resolverComparator =
1874                     new ResolverRankerServiceResolverComparator(this, getTargetIntent(),
1875                         getReferrerPackageName(), null);
1876         }
1877 
1878         return new ChooserListController(
1879                 this,
1880                 mPm,
1881                 getTargetIntent(),
1882                 getReferrerPackageName(),
1883                 mLaunchedFromUid,
1884                 resolverComparator);
1885     }
1886 
1887     @VisibleForTesting
1888     protected Bitmap loadThumbnail(Uri uri, Size size) {
1889         if (uri == null || size == null) {
1890             return null;
1891         }
1892 
1893         try {
1894             return ImageUtils.loadThumbnail(getContentResolver(), uri, size);
1895         } catch (IOException | NullPointerException | SecurityException ex) {
1896             logContentPreviewWarning(uri);
1897         }
1898         return null;
1899     }
1900 
1901     interface ChooserTargetInfo extends TargetInfo {
1902         float getModifiedScore();
1903 
1904         ChooserTarget getChooserTarget();
1905 
1906         /**
1907           * Do not label as 'equals', since this doesn't quite work
1908           * as intended with java 8.
1909           */
1910         default boolean isSimilar(ChooserTargetInfo other) {
1911             if (other == null) return false;
1912 
1913             ChooserTarget ct1 = getChooserTarget();
1914             ChooserTarget ct2 = other.getChooserTarget();
1915 
1916             // If either is null, there is not enough info to make an informed decision
1917             // about equality, so just exit
1918             if (ct1 == null || ct2 == null) return false;
1919 
1920             if (ct1.getComponentName().equals(ct2.getComponentName())
1921                     && TextUtils.equals(getDisplayLabel(), other.getDisplayLabel())
1922                     && TextUtils.equals(getExtendedInfo(), other.getExtendedInfo())) {
1923                 return true;
1924             }
1925 
1926             return false;
1927         }
1928     }
1929 
1930     /**
1931       * Distinguish between targets that selectable by the user, vs those that are
1932       * placeholders for the system while information is loading in an async manner.
1933       */
1934     abstract class NotSelectableTargetInfo implements ChooserTargetInfo {
1935 
1936         public Intent getResolvedIntent() {
1937             return null;
1938         }
1939 
1940         public ComponentName getResolvedComponentName() {
1941             return null;
1942         }
1943 
1944         public boolean start(Activity activity, Bundle options) {
1945             return false;
1946         }
1947 
1948         public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
1949             return false;
1950         }
1951 
1952         public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
1953             return false;
1954         }
1955 
1956         public ResolveInfo getResolveInfo() {
1957             return null;
1958         }
1959 
1960         public CharSequence getDisplayLabel() {
1961             return null;
1962         }
1963 
1964         public CharSequence getExtendedInfo() {
1965             return null;
1966         }
1967 
1968         public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
1969             return null;
1970         }
1971 
1972         public List<Intent> getAllSourceIntents() {
1973             return null;
1974         }
1975 
1976         public float getModifiedScore() {
1977             return -0.1f;
1978         }
1979 
1980         public ChooserTarget getChooserTarget() {
1981             return null;
1982         }
1983 
1984         public boolean isSuspended() {
1985             return false;
1986         }
1987     }
1988 
1989     final class PlaceHolderTargetInfo extends NotSelectableTargetInfo {
1990         public Drawable getDisplayIcon() {
1991             AnimatedVectorDrawable avd = (AnimatedVectorDrawable)
1992                     getDrawable(R.drawable.chooser_direct_share_icon_placeholder);
1993             avd.start(); // Start animation after generation
1994             return avd;
1995         }
1996     }
1997 
1998 
1999     final class EmptyTargetInfo extends NotSelectableTargetInfo {
2000         public Drawable getDisplayIcon() {
2001             return null;
2002         }
2003     }
2004 
2005     final class SelectableTargetInfo implements ChooserTargetInfo {
2006         private final DisplayResolveInfo mSourceInfo;
2007         private final ResolveInfo mBackupResolveInfo;
2008         private final ChooserTarget mChooserTarget;
2009         private final String mDisplayLabel;
2010         private Drawable mBadgeIcon = null;
2011         private CharSequence mBadgeContentDescription;
2012         private Drawable mDisplayIcon;
2013         private final Intent mFillInIntent;
2014         private final int mFillInFlags;
2015         private final float mModifiedScore;
2016         private boolean mIsSuspended = false;
2017 
2018         SelectableTargetInfo(DisplayResolveInfo sourceInfo, ChooserTarget chooserTarget,
2019                 float modifiedScore) {
2020             mSourceInfo = sourceInfo;
2021             mChooserTarget = chooserTarget;
2022             mModifiedScore = modifiedScore;
2023             if (sourceInfo != null) {
2024                 final ResolveInfo ri = sourceInfo.getResolveInfo();
2025                 if (ri != null) {
2026                     final ActivityInfo ai = ri.activityInfo;
2027                     if (ai != null && ai.applicationInfo != null) {
2028                         final PackageManager pm = getPackageManager();
2029                         mBadgeIcon = pm.getApplicationIcon(ai.applicationInfo);
2030                         mBadgeContentDescription = pm.getApplicationLabel(ai.applicationInfo);
2031                         mIsSuspended =
2032                                 (ai.applicationInfo.flags & ApplicationInfo.FLAG_SUSPENDED) != 0;
2033                     }
2034                 }
2035             }
2036             // TODO(b/121287224): do this in the background thread, and only for selected targets
2037             mDisplayIcon = getChooserTargetIconDrawable(chooserTarget);
2038 
2039             if (sourceInfo != null) {
2040                 mBackupResolveInfo = null;
2041             } else {
2042                 mBackupResolveInfo = getPackageManager().resolveActivity(getResolvedIntent(), 0);
2043             }
2044 
2045             mFillInIntent = null;
2046             mFillInFlags = 0;
2047 
2048             mDisplayLabel = sanitizeDisplayLabel(chooserTarget.getTitle());
2049         }
2050 
2051         private SelectableTargetInfo(SelectableTargetInfo other, Intent fillInIntent, int flags) {
2052             mSourceInfo = other.mSourceInfo;
2053             mBackupResolveInfo = other.mBackupResolveInfo;
2054             mChooserTarget = other.mChooserTarget;
2055             mBadgeIcon = other.mBadgeIcon;
2056             mBadgeContentDescription = other.mBadgeContentDescription;
2057             mDisplayIcon = other.mDisplayIcon;
2058             mFillInIntent = fillInIntent;
2059             mFillInFlags = flags;
2060             mModifiedScore = other.mModifiedScore;
2061 
2062             mDisplayLabel = sanitizeDisplayLabel(mChooserTarget.getTitle());
2063         }
2064 
2065         private String sanitizeDisplayLabel(CharSequence label) {
2066             SpannableStringBuilder sb = new SpannableStringBuilder(label);
2067             sb.clearSpans();
2068             return sb.toString();
2069         }
2070 
2071         public boolean isSuspended() {
2072             return mIsSuspended;
2073         }
2074 
2075         /**
2076          * Since ShortcutInfos are returned by ShortcutManager, we can cache the shortcuts and skip
2077          * the call to LauncherApps#getShortcuts(ShortcutQuery).
2078          */
2079         // TODO(121287224): Refactor code to apply the suggestion above
2080         private Drawable getChooserTargetIconDrawable(ChooserTarget target) {
2081             Drawable directShareIcon = null;
2082 
2083             // First get the target drawable and associated activity info
2084             final Icon icon = target.getIcon();
2085             if (icon != null) {
2086                 directShareIcon = icon.loadDrawable(ChooserActivity.this);
2087             } else if (USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS) {
2088                 Bundle extras = target.getIntentExtras();
2089                 if (extras != null && extras.containsKey(Intent.EXTRA_SHORTCUT_ID)) {
2090                     CharSequence shortcutId = extras.getCharSequence(Intent.EXTRA_SHORTCUT_ID);
2091                     LauncherApps launcherApps = (LauncherApps) getSystemService(
2092                             Context.LAUNCHER_APPS_SERVICE);
2093                     final LauncherApps.ShortcutQuery q = new LauncherApps.ShortcutQuery();
2094                     q.setPackage(target.getComponentName().getPackageName());
2095                     q.setShortcutIds(Arrays.asList(shortcutId.toString()));
2096                     q.setQueryFlags(LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC);
2097                     final List<ShortcutInfo> shortcuts = launcherApps.getShortcuts(q, getUser());
2098                     if (shortcuts != null && shortcuts.size() > 0) {
2099                         directShareIcon = launcherApps.getShortcutIconDrawable(shortcuts.get(0), 0);
2100                     }
2101                 }
2102             }
2103 
2104             if (directShareIcon == null) return null;
2105 
2106             ActivityInfo info = null;
2107             try {
2108                 info = mPm.getActivityInfo(target.getComponentName(), 0);
2109             } catch (NameNotFoundException error) {
2110                 Log.e(TAG, "Could not find activity associated with ChooserTarget");
2111             }
2112 
2113             if (info == null) return null;
2114 
2115             // Now fetch app icon and raster with no badging even in work profile
2116             Bitmap appIcon = makePresentationGetter(info).getIconBitmap(
2117                     UserHandle.getUserHandleForUid(UserHandle.myUserId()));
2118 
2119             // Raster target drawable with appIcon as a badge
2120             SimpleIconFactory sif = SimpleIconFactory.obtain(ChooserActivity.this);
2121             Bitmap directShareBadgedIcon = sif.createAppBadgedIconBitmap(directShareIcon, appIcon);
2122             sif.recycle();
2123 
2124             return new BitmapDrawable(getResources(), directShareBadgedIcon);
2125         }
2126 
2127         public float getModifiedScore() {
2128             return mModifiedScore;
2129         }
2130 
2131         @Override
2132         public Intent getResolvedIntent() {
2133             if (mSourceInfo != null) {
2134                 return mSourceInfo.getResolvedIntent();
2135             }
2136 
2137             final Intent targetIntent = new Intent(getTargetIntent());
2138             targetIntent.setComponent(mChooserTarget.getComponentName());
2139             targetIntent.putExtras(mChooserTarget.getIntentExtras());
2140             return targetIntent;
2141         }
2142 
2143         @Override
2144         public ComponentName getResolvedComponentName() {
2145             if (mSourceInfo != null) {
2146                 return mSourceInfo.getResolvedComponentName();
2147             } else if (mBackupResolveInfo != null) {
2148                 return new ComponentName(mBackupResolveInfo.activityInfo.packageName,
2149                         mBackupResolveInfo.activityInfo.name);
2150             }
2151             return null;
2152         }
2153 
2154         private Intent getBaseIntentToSend() {
2155             Intent result = getResolvedIntent();
2156             if (result == null) {
2157                 Log.e(TAG, "ChooserTargetInfo: no base intent available to send");
2158             } else {
2159                 result = new Intent(result);
2160                 if (mFillInIntent != null) {
2161                     result.fillIn(mFillInIntent, mFillInFlags);
2162                 }
2163                 result.fillIn(mReferrerFillInIntent, 0);
2164             }
2165             return result;
2166         }
2167 
2168         @Override
2169         public boolean start(Activity activity, Bundle options) {
2170             throw new RuntimeException("ChooserTargets should be started as caller.");
2171         }
2172 
2173         @Override
2174         public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) {
2175             final Intent intent = getBaseIntentToSend();
2176             if (intent == null) {
2177                 return false;
2178             }
2179             intent.setComponent(mChooserTarget.getComponentName());
2180             intent.putExtras(mChooserTarget.getIntentExtras());
2181 
2182             // Important: we will ignore the target security checks in ActivityManager
2183             // if and only if the ChooserTarget's target package is the same package
2184             // where we got the ChooserTargetService that provided it. This lets a
2185             // ChooserTargetService provide a non-exported or permission-guarded target
2186             // to the chooser for the user to pick.
2187             //
2188             // If mSourceInfo is null, we got this ChooserTarget from the caller or elsewhere
2189             // so we'll obey the caller's normal security checks.
2190             final boolean ignoreTargetSecurity = mSourceInfo != null
2191                     && mSourceInfo.getResolvedComponentName().getPackageName()
2192                     .equals(mChooserTarget.getComponentName().getPackageName());
2193             return activity.startAsCallerImpl(intent, options, ignoreTargetSecurity, userId);
2194         }
2195 
2196         @Override
2197         public boolean startAsUser(Activity activity, Bundle options, UserHandle user) {
2198             throw new RuntimeException("ChooserTargets should be started as caller.");
2199         }
2200 
2201         @Override
2202         public ResolveInfo getResolveInfo() {
2203             return mSourceInfo != null ? mSourceInfo.getResolveInfo() : mBackupResolveInfo;
2204         }
2205 
2206         @Override
2207         public CharSequence getDisplayLabel() {
2208             return mDisplayLabel;
2209         }
2210 
2211         @Override
2212         public CharSequence getExtendedInfo() {
2213             // ChooserTargets have badge icons, so we won't show the extended info to disambiguate.
2214             return null;
2215         }
2216 
2217         @Override
2218         public Drawable getDisplayIcon() {
2219             return mDisplayIcon;
2220         }
2221 
2222         public ChooserTarget getChooserTarget() {
2223             return mChooserTarget;
2224         }
2225 
2226         @Override
2227         public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) {
2228             return new SelectableTargetInfo(this, fillInIntent, flags);
2229         }
2230 
2231         @Override
2232         public List<Intent> getAllSourceIntents() {
2233             final List<Intent> results = new ArrayList<>();
2234             if (mSourceInfo != null) {
2235                 // We only queried the service for the first one in our sourceinfo.
2236                 results.add(mSourceInfo.getAllSourceIntents().get(0));
2237             }
2238             return results;
2239         }
2240     }
2241 
2242     private void handleScroll(View view, int x, int y, int oldx, int oldy) {
2243         if (mChooserRowAdapter != null) {
2244             mChooserRowAdapter.handleScroll(view, y, oldy);
2245         }
2246     }
2247 
2248     /*
2249      * Need to dynamically adjust how many icons can fit per row before we add them,
2250      * which also means setting the correct offset to initially show the content
2251      * preview area + 2 rows of targets
2252      */
2253     private void handleLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
2254             int oldTop, int oldRight, int oldBottom) {
2255         if (mChooserRowAdapter == null || mAdapterView == null) {
2256             return;
2257         }
2258 
2259         final int availableWidth = right - left - v.getPaddingLeft() - v.getPaddingRight();
2260         if (mChooserRowAdapter.consumeLayoutRequest()
2261                 || mChooserRowAdapter.calculateChooserTargetWidth(availableWidth)
2262                 || mAdapterView.getAdapter() == null
2263                 || availableWidth != mCurrAvailableWidth) {
2264             mCurrAvailableWidth = availableWidth;
2265             mAdapterView.setAdapter(mChooserRowAdapter);
2266 
2267             getMainThreadHandler().post(() -> {
2268                 if (mResolverDrawerLayout == null || mChooserRowAdapter == null) {
2269                     return;
2270                 }
2271 
2272                 final int bottomInset = mSystemWindowInsets != null
2273                                             ? mSystemWindowInsets.bottom : 0;
2274                 int offset = bottomInset;
2275                 int rowsToShow = mChooserRowAdapter.getContentPreviewRowCount()
2276                         + mChooserRowAdapter.getProfileRowCount()
2277                         + mChooserRowAdapter.getServiceTargetRowCount()
2278                         + mChooserRowAdapter.getCallerAndRankedTargetRowCount();
2279 
2280                 // then this is most likely not a SEND_* action, so check
2281                 // the app target count
2282                 if (rowsToShow == 0) {
2283                     rowsToShow = mChooserRowAdapter.getCount();
2284                 }
2285 
2286                 // still zero? then use a default height and leave, which
2287                 // can happen when there are no targets to show
2288                 if (rowsToShow == 0) {
2289                     offset += getResources().getDimensionPixelSize(
2290                             R.dimen.chooser_max_collapsed_height);
2291                     mResolverDrawerLayout.setCollapsibleHeightReserved(offset);
2292                     return;
2293                 }
2294 
2295                 int directShareHeight = 0;
2296                 rowsToShow = Math.min(4, rowsToShow);
2297                 for (int i = 0; i < Math.min(rowsToShow, mAdapterView.getChildCount()); i++) {
2298                     View child = mAdapterView.getChildAt(i);
2299                     int height = child.getHeight();
2300                     offset += height;
2301 
2302                     if (child.getTag() != null
2303                             && (child.getTag() instanceof DirectShareViewHolder)) {
2304                         directShareHeight = height;
2305                     }
2306                 }
2307 
2308                 boolean isExpandable = getResources().getConfiguration().orientation
2309                         == Configuration.ORIENTATION_PORTRAIT && !isInMultiWindowMode();
2310                 if (directShareHeight != 0 && isSendAction(getTargetIntent()) && isExpandable) {
2311                     // make sure to leave room for direct share 4->8 expansion
2312                     int requiredExpansionHeight =
2313                             (int) (directShareHeight / DIRECT_SHARE_EXPANSION_RATE);
2314                     int topInset = mSystemWindowInsets != null ? mSystemWindowInsets.top : 0;
2315                     int minHeight = bottom - top - mResolverDrawerLayout.getAlwaysShowHeight()
2316                                         - requiredExpansionHeight - topInset - bottomInset;
2317 
2318                     offset = Math.min(offset, minHeight);
2319                 }
2320 
2321                 mResolverDrawerLayout.setCollapsibleHeightReserved(Math.min(offset, bottom - top));
2322             });
2323         }
2324     }
2325 
2326     public class ChooserListAdapter extends ResolveListAdapter {
2327         public static final int TARGET_BAD = -1;
2328         public static final int TARGET_CALLER = 0;
2329         public static final int TARGET_SERVICE = 1;
2330         public static final int TARGET_STANDARD = 2;
2331         public static final int TARGET_STANDARD_AZ = 3;
2332 
2333         private static final int MAX_SUGGESTED_APP_TARGETS = 4;
2334         private static final int MAX_CHOOSER_TARGETS_PER_APP = 2;
2335 
2336         private static final int MAX_SERVICE_TARGETS = 8;
2337 
2338         private final int mMaxShortcutTargetsPerApp =
2339                 getResources().getInteger(R.integer.config_maxShortcutTargetsPerApp);
2340 
2341         private int mNumShortcutResults = 0;
2342 
2343         // Reserve spots for incoming direct share targets by adding placeholders
2344         private ChooserTargetInfo mPlaceHolderTargetInfo = new PlaceHolderTargetInfo();
2345         private final List<ChooserTargetInfo> mServiceTargets = new ArrayList<>();
2346         private final List<TargetInfo> mCallerTargets = new ArrayList<>();
2347 
2348         private final BaseChooserTargetComparator mBaseTargetComparator
2349                 = new BaseChooserTargetComparator();
2350 
2351         public ChooserListAdapter(Context context, List<Intent> payloadIntents,
2352                 Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
2353                 boolean filterLastUsed, ResolverListController resolverListController) {
2354             // Don't send the initial intents through the shared ResolverActivity path,
2355             // we want to separate them into a different section.
2356             super(context, payloadIntents, null, rList, launchedFromUid, filterLastUsed,
2357                     resolverListController);
2358 
2359             createPlaceHolders();
2360 
2361             if (initialIntents != null) {
2362                 final PackageManager pm = getPackageManager();
2363                 for (int i = 0; i < initialIntents.length; i++) {
2364                     final Intent ii = initialIntents[i];
2365                     if (ii == null) {
2366                         continue;
2367                     }
2368 
2369                     // We reimplement Intent#resolveActivityInfo here because if we have an
2370                     // implicit intent, we want the ResolveInfo returned by PackageManager
2371                     // instead of one we reconstruct ourselves. The ResolveInfo returned might
2372                     // have extra metadata and resolvePackageName set and we want to respect that.
2373                     ResolveInfo ri = null;
2374                     ActivityInfo ai = null;
2375                     final ComponentName cn = ii.getComponent();
2376                     if (cn != null) {
2377                         try {
2378                             ai = pm.getActivityInfo(ii.getComponent(), 0);
2379                             ri = new ResolveInfo();
2380                             ri.activityInfo = ai;
2381                         } catch (PackageManager.NameNotFoundException ignored) {
2382                             // ai will == null below
2383                         }
2384                     }
2385                     if (ai == null) {
2386                         ri = pm.resolveActivity(ii, PackageManager.MATCH_DEFAULT_ONLY);
2387                         ai = ri != null ? ri.activityInfo : null;
2388                     }
2389                     if (ai == null) {
2390                         Log.w(TAG, "No activity found for " + ii);
2391                         continue;
2392                     }
2393                     UserManager userManager =
2394                             (UserManager) getSystemService(Context.USER_SERVICE);
2395                     if (ii instanceof LabeledIntent) {
2396                         LabeledIntent li = (LabeledIntent) ii;
2397                         ri.resolvePackageName = li.getSourcePackage();
2398                         ri.labelRes = li.getLabelResource();
2399                         ri.nonLocalizedLabel = li.getNonLocalizedLabel();
2400                         ri.icon = li.getIconResource();
2401                         ri.iconResourceId = ri.icon;
2402                     }
2403                     if (userManager.isManagedProfile()) {
2404                         ri.noResourceId = true;
2405                         ri.icon = 0;
2406                     }
2407                     ResolveInfoPresentationGetter getter = makePresentationGetter(ri);
2408                     mCallerTargets.add(new DisplayResolveInfo(ii, ri,
2409                             getter.getLabel(), getter.getSubLabel(), ii));
2410                 }
2411             }
2412         }
2413 
2414         @Override
2415         public void handlePackagesChanged() {
2416             if (DEBUG) {
2417                 Log.d(TAG, "clearing queryTargets on package change");
2418             }
2419             createPlaceHolders();
2420             mServicesRequested.clear();
2421             notifyDataSetChanged();
2422 
2423             super.handlePackagesChanged();
2424         }
2425 
2426         @Override
2427         public void notifyDataSetChanged() {
2428             if (!mListViewDataChanged) {
2429                 mChooserHandler.sendEmptyMessageDelayed(ChooserHandler.LIST_VIEW_UPDATE_MESSAGE,
2430                         LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
2431                 mListViewDataChanged = true;
2432             }
2433         }
2434 
2435         private void refreshListView() {
2436             if (mListViewDataChanged) {
2437                 super.notifyDataSetChanged();
2438             }
2439             mListViewDataChanged = false;
2440         }
2441 
2442 
2443         private void createPlaceHolders() {
2444             mNumShortcutResults = 0;
2445             mServiceTargets.clear();
2446             for (int i = 0; i < MAX_SERVICE_TARGETS; i++) {
2447                 mServiceTargets.add(mPlaceHolderTargetInfo);
2448             }
2449         }
2450 
2451         @Override
2452         public View onCreateView(ViewGroup parent) {
2453             return mInflater.inflate(
2454                     com.android.internal.R.layout.resolve_grid_item, parent, false);
2455         }
2456 
2457         @Override
2458         protected void onBindView(View view, TargetInfo info) {
2459             super.onBindView(view, info);
2460 
2461             // If target is loading, show a special placeholder shape in the label, make unclickable
2462             final ViewHolder holder = (ViewHolder) view.getTag();
2463             if (info instanceof PlaceHolderTargetInfo) {
2464                 final int maxWidth = getResources().getDimensionPixelSize(
2465                         R.dimen.chooser_direct_share_label_placeholder_max_width);
2466                 holder.text.setMaxWidth(maxWidth);
2467                 holder.text.setBackground(getResources().getDrawable(
2468                         R.drawable.chooser_direct_share_label_placeholder, getTheme()));
2469                 // Prevent rippling by removing background containing ripple
2470                 holder.itemView.setBackground(null);
2471             } else {
2472                 holder.text.setMaxWidth(Integer.MAX_VALUE);
2473                 holder.text.setBackground(null);
2474                 holder.itemView.setBackground(holder.defaultItemViewBackground);
2475             }
2476         }
2477 
2478         @Override
2479         public void onListRebuilt() {
2480             updateAlphabeticalList();
2481 
2482             // don't support direct share on low ram devices
2483             if (ActivityManager.isLowRamDeviceStatic()) {
2484                 return;
2485             }
2486 
2487             if (USE_SHORTCUT_MANAGER_FOR_DIRECT_TARGETS
2488                         || USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) {
2489                 if (DEBUG) {
2490                     Log.d(TAG, "querying direct share targets from ShortcutManager");
2491                 }
2492 
2493                 queryDirectShareTargets(this, false);
2494             }
2495             if (USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS) {
2496                 if (DEBUG) {
2497                     Log.d(TAG, "List built querying services");
2498                 }
2499 
2500                 queryTargetServices(this);
2501             }
2502         }
2503 
2504         @Override
2505         public boolean shouldGetResolvedFilter() {
2506             return true;
2507         }
2508 
2509         @Override
2510         public int getCount() {
2511             return getRankedTargetCount() + getAlphaTargetCount()
2512                     + getSelectableServiceTargetCount() + getCallerTargetCount();
2513         }
2514 
2515         @Override
2516         public int getUnfilteredCount() {
2517             int appTargets = super.getUnfilteredCount();
2518             if (appTargets > getMaxRankedTargets()) {
2519                 appTargets = appTargets + getMaxRankedTargets();
2520             }
2521             return appTargets + getSelectableServiceTargetCount() + getCallerTargetCount();
2522         }
2523 
2524 
2525         public int getCallerTargetCount() {
2526             return Math.min(mCallerTargets.size(), MAX_SUGGESTED_APP_TARGETS);
2527         }
2528 
2529         /**
2530           * Filter out placeholders and non-selectable service targets
2531           */
2532         public int getSelectableServiceTargetCount() {
2533             int count = 0;
2534             for (ChooserTargetInfo info : mServiceTargets) {
2535                 if (info instanceof SelectableTargetInfo) {
2536                     count++;
2537                 }
2538             }
2539             return count;
2540         }
2541 
2542         public int getServiceTargetCount() {
2543             if (isSendAction(getTargetIntent()) && !ActivityManager.isLowRamDeviceStatic()) {
2544                 return Math.min(mServiceTargets.size(), MAX_SERVICE_TARGETS);
2545             }
2546 
2547             return 0;
2548         }
2549 
2550         int getAlphaTargetCount() {
2551             int standardCount = super.getCount();
2552             return standardCount > getMaxRankedTargets() ? standardCount : 0;
2553         }
2554 
2555         int getRankedTargetCount() {
2556             int spacesAvailable = getMaxRankedTargets() - getCallerTargetCount();
2557             return Math.min(spacesAvailable, super.getCount());
2558         }
2559 
2560         private int getMaxRankedTargets() {
2561             return mChooserRowAdapter == null ? 4 : mChooserRowAdapter.getMaxTargetsPerRow();
2562         }
2563 
2564         public int getPositionTargetType(int position) {
2565             int offset = 0;
2566 
2567             final int serviceTargetCount = getServiceTargetCount();
2568             if (position < serviceTargetCount) {
2569                 return TARGET_SERVICE;
2570             }
2571             offset += serviceTargetCount;
2572 
2573             final int callerTargetCount = getCallerTargetCount();
2574             if (position - offset < callerTargetCount) {
2575                 return TARGET_CALLER;
2576             }
2577             offset += callerTargetCount;
2578 
2579             final int rankedTargetCount = getRankedTargetCount();
2580             if (position - offset < rankedTargetCount) {
2581                 return TARGET_STANDARD;
2582             }
2583             offset += rankedTargetCount;
2584 
2585             final int standardTargetCount = getAlphaTargetCount();
2586             if (position - offset < standardTargetCount) {
2587                 return TARGET_STANDARD_AZ;
2588             }
2589 
2590             return TARGET_BAD;
2591         }
2592 
2593         @Override
2594         public TargetInfo getItem(int position) {
2595             return targetInfoForPosition(position, true);
2596         }
2597 
2598 
2599         /**
2600          * Find target info for a given position.
2601          * Since ChooserActivity displays several sections of content, determine which
2602          * section provides this item.
2603          */
2604         @Override
2605         public TargetInfo targetInfoForPosition(int position, boolean filtered) {
2606             int offset = 0;
2607 
2608             // Direct share targets
2609             final int serviceTargetCount = filtered ? getServiceTargetCount() :
2610                                                getSelectableServiceTargetCount();
2611             if (position < serviceTargetCount) {
2612                 return mServiceTargets.get(position);
2613             }
2614             offset += serviceTargetCount;
2615 
2616             // Targets provided by calling app
2617             final int callerTargetCount = getCallerTargetCount();
2618             if (position - offset < callerTargetCount) {
2619                 return mCallerTargets.get(position - offset);
2620             }
2621             offset += callerTargetCount;
2622 
2623             // Ranked standard app targets
2624             final int rankedTargetCount = getRankedTargetCount();
2625             if (position - offset < rankedTargetCount) {
2626                 return filtered ? super.getItem(position - offset)
2627                         : getDisplayResolveInfo(position - offset);
2628             }
2629             offset += rankedTargetCount;
2630 
2631             // Alphabetical complete app target list.
2632             if (position - offset < getAlphaTargetCount() && !mSortedList.isEmpty()) {
2633                 return mSortedList.get(position - offset);
2634             }
2635 
2636             return null;
2637         }
2638 
2639 
2640         /**
2641          * Evaluate targets for inclusion in the direct share area. May not be included
2642          * if score is too low.
2643          */
2644         public void addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets,
2645                 boolean isShortcutResult) {
2646             if (DEBUG) {
2647                 Log.d(TAG, "addServiceResults " + origTarget + ", " + targets.size()
2648                         + " targets");
2649             }
2650 
2651             if (targets.size() == 0) {
2652                 return;
2653             }
2654 
2655             final float baseScore = getBaseScore(origTarget, isShortcutResult);
2656             Collections.sort(targets, mBaseTargetComparator);
2657 
2658             final int maxTargets = isShortcutResult ? mMaxShortcutTargetsPerApp
2659                                        : MAX_CHOOSER_TARGETS_PER_APP;
2660             float lastScore = 0;
2661             boolean shouldNotify = false;
2662             for (int i = 0, count = Math.min(targets.size(), maxTargets); i < count; i++) {
2663                 final ChooserTarget target = targets.get(i);
2664                 float targetScore = target.getScore();
2665                 targetScore *= baseScore;
2666                 if (i > 0 && targetScore >= lastScore) {
2667                     // Apply a decay so that the top app can't crowd out everything else.
2668                     // This incents ChooserTargetServices to define what's truly better.
2669                     targetScore = lastScore * 0.95f;
2670                 }
2671                 boolean isInserted = insertServiceTarget(
2672                         new SelectableTargetInfo(origTarget, target, targetScore));
2673 
2674                 if (isInserted && isShortcutResult) {
2675                     mNumShortcutResults++;
2676                 }
2677 
2678                 shouldNotify |= isInserted;
2679 
2680                 if (DEBUG) {
2681                     Log.d(TAG, " => " + target.toString() + " score=" + targetScore
2682                             + " base=" + target.getScore()
2683                             + " lastScore=" + lastScore
2684                             + " baseScore=" + baseScore);
2685                 }
2686 
2687                 lastScore = targetScore;
2688             }
2689 
2690             if (shouldNotify) {
2691                 notifyDataSetChanged();
2692             }
2693         }
2694 
2695         private int getNumShortcutResults() {
2696             return mNumShortcutResults;
2697         }
2698 
2699         /**
2700           * Use the scoring system along with artificial boosts to create up to 4 distinct buckets:
2701           * <ol>
2702           *   <li>App-supplied targets
2703           *   <li>Shortcuts ranked via App Prediction Manager
2704           *   <li>Shortcuts ranked via legacy heuristics
2705           *   <li>Legacy direct share targets
2706           * </ol>
2707           */
2708         private float getBaseScore(DisplayResolveInfo target, boolean isShortcutResult) {
2709             if (target == null) {
2710                 return CALLER_TARGET_SCORE_BOOST;
2711             }
2712 
2713             if (isShortcutResult && getAppPredictorForDirectShareIfEnabled() != null) {
2714                 return SHORTCUT_TARGET_SCORE_BOOST;
2715             }
2716 
2717             float score = super.getScore(target);
2718             if (isShortcutResult) {
2719                 return score * SHORTCUT_TARGET_SCORE_BOOST;
2720             }
2721 
2722             return score;
2723         }
2724 
2725         /**
2726          * Calling this marks service target loading complete, and will attempt to no longer
2727          * update the direct share area.
2728          */
2729         public void completeServiceTargetLoading() {
2730             mServiceTargets.removeIf(o -> o instanceof PlaceHolderTargetInfo);
2731 
2732             if (mServiceTargets.isEmpty()) {
2733                 mServiceTargets.add(new EmptyTargetInfo());
2734             }
2735             notifyDataSetChanged();
2736         }
2737 
2738         private boolean insertServiceTarget(ChooserTargetInfo chooserTargetInfo) {
2739             // Avoid inserting any potentially late results
2740             if (mServiceTargets.size() == 1
2741                     && mServiceTargets.get(0) instanceof EmptyTargetInfo) {
2742                 return false;
2743             }
2744 
2745             // Check for duplicates and abort if found
2746             for (ChooserTargetInfo otherTargetInfo : mServiceTargets) {
2747                 if (chooserTargetInfo.isSimilar(otherTargetInfo)) {
2748                     return false;
2749                 }
2750             }
2751 
2752             int currentSize = mServiceTargets.size();
2753             final float newScore = chooserTargetInfo.getModifiedScore();
2754             for (int i = 0; i < Math.min(currentSize, MAX_SERVICE_TARGETS); i++) {
2755                 final ChooserTargetInfo serviceTarget = mServiceTargets.get(i);
2756                 if (serviceTarget == null) {
2757                     mServiceTargets.set(i, chooserTargetInfo);
2758                     return true;
2759                 } else if (newScore > serviceTarget.getModifiedScore()) {
2760                     mServiceTargets.add(i, chooserTargetInfo);
2761                     return true;
2762                 }
2763             }
2764 
2765             if (currentSize < MAX_SERVICE_TARGETS) {
2766                 mServiceTargets.add(chooserTargetInfo);
2767                 return true;
2768             }
2769 
2770             return false;
2771         }
2772     }
2773 
2774     static class BaseChooserTargetComparator implements Comparator<ChooserTarget> {
2775         @Override
2776         public int compare(ChooserTarget lhs, ChooserTarget rhs) {
2777             // Descending order
2778             return (int) Math.signum(rhs.getScore() - lhs.getScore());
2779         }
2780     }
2781 
2782 
2783     private boolean isSendAction(Intent targetIntent) {
2784         if (targetIntent == null) {
2785             return false;
2786         }
2787 
2788         String action = targetIntent.getAction();
2789         if (action == null) {
2790             return false;
2791         }
2792 
2793         if (Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action)) {
2794             return true;
2795         }
2796 
2797         return false;
2798     }
2799 
2800     class ChooserRowAdapter extends BaseAdapter {
2801         private ChooserListAdapter mChooserListAdapter;
2802         private final LayoutInflater mLayoutInflater;
2803 
2804         private DirectShareViewHolder mDirectShareViewHolder;
2805         private int mChooserTargetWidth = 0;
2806         private boolean mShowAzLabelIfPoss;
2807 
2808         private boolean mHideContentPreview = false;
2809         private boolean mLayoutRequested = false;
2810 
2811         private static final int VIEW_TYPE_DIRECT_SHARE = 0;
2812         private static final int VIEW_TYPE_NORMAL = 1;
2813         private static final int VIEW_TYPE_CONTENT_PREVIEW = 2;
2814         private static final int VIEW_TYPE_PROFILE = 3;
2815         private static final int VIEW_TYPE_AZ_LABEL = 4;
2816 
2817         private static final int MAX_TARGETS_PER_ROW_PORTRAIT = 4;
2818         private static final int MAX_TARGETS_PER_ROW_LANDSCAPE = 8;
2819 
2820         private static final int NUM_EXPANSIONS_TO_HIDE_AZ_LABEL = 20;
2821 
2822         public ChooserRowAdapter(ChooserListAdapter wrappedAdapter) {
2823             mChooserListAdapter = wrappedAdapter;
2824             mLayoutInflater = LayoutInflater.from(ChooserActivity.this);
2825 
2826             mShowAzLabelIfPoss = getNumSheetExpansions() < NUM_EXPANSIONS_TO_HIDE_AZ_LABEL;
2827 
2828             wrappedAdapter.registerDataSetObserver(new DataSetObserver() {
2829                 @Override
2830                 public void onChanged() {
2831                     super.onChanged();
2832                     notifyDataSetChanged();
2833                 }
2834 
2835                 @Override
2836                 public void onInvalidated() {
2837                     super.onInvalidated();
2838                     notifyDataSetInvalidated();
2839                 }
2840             });
2841         }
2842 
2843         /**
2844          * Calculate the chooser target width to maximize space per item
2845          *
2846          * @param width The new row width to use for recalculation
2847          * @return true if the view width has changed
2848          */
2849         public boolean calculateChooserTargetWidth(int width) {
2850             if (width == 0) {
2851                 return false;
2852             }
2853 
2854             int newWidth =  width / getMaxTargetsPerRow();
2855             if (newWidth != mChooserTargetWidth) {
2856                 mChooserTargetWidth = newWidth;
2857                 return true;
2858             }
2859 
2860             return false;
2861         }
2862 
2863         private int getMaxTargetsPerRow() {
2864             int maxTargets = MAX_TARGETS_PER_ROW_PORTRAIT;
2865             if (shouldDisplayLandscape(getResources().getConfiguration().orientation)) {
2866                 maxTargets = MAX_TARGETS_PER_ROW_LANDSCAPE;
2867             }
2868 
2869             return maxTargets;
2870         }
2871 
2872         public void hideContentPreview() {
2873             mHideContentPreview = true;
2874             mLayoutRequested = true;
2875             notifyDataSetChanged();
2876         }
2877 
2878         public boolean consumeLayoutRequest() {
2879             boolean oldValue = mLayoutRequested;
2880             mLayoutRequested = false;
2881             return oldValue;
2882         }
2883 
2884         @Override
2885         public boolean areAllItemsEnabled() {
2886             return false;
2887         }
2888 
2889         @Override
2890         public boolean isEnabled(int position) {
2891             int viewType = getItemViewType(position);
2892             if (viewType == VIEW_TYPE_CONTENT_PREVIEW || viewType == VIEW_TYPE_AZ_LABEL) {
2893                 return false;
2894             }
2895             return true;
2896         }
2897 
2898         @Override
2899         public int getCount() {
2900             return (int) (
2901                     getContentPreviewRowCount()
2902                             + getProfileRowCount()
2903                             + getServiceTargetRowCount()
2904                             + getCallerAndRankedTargetRowCount()
2905                             + getAzLabelRowCount()
2906                             + Math.ceil(
2907                             (float) mChooserListAdapter.getAlphaTargetCount()
2908                                     / getMaxTargetsPerRow())
2909             );
2910         }
2911 
2912         public int getContentPreviewRowCount() {
2913             if (!isSendAction(getTargetIntent())) {
2914                 return 0;
2915             }
2916 
2917             if (mHideContentPreview || mChooserListAdapter == null
2918                     || mChooserListAdapter.getCount() == 0) {
2919                 return 0;
2920             }
2921 
2922             return 1;
2923         }
2924 
2925         public int getProfileRowCount() {
2926             return mChooserListAdapter.getOtherProfile() == null ? 0 : 1;
2927         }
2928 
2929         public int getCallerAndRankedTargetRowCount() {
2930             return (int) Math.ceil(
2931                     ((float) mChooserListAdapter.getCallerTargetCount()
2932                             + mChooserListAdapter.getRankedTargetCount()) / getMaxTargetsPerRow());
2933         }
2934 
2935         // There can be at most one row in the listview, that is internally
2936         // a ViewGroup with 2 rows
2937         public int getServiceTargetRowCount() {
2938             if (isSendAction(getTargetIntent()) && !ActivityManager.isLowRamDeviceStatic()) {
2939                 return 1;
2940             }
2941             return 0;
2942         }
2943 
2944         public int getAzLabelRowCount() {
2945             // Only show a label if the a-z list is showing
2946             return (mShowAzLabelIfPoss && mChooserListAdapter.getAlphaTargetCount() > 0) ? 1 : 0;
2947         }
2948 
2949         @Override
2950         public Object getItem(int position) {
2951             // We have nothing useful to return here.
2952             return position;
2953         }
2954 
2955         @Override
2956         public long getItemId(int position) {
2957             return position;
2958         }
2959 
2960         @Override
2961         public View getView(int position, View convertView, ViewGroup parent) {
2962             final RowViewHolder holder;
2963             int viewType = getItemViewType(position);
2964 
2965             if (viewType == VIEW_TYPE_CONTENT_PREVIEW) {
2966                 return createContentPreviewView(convertView, parent);
2967             }
2968 
2969             if (viewType == VIEW_TYPE_PROFILE) {
2970                 return createProfileView(convertView, parent);
2971             }
2972 
2973             if (viewType == VIEW_TYPE_AZ_LABEL) {
2974                 return createAzLabelView(parent);
2975             }
2976 
2977             if (convertView == null) {
2978                 holder = createViewHolder(viewType, parent);
2979             } else {
2980                 holder = (RowViewHolder) convertView.getTag();
2981             }
2982 
2983             bindViewHolder(position, holder);
2984 
2985             return holder.getViewGroup();
2986         }
2987 
2988         @Override
2989         public int getItemViewType(int position) {
2990             int count;
2991 
2992             int countSum = (count = getContentPreviewRowCount());
2993             if (count > 0 && position < countSum) return VIEW_TYPE_CONTENT_PREVIEW;
2994 
2995             countSum += (count = getProfileRowCount());
2996             if (count > 0 && position < countSum) return VIEW_TYPE_PROFILE;
2997 
2998             countSum += (count = getServiceTargetRowCount());
2999             if (count > 0 && position < countSum) return VIEW_TYPE_DIRECT_SHARE;
3000 
3001             countSum += (count = getCallerAndRankedTargetRowCount());
3002             if (count > 0 && position < countSum) return VIEW_TYPE_NORMAL;
3003 
3004             countSum += (count = getAzLabelRowCount());
3005             if (count > 0 && position < countSum) return VIEW_TYPE_AZ_LABEL;
3006 
3007             return VIEW_TYPE_NORMAL;
3008         }
3009 
3010         @Override
3011         public int getViewTypeCount() {
3012             return 5;
3013         }
3014 
3015         private ViewGroup createContentPreviewView(View convertView, ViewGroup parent) {
3016             Intent targetIntent = getTargetIntent();
3017             int previewType = findPreferredContentPreview(targetIntent, getContentResolver());
3018 
3019             if (convertView == null) {
3020                 getMetricsLogger().write(new LogMaker(MetricsEvent.ACTION_SHARE_WITH_PREVIEW)
3021                         .setSubtype(previewType));
3022             }
3023 
3024             return displayContentPreview(previewType, targetIntent, mLayoutInflater,
3025                     (ViewGroup) convertView, parent);
3026         }
3027 
3028         private View createProfileView(View convertView, ViewGroup parent) {
3029             View profileRow = convertView != null ? convertView : mLayoutInflater.inflate(
3030                     R.layout.chooser_profile_row, parent, false);
3031             profileRow.setBackground(
3032                     getResources().getDrawable(R.drawable.chooser_row_layer_list, null));
3033             mProfileView = profileRow.findViewById(R.id.profile_button);
3034             mProfileView.setOnClickListener(ChooserActivity.this::onProfileClick);
3035             bindProfileView();
3036             return profileRow;
3037         }
3038 
3039         private View createAzLabelView(ViewGroup parent) {
3040             return mLayoutInflater.inflate(R.layout.chooser_az_label_row, parent, false);
3041         }
3042 
3043         private RowViewHolder loadViewsIntoRow(RowViewHolder holder) {
3044             final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
3045             final int exactSpec = MeasureSpec.makeMeasureSpec(mChooserTargetWidth,
3046                     MeasureSpec.EXACTLY);
3047             int columnCount = holder.getColumnCount();
3048 
3049             final boolean isDirectShare = holder instanceof DirectShareViewHolder;
3050 
3051             for (int i = 0; i < columnCount; i++) {
3052                 final View v = mChooserListAdapter.createView(holder.getRowByIndex(i));
3053                 final int column = i;
3054                 v.setOnClickListener(new OnClickListener() {
3055                     @Override
3056                     public void onClick(View v) {
3057                         startSelected(holder.getItemIndex(column), false, true);
3058                     }
3059                 });
3060                 v.setOnLongClickListener(new OnLongClickListener() {
3061                     @Override
3062                     public boolean onLongClick(View v) {
3063                         showTargetDetails(
3064                                 mChooserListAdapter.resolveInfoForPosition(
3065                                         holder.getItemIndex(column), true));
3066                         return true;
3067                     }
3068                 });
3069                 ViewGroup row = holder.addView(i, v);
3070 
3071                 // Force Direct Share to be 2 lines and auto-wrap to second line via hoz scroll =
3072                 // false. TextView#setHorizontallyScrolling must be reset after #setLines. Must be
3073                 // done before measuring.
3074                 if (isDirectShare) {
3075                     final ViewHolder vh = (ViewHolder) v.getTag();
3076                     vh.text.setLines(2);
3077                     vh.text.setHorizontallyScrolling(false);
3078                     vh.text2.setVisibility(View.GONE);
3079                 }
3080 
3081                 // Force height to be a given so we don't have visual disruption during scaling.
3082                 v.measure(exactSpec, spec);
3083                 setViewBounds(v, v.getMeasuredWidth(), v.getMeasuredHeight());
3084             }
3085 
3086             final ViewGroup viewGroup = holder.getViewGroup();
3087 
3088             // Pre-measure and fix height so we can scale later.
3089             holder.measure();
3090             setViewBounds(viewGroup, LayoutParams.MATCH_PARENT, holder.getMeasuredRowHeight());
3091 
3092             if (isDirectShare) {
3093                 DirectShareViewHolder dsvh = (DirectShareViewHolder) holder;
3094                 setViewBounds(dsvh.getRow(0), LayoutParams.MATCH_PARENT, dsvh.getMinRowHeight());
3095                 setViewBounds(dsvh.getRow(1), LayoutParams.MATCH_PARENT, dsvh.getMinRowHeight());
3096             }
3097 
3098             viewGroup.setTag(holder);
3099 
3100             return holder;
3101         }
3102 
3103         private void setViewBounds(View view, int widthPx, int heightPx) {
3104             LayoutParams lp = view.getLayoutParams();
3105             if (lp == null) {
3106                 lp = new LayoutParams(widthPx, heightPx);
3107                 view.setLayoutParams(lp);
3108             } else {
3109                 lp.height = heightPx;
3110                 lp.width = widthPx;
3111             }
3112         }
3113 
3114         RowViewHolder createViewHolder(int viewType, ViewGroup parent) {
3115             if (viewType == VIEW_TYPE_DIRECT_SHARE) {
3116                 ViewGroup parentGroup = (ViewGroup) mLayoutInflater.inflate(
3117                         R.layout.chooser_row_direct_share, parent, false);
3118                 ViewGroup row1 = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
3119                         parentGroup, false);
3120                 ViewGroup row2 = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row,
3121                         parentGroup, false);
3122                 parentGroup.addView(row1);
3123                 parentGroup.addView(row2);
3124 
3125                 mDirectShareViewHolder = new DirectShareViewHolder(parentGroup,
3126                         Lists.newArrayList(row1, row2), getMaxTargetsPerRow());
3127                 loadViewsIntoRow(mDirectShareViewHolder);
3128 
3129                 return mDirectShareViewHolder;
3130             } else {
3131                 ViewGroup row = (ViewGroup) mLayoutInflater.inflate(R.layout.chooser_row, parent,
3132                         false);
3133                 RowViewHolder holder = new SingleRowViewHolder(row, getMaxTargetsPerRow());
3134                 loadViewsIntoRow(holder);
3135 
3136                 return holder;
3137             }
3138         }
3139 
3140         /**
3141          * Need to merge CALLER + ranked STANDARD into a single row and prevent a separator from
3142          * showing on top of the AZ list if the AZ label is visible. All other types are placed into
3143          * their own row as determined by their target type, and dividers are added in the list to
3144          * separate each type.
3145          */
3146         int getRowType(int rowPosition) {
3147             // Merge caller and ranked standard into a single row
3148             int positionType = mChooserListAdapter.getPositionTargetType(rowPosition);
3149             if (positionType == ChooserListAdapter.TARGET_CALLER) {
3150                 return ChooserListAdapter.TARGET_STANDARD;
3151             }
3152 
3153             // If an the A-Z label is shown, prevent a separator from appearing by making the A-Z
3154             // row type the same as the suggestion row type
3155             if (getAzLabelRowCount() > 0 && positionType == ChooserListAdapter.TARGET_STANDARD_AZ) {
3156                 return ChooserListAdapter.TARGET_STANDARD;
3157             }
3158 
3159             return positionType;
3160         }
3161 
3162         void bindViewHolder(int rowPosition, RowViewHolder holder) {
3163             final int start = getFirstRowPosition(rowPosition);
3164             final int startType = getRowType(start);
3165             final int lastStartType = getRowType(getFirstRowPosition(rowPosition - 1));
3166 
3167             final ViewGroup row = holder.getViewGroup();
3168 
3169             if (startType != lastStartType
3170                     || rowPosition == getContentPreviewRowCount() + getProfileRowCount()) {
3171                 row.setForeground(
3172                         getResources().getDrawable(R.drawable.chooser_row_layer_list, null));
3173             } else {
3174                 row.setForeground(null);
3175             }
3176 
3177             int columnCount = holder.getColumnCount();
3178             int end = start + columnCount - 1;
3179             while (getRowType(end) != startType && end >= start) {
3180                 end--;
3181             }
3182 
3183             if (end == start && mChooserListAdapter.getItem(start) instanceof EmptyTargetInfo) {
3184                 final TextView textView = row.findViewById(R.id.chooser_row_text_option);
3185 
3186                 if (textView.getVisibility() != View.VISIBLE) {
3187                     textView.setAlpha(0.0f);
3188                     textView.setVisibility(View.VISIBLE);
3189                     textView.setText(R.string.chooser_no_direct_share_targets);
3190 
3191                     ValueAnimator fadeAnim = ObjectAnimator.ofFloat(textView, "alpha", 0.0f, 1.0f);
3192                     fadeAnim.setInterpolator(new DecelerateInterpolator(1.0f));
3193 
3194                     float translationInPx = getResources().getDimensionPixelSize(
3195                             R.dimen.chooser_row_text_option_translate);
3196                     textView.setTranslationY(translationInPx);
3197                     ValueAnimator translateAnim = ObjectAnimator.ofFloat(textView, "translationY",
3198                             0.0f);
3199                     translateAnim.setInterpolator(new DecelerateInterpolator(1.0f));
3200 
3201                     AnimatorSet animSet = new AnimatorSet();
3202                     animSet.setDuration(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
3203                     animSet.setStartDelay(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
3204                     animSet.playTogether(fadeAnim, translateAnim);
3205                     animSet.start();
3206                 }
3207             }
3208 
3209             for (int i = 0; i < columnCount; i++) {
3210                 final View v = holder.getView(i);
3211                 if (start + i <= end) {
3212                     holder.setViewVisibility(i, View.VISIBLE);
3213                     holder.setItemIndex(i, start + i);
3214                     mChooserListAdapter.bindView(holder.getItemIndex(i), v);
3215                 } else {
3216                     holder.setViewVisibility(i, View.INVISIBLE);
3217                 }
3218             }
3219         }
3220 
3221         int getFirstRowPosition(int row) {
3222             row -= getContentPreviewRowCount() + getProfileRowCount();
3223 
3224             final int serviceCount = mChooserListAdapter.getServiceTargetCount();
3225             final int serviceRows = (int) Math.ceil((float) serviceCount
3226                     / ChooserListAdapter.MAX_SERVICE_TARGETS);
3227             if (row < serviceRows) {
3228                 return row * getMaxTargetsPerRow();
3229             }
3230 
3231             final int callerAndRankedCount = mChooserListAdapter.getCallerTargetCount()
3232                                                  + mChooserListAdapter.getRankedTargetCount();
3233             final int callerAndRankedRows = getCallerAndRankedTargetRowCount();
3234             if (row < callerAndRankedRows + serviceRows) {
3235                 return serviceCount + (row - serviceRows) * getMaxTargetsPerRow();
3236             }
3237 
3238             row -= getAzLabelRowCount();
3239 
3240             return callerAndRankedCount + serviceCount
3241                     + (row - callerAndRankedRows - serviceRows) * getMaxTargetsPerRow();
3242         }
3243 
3244         public void handleScroll(View v, int y, int oldy) {
3245             // Only expand direct share area if there is a minimum number of shortcuts,
3246             // which will help reduce the amount of visible shuffling due to older-style
3247             // direct share targets.
3248             int orientation = getResources().getConfiguration().orientation;
3249             boolean canExpandDirectShare =
3250                     mChooserListAdapter.getNumShortcutResults() > getMaxTargetsPerRow()
3251                     && orientation == Configuration.ORIENTATION_PORTRAIT
3252                     && !isInMultiWindowMode();
3253 
3254             if (mDirectShareViewHolder != null && canExpandDirectShare) {
3255                 mDirectShareViewHolder.handleScroll(mAdapterView, y, oldy, getMaxTargetsPerRow());
3256             }
3257         }
3258     }
3259 
3260     abstract class RowViewHolder {
3261         protected int mMeasuredRowHeight;
3262         private int[] mItemIndices;
3263         protected final View[] mCells;
3264         private final int mColumnCount;
3265 
3266         RowViewHolder(int cellCount) {
3267             this.mCells = new View[cellCount];
3268             this.mItemIndices = new int[cellCount];
3269             this.mColumnCount = cellCount;
3270         }
3271 
3272         abstract ViewGroup addView(int index, View v);
3273 
3274         abstract ViewGroup getViewGroup();
3275 
3276         abstract ViewGroup getRowByIndex(int index);
3277 
3278         abstract ViewGroup getRow(int rowNumber);
3279 
3280         abstract void setViewVisibility(int i, int visibility);
3281 
3282         public int getColumnCount() {
3283             return mColumnCount;
3284         }
3285 
3286         public void measure() {
3287             final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
3288             getViewGroup().measure(spec, spec);
3289             mMeasuredRowHeight = getViewGroup().getMeasuredHeight();
3290         }
3291 
3292         public int getMeasuredRowHeight() {
3293             return mMeasuredRowHeight;
3294         }
3295 
3296         public void setItemIndex(int itemIndex, int listIndex) {
3297             mItemIndices[itemIndex] = listIndex;
3298         }
3299 
3300         public int getItemIndex(int itemIndex) {
3301             return mItemIndices[itemIndex];
3302         }
3303 
3304         public View getView(int index) {
3305             return mCells[index];
3306         }
3307     }
3308 
3309     class SingleRowViewHolder extends RowViewHolder {
3310         private final ViewGroup mRow;
3311 
3312         SingleRowViewHolder(ViewGroup row, int cellCount) {
3313             super(cellCount);
3314 
3315             this.mRow = row;
3316         }
3317 
3318         public ViewGroup getViewGroup() {
3319             return mRow;
3320         }
3321 
3322         public ViewGroup getRowByIndex(int index) {
3323             return mRow;
3324         }
3325 
3326         public ViewGroup getRow(int rowNumber) {
3327             if (rowNumber == 0) return mRow;
3328             return null;
3329         }
3330 
3331         public ViewGroup addView(int index, View v) {
3332             mRow.addView(v);
3333             mCells[index] = v;
3334 
3335             return mRow;
3336         }
3337 
3338         public void setViewVisibility(int i, int visibility) {
3339             getView(i).setVisibility(visibility);
3340         }
3341     }
3342 
3343     class DirectShareViewHolder extends RowViewHolder {
3344         private final ViewGroup mParent;
3345         private final List<ViewGroup> mRows;
3346         private int mCellCountPerRow;
3347 
3348         private boolean mHideDirectShareExpansion = false;
3349         private int mDirectShareMinHeight = 0;
3350         private int mDirectShareCurrHeight = 0;
3351         private int mDirectShareMaxHeight = 0;
3352 
3353         private final boolean[] mCellVisibility;
3354 
3355         DirectShareViewHolder(ViewGroup parent, List<ViewGroup> rows, int cellCountPerRow) {
3356             super(rows.size() * cellCountPerRow);
3357 
3358             this.mParent = parent;
3359             this.mRows = rows;
3360             this.mCellCountPerRow = cellCountPerRow;
3361             this.mCellVisibility = new boolean[rows.size() * cellCountPerRow];
3362         }
3363 
3364         public ViewGroup addView(int index, View v) {
3365             ViewGroup row = getRowByIndex(index);
3366             row.addView(v);
3367             mCells[index] = v;
3368 
3369             return row;
3370         }
3371 
3372         public ViewGroup getViewGroup() {
3373             return mParent;
3374         }
3375 
3376         public ViewGroup getRowByIndex(int index) {
3377             return mRows.get(index / mCellCountPerRow);
3378         }
3379 
3380         public ViewGroup getRow(int rowNumber) {
3381             return mRows.get(rowNumber);
3382         }
3383 
3384         public void measure() {
3385             final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
3386             getRow(0).measure(spec, spec);
3387             getRow(1).measure(spec, spec);
3388 
3389             mDirectShareMinHeight = getRow(0).getMeasuredHeight();
3390             mDirectShareCurrHeight = mDirectShareCurrHeight > 0
3391                                          ? mDirectShareCurrHeight : mDirectShareMinHeight;
3392             mDirectShareMaxHeight = 2 * mDirectShareMinHeight;
3393         }
3394 
3395         public int getMeasuredRowHeight() {
3396             return mDirectShareCurrHeight;
3397         }
3398 
3399         public int getMinRowHeight() {
3400             return mDirectShareMinHeight;
3401         }
3402 
3403         public void setViewVisibility(int i, int visibility) {
3404             final View v = getView(i);
3405             if (visibility == View.VISIBLE) {
3406                 mCellVisibility[i] = true;
3407                 v.setVisibility(visibility);
3408                 v.setAlpha(1.0f);
3409             } else if (visibility == View.INVISIBLE && mCellVisibility[i]) {
3410                 mCellVisibility[i] = false;
3411 
3412                 ValueAnimator fadeAnim = ObjectAnimator.ofFloat(v, "alpha", 1.0f, 0f);
3413                 fadeAnim.setDuration(NO_DIRECT_SHARE_ANIM_IN_MILLIS);
3414                 fadeAnim.setInterpolator(new AccelerateInterpolator(1.0f));
3415                 fadeAnim.addListener(new AnimatorListenerAdapter() {
3416                     public void onAnimationEnd(Animator animation) {
3417                         v.setVisibility(View.INVISIBLE);
3418                     }
3419                 });
3420                 fadeAnim.start();
3421             }
3422         }
3423 
3424         public void handleScroll(AbsListView view, int y, int oldy, int maxTargetsPerRow) {
3425             // only exit early if fully collapsed, otherwise onListRebuilt() with shifting
3426             // targets can lock us into an expanded mode
3427             boolean notExpanded = mDirectShareCurrHeight == mDirectShareMinHeight;
3428             if (notExpanded) {
3429                 if (mHideDirectShareExpansion) {
3430                     return;
3431                 }
3432 
3433                 // only expand if we have more than maxTargetsPerRow, and delay that decision
3434                 // until they start to scroll
3435                 if (mChooserListAdapter.getSelectableServiceTargetCount() <= maxTargetsPerRow) {
3436                     mHideDirectShareExpansion = true;
3437                     return;
3438                 }
3439             }
3440 
3441             int yDiff = (int) ((oldy - y) * DIRECT_SHARE_EXPANSION_RATE);
3442 
3443             int prevHeight = mDirectShareCurrHeight;
3444             int newHeight = Math.min(prevHeight + yDiff, mDirectShareMaxHeight);
3445             newHeight = Math.max(newHeight, mDirectShareMinHeight);
3446             yDiff = newHeight - prevHeight;
3447 
3448             if (view == null || view.getChildCount() == 0 || yDiff == 0) {
3449                 return;
3450             }
3451 
3452             // locate the item to expand, and offset the rows below that one
3453             boolean foundExpansion = false;
3454             for (int i = 0; i < view.getChildCount(); i++) {
3455                 View child = view.getChildAt(i);
3456 
3457                 if (foundExpansion) {
3458                     child.offsetTopAndBottom(yDiff);
3459                 } else {
3460                     if (child.getTag() != null && child.getTag() instanceof DirectShareViewHolder) {
3461                         int widthSpec = MeasureSpec.makeMeasureSpec(child.getWidth(),
3462                                 MeasureSpec.EXACTLY);
3463                         int heightSpec = MeasureSpec.makeMeasureSpec(newHeight,
3464                                 MeasureSpec.EXACTLY);
3465                         child.measure(widthSpec, heightSpec);
3466                         child.getLayoutParams().height = child.getMeasuredHeight();
3467                         child.layout(child.getLeft(), child.getTop(), child.getRight(),
3468                                 child.getTop() + child.getMeasuredHeight());
3469 
3470                         foundExpansion = true;
3471                     }
3472                 }
3473             }
3474 
3475             if (foundExpansion) {
3476                 mDirectShareCurrHeight = newHeight;
3477             }
3478         }
3479     }
3480 
3481     static class ChooserTargetServiceConnection implements ServiceConnection {
3482         private DisplayResolveInfo mOriginalTarget;
3483         private ComponentName mConnectedComponent;
3484         private ChooserActivity mChooserActivity;
3485         private final Object mLock = new Object();
3486 
3487         private final IChooserTargetResult mChooserTargetResult = new IChooserTargetResult.Stub() {
3488             @Override
3489             public void sendResult(List<ChooserTarget> targets) throws RemoteException {
3490                 synchronized (mLock) {
3491                     if (mChooserActivity == null) {
3492                         Log.e(TAG, "destroyed ChooserTargetServiceConnection received result from "
3493                                 + mConnectedComponent + "; ignoring...");
3494                         return;
3495                     }
3496                     mChooserActivity.filterServiceTargets(
3497                             mOriginalTarget.getResolveInfo().activityInfo.packageName, targets);
3498                     final Message msg = Message.obtain();
3499                     msg.what = ChooserHandler.CHOOSER_TARGET_SERVICE_RESULT;
3500                     msg.obj = new ServiceResultInfo(mOriginalTarget, targets,
3501                             ChooserTargetServiceConnection.this);
3502                     mChooserActivity.mChooserHandler.sendMessage(msg);
3503                 }
3504             }
3505         };
3506 
3507         public ChooserTargetServiceConnection(ChooserActivity chooserActivity,
3508                 DisplayResolveInfo dri) {
3509             mChooserActivity = chooserActivity;
3510             mOriginalTarget = dri;
3511         }
3512 
3513         @Override
3514         public void onServiceConnected(ComponentName name, IBinder service) {
3515             if (DEBUG) Log.d(TAG, "onServiceConnected: " + name);
3516             synchronized (mLock) {
3517                 if (mChooserActivity == null) {
3518                     Log.e(TAG, "destroyed ChooserTargetServiceConnection got onServiceConnected");
3519                     return;
3520                 }
3521 
3522                 final IChooserTargetService icts = IChooserTargetService.Stub.asInterface(service);
3523                 try {
3524                     icts.getChooserTargets(mOriginalTarget.getResolvedComponentName(),
3525                             mOriginalTarget.getResolveInfo().filter, mChooserTargetResult);
3526                 } catch (RemoteException e) {
3527                     Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e);
3528                     mChooserActivity.unbindService(this);
3529                     mChooserActivity.mServiceConnections.remove(this);
3530                     destroy();
3531                 }
3532             }
3533         }
3534 
3535         @Override
3536         public void onServiceDisconnected(ComponentName name) {
3537             if (DEBUG) Log.d(TAG, "onServiceDisconnected: " + name);
3538             synchronized (mLock) {
3539                 if (mChooserActivity == null) {
3540                     Log.e(TAG,
3541                             "destroyed ChooserTargetServiceConnection got onServiceDisconnected");
3542                     return;
3543                 }
3544 
3545                 mChooserActivity.unbindService(this);
3546                 mChooserActivity.mServiceConnections.remove(this);
3547                 if (mChooserActivity.mServiceConnections.isEmpty()) {
3548                     mChooserActivity.sendVoiceChoicesIfNeeded();
3549                 }
3550                 mConnectedComponent = null;
3551                 destroy();
3552             }
3553         }
3554 
3555         public void destroy() {
3556             synchronized (mLock) {
3557                 mChooserActivity = null;
3558                 mOriginalTarget = null;
3559             }
3560         }
3561 
3562         @Override
3563         public String toString() {
3564             return "ChooserTargetServiceConnection{service="
3565                     + mConnectedComponent + ", activity="
3566                     + (mOriginalTarget != null
3567                     ? mOriginalTarget.getResolveInfo().activityInfo.toString()
3568                     : "<connection destroyed>") + "}";
3569         }
3570     }
3571 
3572     static class ServiceResultInfo {
3573         public final DisplayResolveInfo originalTarget;
3574         public final List<ChooserTarget> resultTargets;
3575         public final ChooserTargetServiceConnection connection;
3576 
3577         public ServiceResultInfo(DisplayResolveInfo ot, List<ChooserTarget> rt,
3578                 ChooserTargetServiceConnection c) {
3579             originalTarget = ot;
3580             resultTargets = rt;
3581             connection = c;
3582         }
3583     }
3584 
3585     static class RefinementResultReceiver extends ResultReceiver {
3586         private ChooserActivity mChooserActivity;
3587         private TargetInfo mSelectedTarget;
3588 
3589         public RefinementResultReceiver(ChooserActivity host, TargetInfo target,
3590                 Handler handler) {
3591             super(handler);
3592             mChooserActivity = host;
3593             mSelectedTarget = target;
3594         }
3595 
3596         @Override
3597         protected void onReceiveResult(int resultCode, Bundle resultData) {
3598             if (mChooserActivity == null) {
3599                 Log.e(TAG, "Destroyed RefinementResultReceiver received a result");
3600                 return;
3601             }
3602             if (resultData == null) {
3603                 Log.e(TAG, "RefinementResultReceiver received null resultData");
3604                 return;
3605             }
3606 
3607             switch (resultCode) {
3608                 case RESULT_CANCELED:
3609                     mChooserActivity.onRefinementCanceled();
3610                     break;
3611                 case RESULT_OK:
3612                     Parcelable intentParcelable = resultData.getParcelable(Intent.EXTRA_INTENT);
3613                     if (intentParcelable instanceof Intent) {
3614                         mChooserActivity.onRefinementResult(mSelectedTarget,
3615                                 (Intent) intentParcelable);
3616                     } else {
3617                         Log.e(TAG, "RefinementResultReceiver received RESULT_OK but no Intent"
3618                                 + " in resultData with key Intent.EXTRA_INTENT");
3619                     }
3620                     break;
3621                 default:
3622                     Log.w(TAG, "Unknown result code " + resultCode
3623                             + " sent to RefinementResultReceiver");
3624                     break;
3625             }
3626         }
3627 
3628         public void destroy() {
3629             mChooserActivity = null;
3630             mSelectedTarget = null;
3631         }
3632     }
3633 
3634     /**
3635      * Used internally to round image corners while obeying view padding.
3636      */
3637     public static class RoundedRectImageView extends ImageView {
3638         private int mRadius = 0;
3639         private Path mPath = new Path();
3640         private Paint mOverlayPaint = new Paint(0);
3641         private Paint mRoundRectPaint = new Paint(0);
3642         private Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
3643         private String mExtraImageCount = null;
3644 
3645         public RoundedRectImageView(Context context) {
3646             super(context);
3647         }
3648 
3649         public RoundedRectImageView(Context context, AttributeSet attrs) {
3650             this(context, attrs, 0);
3651         }
3652 
3653         public RoundedRectImageView(Context context, AttributeSet attrs, int defStyleAttr) {
3654             this(context, attrs, defStyleAttr, 0);
3655         }
3656 
3657         public RoundedRectImageView(Context context, AttributeSet attrs, int defStyleAttr,
3658                 int defStyleRes) {
3659             super(context, attrs, defStyleAttr, defStyleRes);
3660             mRadius = context.getResources().getDimensionPixelSize(R.dimen.chooser_corner_radius);
3661 
3662             mOverlayPaint.setColor(0x99000000);
3663             mOverlayPaint.setStyle(Paint.Style.FILL);
3664 
3665             mRoundRectPaint.setColor(context.getResources().getColor(R.color.chooser_row_divider));
3666             mRoundRectPaint.setStyle(Paint.Style.STROKE);
3667             mRoundRectPaint.setStrokeWidth(context.getResources()
3668                     .getDimensionPixelSize(R.dimen.chooser_preview_image_border));
3669 
3670             mTextPaint.setColor(Color.WHITE);
3671             mTextPaint.setTextSize(context.getResources()
3672                     .getDimensionPixelSize(R.dimen.chooser_preview_image_font_size));
3673             mTextPaint.setTextAlign(Paint.Align.CENTER);
3674         }
3675 
3676         private void updatePath(int width, int height) {
3677             mPath.reset();
3678 
3679             int imageWidth = width - getPaddingRight() - getPaddingLeft();
3680             int imageHeight = height - getPaddingBottom() - getPaddingTop();
3681             mPath.addRoundRect(getPaddingLeft(), getPaddingTop(), imageWidth, imageHeight, mRadius,
3682                     mRadius, Path.Direction.CW);
3683         }
3684 
3685         /**
3686           * Sets the corner radius on all corners
3687           *
3688           * param radius 0 for no radius, &gt; 0 for a visible corner radius
3689           */
3690         public void setRadius(int radius) {
3691             mRadius = radius;
3692             updatePath(getWidth(), getHeight());
3693         }
3694 
3695         /**
3696           * Display an overlay with extra image count on 3rd image
3697           */
3698         public void setExtraImageCount(int count) {
3699             if (count > 0) {
3700                 this.mExtraImageCount = "+" + count;
3701             } else {
3702                 this.mExtraImageCount = null;
3703             }
3704         }
3705 
3706         @Override
3707         protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
3708             super.onSizeChanged(width, height, oldWidth, oldHeight);
3709             updatePath(width, height);
3710         }
3711 
3712         @Override
3713         protected void onDraw(Canvas canvas) {
3714             if (mRadius != 0) {
3715                 canvas.clipPath(mPath);
3716             }
3717 
3718             super.onDraw(canvas);
3719 
3720             int x = getPaddingLeft();
3721             int y = getPaddingRight();
3722             int width = getWidth() - getPaddingRight() - getPaddingLeft();
3723             int height = getHeight() - getPaddingBottom() - getPaddingTop();
3724             if (mExtraImageCount != null) {
3725                 canvas.drawRect(x, y, width, height, mOverlayPaint);
3726 
3727                 int xPos = canvas.getWidth() / 2;
3728                 int yPos = (int) ((canvas.getHeight() / 2.0f)
3729                         - ((mTextPaint.descent() + mTextPaint.ascent()) / 2.0f));
3730 
3731                 canvas.drawText(mExtraImageCount, xPos, yPos, mTextPaint);
3732             }
3733 
3734             canvas.drawRoundRect(x, y, width, height, mRadius, mRadius, mRoundRectPaint);
3735         }
3736     }
3737 }
3738