1 /* 2 * Copyright (C) 2015 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 package com.android.launcher3.allapps; 17 18 import android.content.Context; 19 import android.os.Process; 20 import android.support.annotation.NonNull; 21 import android.support.annotation.Nullable; 22 import android.util.Log; 23 24 import com.android.launcher3.AppInfo; 25 import com.android.launcher3.Launcher; 26 import com.android.launcher3.compat.AlphabeticIndexCompat; 27 import com.android.launcher3.config.FeatureFlags; 28 import com.android.launcher3.discovery.AppDiscoveryAppInfo; 29 import com.android.launcher3.discovery.AppDiscoveryItem; 30 import com.android.launcher3.discovery.AppDiscoveryUpdateState; 31 import com.android.launcher3.util.ComponentKey; 32 import com.android.launcher3.util.ComponentKeyMapper; 33 import com.android.launcher3.util.LabelComparator; 34 35 import java.util.ArrayList; 36 import java.util.Collections; 37 import java.util.HashMap; 38 import java.util.List; 39 import java.util.Locale; 40 import java.util.Map; 41 import java.util.TreeMap; 42 43 /** 44 * The alphabetically sorted list of applications. 45 */ 46 public class AlphabeticalAppsList { 47 48 public static final String TAG = "AlphabeticalAppsList"; 49 private static final boolean DEBUG = false; 50 private static final boolean DEBUG_PREDICTIONS = false; 51 52 private static final int FAST_SCROLL_FRACTION_DISTRIBUTE_BY_ROWS_FRACTION = 0; 53 private static final int FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS = 1; 54 55 private final int mFastScrollDistributionMode = FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS; 56 57 private AppDiscoveryUpdateState mAppDiscoveryUpdateState; 58 59 /** 60 * Info about a fast scroller section, depending if sections are merged, the fast scroller 61 * sections will not be the same set as the section headers. 62 */ 63 public static class FastScrollSectionInfo { 64 // The section name 65 public String sectionName; 66 // The AdapterItem to scroll to for this section 67 public AdapterItem fastScrollToItem; 68 // The touch fraction that should map to this fast scroll section info 69 public float touchFraction; 70 FastScrollSectionInfo(String sectionName)71 public FastScrollSectionInfo(String sectionName) { 72 this.sectionName = sectionName; 73 } 74 } 75 76 /** 77 * Info about a particular adapter item (can be either section or app) 78 */ 79 public static class AdapterItem { 80 /** Common properties */ 81 // The index of this adapter item in the list 82 public int position; 83 // The type of this item 84 public int viewType; 85 86 /** App-only properties */ 87 // The section name of this app. Note that there can be multiple items with different 88 // sectionNames in the same section 89 public String sectionName = null; 90 // The row that this item shows up on 91 public int rowIndex; 92 // The index of this app in the row 93 public int rowAppIndex; 94 // The associated AppInfo for the app 95 public AppInfo appInfo = null; 96 // The index of this app not including sections 97 public int appIndex = -1; 98 asPredictedApp(int pos, String sectionName, AppInfo appInfo, int appIndex)99 public static AdapterItem asPredictedApp(int pos, String sectionName, AppInfo appInfo, 100 int appIndex) { 101 AdapterItem item = asApp(pos, sectionName, appInfo, appIndex); 102 item.viewType = AllAppsGridAdapter.VIEW_TYPE_PREDICTION_ICON; 103 return item; 104 } 105 asApp(int pos, String sectionName, AppInfo appInfo, int appIndex)106 public static AdapterItem asApp(int pos, String sectionName, AppInfo appInfo, 107 int appIndex) { 108 AdapterItem item = new AdapterItem(); 109 item.viewType = AllAppsGridAdapter.VIEW_TYPE_ICON; 110 item.position = pos; 111 item.sectionName = sectionName; 112 item.appInfo = appInfo; 113 item.appIndex = appIndex; 114 return item; 115 } 116 asDiscoveryItem(int pos, String sectionName, AppInfo appInfo, int appIndex)117 public static AdapterItem asDiscoveryItem(int pos, String sectionName, AppInfo appInfo, 118 int appIndex) { 119 AdapterItem item = new AdapterItem(); 120 item.viewType = AllAppsGridAdapter.VIEW_TYPE_DISCOVERY_ITEM; 121 item.position = pos; 122 item.sectionName = sectionName; 123 item.appInfo = appInfo; 124 item.appIndex = appIndex; 125 return item; 126 } 127 asEmptySearch(int pos)128 public static AdapterItem asEmptySearch(int pos) { 129 AdapterItem item = new AdapterItem(); 130 item.viewType = AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH; 131 item.position = pos; 132 return item; 133 } 134 asPredictionDivider(int pos)135 public static AdapterItem asPredictionDivider(int pos) { 136 AdapterItem item = new AdapterItem(); 137 item.viewType = AllAppsGridAdapter.VIEW_TYPE_PREDICTION_DIVIDER; 138 item.position = pos; 139 return item; 140 } 141 asMarketDivider(int pos)142 public static AdapterItem asMarketDivider(int pos) { 143 AdapterItem item = new AdapterItem(); 144 item.viewType = AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET_DIVIDER; 145 item.position = pos; 146 return item; 147 } 148 asLoadingDivider(int pos)149 public static AdapterItem asLoadingDivider(int pos) { 150 AdapterItem item = new AdapterItem(); 151 item.viewType = AllAppsGridAdapter.VIEW_TYPE_APPS_LOADING_DIVIDER; 152 item.position = pos; 153 return item; 154 } 155 asMarketSearch(int pos)156 public static AdapterItem asMarketSearch(int pos) { 157 AdapterItem item = new AdapterItem(); 158 item.viewType = AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET; 159 item.position = pos; 160 return item; 161 } 162 } 163 164 private final Launcher mLauncher; 165 166 // The set of apps from the system not including predictions 167 private final List<AppInfo> mApps = new ArrayList<>(); 168 private final HashMap<ComponentKey, AppInfo> mComponentToAppMap = new HashMap<>(); 169 170 // The set of filtered apps with the current filter 171 private final List<AppInfo> mFilteredApps = new ArrayList<>(); 172 // The current set of adapter items 173 private final ArrayList<AdapterItem> mAdapterItems = new ArrayList<>(); 174 // The set of sections that we allow fast-scrolling to (includes non-merged sections) 175 private final List<FastScrollSectionInfo> mFastScrollerSections = new ArrayList<>(); 176 // The set of predicted app component names 177 private final List<ComponentKeyMapper<AppInfo>> mPredictedAppComponents = new ArrayList<>(); 178 // The set of predicted apps resolved from the component names and the current set of apps 179 private final List<AppInfo> mPredictedApps = new ArrayList<>(); 180 private final List<AppDiscoveryAppInfo> mDiscoveredApps = new ArrayList<>(); 181 182 // The of ordered component names as a result of a search query 183 private ArrayList<ComponentKey> mSearchResults; 184 private HashMap<CharSequence, String> mCachedSectionNames = new HashMap<>(); 185 private AllAppsGridAdapter mAdapter; 186 private AlphabeticIndexCompat mIndexer; 187 private AppInfoComparator mAppNameComparator; 188 private int mNumAppsPerRow; 189 private int mNumPredictedAppsPerRow; 190 private int mNumAppRowsInAdapter; 191 AlphabeticalAppsList(Context context)192 public AlphabeticalAppsList(Context context) { 193 mLauncher = Launcher.getLauncher(context); 194 mIndexer = new AlphabeticIndexCompat(context); 195 mAppNameComparator = new AppInfoComparator(context); 196 } 197 198 /** 199 * Sets the number of apps per row. 200 */ setNumAppsPerRow(int numAppsPerRow, int numPredictedAppsPerRow)201 public void setNumAppsPerRow(int numAppsPerRow, int numPredictedAppsPerRow) { 202 mNumAppsPerRow = numAppsPerRow; 203 mNumPredictedAppsPerRow = numPredictedAppsPerRow; 204 205 updateAdapterItems(); 206 } 207 208 /** 209 * Sets the adapter to notify when this dataset changes. 210 */ setAdapter(AllAppsGridAdapter adapter)211 public void setAdapter(AllAppsGridAdapter adapter) { 212 mAdapter = adapter; 213 } 214 215 /** 216 * Returns all the apps. 217 */ getApps()218 public List<AppInfo> getApps() { 219 return mApps; 220 } 221 222 /** 223 * Returns the predicted apps. 224 */ getPredictedApps()225 public List<AppInfo> getPredictedApps() { 226 return mPredictedApps; 227 } 228 229 /** 230 * Returns fast scroller sections of all the current filtered applications. 231 */ getFastScrollerSections()232 public List<FastScrollSectionInfo> getFastScrollerSections() { 233 return mFastScrollerSections; 234 } 235 236 /** 237 * Returns the current filtered list of applications broken down into their sections. 238 */ getAdapterItems()239 public List<AdapterItem> getAdapterItems() { 240 return mAdapterItems; 241 } 242 243 /** 244 * Returns the number of rows of applications (not including predictions) 245 */ getNumAppRows()246 public int getNumAppRows() { 247 return mNumAppRowsInAdapter; 248 } 249 250 /** 251 * Returns the number of applications in this list. 252 */ getNumFilteredApps()253 public int getNumFilteredApps() { 254 return mFilteredApps.size(); 255 } 256 257 /** 258 * Returns whether there are is a filter set. 259 */ hasFilter()260 public boolean hasFilter() { 261 return (mSearchResults != null); 262 } 263 264 /** 265 * Returns whether there are no filtered results. 266 */ hasNoFilteredResults()267 public boolean hasNoFilteredResults() { 268 return (mSearchResults != null) && mFilteredApps.isEmpty(); 269 } 270 shouldShowEmptySearch()271 boolean shouldShowEmptySearch() { 272 return hasNoFilteredResults() && !isAppDiscoveryRunning() && mDiscoveredApps.isEmpty(); 273 } 274 275 /** 276 * Sets the sorted list of filtered components. 277 */ setOrderedFilter(ArrayList<ComponentKey> f)278 public boolean setOrderedFilter(ArrayList<ComponentKey> f) { 279 if (mSearchResults != f) { 280 boolean same = mSearchResults != null && mSearchResults.equals(f); 281 mSearchResults = f; 282 updateAdapterItems(); 283 return !same; 284 } 285 return false; 286 } 287 onAppDiscoverySearchUpdate(@ullable AppDiscoveryItem app, @NonNull AppDiscoveryUpdateState state)288 public void onAppDiscoverySearchUpdate(@Nullable AppDiscoveryItem app, 289 @NonNull AppDiscoveryUpdateState state) { 290 mAppDiscoveryUpdateState = state; 291 switch (state) { 292 case START: 293 mDiscoveredApps.clear(); 294 break; 295 case UPDATE: 296 mDiscoveredApps.add(new AppDiscoveryAppInfo(app)); 297 break; 298 } 299 updateAdapterItems(); 300 } 301 processPredictedAppComponents(List<ComponentKeyMapper<AppInfo>> components)302 private List<AppInfo> processPredictedAppComponents(List<ComponentKeyMapper<AppInfo>> components) { 303 if (mComponentToAppMap.isEmpty()) { 304 // Apps have not been bound yet. 305 return Collections.emptyList(); 306 } 307 308 List<AppInfo> predictedApps = new ArrayList<>(); 309 for (ComponentKeyMapper<AppInfo> mapper : components) { 310 AppInfo info = mapper.getItem(mComponentToAppMap); 311 if (info != null) { 312 predictedApps.add(info); 313 } else { 314 if (FeatureFlags.IS_DOGFOOD_BUILD) { 315 Log.e(TAG, "Predicted app not found: " + mapper); 316 } 317 } 318 // Stop at the number of predicted apps 319 if (predictedApps.size() == mNumPredictedAppsPerRow) { 320 break; 321 } 322 } 323 return predictedApps; 324 } 325 326 /** 327 * Sets the current set of predicted apps. 328 * 329 * This can be called before we get the full set of applications, we should merge the results 330 * only in onAppsUpdated() which is idempotent. 331 * 332 * If the number of predicted apps is the same as the previous list of predicted apps, 333 * we can optimize by swapping them in place. 334 */ setPredictedApps(List<ComponentKeyMapper<AppInfo>> apps)335 public void setPredictedApps(List<ComponentKeyMapper<AppInfo>> apps) { 336 mPredictedAppComponents.clear(); 337 mPredictedAppComponents.addAll(apps); 338 339 List<AppInfo> newPredictedApps = processPredictedAppComponents(apps); 340 // We only need to do work if any of the visible predicted apps have changed. 341 if (!newPredictedApps.equals(mPredictedApps)) { 342 if (newPredictedApps.size() == mPredictedApps.size()) { 343 swapInNewPredictedApps(newPredictedApps); 344 } else { 345 // We need to update the appIndex of all the items. 346 onAppsUpdated(); 347 } 348 } 349 } 350 351 /** 352 * Swaps out the old predicted apps with the new predicted apps, in place. This optimization 353 * allows us to skip an entire relayout that would otherwise be called by notifyDataSetChanged. 354 * 355 * Note: This should only be called if the # of predicted apps is the same. 356 * This method assumes that predicted apps are the first items in the adapter. 357 */ swapInNewPredictedApps(List<AppInfo> apps)358 private void swapInNewPredictedApps(List<AppInfo> apps) { 359 mPredictedApps.clear(); 360 mPredictedApps.addAll(apps); 361 362 int size = apps.size(); 363 for (int i = 0; i < size; ++i) { 364 AppInfo info = apps.get(i); 365 AdapterItem appItem = AdapterItem.asPredictedApp(i, "", info, i); 366 appItem.rowAppIndex = i; 367 mAdapterItems.set(i, appItem); 368 mFilteredApps.set(i, info); 369 mAdapter.notifyItemChanged(i); 370 } 371 } 372 373 /** 374 * Sets the current set of apps. 375 */ setApps(List<AppInfo> apps)376 public void setApps(List<AppInfo> apps) { 377 mComponentToAppMap.clear(); 378 addOrUpdateApps(apps); 379 } 380 381 /** 382 * Adds or updates existing apps in the list 383 */ addOrUpdateApps(List<AppInfo> apps)384 public void addOrUpdateApps(List<AppInfo> apps) { 385 for (AppInfo app : apps) { 386 mComponentToAppMap.put(app.toComponentKey(), app); 387 } 388 onAppsUpdated(); 389 } 390 391 /** 392 * Removes some apps from the list. 393 */ removeApps(List<AppInfo> apps)394 public void removeApps(List<AppInfo> apps) { 395 for (AppInfo app : apps) { 396 mComponentToAppMap.remove(app.toComponentKey()); 397 } 398 onAppsUpdated(); 399 } 400 401 /** 402 * Updates internals when the set of apps are updated. 403 */ onAppsUpdated()404 private void onAppsUpdated() { 405 // Sort the list of apps 406 mApps.clear(); 407 mApps.addAll(mComponentToAppMap.values()); 408 Collections.sort(mApps, mAppNameComparator); 409 410 // As a special case for some languages (currently only Simplified Chinese), we may need to 411 // coalesce sections 412 Locale curLocale = mLauncher.getResources().getConfiguration().locale; 413 boolean localeRequiresSectionSorting = curLocale.equals(Locale.SIMPLIFIED_CHINESE); 414 if (localeRequiresSectionSorting) { 415 // Compute the section headers. We use a TreeMap with the section name comparator to 416 // ensure that the sections are ordered when we iterate over it later 417 TreeMap<String, ArrayList<AppInfo>> sectionMap = new TreeMap<>(new LabelComparator()); 418 for (AppInfo info : mApps) { 419 // Add the section to the cache 420 String sectionName = getAndUpdateCachedSectionName(info.title); 421 422 // Add it to the mapping 423 ArrayList<AppInfo> sectionApps = sectionMap.get(sectionName); 424 if (sectionApps == null) { 425 sectionApps = new ArrayList<>(); 426 sectionMap.put(sectionName, sectionApps); 427 } 428 sectionApps.add(info); 429 } 430 431 // Add each of the section apps to the list in order 432 mApps.clear(); 433 for (Map.Entry<String, ArrayList<AppInfo>> entry : sectionMap.entrySet()) { 434 mApps.addAll(entry.getValue()); 435 } 436 } else { 437 // Just compute the section headers for use below 438 for (AppInfo info : mApps) { 439 // Add the section to the cache 440 getAndUpdateCachedSectionName(info.title); 441 } 442 } 443 444 // Recompose the set of adapter items from the current set of apps 445 updateAdapterItems(); 446 } 447 448 /** 449 * Updates the set of filtered apps with the current filter. At this point, we expect 450 * mCachedSectionNames to have been calculated for the set of all apps in mApps. 451 */ updateAdapterItems()452 private void updateAdapterItems() { 453 refillAdapterItems(); 454 refreshRecyclerView(); 455 } 456 refreshRecyclerView()457 private void refreshRecyclerView() { 458 if (mAdapter != null) { 459 mAdapter.notifyDataSetChanged(); 460 } 461 } 462 refillAdapterItems()463 private void refillAdapterItems() { 464 String lastSectionName = null; 465 FastScrollSectionInfo lastFastScrollerSectionInfo = null; 466 int position = 0; 467 int appIndex = 0; 468 469 // Prepare to update the list of sections, filtered apps, etc. 470 mFilteredApps.clear(); 471 mFastScrollerSections.clear(); 472 mAdapterItems.clear(); 473 474 if (DEBUG_PREDICTIONS) { 475 if (mPredictedAppComponents.isEmpty() && !mApps.isEmpty()) { 476 mPredictedAppComponents.add(new ComponentKeyMapper<AppInfo>(new ComponentKey(mApps.get(0).componentName, 477 Process.myUserHandle()))); 478 mPredictedAppComponents.add(new ComponentKeyMapper<AppInfo>(new ComponentKey(mApps.get(0).componentName, 479 Process.myUserHandle()))); 480 mPredictedAppComponents.add(new ComponentKeyMapper<AppInfo>(new ComponentKey(mApps.get(0).componentName, 481 Process.myUserHandle()))); 482 mPredictedAppComponents.add(new ComponentKeyMapper<AppInfo>(new ComponentKey(mApps.get(0).componentName, 483 Process.myUserHandle()))); 484 } 485 } 486 487 // Process the predicted app components 488 mPredictedApps.clear(); 489 if (mPredictedAppComponents != null && !mPredictedAppComponents.isEmpty() && !hasFilter()) { 490 mPredictedApps.addAll(processPredictedAppComponents(mPredictedAppComponents)); 491 492 if (!mPredictedApps.isEmpty()) { 493 // Add a section for the predictions 494 lastFastScrollerSectionInfo = new FastScrollSectionInfo(""); 495 mFastScrollerSections.add(lastFastScrollerSectionInfo); 496 497 // Add the predicted app items 498 for (AppInfo info : mPredictedApps) { 499 AdapterItem appItem = AdapterItem.asPredictedApp(position++, "", info, 500 appIndex++); 501 if (lastFastScrollerSectionInfo.fastScrollToItem == null) { 502 lastFastScrollerSectionInfo.fastScrollToItem = appItem; 503 } 504 mAdapterItems.add(appItem); 505 mFilteredApps.add(info); 506 } 507 508 mAdapterItems.add(AdapterItem.asPredictionDivider(position++)); 509 } 510 } 511 512 // Recreate the filtered and sectioned apps (for convenience for the grid layout) from the 513 // ordered set of sections 514 for (AppInfo info : getFiltersAppInfos()) { 515 String sectionName = getAndUpdateCachedSectionName(info.title); 516 517 // Create a new section if the section names do not match 518 if (!sectionName.equals(lastSectionName)) { 519 lastSectionName = sectionName; 520 lastFastScrollerSectionInfo = new FastScrollSectionInfo(sectionName); 521 mFastScrollerSections.add(lastFastScrollerSectionInfo); 522 } 523 524 // Create an app item 525 AdapterItem appItem = AdapterItem.asApp(position++, sectionName, info, appIndex++); 526 if (lastFastScrollerSectionInfo.fastScrollToItem == null) { 527 lastFastScrollerSectionInfo.fastScrollToItem = appItem; 528 } 529 mAdapterItems.add(appItem); 530 mFilteredApps.add(info); 531 } 532 533 if (hasFilter()) { 534 if (isAppDiscoveryRunning() || mDiscoveredApps.size() > 0) { 535 mAdapterItems.add(AdapterItem.asLoadingDivider(position++)); 536 // Append all app discovery results 537 for (int i = 0; i < mDiscoveredApps.size(); i++) { 538 AppDiscoveryAppInfo appDiscoveryAppInfo = mDiscoveredApps.get(i); 539 if (appDiscoveryAppInfo.isRecent) { 540 // already handled in getFilteredAppInfos() 541 continue; 542 } 543 AdapterItem item = AdapterItem.asDiscoveryItem(position++, 544 "", appDiscoveryAppInfo, appIndex++); 545 mAdapterItems.add(item); 546 } 547 548 if (!isAppDiscoveryRunning()) { 549 mAdapterItems.add(AdapterItem.asMarketSearch(position++)); 550 } 551 } else { 552 // Append the search market item 553 if (hasNoFilteredResults()) { 554 mAdapterItems.add(AdapterItem.asEmptySearch(position++)); 555 } else { 556 mAdapterItems.add(AdapterItem.asMarketDivider(position++)); 557 } 558 mAdapterItems.add(AdapterItem.asMarketSearch(position++)); 559 } 560 } 561 562 if (mNumAppsPerRow != 0) { 563 // Update the number of rows in the adapter after we do all the merging (otherwise, we 564 // would have to shift the values again) 565 int numAppsInSection = 0; 566 int numAppsInRow = 0; 567 int rowIndex = -1; 568 for (AdapterItem item : mAdapterItems) { 569 item.rowIndex = 0; 570 if (AllAppsGridAdapter.isDividerViewType(item.viewType)) { 571 numAppsInSection = 0; 572 } else if (AllAppsGridAdapter.isIconViewType(item.viewType)) { 573 if (numAppsInSection % mNumAppsPerRow == 0) { 574 numAppsInRow = 0; 575 rowIndex++; 576 } 577 item.rowIndex = rowIndex; 578 item.rowAppIndex = numAppsInRow; 579 numAppsInSection++; 580 numAppsInRow++; 581 } 582 } 583 mNumAppRowsInAdapter = rowIndex + 1; 584 585 // Pre-calculate all the fast scroller fractions 586 switch (mFastScrollDistributionMode) { 587 case FAST_SCROLL_FRACTION_DISTRIBUTE_BY_ROWS_FRACTION: 588 float rowFraction = 1f / mNumAppRowsInAdapter; 589 for (FastScrollSectionInfo info : mFastScrollerSections) { 590 AdapterItem item = info.fastScrollToItem; 591 if (!AllAppsGridAdapter.isIconViewType(item.viewType)) { 592 info.touchFraction = 0f; 593 continue; 594 } 595 596 float subRowFraction = item.rowAppIndex * (rowFraction / mNumAppsPerRow); 597 info.touchFraction = item.rowIndex * rowFraction + subRowFraction; 598 } 599 break; 600 case FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS: 601 float perSectionTouchFraction = 1f / mFastScrollerSections.size(); 602 float cumulativeTouchFraction = 0f; 603 for (FastScrollSectionInfo info : mFastScrollerSections) { 604 AdapterItem item = info.fastScrollToItem; 605 if (!AllAppsGridAdapter.isIconViewType(item.viewType)) { 606 info.touchFraction = 0f; 607 continue; 608 } 609 info.touchFraction = cumulativeTouchFraction; 610 cumulativeTouchFraction += perSectionTouchFraction; 611 } 612 break; 613 } 614 } 615 } 616 isAppDiscoveryRunning()617 public boolean isAppDiscoveryRunning() { 618 return mAppDiscoveryUpdateState == AppDiscoveryUpdateState.START 619 || mAppDiscoveryUpdateState == AppDiscoveryUpdateState.UPDATE; 620 } 621 getFiltersAppInfos()622 private List<AppInfo> getFiltersAppInfos() { 623 if (mSearchResults == null) { 624 return mApps; 625 } 626 627 ArrayList<AppInfo> result = new ArrayList<>(); 628 for (ComponentKey key : mSearchResults) { 629 AppInfo match = mComponentToAppMap.get(key); 630 if (match != null) { 631 result.add(match); 632 } 633 } 634 635 // adding recently used instant apps 636 if (mDiscoveredApps.size() > 0) { 637 for (int i = 0; i < mDiscoveredApps.size(); i++) { 638 AppDiscoveryAppInfo discoveryAppInfo = mDiscoveredApps.get(i); 639 if (discoveryAppInfo.isRecent) { 640 result.add(discoveryAppInfo); 641 } 642 } 643 Collections.sort(result, mAppNameComparator); 644 } 645 return result; 646 } 647 findApp(ComponentKeyMapper<AppInfo> mapper)648 public AppInfo findApp(ComponentKeyMapper<AppInfo> mapper) { 649 return mapper.getItem(mComponentToAppMap); 650 } 651 652 /** 653 * Returns the cached section name for the given title, recomputing and updating the cache if 654 * the title has no cached section name. 655 */ getAndUpdateCachedSectionName(CharSequence title)656 private String getAndUpdateCachedSectionName(CharSequence title) { 657 String sectionName = mCachedSectionNames.get(title); 658 if (sectionName == null) { 659 sectionName = mIndexer.computeSectionName(title); 660 mCachedSectionNames.put(title, sectionName); 661 } 662 return sectionName; 663 } 664 665 } 666