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