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