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.app.ActivityManager; 23 import android.app.prediction.AppTarget; 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.pm.ActivityInfo; 28 import android.content.pm.LabeledIntent; 29 import android.content.pm.PackageManager; 30 import android.content.pm.ResolveInfo; 31 import android.content.pm.ShortcutInfo; 32 import android.graphics.drawable.Drawable; 33 import android.os.AsyncTask; 34 import android.os.Trace; 35 import android.os.UserHandle; 36 import android.os.UserManager; 37 import android.provider.DeviceConfig; 38 import android.service.chooser.ChooserTarget; 39 import android.text.Layout; 40 import android.text.TextUtils; 41 import android.util.Log; 42 import android.view.View; 43 import android.view.ViewGroup; 44 import android.widget.TextView; 45 46 import androidx.annotation.MainThread; 47 import androidx.annotation.Nullable; 48 import androidx.annotation.WorkerThread; 49 50 import com.android.intentresolver.chooser.DisplayResolveInfo; 51 import com.android.intentresolver.chooser.DisplayResolveInfoAzInfoComparator; 52 import com.android.intentresolver.chooser.MultiDisplayResolveInfo; 53 import com.android.intentresolver.chooser.NotSelectableTargetInfo; 54 import com.android.intentresolver.chooser.SelectableTargetInfo; 55 import com.android.intentresolver.chooser.TargetInfo; 56 import com.android.intentresolver.icons.TargetDataLoader; 57 import com.android.intentresolver.logging.EventLog; 58 import com.android.intentresolver.widget.BadgeTextView; 59 import com.android.internal.annotations.VisibleForTesting; 60 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; 61 62 import java.util.ArrayList; 63 import java.util.HashSet; 64 import java.util.List; 65 import java.util.Map; 66 import java.util.Objects; 67 import java.util.Set; 68 import java.util.concurrent.Executor; 69 import java.util.stream.Collectors; 70 71 public class ChooserListAdapter extends ResolverListAdapter { 72 73 /** 74 * Delegate interface for injecting a chooser-specific operation to be performed before handling 75 * a package-change event. This allows the "driver" invoking the package-change to be generic, 76 * with no knowledge specific to the chooser implementation. 77 */ 78 public interface PackageChangeCallback { 79 /** Perform any steps necessary before processing the package-change event. */ beforeHandlingPackagesChanged()80 void beforeHandlingPackagesChanged(); 81 } 82 83 private static final String TAG = "ChooserListAdapter"; 84 private static final boolean DEBUG = false; 85 86 public static final int NO_POSITION = -1; 87 public static final int TARGET_BAD = -1; 88 public static final int TARGET_CALLER = 0; 89 public static final int TARGET_SERVICE = 1; 90 public static final int TARGET_STANDARD = 2; 91 public static final int TARGET_STANDARD_AZ = 3; 92 93 private static final int MAX_SUGGESTED_APP_TARGETS = 4; 94 95 /** {@link #getBaseScore} */ 96 public static final float CALLER_TARGET_SCORE_BOOST = 900.f; 97 /** {@link #getBaseScore} */ 98 public static final float SHORTCUT_TARGET_SCORE_BOOST = 90.f; 99 100 private final Intent mReferrerFillInIntent; 101 102 private final int mMaxRankedTargets; 103 104 private final EventLog mEventLog; 105 106 private final Set<TargetInfo> mRequestedIcons = new HashSet<>(); 107 108 @Nullable 109 private final PackageChangeCallback mPackageChangeCallback; 110 111 // Reserve spots for incoming direct share targets by adding placeholders 112 private final TargetInfo mPlaceHolderTargetInfo; 113 private final TargetDataLoader mTargetDataLoader; 114 private final boolean mUseBadgeTextViewForLabels; 115 private final List<TargetInfo> mServiceTargets = new ArrayList<>(); 116 private final List<DisplayResolveInfo> mCallerTargets = new ArrayList<>(); 117 118 private final ShortcutSelectionLogic mShortcutSelectionLogic; 119 120 // Sorted list of DisplayResolveInfos for the alphabetical app section. 121 private final List<DisplayResolveInfo> mSortedList = new ArrayList<>(); 122 123 private final ItemRevealAnimationTracker mAnimationTracker = new ItemRevealAnimationTracker(); 124 125 // For pinned direct share labels, if the text spans multiple lines, the TextView will consume 126 // the full width, even if the characters actually take up less than that. Measure the actual 127 // line widths and constrain the View's width based upon that so that the pin doesn't end up 128 // very far from the text. 129 private final View.OnLayoutChangeListener mPinTextSpacingListener = 130 new View.OnLayoutChangeListener() { 131 @Override 132 public void onLayoutChange(View v, int left, int top, int right, int bottom, 133 int oldLeft, int oldTop, int oldRight, int oldBottom) { 134 TextView textView = (TextView) v; 135 Layout layout = textView.getLayout(); 136 if (layout != null) { 137 int textWidth = 0; 138 for (int line = 0; line < layout.getLineCount(); line++) { 139 textWidth = Math.max((int) Math.ceil(layout.getLineMax(line)), 140 textWidth); 141 } 142 int desiredWidth = textWidth + textView.getPaddingLeft() 143 + textView.getPaddingRight(); 144 if (textView.getWidth() > desiredWidth) { 145 ViewGroup.LayoutParams params = textView.getLayoutParams(); 146 params.width = desiredWidth; 147 textView.setLayoutParams(params); 148 // Need to wait until layout pass is over before requesting layout. 149 textView.post(() -> textView.requestLayout()); 150 } 151 textView.removeOnLayoutChangeListener(this); 152 } 153 } 154 }; 155 156 private boolean mAnimateItems = true; 157 ChooserListAdapter( Context context, List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList, boolean filterLastUsed, ResolverListController resolverListController, UserHandle userHandle, Intent targetIntent, Intent referrerFillInIntent, ResolverListCommunicator resolverListCommunicator, PackageManager packageManager, EventLog eventLog, int maxRankedTargets, UserHandle initialIntentsUserSpace, TargetDataLoader targetDataLoader, @Nullable PackageChangeCallback packageChangeCallback, FeatureFlags featureFlags)158 public ChooserListAdapter( 159 Context context, 160 List<Intent> payloadIntents, 161 Intent[] initialIntents, 162 List<ResolveInfo> rList, 163 boolean filterLastUsed, 164 ResolverListController resolverListController, 165 UserHandle userHandle, 166 Intent targetIntent, 167 Intent referrerFillInIntent, 168 ResolverListCommunicator resolverListCommunicator, 169 PackageManager packageManager, 170 EventLog eventLog, 171 int maxRankedTargets, 172 UserHandle initialIntentsUserSpace, 173 TargetDataLoader targetDataLoader, 174 @Nullable PackageChangeCallback packageChangeCallback, 175 FeatureFlags featureFlags) { 176 this( 177 context, 178 payloadIntents, 179 initialIntents, 180 rList, 181 filterLastUsed, 182 resolverListController, 183 userHandle, 184 targetIntent, 185 referrerFillInIntent, 186 resolverListCommunicator, 187 packageManager, 188 eventLog, 189 maxRankedTargets, 190 initialIntentsUserSpace, 191 targetDataLoader, 192 packageChangeCallback, 193 AsyncTask.SERIAL_EXECUTOR, 194 context.getMainExecutor(), 195 featureFlags); 196 } 197 198 @VisibleForTesting ChooserListAdapter( Context context, List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList, boolean filterLastUsed, ResolverListController resolverListController, UserHandle userHandle, Intent targetIntent, Intent referrerFillInIntent, ResolverListCommunicator resolverListCommunicator, PackageManager packageManager, EventLog eventLog, int maxRankedTargets, UserHandle initialIntentsUserSpace, TargetDataLoader targetDataLoader, @Nullable PackageChangeCallback packageChangeCallback, Executor bgExecutor, Executor mainExecutor, FeatureFlags featureFlags)199 public ChooserListAdapter( 200 Context context, 201 List<Intent> payloadIntents, 202 Intent[] initialIntents, 203 List<ResolveInfo> rList, 204 boolean filterLastUsed, 205 ResolverListController resolverListController, 206 UserHandle userHandle, 207 Intent targetIntent, 208 Intent referrerFillInIntent, 209 ResolverListCommunicator resolverListCommunicator, 210 PackageManager packageManager, 211 EventLog eventLog, 212 int maxRankedTargets, 213 UserHandle initialIntentsUserSpace, 214 TargetDataLoader targetDataLoader, 215 @Nullable PackageChangeCallback packageChangeCallback, 216 Executor bgExecutor, 217 Executor mainExecutor, 218 FeatureFlags featureFlags) { 219 // Don't send the initial intents through the shared ResolverActivity path, 220 // we want to separate them into a different section. 221 super( 222 context, 223 payloadIntents, 224 null, 225 rList, 226 filterLastUsed, 227 resolverListController, 228 userHandle, 229 targetIntent, 230 resolverListCommunicator, 231 initialIntentsUserSpace, 232 targetDataLoader, 233 bgExecutor, 234 mainExecutor); 235 236 mMaxRankedTargets = maxRankedTargets; 237 mReferrerFillInIntent = referrerFillInIntent; 238 239 mPlaceHolderTargetInfo = NotSelectableTargetInfo.newPlaceHolderTargetInfo(context); 240 mTargetDataLoader = targetDataLoader; 241 mPackageChangeCallback = packageChangeCallback; 242 mUseBadgeTextViewForLabels = featureFlags.bespokeLabelView(); 243 createPlaceHolders(); 244 mEventLog = eventLog; 245 mShortcutSelectionLogic = new ShortcutSelectionLogic( 246 context.getResources().getInteger(R.integer.config_maxShortcutTargetsPerApp), 247 DeviceConfig.getBoolean( 248 DeviceConfig.NAMESPACE_SYSTEMUI, 249 SystemUiDeviceConfigFlags.APPLY_SHARING_APP_LIMITS_IN_SYSUI, 250 true) 251 ); 252 253 if (initialIntents != null) { 254 for (int i = 0; i < initialIntents.length; i++) { 255 final Intent ii = initialIntents[i]; 256 if (ii == null) { 257 continue; 258 } 259 260 // We reimplement Intent#resolveActivityInfo here because if we have an 261 // implicit intent, we want the ResolveInfo returned by PackageManager 262 // instead of one we reconstruct ourselves. The ResolveInfo returned might 263 // have extra metadata and resolvePackageName set and we want to respect that. 264 ResolveInfo ri = null; 265 ActivityInfo ai = null; 266 final ComponentName cn = ii.getComponent(); 267 if (cn != null) { 268 try { 269 ai = packageManager.getActivityInfo( 270 ii.getComponent(), 271 PackageManager.ComponentInfoFlags.of(PackageManager.GET_META_DATA)); 272 ri = new ResolveInfo(); 273 ri.activityInfo = ai; 274 } catch (PackageManager.NameNotFoundException ignored) { 275 // ai will == null below 276 } 277 } 278 if (ai == null) { 279 // Because of AIDL bug, resolveActivity can't accept subclasses of Intent. 280 final Intent rii = (ii.getClass() == Intent.class) ? ii : new Intent(ii); 281 ri = packageManager.resolveActivity( 282 rii, 283 PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY)); 284 ai = ri != null ? ri.activityInfo : null; 285 } 286 if (ai == null) { 287 Log.w(TAG, "No activity found for " + ii); 288 continue; 289 } 290 UserManager userManager = 291 (UserManager) context.getSystemService(Context.USER_SERVICE); 292 if (ii instanceof LabeledIntent) { 293 LabeledIntent li = (LabeledIntent) ii; 294 ri.resolvePackageName = li.getSourcePackage(); 295 ri.labelRes = li.getLabelResource(); 296 ri.nonLocalizedLabel = li.getNonLocalizedLabel(); 297 ri.icon = li.getIconResource(); 298 ri.iconResourceId = ri.icon; 299 } 300 if (userManager.isManagedProfile()) { 301 ri.noResourceId = true; 302 ri.icon = 0; 303 } 304 ri.userHandle = initialIntentsUserSpace; 305 DisplayResolveInfo displayResolveInfo = 306 DisplayResolveInfo.newDisplayResolveInfo(ii, ri, ii); 307 mCallerTargets.add(displayResolveInfo); 308 if (mCallerTargets.size() == MAX_SUGGESTED_APP_TARGETS) break; 309 } 310 } 311 } 312 setAnimateItems(boolean animateItems)313 public void setAnimateItems(boolean animateItems) { 314 mAnimateItems = animateItems; 315 } 316 317 @Override handlePackagesChanged()318 public void handlePackagesChanged() { 319 if (mPackageChangeCallback != null) { 320 mPackageChangeCallback.beforeHandlingPackagesChanged(); 321 } 322 if (DEBUG) { 323 Log.d(TAG, "clearing queryTargets on package change"); 324 } 325 createPlaceHolders(); 326 mResolverListCommunicator.onHandlePackagesChanged(this); 327 328 } 329 330 @Override rebuildList(boolean doPostProcessing)331 public boolean rebuildList(boolean doPostProcessing) { 332 mAnimationTracker.reset(); 333 mSortedList.clear(); 334 boolean result = super.rebuildList(doPostProcessing); 335 notifyDataSetChanged(); 336 return result; 337 } 338 createPlaceHolders()339 private void createPlaceHolders() { 340 mServiceTargets.clear(); 341 for (int i = 0; i < mMaxRankedTargets; ++i) { 342 mServiceTargets.add(mPlaceHolderTargetInfo); 343 } 344 } 345 346 @Override onCreateView(ViewGroup parent)347 View onCreateView(ViewGroup parent) { 348 return mInflater.inflate( 349 mUseBadgeTextViewForLabels 350 ? R.layout.chooser_grid_item 351 : R.layout.resolve_grid_item, 352 parent, 353 false); 354 } 355 356 @Override onDestroy()357 public void onDestroy() { 358 super.onDestroy(); 359 notifyDataSetChanged(); 360 } 361 362 @VisibleForTesting 363 @Override onBindView(View view, TargetInfo info, int position)364 public void onBindView(View view, TargetInfo info, int position) { 365 view.setEnabled(!isDestroyed()); 366 final ViewHolder holder = (ViewHolder) view.getTag(); 367 368 resetViewHolder(holder); 369 // Always remove the spacing listener, attach as needed to direct share targets below. 370 holder.text.removeOnLayoutChangeListener(mPinTextSpacingListener); 371 372 if (info == null) { 373 holder.icon.setImageDrawable(loadIconPlaceholder()); 374 return; 375 } 376 377 final CharSequence displayLabel = Objects.requireNonNullElse(info.getDisplayLabel(), ""); 378 final CharSequence extendedInfo = Objects.requireNonNullElse(info.getExtendedInfo(), ""); 379 holder.bindLabel(displayLabel, extendedInfo); 380 if (mAnimateItems && !TextUtils.isEmpty(displayLabel)) { 381 mAnimationTracker.animateLabel(holder.text, info); 382 } 383 if (mAnimateItems 384 && !TextUtils.isEmpty(extendedInfo) 385 && holder.text2.getVisibility() == View.VISIBLE) { 386 mAnimationTracker.animateLabel(holder.text2, info); 387 } 388 389 if (info.isSelectableTargetInfo()) { 390 // direct share targets should append the application name for a better readout 391 DisplayResolveInfo rInfo = info.getDisplayResolveInfo(); 392 CharSequence appName = 393 Objects.requireNonNullElse(rInfo == null ? null : rInfo.getDisplayLabel(), ""); 394 String contentDescription = 395 String.join(" ", info.getDisplayLabel(), extendedInfo, appName); 396 if (info.isPinned()) { 397 contentDescription = String.join( 398 ". ", 399 contentDescription, 400 mContext.getResources().getString(R.string.pinned)); 401 } 402 updateContentDescription(holder, contentDescription); 403 if (!info.hasDisplayIcon()) { 404 loadDirectShareIcon((SelectableTargetInfo) info); 405 } 406 } else if (info.isDisplayResolveInfo()) { 407 if (info.isPinned()) { 408 updateContentDescription( 409 holder, 410 String.join( 411 ". ", 412 info.getDisplayLabel(), 413 mContext.getResources().getString(R.string.pinned))); 414 } 415 DisplayResolveInfo dri = (DisplayResolveInfo) info; 416 if (!dri.hasDisplayIcon()) { 417 loadIcon(dri); 418 } 419 if (!dri.hasDisplayLabel()) { 420 loadLabel(dri); 421 } 422 } 423 424 holder.bindIcon(info); 425 if (mAnimateItems && info.hasDisplayIcon()) { 426 mAnimationTracker.animateIcon(holder.icon, info); 427 } 428 429 if (info.isPlaceHolderTargetInfo()) { 430 bindPlaceholder(holder); 431 } 432 433 if (info.isMultiDisplayResolveInfo()) { 434 // If the target is grouped show an indicator 435 bindGroupIndicator( 436 holder, 437 mContext.getDrawable(R.drawable.chooser_group_background)); 438 } else if (info.isPinned() && (getPositionTargetType(position) == TARGET_STANDARD 439 || getPositionTargetType(position) == TARGET_SERVICE)) { 440 // If the appShare or directShare target is pinned and in the suggested row show a 441 // pinned indicator 442 bindPinnedIndicator(holder, mContext.getDrawable(R.drawable.chooser_pinned_background)); 443 holder.text.addOnLayoutChangeListener(mPinTextSpacingListener); 444 } 445 } 446 resetViewHolder(ViewHolder holder)447 private void resetViewHolder(ViewHolder holder) { 448 holder.reset(); 449 holder.itemView.setBackground(holder.defaultItemViewBackground); 450 451 if (mUseBadgeTextViewForLabels) { 452 ((BadgeTextView) holder.text).setBadgeDrawable(null); 453 } 454 holder.text.setBackground(null); 455 holder.text.setPaddingRelative(0, 0, 0, 0); 456 } 457 updateContentDescription(ViewHolder holder, String description)458 private void updateContentDescription(ViewHolder holder, String description) { 459 holder.itemView.setContentDescription(description); 460 } 461 bindPlaceholder(ViewHolder holder)462 private void bindPlaceholder(ViewHolder holder) { 463 holder.itemView.setBackground(null); 464 } 465 bindGroupIndicator(ViewHolder holder, Drawable indicator)466 private void bindGroupIndicator(ViewHolder holder, Drawable indicator) { 467 if (mUseBadgeTextViewForLabels) { 468 ((BadgeTextView) holder.text).setBadgeDrawable(indicator); 469 } else { 470 holder.text.setPaddingRelative(0, 0, /*end = */indicator.getIntrinsicWidth(), 0); 471 holder.text.setBackground(indicator); 472 } 473 } 474 bindPinnedIndicator(ViewHolder holder, Drawable indicator)475 private void bindPinnedIndicator(ViewHolder holder, Drawable indicator) { 476 holder.text.setPaddingRelative(/*start = */indicator.getIntrinsicWidth(), 0, 0, 0); 477 holder.text.setBackground(indicator); 478 } 479 loadDirectShareIcon(SelectableTargetInfo info)480 private void loadDirectShareIcon(SelectableTargetInfo info) { 481 if (mRequestedIcons.add(info)) { 482 Drawable icon = mTargetDataLoader.getOrLoadDirectShareIcon( 483 info, 484 getUserHandle(), 485 (drawable) -> onDirectShareIconLoaded(info, drawable, true)); 486 if (icon != null) { 487 onDirectShareIconLoaded(info, icon, false); 488 } 489 } 490 } 491 onDirectShareIconLoaded( SelectableTargetInfo mTargetInfo, @Nullable Drawable icon, boolean notify)492 private void onDirectShareIconLoaded( 493 SelectableTargetInfo mTargetInfo, @Nullable Drawable icon, boolean notify) { 494 if (icon != null && !mTargetInfo.hasDisplayIcon()) { 495 mTargetInfo.getDisplayIconHolder().setDisplayIcon(icon); 496 if (notify) { 497 notifyDataSetChanged(); 498 } 499 } 500 } 501 502 /** 503 * Group application targets 504 */ updateAlphabeticalList()505 public void updateAlphabeticalList() { 506 updateAlphabeticalList(() -> {}); 507 } 508 509 /** 510 * Group application targets 511 */ updateAlphabeticalList(Runnable onCompleted)512 public void updateAlphabeticalList(Runnable onCompleted) { 513 final DisplayResolveInfoAzInfoComparator 514 comparator = new DisplayResolveInfoAzInfoComparator(mContext); 515 final List<DisplayResolveInfo> allTargets = new ArrayList<>(); 516 allTargets.addAll(getTargetsInCurrentDisplayList()); 517 allTargets.addAll(mCallerTargets); 518 519 new AsyncTask<Void, Void, List<DisplayResolveInfo>>() { 520 @Override 521 protected List<DisplayResolveInfo> doInBackground(Void... voids) { 522 try { 523 Trace.beginSection("update-alphabetical-list"); 524 return updateList(); 525 } finally { 526 Trace.endSection(); 527 } 528 } 529 530 private List<DisplayResolveInfo> updateList() { 531 loadMissingLabels(allTargets); 532 533 // Consolidate multiple targets from same app. 534 return allTargets 535 .stream() 536 .collect(Collectors.groupingBy(target -> 537 target.getResolvedComponentName().getPackageName() 538 + "#" + target.getDisplayLabel() 539 + '#' + target.getResolveInfo().userHandle.getIdentifier() 540 )) 541 .values() 542 .stream() 543 .map(appTargets -> 544 (appTargets.size() == 1) 545 ? appTargets.get(0) 546 : MultiDisplayResolveInfo.newMultiDisplayResolveInfo( 547 appTargets)) 548 .sorted(comparator) 549 .collect(Collectors.toList()); 550 } 551 552 @Override 553 protected void onPostExecute(List<DisplayResolveInfo> newList) { 554 mSortedList.clear(); 555 mSortedList.addAll(newList); 556 notifyDataSetChanged(); 557 onCompleted.run(); 558 } 559 560 private void loadMissingLabels(List<DisplayResolveInfo> targets) { 561 for (DisplayResolveInfo target: targets) { 562 mTargetDataLoader.getOrLoadLabel(target); 563 } 564 } 565 }.execute(); 566 } 567 568 @Override getCount()569 public int getCount() { 570 return getRankedTargetCount() + getAlphaTargetCount() 571 + getSelectableServiceTargetCount() + getCallerTargetCount(); 572 } 573 574 @Override getUnfilteredCount()575 public int getUnfilteredCount() { 576 int appTargets = super.getUnfilteredCount(); 577 if (appTargets > mMaxRankedTargets) { 578 // TODO: what does this condition mean? 579 appTargets = appTargets + mMaxRankedTargets; 580 } 581 return appTargets + getSelectableServiceTargetCount() + getCallerTargetCount(); 582 } 583 584 getCallerTargetCount()585 public int getCallerTargetCount() { 586 return mCallerTargets.size(); 587 } 588 589 /** 590 * Filter out placeholders and non-selectable service targets 591 */ getSelectableServiceTargetCount()592 public int getSelectableServiceTargetCount() { 593 int count = 0; 594 for (TargetInfo info : mServiceTargets) { 595 if (info.isSelectableTargetInfo()) { 596 count++; 597 } 598 } 599 return count; 600 } 601 hasSendAction(Intent intent)602 private static boolean hasSendAction(Intent intent) { 603 String action = intent.getAction(); 604 return Intent.ACTION_SEND.equals(action) 605 || Intent.ACTION_SEND_MULTIPLE.equals(action); 606 } 607 getServiceTargetCount()608 public int getServiceTargetCount() { 609 if (hasSendAction(getTargetIntent()) && !ActivityManager.isLowRamDeviceStatic()) { 610 return Math.min(mServiceTargets.size(), mMaxRankedTargets); 611 } 612 613 return 0; 614 } 615 getAlphaTargetCount()616 public int getAlphaTargetCount() { 617 int groupedCount = mSortedList.size(); 618 int ungroupedCount = mCallerTargets.size() + getDisplayResolveInfoCount(); 619 return (ungroupedCount > mMaxRankedTargets) ? groupedCount : 0; 620 } 621 622 /** 623 * Fetch ranked app target count 624 */ getRankedTargetCount()625 public int getRankedTargetCount() { 626 int spacesAvailable = mMaxRankedTargets - getCallerTargetCount(); 627 return Math.min(spacesAvailable, super.getCount()); 628 } 629 630 /** Get all the {@link DisplayResolveInfo} data for our targets. */ getDisplayResolveInfos()631 public DisplayResolveInfo[] getDisplayResolveInfos() { 632 int size = getDisplayResolveInfoCount(); 633 DisplayResolveInfo[] resolvedTargets = new DisplayResolveInfo[size]; 634 for (int i = 0; i < size; i++) { 635 resolvedTargets[i] = getDisplayResolveInfo(i); 636 } 637 return resolvedTargets; 638 } 639 getPositionTargetType(int position)640 public int getPositionTargetType(int position) { 641 int offset = 0; 642 643 final int serviceTargetCount = getServiceTargetCount(); 644 if (position < serviceTargetCount) { 645 return TARGET_SERVICE; 646 } 647 offset += serviceTargetCount; 648 649 final int callerTargetCount = getCallerTargetCount(); 650 if (position - offset < callerTargetCount) { 651 return TARGET_CALLER; 652 } 653 offset += callerTargetCount; 654 655 final int rankedTargetCount = getRankedTargetCount(); 656 if (position - offset < rankedTargetCount) { 657 return TARGET_STANDARD; 658 } 659 offset += rankedTargetCount; 660 661 final int standardTargetCount = getAlphaTargetCount(); 662 if (position - offset < standardTargetCount) { 663 return TARGET_STANDARD_AZ; 664 } 665 666 return TARGET_BAD; 667 } 668 669 @Override getItem(int position)670 public TargetInfo getItem(int position) { 671 return targetInfoForPosition(position, true); 672 } 673 674 /** 675 * Find target info for a given position. 676 * Since ChooserActivity displays several sections of content, determine which 677 * section provides this item. 678 */ 679 @Override targetInfoForPosition(int position, boolean filtered)680 public TargetInfo targetInfoForPosition(int position, boolean filtered) { 681 if (position == NO_POSITION) { 682 return null; 683 } 684 685 int offset = 0; 686 687 // Direct share targets 688 final int serviceTargetCount = filtered ? getServiceTargetCount() : 689 getSelectableServiceTargetCount(); 690 if (position < serviceTargetCount) { 691 return mServiceTargets.get(position); 692 } 693 offset += serviceTargetCount; 694 695 // Targets provided by calling app 696 final int callerTargetCount = getCallerTargetCount(); 697 if (position - offset < callerTargetCount) { 698 return mCallerTargets.get(position - offset); 699 } 700 offset += callerTargetCount; 701 702 // Ranked standard app targets 703 final int rankedTargetCount = getRankedTargetCount(); 704 if (position - offset < rankedTargetCount) { 705 return filtered ? super.getItem(position - offset) 706 : getDisplayResolveInfo(position - offset); 707 } 708 offset += rankedTargetCount; 709 710 // Alphabetical complete app target list. 711 if (position - offset < getAlphaTargetCount() && !mSortedList.isEmpty()) { 712 return mSortedList.get(position - offset); 713 } 714 715 return null; 716 } 717 718 // Check whether {@code dri} should be added into mDisplayList. 719 @Override shouldAddResolveInfo(DisplayResolveInfo dri)720 protected boolean shouldAddResolveInfo(DisplayResolveInfo dri) { 721 // Checks if this info is already listed in callerTargets. 722 for (TargetInfo existingInfo : mCallerTargets) { 723 if (ResolveInfoHelpers.resolveInfoMatch( 724 dri.getResolveInfo(), existingInfo.getResolveInfo())) { 725 return false; 726 } 727 } 728 return super.shouldAddResolveInfo(dri); 729 } 730 731 /** 732 * Fetch surfaced direct share target info 733 */ getSurfacedTargetInfo()734 public List<TargetInfo> getSurfacedTargetInfo() { 735 return mServiceTargets.subList(0, 736 Math.min(mMaxRankedTargets, getSelectableServiceTargetCount())); 737 } 738 739 740 /** 741 * Evaluate targets for inclusion in the direct share area. May not be included 742 * if score is too low. 743 */ addServiceResults( @ullable DisplayResolveInfo origTarget, List<ChooserTarget> targets, int targetType, Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos, Map<ChooserTarget, AppTarget> directShareToAppTargets)744 public void addServiceResults( 745 @Nullable DisplayResolveInfo origTarget, 746 List<ChooserTarget> targets, 747 int targetType, 748 Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos, 749 Map<ChooserTarget, AppTarget> directShareToAppTargets) { 750 // Avoid inserting any potentially late results. 751 if ((mServiceTargets.size() == 1) && mServiceTargets.get(0).isEmptyTargetInfo()) { 752 return; 753 } 754 boolean isShortcutResult = targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER 755 || targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE; 756 boolean isUpdated = mShortcutSelectionLogic.addServiceResults( 757 origTarget, 758 getBaseScore(origTarget, targetType), 759 targets, 760 isShortcutResult, 761 directShareToShortcutInfos, 762 directShareToAppTargets, 763 mContext.createContextAsUser(getUserHandle(), 0), 764 getTargetIntent(), 765 mReferrerFillInIntent, 766 mMaxRankedTargets, 767 mServiceTargets); 768 if (isUpdated) { 769 notifyDataSetChanged(); 770 } 771 } 772 773 /** 774 * Use the scoring system along with artificial boosts to create up to 4 distinct buckets: 775 * <ol> 776 * <li>App-supplied targets 777 * <li>Shortcuts ranked via App Prediction Manager 778 * <li>Shortcuts ranked via legacy heuristics 779 * <li>Legacy direct share targets 780 * </ol> 781 */ getBaseScore( DisplayResolveInfo target, int targetType)782 public float getBaseScore( 783 DisplayResolveInfo target, 784 int targetType) { 785 if (target == null) { 786 return CALLER_TARGET_SCORE_BOOST; 787 } 788 float score = super.getScore(target); 789 if (targetType == TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER 790 || targetType == TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE) { 791 return score * SHORTCUT_TARGET_SCORE_BOOST; 792 } 793 return score; 794 } 795 796 /** 797 * Calling this marks service target loading complete, and will attempt to no longer 798 * update the direct share area. 799 */ completeServiceTargetLoading()800 public void completeServiceTargetLoading() { 801 mServiceTargets.removeIf(o -> o.isPlaceHolderTargetInfo()); 802 if (mServiceTargets.isEmpty()) { 803 mServiceTargets.add(NotSelectableTargetInfo.newEmptyTargetInfo()); 804 mEventLog.logSharesheetEmptyDirectShareRow(); 805 } 806 notifyDataSetChanged(); 807 } 808 809 /** 810 * Rather than fully sorting the input list, this sorting task will put the top k elements 811 * in the head of input list and fill the tail with other elements in undetermined order. 812 */ 813 @Override 814 @WorkerThread sortComponents(List<ResolvedComponentInfo> components)815 protected void sortComponents(List<ResolvedComponentInfo> components) { 816 Trace.beginSection("ChooserListAdapter#SortingTask"); 817 mResolverListController.topK(components, mMaxRankedTargets); 818 Trace.endSection(); 819 } 820 821 @Override 822 @MainThread onComponentsSorted( @ullable List<ResolvedComponentInfo> sortedComponents, boolean doPostProcessing)823 protected void onComponentsSorted( 824 @Nullable List<ResolvedComponentInfo> sortedComponents, boolean doPostProcessing) { 825 processSortedList(sortedComponents, doPostProcessing); 826 if (doPostProcessing) { 827 notifyDataSetChanged(); 828 } 829 } 830 } 831