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