• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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 com.android.internal.app.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE;
20 import static com.android.internal.app.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER;
21 
22 import android.app.ActivityManager;
23 import android.app.prediction.AppPredictor;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.pm.ActivityInfo;
28 import android.content.pm.LabeledIntent;
29 import android.content.pm.PackageManager;
30 import android.content.pm.ResolveInfo;
31 import android.content.pm.ShortcutInfo;
32 import android.graphics.drawable.Drawable;
33 import android.os.AsyncTask;
34 import android.os.Trace;
35 import android.os.UserHandle;
36 import android.os.UserManager;
37 import android.provider.DeviceConfig;
38 import android.service.chooser.ChooserTarget;
39 import android.text.Layout;
40 import android.util.Log;
41 import android.view.View;
42 import android.view.ViewGroup;
43 import android.widget.TextView;
44 
45 import com.android.internal.R;
46 import com.android.internal.annotations.VisibleForTesting;
47 import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
48 import com.android.internal.app.chooser.ChooserTargetInfo;
49 import com.android.internal.app.chooser.DisplayResolveInfo;
50 import com.android.internal.app.chooser.MultiDisplayResolveInfo;
51 import com.android.internal.app.chooser.SelectableTargetInfo;
52 import com.android.internal.app.chooser.TargetInfo;
53 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
54 
55 import java.util.ArrayList;
56 import java.util.Collections;
57 import java.util.HashMap;
58 import java.util.List;
59 import java.util.Map;
60 
61 public class ChooserListAdapter extends ResolverListAdapter {
62     private static final String TAG = "ChooserListAdapter";
63     private static final boolean DEBUG = false;
64 
65     private boolean mEnableStackedApps = true;
66 
67     public static final int NO_POSITION = -1;
68     public static final int TARGET_BAD = -1;
69     public static final int TARGET_CALLER = 0;
70     public static final int TARGET_SERVICE = 1;
71     public static final int TARGET_STANDARD = 2;
72     public static final int TARGET_STANDARD_AZ = 3;
73 
74     private static final int MAX_SUGGESTED_APP_TARGETS = 4;
75     private static final int MAX_CHOOSER_TARGETS_PER_APP = 2;
76 
77     /** {@link #getBaseScore} */
78     public static final float CALLER_TARGET_SCORE_BOOST = 900.f;
79     /** {@link #getBaseScore} */
80     public static final float SHORTCUT_TARGET_SCORE_BOOST = 90.f;
81     private static final float PINNED_SHORTCUT_TARGET_SCORE_BOOST = 1000.f;
82 
83     private final int mMaxShortcutTargetsPerApp;
84     private final ChooserListCommunicator mChooserListCommunicator;
85     private final SelectableTargetInfo.SelectableTargetInfoCommunicator
86             mSelectableTargetInfoCommunicator;
87     private final ChooserActivityLogger mChooserActivityLogger;
88 
89     private int mNumShortcutResults = 0;
90     private final Map<SelectableTargetInfo, LoadDirectShareIconTask> mIconLoaders = new HashMap<>();
91     private boolean mApplySharingAppLimits;
92 
93     // Reserve spots for incoming direct share targets by adding placeholders
94     private ChooserTargetInfo
95             mPlaceHolderTargetInfo = new ChooserActivity.PlaceHolderTargetInfo();
96     private final List<ChooserTargetInfo> mServiceTargets = new ArrayList<>();
97     private final List<DisplayResolveInfo> mCallerTargets = new ArrayList<>();
98 
99     private final ChooserActivity.BaseChooserTargetComparator mBaseTargetComparator =
100             new ChooserActivity.BaseChooserTargetComparator();
101     private boolean mListViewDataChanged = false;
102 
103     // Sorted list of DisplayResolveInfos for the alphabetical app section.
104     private List<DisplayResolveInfo> mSortedList = new ArrayList<>();
105     private AppPredictor mAppPredictor;
106     private AppPredictor.Callback mAppPredictorCallback;
107 
108     // For pinned direct share labels, if the text spans multiple lines, the TextView will consume
109     // the full width, even if the characters actually take up less than that. Measure the actual
110     // line widths and constrain the View's width based upon that so that the pin doesn't end up
111     // very far from the text.
112     private final View.OnLayoutChangeListener mPinTextSpacingListener =
113             new View.OnLayoutChangeListener() {
114                 @Override
115                 public void onLayoutChange(View v, int left, int top, int right, int bottom,
116                         int oldLeft, int oldTop, int oldRight, int oldBottom) {
117                     TextView textView = (TextView) v;
118                     Layout layout = textView.getLayout();
119                     if (layout != null) {
120                         int textWidth = 0;
121                         for (int line = 0; line < layout.getLineCount(); line++) {
122                             textWidth = Math.max((int) Math.ceil(layout.getLineMax(line)),
123                                     textWidth);
124                         }
125                         int desiredWidth = textWidth + textView.getPaddingLeft()
126                                 + textView.getPaddingRight();
127                         if (textView.getWidth() > desiredWidth) {
128                             ViewGroup.LayoutParams params = textView.getLayoutParams();
129                             params.width = desiredWidth;
130                             textView.setLayoutParams(params);
131                             // Need to wait until layout pass is over before requesting layout.
132                             textView.post(() -> textView.requestLayout());
133                         }
134                         textView.removeOnLayoutChangeListener(this);
135                     }
136                 }
137             };
138 
ChooserListAdapter(Context context, List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList, boolean filterLastUsed, ResolverListController resolverListController, ChooserListCommunicator chooserListCommunicator, SelectableTargetInfo.SelectableTargetInfoCommunicator selectableTargetInfoCommunicator, PackageManager packageManager, ChooserActivityLogger chooserActivityLogger)139     public ChooserListAdapter(Context context, List<Intent> payloadIntents,
140             Intent[] initialIntents, List<ResolveInfo> rList,
141             boolean filterLastUsed, ResolverListController resolverListController,
142             ChooserListCommunicator chooserListCommunicator,
143             SelectableTargetInfo.SelectableTargetInfoCommunicator selectableTargetInfoCommunicator,
144             PackageManager packageManager,
145             ChooserActivityLogger chooserActivityLogger) {
146         // Don't send the initial intents through the shared ResolverActivity path,
147         // we want to separate them into a different section.
148         super(context, payloadIntents, null, rList, filterLastUsed,
149                 resolverListController, chooserListCommunicator, false);
150 
151         mMaxShortcutTargetsPerApp =
152                 context.getResources().getInteger(R.integer.config_maxShortcutTargetsPerApp);
153         mChooserListCommunicator = chooserListCommunicator;
154         createPlaceHolders();
155         mSelectableTargetInfoCommunicator = selectableTargetInfoCommunicator;
156         mChooserActivityLogger = chooserActivityLogger;
157 
158         if (initialIntents != null) {
159             for (int i = 0; i < initialIntents.length; i++) {
160                 final Intent ii = initialIntents[i];
161                 if (ii == null) {
162                     continue;
163                 }
164 
165                 // We reimplement Intent#resolveActivityInfo here because if we have an
166                 // implicit intent, we want the ResolveInfo returned by PackageManager
167                 // instead of one we reconstruct ourselves. The ResolveInfo returned might
168                 // have extra metadata and resolvePackageName set and we want to respect that.
169                 ResolveInfo ri = null;
170                 ActivityInfo ai = null;
171                 final ComponentName cn = ii.getComponent();
172                 if (cn != null) {
173                     try {
174                         ai = packageManager.getActivityInfo(ii.getComponent(), 0);
175                         ri = new ResolveInfo();
176                         ri.activityInfo = ai;
177                     } catch (PackageManager.NameNotFoundException ignored) {
178                         // ai will == null below
179                     }
180                 }
181                 if (ai == null) {
182                     // Because of AIDL bug, resolveActivity can't accept subclasses of Intent.
183                     final Intent rii = (ii.getClass() == Intent.class) ? ii : new Intent(ii);
184                     ri = packageManager.resolveActivity(rii, PackageManager.MATCH_DEFAULT_ONLY);
185                     ai = ri != null ? ri.activityInfo : null;
186                 }
187                 if (ai == null) {
188                     Log.w(TAG, "No activity found for " + ii);
189                     continue;
190                 }
191                 UserManager userManager =
192                         (UserManager) context.getSystemService(Context.USER_SERVICE);
193                 if (ii instanceof LabeledIntent) {
194                     LabeledIntent li = (LabeledIntent) ii;
195                     ri.resolvePackageName = li.getSourcePackage();
196                     ri.labelRes = li.getLabelResource();
197                     ri.nonLocalizedLabel = li.getNonLocalizedLabel();
198                     ri.icon = li.getIconResource();
199                     ri.iconResourceId = ri.icon;
200                 }
201                 if (userManager.isManagedProfile()) {
202                     ri.noResourceId = true;
203                     ri.icon = 0;
204                 }
205                 mCallerTargets.add(new DisplayResolveInfo(ii, ri, ii, makePresentationGetter(ri)));
206                 if (mCallerTargets.size() == MAX_SUGGESTED_APP_TARGETS) break;
207             }
208         }
209         mApplySharingAppLimits = DeviceConfig.getBoolean(
210                 DeviceConfig.NAMESPACE_SYSTEMUI,
211                 SystemUiDeviceConfigFlags.APPLY_SHARING_APP_LIMITS_IN_SYSUI,
212                 true);
213     }
214 
getAppPredictor()215     AppPredictor getAppPredictor() {
216         return mAppPredictor;
217     }
218 
219     @Override
handlePackagesChanged()220     public void handlePackagesChanged() {
221         if (DEBUG) {
222             Log.d(TAG, "clearing queryTargets on package change");
223         }
224         createPlaceHolders();
225         mChooserListCommunicator.onHandlePackagesChanged(this);
226 
227     }
228 
229     @Override
notifyDataSetChanged()230     public void notifyDataSetChanged() {
231         if (!mListViewDataChanged) {
232             mChooserListCommunicator.sendListViewUpdateMessage(getUserHandle());
233             mListViewDataChanged = true;
234         }
235     }
236 
refreshListView()237     void refreshListView() {
238         if (mListViewDataChanged) {
239             super.notifyDataSetChanged();
240         }
241         mListViewDataChanged = false;
242     }
243 
createPlaceHolders()244     private void createPlaceHolders() {
245         mNumShortcutResults = 0;
246         mServiceTargets.clear();
247         for (int i = 0; i < mChooserListCommunicator.getMaxRankedTargets(); i++) {
248             mServiceTargets.add(mPlaceHolderTargetInfo);
249         }
250     }
251 
252     @Override
onCreateView(ViewGroup parent)253     View onCreateView(ViewGroup parent) {
254         return mInflater.inflate(
255                 com.android.internal.R.layout.resolve_grid_item, parent, false);
256     }
257 
258     @Override
onBindView(View view, TargetInfo info, int position)259     protected void onBindView(View view, TargetInfo info, int position) {
260         final ViewHolder holder = (ViewHolder) view.getTag();
261 
262         if (info == null) {
263             holder.icon.setImageDrawable(
264                     mContext.getDrawable(R.drawable.resolver_icon_placeholder));
265             return;
266         }
267 
268         holder.bindLabel(info.getDisplayLabel(), info.getExtendedInfo(), alwaysShowSubLabel());
269         holder.bindIcon(info);
270         if (info instanceof SelectableTargetInfo) {
271             // direct share targets should append the application name for a better readout
272             SelectableTargetInfo sti = (SelectableTargetInfo) info;
273             DisplayResolveInfo rInfo = sti.getDisplayResolveInfo();
274             CharSequence appName = rInfo != null ? rInfo.getDisplayLabel() : "";
275             CharSequence extendedInfo = info.getExtendedInfo();
276             String contentDescription = String.join(" ", info.getDisplayLabel(),
277                     extendedInfo != null ? extendedInfo : "", appName);
278             holder.updateContentDescription(contentDescription);
279             if (!sti.hasDisplayIcon()) {
280                 loadDirectShareIcon(sti);
281             }
282         } else if (info instanceof DisplayResolveInfo) {
283             DisplayResolveInfo dri = (DisplayResolveInfo) info;
284             if (!dri.hasDisplayIcon()) {
285                 loadIcon(dri);
286             }
287         }
288 
289         // If target is loading, show a special placeholder shape in the label, make unclickable
290         if (info instanceof ChooserActivity.PlaceHolderTargetInfo) {
291             final int maxWidth = mContext.getResources().getDimensionPixelSize(
292                     R.dimen.chooser_direct_share_label_placeholder_max_width);
293             holder.text.setMaxWidth(maxWidth);
294             holder.text.setBackground(mContext.getResources().getDrawable(
295                     R.drawable.chooser_direct_share_label_placeholder, mContext.getTheme()));
296             // Prevent rippling by removing background containing ripple
297             holder.itemView.setBackground(null);
298         } else {
299             holder.text.setMaxWidth(Integer.MAX_VALUE);
300             holder.text.setBackground(null);
301             holder.itemView.setBackground(holder.defaultItemViewBackground);
302         }
303 
304         // Always remove the spacing listener, attach as needed to direct share targets below.
305         holder.text.removeOnLayoutChangeListener(mPinTextSpacingListener);
306 
307         if (info instanceof MultiDisplayResolveInfo) {
308             // If the target is grouped show an indicator
309             Drawable bkg = mContext.getDrawable(R.drawable.chooser_group_background);
310             holder.text.setPaddingRelative(0, 0, bkg.getIntrinsicWidth() /* end */, 0);
311             holder.text.setBackground(bkg);
312         } else if (info.isPinned() && (getPositionTargetType(position) == TARGET_STANDARD
313                 || getPositionTargetType(position) == TARGET_SERVICE)) {
314             // If the appShare or directShare target is pinned and in the suggested row show a
315             // pinned indicator
316             Drawable bkg = mContext.getDrawable(R.drawable.chooser_pinned_background);
317             holder.text.setPaddingRelative(bkg.getIntrinsicWidth() /* start */, 0, 0, 0);
318             holder.text.setBackground(bkg);
319             holder.text.addOnLayoutChangeListener(mPinTextSpacingListener);
320         } else {
321             holder.text.setBackground(null);
322             holder.text.setPaddingRelative(0, 0, 0, 0);
323         }
324     }
325 
loadDirectShareIcon(SelectableTargetInfo info)326     private void loadDirectShareIcon(SelectableTargetInfo info) {
327         LoadDirectShareIconTask task = (LoadDirectShareIconTask) mIconLoaders.get(info);
328         if (task == null) {
329             task = createLoadDirectShareIconTask(info);
330             mIconLoaders.put(info, task);
331             task.loadIcon();
332         }
333     }
334 
335     @VisibleForTesting
createLoadDirectShareIconTask(SelectableTargetInfo info)336     protected LoadDirectShareIconTask createLoadDirectShareIconTask(SelectableTargetInfo info) {
337         return new LoadDirectShareIconTask(info);
338     }
339 
updateAlphabeticalList()340     void updateAlphabeticalList() {
341         new AsyncTask<Void, Void, List<DisplayResolveInfo>>() {
342             @Override
343             protected List<DisplayResolveInfo> doInBackground(Void... voids) {
344                 List<DisplayResolveInfo> allTargets = new ArrayList<>();
345                 allTargets.addAll(mDisplayList);
346                 allTargets.addAll(mCallerTargets);
347                 if (!mEnableStackedApps) {
348                     return allTargets;
349                 }
350                 // Consolidate multiple targets from same app.
351                 Map<String, DisplayResolveInfo> consolidated = new HashMap<>();
352                 for (DisplayResolveInfo info : allTargets) {
353                     String resolvedTarget = info.getResolvedComponentName().getPackageName()
354                             + '#' + info.getDisplayLabel();
355                     DisplayResolveInfo multiDri = consolidated.get(resolvedTarget);
356                     if (multiDri == null) {
357                         consolidated.put(resolvedTarget, info);
358                     } else if (multiDri instanceof MultiDisplayResolveInfo) {
359                         ((MultiDisplayResolveInfo) multiDri).addTarget(info);
360                     } else {
361                         // create consolidated target from the single DisplayResolveInfo
362                         MultiDisplayResolveInfo multiDisplayResolveInfo =
363                                 new MultiDisplayResolveInfo(resolvedTarget, multiDri);
364                         multiDisplayResolveInfo.addTarget(info);
365                         consolidated.put(resolvedTarget, multiDisplayResolveInfo);
366                     }
367                 }
368                 List<DisplayResolveInfo> groupedTargets = new ArrayList<>();
369                 groupedTargets.addAll(consolidated.values());
370                 Collections.sort(groupedTargets, new ChooserActivity.AzInfoComparator(mContext));
371                 return groupedTargets;
372             }
373             @Override
374             protected void onPostExecute(List<DisplayResolveInfo> newList) {
375                 mSortedList = newList;
376                 notifyDataSetChanged();
377             }
378         }.execute();
379     }
380 
381     @Override
getCount()382     public int getCount() {
383         return getRankedTargetCount() + getAlphaTargetCount()
384                 + getSelectableServiceTargetCount() + getCallerTargetCount();
385     }
386 
387     @Override
getUnfilteredCount()388     public int getUnfilteredCount() {
389         int appTargets = super.getUnfilteredCount();
390         if (appTargets > mChooserListCommunicator.getMaxRankedTargets()) {
391             appTargets = appTargets + mChooserListCommunicator.getMaxRankedTargets();
392         }
393         return appTargets + getSelectableServiceTargetCount() + getCallerTargetCount();
394     }
395 
396 
getCallerTargetCount()397     public int getCallerTargetCount() {
398         return mCallerTargets.size();
399     }
400 
401     /**
402      * Filter out placeholders and non-selectable service targets
403      */
getSelectableServiceTargetCount()404     public int getSelectableServiceTargetCount() {
405         int count = 0;
406         for (ChooserTargetInfo info : mServiceTargets) {
407             if (info instanceof SelectableTargetInfo) {
408                 count++;
409             }
410         }
411         return count;
412     }
413 
getServiceTargetCount()414     public int getServiceTargetCount() {
415         if (mChooserListCommunicator.isSendAction(mChooserListCommunicator.getTargetIntent())
416                 && !ActivityManager.isLowRamDeviceStatic()) {
417             return Math.min(mServiceTargets.size(), mChooserListCommunicator.getMaxRankedTargets());
418         }
419 
420         return 0;
421     }
422 
getAlphaTargetCount()423     int getAlphaTargetCount() {
424         int groupedCount = mSortedList.size();
425         int ungroupedCount = mCallerTargets.size() + mDisplayList.size();
426         return ungroupedCount > mChooserListCommunicator.getMaxRankedTargets() ? groupedCount : 0;
427     }
428 
429     /**
430      * Fetch ranked app target count
431      */
getRankedTargetCount()432     public int getRankedTargetCount() {
433         int spacesAvailable =
434                 mChooserListCommunicator.getMaxRankedTargets() - getCallerTargetCount();
435         return Math.min(spacesAvailable, super.getCount());
436     }
437 
getPositionTargetType(int position)438     public int getPositionTargetType(int position) {
439         int offset = 0;
440 
441         final int serviceTargetCount = getServiceTargetCount();
442         if (position < serviceTargetCount) {
443             return TARGET_SERVICE;
444         }
445         offset += serviceTargetCount;
446 
447         final int callerTargetCount = getCallerTargetCount();
448         if (position - offset < callerTargetCount) {
449             return TARGET_CALLER;
450         }
451         offset += callerTargetCount;
452 
453         final int rankedTargetCount = getRankedTargetCount();
454         if (position - offset < rankedTargetCount) {
455             return TARGET_STANDARD;
456         }
457         offset += rankedTargetCount;
458 
459         final int standardTargetCount = getAlphaTargetCount();
460         if (position - offset < standardTargetCount) {
461             return TARGET_STANDARD_AZ;
462         }
463 
464         return TARGET_BAD;
465     }
466 
467     @Override
getItem(int position)468     public TargetInfo getItem(int position) {
469         return targetInfoForPosition(position, true);
470     }
471 
472 
473     /**
474      * Find target info for a given position.
475      * Since ChooserActivity displays several sections of content, determine which
476      * section provides this item.
477      */
478     @Override
targetInfoForPosition(int position, boolean filtered)479     public TargetInfo targetInfoForPosition(int position, boolean filtered) {
480         if (position == NO_POSITION) {
481             return null;
482         }
483 
484         int offset = 0;
485 
486         // Direct share targets
487         final int serviceTargetCount = filtered ? getServiceTargetCount() :
488                 getSelectableServiceTargetCount();
489         if (position < serviceTargetCount) {
490             return mServiceTargets.get(position);
491         }
492         offset += serviceTargetCount;
493 
494         // Targets provided by calling app
495         final int callerTargetCount = getCallerTargetCount();
496         if (position - offset < callerTargetCount) {
497             return mCallerTargets.get(position - offset);
498         }
499         offset += callerTargetCount;
500 
501         // Ranked standard app targets
502         final int rankedTargetCount = getRankedTargetCount();
503         if (position - offset < rankedTargetCount) {
504             return filtered ? super.getItem(position - offset)
505                     : getDisplayResolveInfo(position - offset);
506         }
507         offset += rankedTargetCount;
508 
509         // Alphabetical complete app target list.
510         if (position - offset < getAlphaTargetCount() && !mSortedList.isEmpty()) {
511             return mSortedList.get(position - offset);
512         }
513 
514         return null;
515     }
516 
517     // Check whether {@code dri} should be added into mDisplayList.
518     @Override
shouldAddResolveInfo(DisplayResolveInfo dri)519     protected boolean shouldAddResolveInfo(DisplayResolveInfo dri) {
520         // Checks if this info is already listed in callerTargets.
521         for (TargetInfo existingInfo : mCallerTargets) {
522             if (mResolverListCommunicator
523                     .resolveInfoMatch(dri.getResolveInfo(), existingInfo.getResolveInfo())) {
524                 return false;
525             }
526         }
527         return super.shouldAddResolveInfo(dri);
528     }
529 
530     /**
531      * Fetch surfaced direct share target info
532      */
getSurfacedTargetInfo()533     public List<ChooserTargetInfo> getSurfacedTargetInfo() {
534         int maxSurfacedTargets = mChooserListCommunicator.getMaxRankedTargets();
535         return mServiceTargets.subList(0,
536                 Math.min(maxSurfacedTargets, getSelectableServiceTargetCount()));
537     }
538 
539 
540     /**
541      * Evaluate targets for inclusion in the direct share area. May not be included
542      * if score is too low.
543      */
addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets, @ChooserActivity.ShareTargetType int targetType, Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos)544     public void addServiceResults(DisplayResolveInfo origTarget, List<ChooserTarget> targets,
545             @ChooserActivity.ShareTargetType int targetType,
546             Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos) {
547         if (DEBUG) {
548             Log.d(TAG, "addServiceResults " + origTarget.getResolvedComponentName() + ", "
549                     + targets.size()
550                     + " targets");
551         }
552         if (targets.size() == 0) {
553             return;
554         }
555         final float baseScore = getBaseScore(origTarget, targetType);
556         Collections.sort(targets, mBaseTargetComparator);
557         final boolean isShortcutResult =
558                 (targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER
559                         || targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE);
560         final int maxTargets = isShortcutResult ? mMaxShortcutTargetsPerApp
561                 : MAX_CHOOSER_TARGETS_PER_APP;
562         final int targetsLimit = mApplySharingAppLimits ? Math.min(targets.size(), maxTargets)
563                 : targets.size();
564         float lastScore = 0;
565         boolean shouldNotify = false;
566         for (int i = 0, count = targetsLimit; i < count; i++) {
567             final ChooserTarget target = targets.get(i);
568             float targetScore = target.getScore();
569             if (mApplySharingAppLimits) {
570                 targetScore *= baseScore;
571                 if (i > 0 && targetScore >= lastScore) {
572                     // Apply a decay so that the top app can't crowd out everything else.
573                     // This incents ChooserTargetServices to define what's truly better.
574                     targetScore = lastScore * 0.95f;
575                 }
576             }
577             ShortcutInfo shortcutInfo = isShortcutResult ? directShareToShortcutInfos.get(target)
578                     : null;
579             if ((shortcutInfo != null) && shortcutInfo.isPinned()) {
580                 targetScore += PINNED_SHORTCUT_TARGET_SCORE_BOOST;
581             }
582             UserHandle userHandle = getUserHandle();
583             Context contextAsUser = mContext.createContextAsUser(userHandle, 0 /* flags */);
584             boolean isInserted = insertServiceTarget(new SelectableTargetInfo(contextAsUser,
585                     origTarget, target, targetScore, mSelectableTargetInfoCommunicator,
586                     shortcutInfo));
587 
588             if (isInserted && isShortcutResult) {
589                 mNumShortcutResults++;
590             }
591 
592             shouldNotify |= isInserted;
593 
594             if (DEBUG) {
595                 Log.d(TAG, " => " + target.toString() + " score=" + targetScore
596                         + " base=" + target.getScore()
597                         + " lastScore=" + lastScore
598                         + " baseScore=" + baseScore
599                         + " applyAppLimit=" + mApplySharingAppLimits);
600             }
601 
602             lastScore = targetScore;
603         }
604 
605         if (shouldNotify) {
606             notifyDataSetChanged();
607         }
608     }
609 
610     /**
611      * The return number have to exceed a minimum limit to make direct share area expandable. When
612      * append direct share targets is enabled, return count of all available targets parking in the
613      * memory; otherwise, it is shortcuts count which will help reduce the amount of visible
614      * shuffling due to older-style direct share targets.
615      */
getNumServiceTargetsForExpand()616     int getNumServiceTargetsForExpand() {
617         return mNumShortcutResults;
618     }
619 
620     /**
621      * Use the scoring system along with artificial boosts to create up to 4 distinct buckets:
622      * <ol>
623      *   <li>App-supplied targets
624      *   <li>Shortcuts ranked via App Prediction Manager
625      *   <li>Shortcuts ranked via legacy heuristics
626      *   <li>Legacy direct share targets
627      * </ol>
628      */
getBaseScore( DisplayResolveInfo target, @ChooserActivity.ShareTargetType int targetType)629     public float getBaseScore(
630             DisplayResolveInfo target,
631             @ChooserActivity.ShareTargetType int targetType) {
632         if (target == null) {
633             return CALLER_TARGET_SCORE_BOOST;
634         }
635         float score = super.getScore(target);
636         if (targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER
637                 || targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE) {
638             return score * SHORTCUT_TARGET_SCORE_BOOST;
639         }
640         return score;
641     }
642 
643     /**
644      * Calling this marks service target loading complete, and will attempt to no longer
645      * update the direct share area.
646      */
completeServiceTargetLoading()647     public void completeServiceTargetLoading() {
648         mServiceTargets.removeIf(o -> o instanceof ChooserActivity.PlaceHolderTargetInfo);
649         if (mServiceTargets.isEmpty()) {
650             mServiceTargets.add(new ChooserActivity.EmptyTargetInfo());
651             mChooserActivityLogger.logSharesheetEmptyDirectShareRow();
652         }
653         notifyDataSetChanged();
654     }
655 
insertServiceTarget(ChooserTargetInfo chooserTargetInfo)656     private boolean insertServiceTarget(ChooserTargetInfo chooserTargetInfo) {
657         // Avoid inserting any potentially late results
658         if (mServiceTargets.size() == 1
659                 && mServiceTargets.get(0) instanceof ChooserActivity.EmptyTargetInfo) {
660             return false;
661         }
662 
663         // Check for duplicates and abort if found
664         for (ChooserTargetInfo otherTargetInfo : mServiceTargets) {
665             if (chooserTargetInfo.isSimilar(otherTargetInfo)) {
666                 return false;
667             }
668         }
669 
670         int currentSize = mServiceTargets.size();
671         final float newScore = chooserTargetInfo.getModifiedScore();
672         for (int i = 0; i < Math.min(currentSize, mChooserListCommunicator.getMaxRankedTargets());
673                 i++) {
674             final ChooserTargetInfo serviceTarget = mServiceTargets.get(i);
675             if (serviceTarget == null) {
676                 mServiceTargets.set(i, chooserTargetInfo);
677                 return true;
678             } else if (newScore > serviceTarget.getModifiedScore()) {
679                 mServiceTargets.add(i, chooserTargetInfo);
680                 return true;
681             }
682         }
683 
684         if (currentSize < mChooserListCommunicator.getMaxRankedTargets()) {
685             mServiceTargets.add(chooserTargetInfo);
686             return true;
687         }
688 
689         return false;
690     }
691 
getChooserTargetForValue(int value)692     public ChooserTarget getChooserTargetForValue(int value) {
693         return mServiceTargets.get(value).getChooserTarget();
694     }
695 
alwaysShowSubLabel()696     protected boolean alwaysShowSubLabel() {
697         // Always show a subLabel for visual consistency across list items. Show an empty
698         // subLabel if the subLabel is the same as the label
699         return true;
700     }
701 
702     /**
703      * Rather than fully sorting the input list, this sorting task will put the top k elements
704      * in the head of input list and fill the tail with other elements in undetermined order.
705      */
706     @Override
707     AsyncTask<List<ResolvedComponentInfo>,
708                 Void,
createSortingTask(boolean doPostProcessing)709                 List<ResolvedComponentInfo>> createSortingTask(boolean doPostProcessing) {
710         return new AsyncTask<List<ResolvedComponentInfo>,
711                 Void,
712                 List<ResolvedComponentInfo>>() {
713             @Override
714             protected List<ResolvedComponentInfo> doInBackground(
715                     List<ResolvedComponentInfo>... params) {
716                 Trace.beginSection("ChooserListAdapter#SortingTask");
717                 mResolverListController.topK(params[0],
718                         mChooserListCommunicator.getMaxRankedTargets());
719                 Trace.endSection();
720                 return params[0];
721             }
722             @Override
723             protected void onPostExecute(List<ResolvedComponentInfo> sortedComponents) {
724                 processSortedList(sortedComponents, doPostProcessing);
725                 if (doPostProcessing) {
726                     mChooserListCommunicator.updateProfileViewButton();
727                     notifyDataSetChanged();
728                 }
729             }
730         };
731     }
732 
733     public void setAppPredictor(AppPredictor appPredictor) {
734         mAppPredictor = appPredictor;
735     }
736 
737     public void setAppPredictorCallback(AppPredictor.Callback appPredictorCallback) {
738         mAppPredictorCallback = appPredictorCallback;
739     }
740 
741     public void destroyAppPredictor() {
742         if (getAppPredictor() != null) {
743             getAppPredictor().unregisterPredictionUpdates(mAppPredictorCallback);
744             getAppPredictor().destroy();
745             setAppPredictor(null);
746         }
747     }
748 
749     /**
750      * Necessary methods to communicate between {@link ChooserListAdapter}
751      * and {@link ChooserActivity}.
752      */
753     @VisibleForTesting
754     public interface ChooserListCommunicator extends ResolverListCommunicator {
755 
756         int getMaxRankedTargets();
757 
758         void sendListViewUpdateMessage(UserHandle userHandle);
759 
760         boolean isSendAction(Intent targetIntent);
761     }
762 
763     /**
764      * Loads direct share targets icons.
765      */
766     @VisibleForTesting
767     public class LoadDirectShareIconTask extends AsyncTask<Void, Void, Boolean> {
768         private final SelectableTargetInfo mTargetInfo;
769 
770         private LoadDirectShareIconTask(SelectableTargetInfo targetInfo) {
771             mTargetInfo = targetInfo;
772         }
773 
774         @Override
775         protected Boolean doInBackground(Void... voids) {
776             return mTargetInfo.loadIcon();
777         }
778 
779         @Override
780         protected void onPostExecute(Boolean isLoaded) {
781             if (isLoaded) {
782                 notifyDataSetChanged();
783             }
784         }
785 
786         /**
787          * An alias for execute to use with unit tests.
788          */
789         public void loadIcon() {
790             execute();
791         }
792     }
793 }
794