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