• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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