blob: 29767bf46560665cfb7c655ce62bca1611144bef [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.allapps;
import android.content.Context;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.DiffUtil;
import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.LabelComparator;
import com.android.launcher3.views.ActivityContext;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.TreeMap;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* The alphabetically sorted list of applications.
*
* @param <T> Type of context inflating this view.
*/
public class AlphabeticalAppsList<T extends Context & ActivityContext> implements
AllAppsStore.OnUpdateListener {
public static final String TAG = "AlphabeticalAppsList";
private final WorkProfileManager mWorkProviderManager;
/**
* Info about a fast scroller section, depending if sections are merged, the fast scroller
* sections will not be the same set as the section headers.
*/
public static class FastScrollSectionInfo {
// The section name
public final String sectionName;
// The item position
public final int position;
public FastScrollSectionInfo(String sectionName, int position) {
this.sectionName = sectionName;
this.position = position;
}
}
private final T mActivityContext;
// The set of apps from the system
private final List<AppInfo> mApps = new ArrayList<>();
@Nullable
private final AllAppsStore mAllAppsStore;
// The number of results in current adapter
private int mAccessibilityResultsCount = 0;
// The current set of adapter items
private final ArrayList<AdapterItem> mAdapterItems = new ArrayList<>();
// The set of sections that we allow fast-scrolling to (includes non-merged sections)
private final List<FastScrollSectionInfo> mFastScrollerSections = new ArrayList<>();
// The of ordered component names as a result of a search query
private final ArrayList<AdapterItem> mSearchResults = new ArrayList<>();
private BaseAllAppsAdapter<T> mAdapter;
private AppInfoComparator mAppNameComparator;
private final int mNumAppsPerRowAllApps;
private int mNumAppRowsInAdapter;
private Predicate<ItemInfo> mItemFilter;
public AlphabeticalAppsList(Context context, @Nullable AllAppsStore appsStore,
WorkProfileManager workProfileManager) {
mAllAppsStore = appsStore;
mActivityContext = ActivityContext.lookupContext(context);
mAppNameComparator = new AppInfoComparator(context);
mWorkProviderManager = workProfileManager;
mNumAppsPerRowAllApps = mActivityContext.getDeviceProfile().inv.numAllAppsColumns;
if (mAllAppsStore != null) {
mAllAppsStore.addUpdateListener(this);
}
}
public void updateItemFilter(Predicate<ItemInfo> itemFilter) {
this.mItemFilter = itemFilter;
onAppsUpdated();
}
/**
* Sets the adapter to notify when this dataset changes.
*/
public void setAdapter(BaseAllAppsAdapter<T> adapter) {
mAdapter = adapter;
}
/**
* Returns fast scroller sections of all the current filtered applications.
*/
public List<FastScrollSectionInfo> getFastScrollerSections() {
return mFastScrollerSections;
}
/**
* Returns the current filtered list of applications broken down into their sections.
*/
public List<AdapterItem> getAdapterItems() {
return mAdapterItems;
}
/**
* Returns the child adapter item with IME launch focus.
*/
public AdapterItem getFocusedChild() {
if (mAdapterItems.size() == 0 || getFocusedChildIndex() == -1) {
return null;
}
return mAdapterItems.get(getFocusedChildIndex());
}
/**
* Returns the index of the child with IME launch focus.
*/
public int getFocusedChildIndex() {
for (AdapterItem item : mAdapterItems) {
if (item.isCountedForAccessibility()) {
return mAdapterItems.indexOf(item);
}
}
return -1;
}
/**
* Returns the number of rows of applications
*/
public int getNumAppRows() {
return mNumAppRowsInAdapter;
}
/**
* Returns the number of applications in this list.
*/
public int getNumFilteredApps() {
return mAccessibilityResultsCount;
}
/**
* Returns whether there are search results which will hide the A-Z list.
*/
public boolean hasSearchResults() {
return !mSearchResults.isEmpty();
}
/**
* Sets results list for search
*/
public boolean setSearchResults(ArrayList<AdapterItem> results) {
if (Objects.equals(results, mSearchResults)) {
return false;
}
mSearchResults.clear();
if (results != null) {
mSearchResults.addAll(results);
}
updateAdapterItems();
return true;
}
/**
* Updates internals when the set of apps are updated.
*/
@Override
public void onAppsUpdated() {
if (mAllAppsStore == null) {
return;
}
// Sort the list of apps
mApps.clear();
Stream<AppInfo> appSteam = Stream.of(mAllAppsStore.getApps());
if (!hasSearchResults() && mItemFilter != null) {
appSteam = appSteam.filter(mItemFilter);
}
appSteam = appSteam.sorted(mAppNameComparator);
// As a special case for some languages (currently only Simplified Chinese), we may need to
// coalesce sections
Locale curLocale = mActivityContext.getResources().getConfiguration().locale;
boolean localeRequiresSectionSorting = curLocale.equals(Locale.SIMPLIFIED_CHINESE);
if (localeRequiresSectionSorting) {
// Compute the section headers. We use a TreeMap with the section name comparator to
// ensure that the sections are ordered when we iterate over it later
appSteam = appSteam.collect(Collectors.groupingBy(
info -> info.sectionName,
() -> new TreeMap<>(new LabelComparator()),
Collectors.toCollection(ArrayList::new)))
.values()
.stream()
.flatMap(ArrayList::stream);
}
appSteam.forEachOrdered(mApps::add);
// Recompose the set of adapter items from the current set of apps
if (mSearchResults.isEmpty()) {
updateAdapterItems();
}
}
/**
* Updates the set of filtered apps with the current filter. At this point, we expect
* mCachedSectionNames to have been calculated for the set of all apps in mApps.
*/
public void updateAdapterItems() {
List<AdapterItem> oldItems = new ArrayList<>(mAdapterItems);
// Prepare to update the list of sections, filtered apps, etc.
mFastScrollerSections.clear();
mAdapterItems.clear();
mAccessibilityResultsCount = 0;
// Recreate the filtered and sectioned apps (for convenience for the grid layout) from the
// ordered set of sections
if (hasSearchResults()) {
mAdapterItems.addAll(mSearchResults);
} else {
int position = 0;
boolean addApps = true;
if (mWorkProviderManager != null) {
position += mWorkProviderManager.addWorkItems(mAdapterItems);
addApps = mWorkProviderManager.shouldShowWorkApps();
}
if (addApps) {
String lastSectionName = null;
for (AppInfo info : mApps) {
mAdapterItems.add(AdapterItem.asApp(info));
String sectionName = info.sectionName;
// Create a new section if the section names do not match
if (!sectionName.equals(lastSectionName)) {
lastSectionName = sectionName;
mFastScrollerSections.add(new FastScrollSectionInfo(sectionName, position));
}
position++;
}
}
}
mAccessibilityResultsCount = (int) mAdapterItems.stream()
.filter(AdapterItem::isCountedForAccessibility).count();
if (mNumAppsPerRowAllApps != 0) {
// Update the number of rows in the adapter after we do all the merging (otherwise, we
// would have to shift the values again)
int numAppsInSection = 0;
int numAppsInRow = 0;
int rowIndex = -1;
for (AdapterItem item : mAdapterItems) {
item.rowIndex = 0;
if (BaseAllAppsAdapter.isDividerViewType(item.viewType)) {
numAppsInSection = 0;
} else if (BaseAllAppsAdapter.isIconViewType(item.viewType)) {
if (numAppsInSection % mNumAppsPerRowAllApps == 0) {
numAppsInRow = 0;
rowIndex++;
}
item.rowIndex = rowIndex;
item.rowAppIndex = numAppsInRow;
numAppsInSection++;
numAppsInRow++;
}
}
mNumAppRowsInAdapter = rowIndex + 1;
}
if (mAdapter != null) {
DiffUtil.calculateDiff(new MyDiffCallback(oldItems, mAdapterItems), false)
.dispatchUpdatesTo(mAdapter);
}
}
private static class MyDiffCallback extends DiffUtil.Callback {
private final List<AdapterItem> mOldList;
private final List<AdapterItem> mNewList;
MyDiffCallback(List<AdapterItem> oldList, List<AdapterItem> newList) {
mOldList = oldList;
mNewList = newList;
}
@Override
public int getOldListSize() {
return mOldList.size();
}
@Override
public int getNewListSize() {
return mNewList.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return mOldList.get(oldItemPosition).isSameAs(mNewList.get(newItemPosition));
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return mOldList.get(oldItemPosition).isContentSame(mNewList.get(newItemPosition));
}
}
}