• 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.intentresolver;
18 
19 import static com.android.intentresolver.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE;
20 import static com.android.intentresolver.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER;
21 
22 import android.annotation.Nullable;
23 import android.app.ActivityManager;
24 import android.app.prediction.AppTarget;
25 import android.content.ComponentName;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.pm.ActivityInfo;
29 import android.content.pm.LabeledIntent;
30 import android.content.pm.LauncherApps;
31 import android.content.pm.PackageManager;
32 import android.content.pm.ResolveInfo;
33 import android.content.pm.ShortcutInfo;
34 import android.graphics.Bitmap;
35 import android.graphics.drawable.BitmapDrawable;
36 import android.graphics.drawable.Drawable;
37 import android.graphics.drawable.Icon;
38 import android.os.AsyncTask;
39 import android.os.Trace;
40 import android.os.UserHandle;
41 import android.os.UserManager;
42 import android.provider.DeviceConfig;
43 import android.service.chooser.ChooserTarget;
44 import android.text.Layout;
45 import android.util.Log;
46 import android.view.View;
47 import android.view.ViewGroup;
48 import android.widget.TextView;
49 
50 import androidx.annotation.WorkerThread;
51 
52 import com.android.intentresolver.chooser.DisplayResolveInfo;
53 import com.android.intentresolver.chooser.MultiDisplayResolveInfo;
54 import com.android.intentresolver.chooser.NotSelectableTargetInfo;
55 import com.android.intentresolver.chooser.SelectableTargetInfo;
56 import com.android.intentresolver.chooser.TargetInfo;
57 import com.android.internal.annotations.VisibleForTesting;
58 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
59 
60 import java.util.ArrayList;
61 import java.util.HashMap;
62 import java.util.List;
63 import java.util.Map;
64 import java.util.stream.Collectors;
65 
66 public class ChooserListAdapter extends ResolverListAdapter {
67     private static final String TAG = "ChooserListAdapter";
68     private static final boolean DEBUG = false;
69 
70     public static final int NO_POSITION = -1;
71     public static final int TARGET_BAD = -1;
72     public static final int TARGET_CALLER = 0;
73     public static final int TARGET_SERVICE = 1;
74     public static final int TARGET_STANDARD = 2;
75     public static final int TARGET_STANDARD_AZ = 3;
76 
77     private static final int MAX_SUGGESTED_APP_TARGETS = 4;
78 
79     /** {@link #getBaseScore} */
80     public static final float CALLER_TARGET_SCORE_BOOST = 900.f;
81     /** {@link #getBaseScore} */
82     public static final float SHORTCUT_TARGET_SCORE_BOOST = 90.f;
83 
84     private final ChooserRequestParameters mChooserRequest;
85     private final int mMaxRankedTargets;
86 
87     private final ChooserActivityLogger mChooserActivityLogger;
88 
89     private final Map<TargetInfo, AsyncTask> mIconLoaders = new HashMap<>();
90 
91     // Reserve spots for incoming direct share targets by adding placeholders
92     private final TargetInfo mPlaceHolderTargetInfo;
93     private final List<TargetInfo> mServiceTargets = new ArrayList<>();
94     private final List<DisplayResolveInfo> mCallerTargets = new ArrayList<>();
95 
96     private final ShortcutSelectionLogic mShortcutSelectionLogic;
97 
98     // Sorted list of DisplayResolveInfos for the alphabetical app section.
99     private List<DisplayResolveInfo> mSortedList = new ArrayList<>();
100 
101     // For pinned direct share labels, if the text spans multiple lines, the TextView will consume
102     // the full width, even if the characters actually take up less than that. Measure the actual
103     // line widths and constrain the View's width based upon that so that the pin doesn't end up
104     // very far from the text.
105     private final View.OnLayoutChangeListener mPinTextSpacingListener =
106             new View.OnLayoutChangeListener() {
107                 @Override
108                 public void onLayoutChange(View v, int left, int top, int right, int bottom,
109                         int oldLeft, int oldTop, int oldRight, int oldBottom) {
110                     TextView textView = (TextView) v;
111                     Layout layout = textView.getLayout();
112                     if (layout != null) {
113                         int textWidth = 0;
114                         for (int line = 0; line < layout.getLineCount(); line++) {
115                             textWidth = Math.max((int) Math.ceil(layout.getLineMax(line)),
116                                     textWidth);
117                         }
118                         int desiredWidth = textWidth + textView.getPaddingLeft()
119                                 + textView.getPaddingRight();
120                         if (textView.getWidth() > desiredWidth) {
121                             ViewGroup.LayoutParams params = textView.getLayoutParams();
122                             params.width = desiredWidth;
123                             textView.setLayoutParams(params);
124                             // Need to wait until layout pass is over before requesting layout.
125                             textView.post(() -> textView.requestLayout());
126                         }
127                         textView.removeOnLayoutChangeListener(this);
128                     }
129                 }
130             };
131 
ChooserListAdapter( Context context, List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList, boolean filterLastUsed, ResolverListController resolverListController, UserHandle userHandle, Intent targetIntent, ResolverListCommunicator resolverListCommunicator, PackageManager packageManager, ChooserActivityLogger chooserActivityLogger, ChooserRequestParameters chooserRequest, int maxRankedTargets)132     public ChooserListAdapter(
133             Context context,
134             List<Intent> payloadIntents,
135             Intent[] initialIntents,
136             List<ResolveInfo> rList,
137             boolean filterLastUsed,
138             ResolverListController resolverListController,
139             UserHandle userHandle,
140             Intent targetIntent,
141             ResolverListCommunicator resolverListCommunicator,
142             PackageManager packageManager,
143             ChooserActivityLogger chooserActivityLogger,
144             ChooserRequestParameters chooserRequest,
145             int maxRankedTargets) {
146         // Don't send the initial intents through the shared ResolverActivity path,
147         // we want to separate them into a different section.
148         super(
149                 context,
150                 payloadIntents,
151                 null,
152                 rList,
153                 filterLastUsed,
154                 resolverListController,
155                 userHandle,
156                 targetIntent,
157                 resolverListCommunicator,
158                 false);
159 
160         mChooserRequest = chooserRequest;
161         mMaxRankedTargets = maxRankedTargets;
162 
163         mPlaceHolderTargetInfo = NotSelectableTargetInfo.newPlaceHolderTargetInfo(context);
164         createPlaceHolders();
165         mChooserActivityLogger = chooserActivityLogger;
166         mShortcutSelectionLogic = new ShortcutSelectionLogic(
167                 context.getResources().getInteger(R.integer.config_maxShortcutTargetsPerApp),
168                 DeviceConfig.getBoolean(
169                         DeviceConfig.NAMESPACE_SYSTEMUI,
170                         SystemUiDeviceConfigFlags.APPLY_SHARING_APP_LIMITS_IN_SYSUI,
171                         true)
172         );
173 
174         if (initialIntents != null) {
175             for (int i = 0; i < initialIntents.length; i++) {
176                 final Intent ii = initialIntents[i];
177                 if (ii == null) {
178                     continue;
179                 }
180 
181                 // We reimplement Intent#resolveActivityInfo here because if we have an
182                 // implicit intent, we want the ResolveInfo returned by PackageManager
183                 // instead of one we reconstruct ourselves. The ResolveInfo returned might
184                 // have extra metadata and resolvePackageName set and we want to respect that.
185                 ResolveInfo ri = null;
186                 ActivityInfo ai = null;
187                 final ComponentName cn = ii.getComponent();
188                 if (cn != null) {
189                     try {
190                         ai = packageManager.getActivityInfo(
191                                 ii.getComponent(),
192                                 PackageManager.ComponentInfoFlags.of(PackageManager.GET_META_DATA));
193                         ri = new ResolveInfo();
194                         ri.activityInfo = ai;
195                     } catch (PackageManager.NameNotFoundException ignored) {
196                         // ai will == null below
197                     }
198                 }
199                 if (ai == null) {
200                     // Because of AIDL bug, resolveActivity can't accept subclasses of Intent.
201                     final Intent rii = (ii.getClass() == Intent.class) ? ii : new Intent(ii);
202                     ri = packageManager.resolveActivity(
203                             rii,
204                             PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY));
205                     ai = ri != null ? ri.activityInfo : null;
206                 }
207                 if (ai == null) {
208                     Log.w(TAG, "No activity found for " + ii);
209                     continue;
210                 }
211                 UserManager userManager =
212                         (UserManager) context.getSystemService(Context.USER_SERVICE);
213                 if (ii instanceof LabeledIntent) {
214                     LabeledIntent li = (LabeledIntent) ii;
215                     ri.resolvePackageName = li.getSourcePackage();
216                     ri.labelRes = li.getLabelResource();
217                     ri.nonLocalizedLabel = li.getNonLocalizedLabel();
218                     ri.icon = li.getIconResource();
219                     ri.iconResourceId = ri.icon;
220                 }
221                 if (userManager.isManagedProfile()) {
222                     ri.noResourceId = true;
223                     ri.icon = 0;
224                 }
225                 DisplayResolveInfo displayResolveInfo = DisplayResolveInfo.newDisplayResolveInfo(
226                         ii, ri, ii, mPresentationFactory.makePresentationGetter(ri));
227                 mCallerTargets.add(displayResolveInfo);
228                 if (mCallerTargets.size() == MAX_SUGGESTED_APP_TARGETS) break;
229             }
230         }
231     }
232 
233     @Override
handlePackagesChanged()234     public void handlePackagesChanged() {
235         if (DEBUG) {
236             Log.d(TAG, "clearing queryTargets on package change");
237         }
238         createPlaceHolders();
239         mResolverListCommunicator.onHandlePackagesChanged(this);
240 
241     }
242 
createPlaceHolders()243     private void createPlaceHolders() {
244         mServiceTargets.clear();
245         for (int i = 0; i < mMaxRankedTargets; ++i) {
246             mServiceTargets.add(mPlaceHolderTargetInfo);
247         }
248     }
249 
250     @Override
onCreateView(ViewGroup parent)251     View onCreateView(ViewGroup parent) {
252         return mInflater.inflate(R.layout.resolve_grid_item, parent, false);
253     }
254 
255     @VisibleForTesting
256     @Override
onBindView(View view, TargetInfo info, int position)257     public void onBindView(View view, TargetInfo info, int position) {
258         final ViewHolder holder = (ViewHolder) view.getTag();
259 
260         if (info == null) {
261             holder.icon.setImageDrawable(loadIconPlaceholder());
262             return;
263         }
264 
265         holder.bindLabel(info.getDisplayLabel(), info.getExtendedInfo(), alwaysShowSubLabel());
266         holder.bindIcon(info, /*animate =*/ true);
267         if (info.isSelectableTargetInfo()) {
268             // direct share targets should append the application name for a better readout
269             DisplayResolveInfo rInfo = info.getDisplayResolveInfo();
270             CharSequence appName = rInfo != null ? rInfo.getDisplayLabel() : "";
271             CharSequence extendedInfo = info.getExtendedInfo();
272             String contentDescription = String.join(" ", info.getDisplayLabel(),
273                     extendedInfo != null ? extendedInfo : "", appName);
274             holder.updateContentDescription(contentDescription);
275             if (!info.hasDisplayIcon()) {
276                 loadDirectShareIcon((SelectableTargetInfo) info);
277             }
278         } else if (info.isDisplayResolveInfo()) {
279             DisplayResolveInfo dri = (DisplayResolveInfo) info;
280             if (!dri.hasDisplayIcon()) {
281                 loadIcon(dri);
282             }
283         }
284 
285         // If target is loading, show a special placeholder shape in the label, make unclickable
286         if (info.isPlaceHolderTargetInfo()) {
287             final int maxWidth = mContext.getResources().getDimensionPixelSize(
288                     R.dimen.chooser_direct_share_label_placeholder_max_width);
289             holder.text.setMaxWidth(maxWidth);
290             holder.text.setBackground(mContext.getResources().getDrawable(
291                     R.drawable.chooser_direct_share_label_placeholder, mContext.getTheme()));
292             // Prevent rippling by removing background containing ripple
293             holder.itemView.setBackground(null);
294         } else {
295             holder.text.setMaxWidth(Integer.MAX_VALUE);
296             holder.text.setBackground(null);
297             holder.itemView.setBackground(holder.defaultItemViewBackground);
298         }
299 
300         // Always remove the spacing listener, attach as needed to direct share targets below.
301         holder.text.removeOnLayoutChangeListener(mPinTextSpacingListener);
302 
303         if (info.isMultiDisplayResolveInfo()) {
304             // If the target is grouped show an indicator
305             Drawable bkg = mContext.getDrawable(R.drawable.chooser_group_background);
306             holder.text.setPaddingRelative(0, 0, bkg.getIntrinsicWidth() /* end */, 0);
307             holder.text.setBackground(bkg);
308         } else if (info.isPinned() && (getPositionTargetType(position) == TARGET_STANDARD
309                 || getPositionTargetType(position) == TARGET_SERVICE)) {
310             // If the appShare or directShare target is pinned and in the suggested row show a
311             // pinned indicator
312             Drawable bkg = mContext.getDrawable(R.drawable.chooser_pinned_background);
313             holder.text.setPaddingRelative(bkg.getIntrinsicWidth() /* start */, 0, 0, 0);
314             holder.text.setBackground(bkg);
315             holder.text.addOnLayoutChangeListener(mPinTextSpacingListener);
316         } else {
317             holder.text.setBackground(null);
318             holder.text.setPaddingRelative(0, 0, 0, 0);
319         }
320     }
321 
loadDirectShareIcon(SelectableTargetInfo info)322     private void loadDirectShareIcon(SelectableTargetInfo info) {
323         LoadDirectShareIconTask task = (LoadDirectShareIconTask) mIconLoaders.get(info);
324         if (task == null) {
325             task = createLoadDirectShareIconTask(info);
326             mIconLoaders.put(info, task);
327             task.loadIcon();
328         }
329     }
330 
331     @VisibleForTesting
createLoadDirectShareIconTask(SelectableTargetInfo info)332     protected LoadDirectShareIconTask createLoadDirectShareIconTask(SelectableTargetInfo info) {
333         return new LoadDirectShareIconTask(
334                 mContext.createContextAsUser(getUserHandle(), 0),
335                 info);
336     }
337 
updateAlphabeticalList()338     void updateAlphabeticalList() {
339         // TODO: this procedure seems like it should be relatively lightweight. Why does it need to
340         // run in an `AsyncTask`?
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(getTargetsInCurrentDisplayList());
346                 allTargets.addAll(mCallerTargets);
347 
348                 // Consolidate multiple targets from same app.
349                 return allTargets
350                         .stream()
351                         .collect(Collectors.groupingBy(target ->
352                                 target.getResolvedComponentName().getPackageName()
353                                 + "#" + target.getDisplayLabel()
354                         ))
355                         .values()
356                         .stream()
357                         .map(appTargets ->
358                                 (appTargets.size() == 1)
359                                 ? appTargets.get(0)
360                                 : MultiDisplayResolveInfo.newMultiDisplayResolveInfo(appTargets))
361                         .sorted(new ChooserActivity.AzInfoComparator(mContext))
362                         .collect(Collectors.toList());
363             }
364             @Override
365             protected void onPostExecute(List<DisplayResolveInfo> newList) {
366                 mSortedList = newList;
367                 notifyDataSetChanged();
368             }
369         }.execute();
370     }
371 
372     @Override
getCount()373     public int getCount() {
374         return getRankedTargetCount() + getAlphaTargetCount()
375                 + getSelectableServiceTargetCount() + getCallerTargetCount();
376     }
377 
378     @Override
getUnfilteredCount()379     public int getUnfilteredCount() {
380         int appTargets = super.getUnfilteredCount();
381         if (appTargets > mMaxRankedTargets) {
382             // TODO: what does this condition mean?
383             appTargets = appTargets + mMaxRankedTargets;
384         }
385         return appTargets + getSelectableServiceTargetCount() + getCallerTargetCount();
386     }
387 
388 
getCallerTargetCount()389     public int getCallerTargetCount() {
390         return mCallerTargets.size();
391     }
392 
393     /**
394      * Filter out placeholders and non-selectable service targets
395      */
getSelectableServiceTargetCount()396     public int getSelectableServiceTargetCount() {
397         int count = 0;
398         for (TargetInfo info : mServiceTargets) {
399             if (info.isSelectableTargetInfo()) {
400                 count++;
401             }
402         }
403         return count;
404     }
405 
getServiceTargetCount()406     public int getServiceTargetCount() {
407         if (mChooserRequest.isSendActionTarget() && !ActivityManager.isLowRamDeviceStatic()) {
408             return Math.min(mServiceTargets.size(), mMaxRankedTargets);
409         }
410 
411         return 0;
412     }
413 
getAlphaTargetCount()414     public int getAlphaTargetCount() {
415         int groupedCount = mSortedList.size();
416         int ungroupedCount = mCallerTargets.size() + getDisplayResolveInfoCount();
417         return (ungroupedCount > mMaxRankedTargets) ? groupedCount : 0;
418     }
419 
420     /**
421      * Fetch ranked app target count
422      */
getRankedTargetCount()423     public int getRankedTargetCount() {
424         int spacesAvailable = mMaxRankedTargets - getCallerTargetCount();
425         return Math.min(spacesAvailable, super.getCount());
426     }
427 
428     /** Get all the {@link DisplayResolveInfo} data for our targets. */
getDisplayResolveInfos()429     public DisplayResolveInfo[] getDisplayResolveInfos() {
430         int size = getDisplayResolveInfoCount();
431         DisplayResolveInfo[] resolvedTargets = new DisplayResolveInfo[size];
432         for (int i = 0; i < size; i++) {
433             resolvedTargets[i] = getDisplayResolveInfo(i);
434         }
435         return resolvedTargets;
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      * Find target info for a given position.
474      * Since ChooserActivity displays several sections of content, determine which
475      * section provides this item.
476      */
477     @Override
targetInfoForPosition(int position, boolean filtered)478     public TargetInfo targetInfoForPosition(int position, boolean filtered) {
479         if (position == NO_POSITION) {
480             return null;
481         }
482 
483         int offset = 0;
484 
485         // Direct share targets
486         final int serviceTargetCount = filtered ? getServiceTargetCount() :
487                 getSelectableServiceTargetCount();
488         if (position < serviceTargetCount) {
489             return mServiceTargets.get(position);
490         }
491         offset += serviceTargetCount;
492 
493         // Targets provided by calling app
494         final int callerTargetCount = getCallerTargetCount();
495         if (position - offset < callerTargetCount) {
496             return mCallerTargets.get(position - offset);
497         }
498         offset += callerTargetCount;
499 
500         // Ranked standard app targets
501         final int rankedTargetCount = getRankedTargetCount();
502         if (position - offset < rankedTargetCount) {
503             return filtered ? super.getItem(position - offset)
504                     : getDisplayResolveInfo(position - offset);
505         }
506         offset += rankedTargetCount;
507 
508         // Alphabetical complete app target list.
509         if (position - offset < getAlphaTargetCount() && !mSortedList.isEmpty()) {
510             return mSortedList.get(position - offset);
511         }
512 
513         return null;
514     }
515 
516     // Check whether {@code dri} should be added into mDisplayList.
517     @Override
shouldAddResolveInfo(DisplayResolveInfo dri)518     protected boolean shouldAddResolveInfo(DisplayResolveInfo dri) {
519         // Checks if this info is already listed in callerTargets.
520         for (TargetInfo existingInfo : mCallerTargets) {
521             if (mResolverListCommunicator.resolveInfoMatch(
522                     dri.getResolveInfo(), existingInfo.getResolveInfo())) {
523                 return false;
524             }
525         }
526         return super.shouldAddResolveInfo(dri);
527     }
528 
529     /**
530      * Fetch surfaced direct share target info
531      */
getSurfacedTargetInfo()532     public List<TargetInfo> getSurfacedTargetInfo() {
533         return mServiceTargets.subList(0,
534                 Math.min(mMaxRankedTargets, getSelectableServiceTargetCount()));
535     }
536 
537 
538     /**
539      * Evaluate targets for inclusion in the direct share area. May not be included
540      * if score is too low.
541      */
addServiceResults( @ullable DisplayResolveInfo origTarget, List<ChooserTarget> targets, @ChooserActivity.ShareTargetType int targetType, Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos, Map<ChooserTarget, AppTarget> directShareToAppTargets)542     public void addServiceResults(
543             @Nullable DisplayResolveInfo origTarget,
544             List<ChooserTarget> targets,
545             @ChooserActivity.ShareTargetType int targetType,
546             Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos,
547             Map<ChooserTarget, AppTarget> directShareToAppTargets) {
548         // Avoid inserting any potentially late results.
549         if ((mServiceTargets.size() == 1) && mServiceTargets.get(0).isEmptyTargetInfo()) {
550             return;
551         }
552         boolean isShortcutResult = targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER
553                 || targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE;
554         boolean isUpdated = mShortcutSelectionLogic.addServiceResults(
555                 origTarget,
556                 getBaseScore(origTarget, targetType),
557                 targets,
558                 isShortcutResult,
559                 directShareToShortcutInfos,
560                 directShareToAppTargets,
561                 mContext.createContextAsUser(getUserHandle(), 0),
562                 mChooserRequest.getTargetIntent(),
563                 mChooserRequest.getReferrerFillInIntent(),
564                 mMaxRankedTargets,
565                 mServiceTargets);
566         if (isUpdated) {
567             notifyDataSetChanged();
568         }
569     }
570 
571     /**
572      * Use the scoring system along with artificial boosts to create up to 4 distinct buckets:
573      * <ol>
574      *   <li>App-supplied targets
575      *   <li>Shortcuts ranked via App Prediction Manager
576      *   <li>Shortcuts ranked via legacy heuristics
577      *   <li>Legacy direct share targets
578      * </ol>
579      */
getBaseScore( DisplayResolveInfo target, @ChooserActivity.ShareTargetType int targetType)580     public float getBaseScore(
581             DisplayResolveInfo target,
582             @ChooserActivity.ShareTargetType int targetType) {
583         if (target == null) {
584             return CALLER_TARGET_SCORE_BOOST;
585         }
586         float score = super.getScore(target);
587         if (targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER
588                 || targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE) {
589             return score * SHORTCUT_TARGET_SCORE_BOOST;
590         }
591         return score;
592     }
593 
594     /**
595      * Calling this marks service target loading complete, and will attempt to no longer
596      * update the direct share area.
597      */
completeServiceTargetLoading()598     public void completeServiceTargetLoading() {
599         mServiceTargets.removeIf(o -> o.isPlaceHolderTargetInfo());
600         if (mServiceTargets.isEmpty()) {
601             mServiceTargets.add(NotSelectableTargetInfo.newEmptyTargetInfo());
602             mChooserActivityLogger.logSharesheetEmptyDirectShareRow();
603         }
604         notifyDataSetChanged();
605     }
606 
alwaysShowSubLabel()607     protected boolean alwaysShowSubLabel() {
608         // Always show a subLabel for visual consistency across list items. Show an empty
609         // subLabel if the subLabel is the same as the label
610         return true;
611     }
612 
613     /**
614      * Rather than fully sorting the input list, this sorting task will put the top k elements
615      * in the head of input list and fill the tail with other elements in undetermined order.
616      */
617     @Override
618     AsyncTask<List<ResolvedComponentInfo>,
619                 Void,
createSortingTask(boolean doPostProcessing)620                 List<ResolvedComponentInfo>> createSortingTask(boolean doPostProcessing) {
621         return new AsyncTask<List<ResolvedComponentInfo>,
622                 Void,
623                 List<ResolvedComponentInfo>>() {
624             @Override
625             protected List<ResolvedComponentInfo> doInBackground(
626                     List<ResolvedComponentInfo>... params) {
627                 Trace.beginSection("ChooserListAdapter#SortingTask");
628                 mResolverListController.topK(params[0], mMaxRankedTargets);
629                 Trace.endSection();
630                 return params[0];
631             }
632             @Override
633             protected void onPostExecute(List<ResolvedComponentInfo> sortedComponents) {
634                 processSortedList(sortedComponents, doPostProcessing);
635                 if (doPostProcessing) {
636                     mResolverListCommunicator.updateProfileViewButton();
637                     notifyDataSetChanged();
638                 }
639             }
640         };
641     }
642 
643     /**
644      * Loads direct share targets icons.
645      */
646     @VisibleForTesting
647     public class LoadDirectShareIconTask extends AsyncTask<Void, Void, Drawable> {
648         private final Context mContext;
649         private final SelectableTargetInfo mTargetInfo;
650 
651         private LoadDirectShareIconTask(Context context, SelectableTargetInfo targetInfo) {
652             mContext = context;
653             mTargetInfo = targetInfo;
654         }
655 
656         @Override
657         protected Drawable doInBackground(Void... voids) {
658             Drawable drawable;
659             try {
660                 drawable = getChooserTargetIconDrawable(
661                         mContext,
662                         mTargetInfo.getChooserTargetIcon(),
663                         mTargetInfo.getChooserTargetComponentName(),
664                         mTargetInfo.getDirectShareShortcutInfo());
665             } catch (Exception e) {
666                 Log.e(TAG,
667                         "Failed to load shortcut icon for "
668                                 + mTargetInfo.getChooserTargetComponentName(),
669                         e);
670                 drawable = loadIconPlaceholder();
671             }
672             return drawable;
673         }
674 
675         @Override
676         protected void onPostExecute(@Nullable Drawable icon) {
677             if (icon != null && !mTargetInfo.hasDisplayIcon()) {
678                 mTargetInfo.getDisplayIconHolder().setDisplayIcon(icon);
679                 notifyDataSetChanged();
680             }
681         }
682 
683         @WorkerThread
684         private Drawable getChooserTargetIconDrawable(
685                 Context context,
686                 @Nullable Icon icon,
687                 ComponentName targetComponentName,
688                 @Nullable ShortcutInfo shortcutInfo) {
689             Drawable directShareIcon = null;
690 
691             // First get the target drawable and associated activity info
692             if (icon != null) {
693                 directShareIcon = icon.loadDrawable(context);
694             } else if (shortcutInfo != null) {
695                 LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
696                 if (launcherApps != null) {
697                     directShareIcon = launcherApps.getShortcutIconDrawable(shortcutInfo, 0);
698                 }
699             }
700 
701             if (directShareIcon == null) {
702                 return null;
703             }
704 
705             ActivityInfo info = null;
706             try {
707                 info = context.getPackageManager().getActivityInfo(targetComponentName, 0);
708             } catch (PackageManager.NameNotFoundException error) {
709                 Log.e(TAG, "Could not find activity associated with ChooserTarget");
710             }
711 
712             if (info == null) {
713                 return null;
714             }
715 
716             // Now fetch app icon and raster with no badging even in work profile
717             Bitmap appIcon = mPresentationFactory.makePresentationGetter(info).getIconBitmap(null);
718 
719             // Raster target drawable with appIcon as a badge
720             SimpleIconFactory sif = SimpleIconFactory.obtain(context);
721             Bitmap directShareBadgedIcon = sif.createAppBadgedIconBitmap(directShareIcon, appIcon);
722             sif.recycle();
723 
724             return new BitmapDrawable(context.getResources(), directShareBadgedIcon);
725         }
726 
727         /**
728          * An alias for execute to use with unit tests.
729          */
730         public void loadIcon() {
731             execute();
732         }
733     }
734 }
735