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