• 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 
20 import androidx.annotation.Nullable;
21 import androidx.recyclerview.widget.DiffUtil;
22 
23 import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
24 import com.android.launcher3.model.data.AppInfo;
25 import com.android.launcher3.model.data.ItemInfo;
26 import com.android.launcher3.util.LabelComparator;
27 import com.android.launcher3.views.ActivityContext;
28 
29 import java.util.ArrayList;
30 import java.util.List;
31 import java.util.Locale;
32 import java.util.Objects;
33 import java.util.TreeMap;
34 import java.util.function.Predicate;
35 import java.util.stream.Collectors;
36 import java.util.stream.Stream;
37 
38 /**
39  * The alphabetically sorted list of applications.
40  *
41  * @param <T> Type of context inflating this view.
42  */
43 public class AlphabeticalAppsList<T extends Context & ActivityContext> implements
44         AllAppsStore.OnUpdateListener {
45 
46     public static final String TAG = "AlphabeticalAppsList";
47 
48     private final WorkProfileManager mWorkProviderManager;
49 
50     /**
51      * Info about a fast scroller section, depending if sections are merged, the fast scroller
52      * sections will not be the same set as the section headers.
53      */
54     public static class FastScrollSectionInfo {
55         // The section name
56         public final String sectionName;
57         // The item position
58         public final int position;
59 
FastScrollSectionInfo(String sectionName, int position)60         public FastScrollSectionInfo(String sectionName, int position) {
61             this.sectionName = sectionName;
62             this.position = position;
63         }
64     }
65 
66 
67     private final T mActivityContext;
68 
69     // The set of apps from the system
70     private final List<AppInfo> mApps = new ArrayList<>();
71     @Nullable
72     private final AllAppsStore mAllAppsStore;
73 
74     // The number of results in current adapter
75     private int mAccessibilityResultsCount = 0;
76     // The current set of adapter items
77     private final ArrayList<AdapterItem> mAdapterItems = new ArrayList<>();
78     // The set of sections that we allow fast-scrolling to (includes non-merged sections)
79     private final List<FastScrollSectionInfo> mFastScrollerSections = new ArrayList<>();
80 
81     // The of ordered component names as a result of a search query
82     private final ArrayList<AdapterItem> mSearchResults = new ArrayList<>();
83     private BaseAllAppsAdapter<T> mAdapter;
84     private AppInfoComparator mAppNameComparator;
85     private final int mNumAppsPerRowAllApps;
86     private int mNumAppRowsInAdapter;
87     private Predicate<ItemInfo> mItemFilter;
88 
AlphabeticalAppsList(Context context, @Nullable AllAppsStore appsStore, WorkProfileManager workProfileManager)89     public AlphabeticalAppsList(Context context, @Nullable AllAppsStore appsStore,
90             WorkProfileManager workProfileManager) {
91         mAllAppsStore = appsStore;
92         mActivityContext = ActivityContext.lookupContext(context);
93         mAppNameComparator = new AppInfoComparator(context);
94         mWorkProviderManager = workProfileManager;
95         mNumAppsPerRowAllApps = mActivityContext.getDeviceProfile().inv.numAllAppsColumns;
96         if (mAllAppsStore != null) {
97             mAllAppsStore.addUpdateListener(this);
98         }
99     }
100 
updateItemFilter(Predicate<ItemInfo> itemFilter)101     public void updateItemFilter(Predicate<ItemInfo> itemFilter) {
102         this.mItemFilter = itemFilter;
103         onAppsUpdated();
104     }
105 
106     /**
107      * Sets the adapter to notify when this dataset changes.
108      */
setAdapter(BaseAllAppsAdapter<T> adapter)109     public void setAdapter(BaseAllAppsAdapter<T> adapter) {
110         mAdapter = adapter;
111     }
112 
113     /**
114      * Returns fast scroller sections of all the current filtered applications.
115      */
getFastScrollerSections()116     public List<FastScrollSectionInfo> getFastScrollerSections() {
117         return mFastScrollerSections;
118     }
119 
120     /**
121      * Returns the current filtered list of applications broken down into their sections.
122      */
getAdapterItems()123     public List<AdapterItem> getAdapterItems() {
124         return mAdapterItems;
125     }
126 
127     /**
128      * Returns the child adapter item with IME launch focus.
129      */
getFocusedChild()130     public AdapterItem getFocusedChild() {
131         if (mAdapterItems.size() == 0 || getFocusedChildIndex() == -1) {
132             return null;
133         }
134         return mAdapterItems.get(getFocusedChildIndex());
135     }
136 
137     /**
138      * Returns the index of the child with IME launch focus.
139      */
getFocusedChildIndex()140     public int getFocusedChildIndex() {
141         for (AdapterItem item : mAdapterItems) {
142             if (item.isCountedForAccessibility()) {
143                 return mAdapterItems.indexOf(item);
144             }
145         }
146         return -1;
147     }
148 
149     /**
150      * Returns the number of rows of applications
151      */
getNumAppRows()152     public int getNumAppRows() {
153         return mNumAppRowsInAdapter;
154     }
155 
156     /**
157      * Returns the number of applications in this list.
158      */
getNumFilteredApps()159     public int getNumFilteredApps() {
160         return mAccessibilityResultsCount;
161     }
162 
163     /**
164      * Returns whether there are search results which will hide the A-Z list.
165      */
hasSearchResults()166     public boolean hasSearchResults() {
167         return !mSearchResults.isEmpty();
168     }
169 
170     /**
171      * Sets results list for search
172      */
setSearchResults(ArrayList<AdapterItem> results)173     public boolean setSearchResults(ArrayList<AdapterItem> results) {
174         if (Objects.equals(results, mSearchResults)) {
175             return false;
176         }
177         mSearchResults.clear();
178         if (results != null) {
179             mSearchResults.addAll(results);
180         }
181         updateAdapterItems();
182         return true;
183     }
184 
185     /**
186      * Updates internals when the set of apps are updated.
187      */
188     @Override
onAppsUpdated()189     public void onAppsUpdated() {
190         if (mAllAppsStore == null) {
191             return;
192         }
193         // Sort the list of apps
194         mApps.clear();
195 
196         Stream<AppInfo> appSteam = Stream.of(mAllAppsStore.getApps());
197         if (!hasSearchResults() && mItemFilter != null) {
198             appSteam = appSteam.filter(mItemFilter);
199         }
200         appSteam = appSteam.sorted(mAppNameComparator);
201 
202         // As a special case for some languages (currently only Simplified Chinese), we may need to
203         // coalesce sections
204         Locale curLocale = mActivityContext.getResources().getConfiguration().locale;
205         boolean localeRequiresSectionSorting = curLocale.equals(Locale.SIMPLIFIED_CHINESE);
206         if (localeRequiresSectionSorting) {
207             // Compute the section headers. We use a TreeMap with the section name comparator to
208             // ensure that the sections are ordered when we iterate over it later
209             appSteam = appSteam.collect(Collectors.groupingBy(
210                     info -> info.sectionName,
211                     () -> new TreeMap<>(new LabelComparator()),
212                     Collectors.toCollection(ArrayList::new)))
213                     .values()
214                     .stream()
215                     .flatMap(ArrayList::stream);
216         }
217 
218         appSteam.forEachOrdered(mApps::add);
219         // Recompose the set of adapter items from the current set of apps
220         if (mSearchResults.isEmpty()) {
221             updateAdapterItems();
222         }
223     }
224 
225     /**
226      * Updates the set of filtered apps with the current filter. At this point, we expect
227      * mCachedSectionNames to have been calculated for the set of all apps in mApps.
228      */
updateAdapterItems()229     public void updateAdapterItems() {
230         List<AdapterItem> oldItems = new ArrayList<>(mAdapterItems);
231         // Prepare to update the list of sections, filtered apps, etc.
232         mFastScrollerSections.clear();
233         mAdapterItems.clear();
234         mAccessibilityResultsCount = 0;
235 
236         // Recreate the filtered and sectioned apps (for convenience for the grid layout) from the
237         // ordered set of sections
238         if (hasSearchResults()) {
239             mAdapterItems.addAll(mSearchResults);
240         } else {
241             int position = 0;
242             boolean addApps = true;
243             if (mWorkProviderManager != null) {
244                 position += mWorkProviderManager.addWorkItems(mAdapterItems);
245                 addApps = mWorkProviderManager.shouldShowWorkApps();
246             }
247             if (addApps) {
248                 String lastSectionName = null;
249                 for (AppInfo info : mApps) {
250                     mAdapterItems.add(AdapterItem.asApp(info));
251 
252                     String sectionName = info.sectionName;
253                     // Create a new section if the section names do not match
254                     if (!sectionName.equals(lastSectionName)) {
255                         lastSectionName = sectionName;
256                         mFastScrollerSections.add(new FastScrollSectionInfo(sectionName, position));
257                     }
258                     position++;
259                 }
260             }
261         }
262         mAccessibilityResultsCount = (int) mAdapterItems.stream()
263                 .filter(AdapterItem::isCountedForAccessibility).count();
264 
265         if (mNumAppsPerRowAllApps != 0) {
266             // Update the number of rows in the adapter after we do all the merging (otherwise, we
267             // would have to shift the values again)
268             int numAppsInSection = 0;
269             int numAppsInRow = 0;
270             int rowIndex = -1;
271             for (AdapterItem item : mAdapterItems) {
272                 item.rowIndex = 0;
273                 if (BaseAllAppsAdapter.isDividerViewType(item.viewType)) {
274                     numAppsInSection = 0;
275                 } else if (BaseAllAppsAdapter.isIconViewType(item.viewType)) {
276                     if (numAppsInSection % mNumAppsPerRowAllApps == 0) {
277                         numAppsInRow = 0;
278                         rowIndex++;
279                     }
280                     item.rowIndex = rowIndex;
281                     item.rowAppIndex = numAppsInRow;
282                     numAppsInSection++;
283                     numAppsInRow++;
284                 }
285             }
286             mNumAppRowsInAdapter = rowIndex + 1;
287         }
288 
289         if (mAdapter != null) {
290             DiffUtil.calculateDiff(new MyDiffCallback(oldItems, mAdapterItems), false)
291                     .dispatchUpdatesTo(mAdapter);
292         }
293     }
294 
295     private static class MyDiffCallback extends DiffUtil.Callback {
296 
297         private final List<AdapterItem> mOldList;
298         private final List<AdapterItem> mNewList;
299 
MyDiffCallback(List<AdapterItem> oldList, List<AdapterItem> newList)300         MyDiffCallback(List<AdapterItem> oldList, List<AdapterItem> newList) {
301             mOldList = oldList;
302             mNewList = newList;
303         }
304 
305         @Override
getOldListSize()306         public int getOldListSize() {
307             return mOldList.size();
308         }
309 
310         @Override
getNewListSize()311         public int getNewListSize() {
312             return mNewList.size();
313         }
314 
315         @Override
areItemsTheSame(int oldItemPosition, int newItemPosition)316         public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
317             return mOldList.get(oldItemPosition).isSameAs(mNewList.get(newItemPosition));
318         }
319 
320         @Override
areContentsTheSame(int oldItemPosition, int newItemPosition)321         public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
322             return mOldList.get(oldItemPosition).isContentSame(mNewList.get(newItemPosition));
323         }
324     }
325 
326 }
327