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