• 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 
19 import android.content.Context;
20 
21 import com.android.launcher3.BaseDraggingActivity;
22 import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
23 import com.android.launcher3.config.FeatureFlags;
24 import com.android.launcher3.model.data.AppInfo;
25 import com.android.launcher3.util.ItemInfoMatcher;
26 import com.android.launcher3.util.LabelComparator;
27 
28 import java.util.ArrayList;
29 import java.util.Collections;
30 import java.util.List;
31 import java.util.Locale;
32 import java.util.Map;
33 import java.util.Objects;
34 import java.util.TreeMap;
35 
36 /**
37  * The alphabetically sorted list of applications.
38  */
39 public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener {
40 
41     public static final String TAG = "AlphabeticalAppsList";
42 
43     private static final int FAST_SCROLL_FRACTION_DISTRIBUTE_BY_ROWS_FRACTION = 0;
44     private static final int FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS = 1;
45 
46     private final int mFastScrollDistributionMode = FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS;
47     private final WorkAdapterProvider mWorkAdapterProvider;
48 
49     /**
50      * Info about a fast scroller section, depending if sections are merged, the fast scroller
51      * sections will not be the same set as the section headers.
52      */
53     public static class FastScrollSectionInfo {
54         // The section name
55         public String sectionName;
56         // The AdapterItem to scroll to for this section
57         public AdapterItem fastScrollToItem;
58         // The touch fraction that should map to this fast scroll section info
59         public float touchFraction;
60 
FastScrollSectionInfo(String sectionName)61         public FastScrollSectionInfo(String sectionName) {
62             this.sectionName = sectionName;
63         }
64     }
65 
66 
67     private final BaseDraggingActivity mLauncher;
68 
69     // The set of apps from the system
70     private final List<AppInfo> mApps = new ArrayList<>();
71     private final AllAppsStore mAllAppsStore;
72 
73     // The number of results in current adapter
74     private int mAccessibilityResultsCount = 0;
75     // The current set of adapter items
76     private final ArrayList<AdapterItem> mAdapterItems = new ArrayList<>();
77     // The set of sections that we allow fast-scrolling to (includes non-merged sections)
78     private final List<FastScrollSectionInfo> mFastScrollerSections = new ArrayList<>();
79 
80     // The of ordered component names as a result of a search query
81     private ArrayList<AdapterItem> mSearchResults;
82     private AllAppsGridAdapter mAdapter;
83     private AppInfoComparator mAppNameComparator;
84     private final int mNumAppsPerRow;
85     private int mNumAppRowsInAdapter;
86     private ItemInfoMatcher mItemFilter;
87 
AlphabeticalAppsList(Context context, AllAppsStore appsStore, WorkAdapterProvider adapterProvider)88     public AlphabeticalAppsList(Context context, AllAppsStore appsStore,
89             WorkAdapterProvider adapterProvider) {
90         mAllAppsStore = appsStore;
91         mLauncher = BaseDraggingActivity.fromContext(context);
92         mAppNameComparator = new AppInfoComparator(context);
93         mWorkAdapterProvider = adapterProvider;
94         mNumAppsPerRow = mLauncher.getDeviceProfile().inv.numColumns;
95         mAllAppsStore.addUpdateListener(this);
96     }
97 
updateItemFilter(ItemInfoMatcher itemFilter)98     public void updateItemFilter(ItemInfoMatcher itemFilter) {
99         this.mItemFilter = itemFilter;
100         onAppsUpdated();
101     }
102 
103     /**
104      * Sets the adapter to notify when this dataset changes.
105      */
setAdapter(AllAppsGridAdapter adapter)106     public void setAdapter(AllAppsGridAdapter adapter) {
107         mAdapter = adapter;
108     }
109 
110     /**
111      * Returns all the apps.
112      */
getApps()113     public List<AppInfo> getApps() {
114         return mApps;
115     }
116 
117     /**
118      * Returns fast scroller sections of all the current filtered applications.
119      */
getFastScrollerSections()120     public List<FastScrollSectionInfo> getFastScrollerSections() {
121         return mFastScrollerSections;
122     }
123 
124     /**
125      * Returns the current filtered list of applications broken down into their sections.
126      */
getAdapterItems()127     public List<AdapterItem> getAdapterItems() {
128         return mAdapterItems;
129     }
130 
131     /**
132      * Returns the child adapter item with IME launch focus.
133      */
getFocusedChild()134     public AdapterItem getFocusedChild() {
135         if (mAdapterItems.size() == 0 || getFocusedChildIndex() == -1) {
136             return null;
137         }
138         return mAdapterItems.get(getFocusedChildIndex());
139     }
140 
141     /**
142      * Returns the index of the child with IME launch focus.
143      */
getFocusedChildIndex()144     public int getFocusedChildIndex() {
145         for (AdapterItem item : mAdapterItems) {
146             if (item.isCountedForAccessibility()) {
147                 return mAdapterItems.indexOf(item);
148             }
149         }
150         return -1;
151     }
152 
153     /**
154      * Returns the number of rows of applications
155      */
getNumAppRows()156     public int getNumAppRows() {
157         return mNumAppRowsInAdapter;
158     }
159 
160     /**
161      * Returns the number of applications in this list.
162      */
getNumFilteredApps()163     public int getNumFilteredApps() {
164         return mAccessibilityResultsCount;
165     }
166 
167     /**
168      * Returns whether there are is a filter set.
169      */
hasFilter()170     public boolean hasFilter() {
171         return (mSearchResults != null);
172     }
173 
174     /**
175      * Returns whether there are no filtered results.
176      */
hasNoFilteredResults()177     public boolean hasNoFilteredResults() {
178         return (mSearchResults != null) && mAccessibilityResultsCount == 0;
179     }
180 
181     /**
182      * Sets results list for search
183      */
setSearchResults(ArrayList<AdapterItem> results)184     public boolean setSearchResults(ArrayList<AdapterItem> results) {
185         if (!Objects.equals(results, mSearchResults)) {
186             mSearchResults = results;
187             updateAdapterItems();
188             return true;
189         }
190         return false;
191     }
192 
appendSearchResults(ArrayList<AdapterItem> results)193     public boolean appendSearchResults(ArrayList<AdapterItem> results) {
194         if (mSearchResults != null && results != null && results.size() > 0) {
195             updateSearchAdapterItems(results, mSearchResults.size());
196             refreshRecyclerView();
197             return true;
198         }
199         return false;
200     }
201 
updateSearchAdapterItems(ArrayList<AdapterItem> list, int offset)202     void updateSearchAdapterItems(ArrayList<AdapterItem> list, int offset) {
203         for (int i = 0; i < list.size(); i++) {
204             AdapterItem adapterItem = list.get(i);
205             adapterItem.position = offset + i;
206             mAdapterItems.add(adapterItem);
207 
208             if (adapterItem.isCountedForAccessibility()) {
209                 mAccessibilityResultsCount++;
210             }
211         }
212     }
213 
214     /**
215      * Updates internals when the set of apps are updated.
216      */
217     @Override
onAppsUpdated()218     public void onAppsUpdated() {
219         // Sort the list of apps
220         mApps.clear();
221 
222         for (AppInfo app : mAllAppsStore.getApps()) {
223             if (mItemFilter == null || mItemFilter.matches(app, null) || hasFilter()) {
224                 mApps.add(app);
225             }
226         }
227 
228         Collections.sort(mApps, mAppNameComparator);
229 
230         // As a special case for some languages (currently only Simplified Chinese), we may need to
231         // coalesce sections
232         Locale curLocale = mLauncher.getResources().getConfiguration().locale;
233         boolean localeRequiresSectionSorting = curLocale.equals(Locale.SIMPLIFIED_CHINESE);
234         if (localeRequiresSectionSorting) {
235             // Compute the section headers. We use a TreeMap with the section name comparator to
236             // ensure that the sections are ordered when we iterate over it later
237             TreeMap<String, ArrayList<AppInfo>> sectionMap = new TreeMap<>(new LabelComparator());
238             for (AppInfo info : mApps) {
239                 // Add the section to the cache
240                 String sectionName = info.sectionName;
241 
242                 // Add it to the mapping
243                 ArrayList<AppInfo> sectionApps = sectionMap.get(sectionName);
244                 if (sectionApps == null) {
245                     sectionApps = new ArrayList<>();
246                     sectionMap.put(sectionName, sectionApps);
247                 }
248                 sectionApps.add(info);
249             }
250 
251             // Add each of the section apps to the list in order
252             mApps.clear();
253             for (Map.Entry<String, ArrayList<AppInfo>> entry : sectionMap.entrySet()) {
254                 mApps.addAll(entry.getValue());
255             }
256         }
257 
258         // Recompose the set of adapter items from the current set of apps
259         if (mSearchResults == null) {
260             updateAdapterItems();
261         }
262     }
263 
264     /**
265      * Updates the set of filtered apps with the current filter. At this point, we expect
266      * mCachedSectionNames to have been calculated for the set of all apps in mApps.
267      */
updateAdapterItems()268     public void updateAdapterItems() {
269         refillAdapterItems();
270         refreshRecyclerView();
271     }
272 
refreshRecyclerView()273     private void refreshRecyclerView() {
274         if (mAdapter != null) {
275             mAdapter.notifyDataSetChanged();
276         }
277     }
278 
refillAdapterItems()279     private void refillAdapterItems() {
280         String lastSectionName = null;
281         FastScrollSectionInfo lastFastScrollerSectionInfo = null;
282         int position = 0;
283         int appIndex = 0;
284 
285         // Prepare to update the list of sections, filtered apps, etc.
286         mAccessibilityResultsCount = 0;
287         mFastScrollerSections.clear();
288         mAdapterItems.clear();
289 
290         // Recreate the filtered and sectioned apps (for convenience for the grid layout) from the
291         // ordered set of sections
292 
293         if (!hasFilter()) {
294             mAccessibilityResultsCount = mApps.size();
295             if (mWorkAdapterProvider != null) {
296                 position += mWorkAdapterProvider.addWorkItems(mAdapterItems);
297                 if (!mWorkAdapterProvider.shouldShowWorkApps()) {
298                     return;
299                 }
300             }
301             for (AppInfo info : mApps) {
302                 String sectionName = info.sectionName;
303 
304                 // Create a new section if the section names do not match
305                 if (!sectionName.equals(lastSectionName)) {
306                     lastSectionName = sectionName;
307                     lastFastScrollerSectionInfo = new FastScrollSectionInfo(sectionName);
308                     mFastScrollerSections.add(lastFastScrollerSectionInfo);
309                 }
310 
311                 // Create an app item
312                 AdapterItem appItem = AdapterItem.asApp(position++, sectionName, info,
313                         appIndex++);
314                 if (lastFastScrollerSectionInfo.fastScrollToItem == null) {
315                     lastFastScrollerSectionInfo.fastScrollToItem = appItem;
316                 }
317 
318                 mAdapterItems.add(appItem);
319             }
320         } else {
321             updateSearchAdapterItems(mSearchResults, 0);
322             if (!FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
323                 // Append the search market item
324                 if (hasNoFilteredResults()) {
325                     mAdapterItems.add(AdapterItem.asEmptySearch(position++));
326                 } else {
327                     mAdapterItems.add(AdapterItem.asAllAppsDivider(position++));
328                 }
329                 mAdapterItems.add(AdapterItem.asMarketSearch(position++));
330 
331             }
332         }
333         if (mNumAppsPerRow != 0) {
334             // Update the number of rows in the adapter after we do all the merging (otherwise, we
335             // would have to shift the values again)
336             int numAppsInSection = 0;
337             int numAppsInRow = 0;
338             int rowIndex = -1;
339             for (AdapterItem item : mAdapterItems) {
340                 item.rowIndex = 0;
341                 if (AllAppsGridAdapter.isDividerViewType(item.viewType)) {
342                     numAppsInSection = 0;
343                 } else if (AllAppsGridAdapter.isIconViewType(item.viewType)) {
344                     if (numAppsInSection % mNumAppsPerRow == 0) {
345                         numAppsInRow = 0;
346                         rowIndex++;
347                     }
348                     item.rowIndex = rowIndex;
349                     item.rowAppIndex = numAppsInRow;
350                     numAppsInSection++;
351                     numAppsInRow++;
352                 }
353             }
354             mNumAppRowsInAdapter = rowIndex + 1;
355 
356             // Pre-calculate all the fast scroller fractions
357             switch (mFastScrollDistributionMode) {
358                 case FAST_SCROLL_FRACTION_DISTRIBUTE_BY_ROWS_FRACTION:
359                     float rowFraction = 1f / mNumAppRowsInAdapter;
360                     for (FastScrollSectionInfo info : mFastScrollerSections) {
361                         AdapterItem item = info.fastScrollToItem;
362                         if (!AllAppsGridAdapter.isIconViewType(item.viewType)) {
363                             info.touchFraction = 0f;
364                             continue;
365                         }
366 
367                         float subRowFraction = item.rowAppIndex * (rowFraction / mNumAppsPerRow);
368                         info.touchFraction = item.rowIndex * rowFraction + subRowFraction;
369                     }
370                     break;
371                 case FAST_SCROLL_FRACTION_DISTRIBUTE_BY_NUM_SECTIONS:
372                     float perSectionTouchFraction = 1f / mFastScrollerSections.size();
373                     float cumulativeTouchFraction = 0f;
374                     for (FastScrollSectionInfo info : mFastScrollerSections) {
375                         AdapterItem item = info.fastScrollToItem;
376                         if (!AllAppsGridAdapter.isIconViewType(item.viewType)) {
377                             info.touchFraction = 0f;
378                             continue;
379                         }
380                         info.touchFraction = cumulativeTouchFraction;
381                         cumulativeTouchFraction += perSectionTouchFraction;
382                     }
383                     break;
384             }
385         }
386     }
387 }
388