Cleaning up some app model code.
- Preventing extra sorting when adding/updating apps
- Preventing extra logic when filtering apps
- Fixing overlapping prediction bar when all predictions are removed
- Fixing crash when retrieving section names for AppInfos whose titles
have been updated, but updateApps has not yet been called.
Change-Id: I1da468b0fd5c5cc404b6a5e6146a268fefeca267
diff --git a/src/com/android/launcher3/AlphabeticalAppsList.java b/src/com/android/launcher3/AlphabeticalAppsList.java
index 82aaeb9..5e05d11 100644
--- a/src/com/android/launcher3/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/AlphabeticalAppsList.java
@@ -5,14 +5,10 @@
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import com.android.launcher3.compat.AlphabeticIndexCompat;
-import com.android.launcher3.compat.UserHandleCompat;
-import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.model.AppNameComparator;
-import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
@@ -121,10 +117,10 @@
}
/**
- * A callback to notify of changes to the filter.
+ * Callback to notify when the set of adapter items have changed.
*/
- public interface FilterChangedCallback {
- void onFilterChanged();
+ public interface AdapterChangedCallback {
+ void onAdapterItemsChanged();
}
/**
@@ -178,12 +174,20 @@
private static final int MAX_NUM_MERGES_PHONE = 2;
private Context mContext;
+
+ // The set of apps from the system not including predictions
private List<AppInfo> mApps = new ArrayList<>();
+ // The set of filtered apps with the current filter
private List<AppInfo> mFilteredApps = new ArrayList<>();
- private List<AdapterItem> mSectionedFilteredApps = new ArrayList<>();
+ // The current set of adapter items
+ private List<AdapterItem> mAdapterItems = new ArrayList<>();
+ // The set of sections for the apps with the current filter
private List<SectionInfo> mSections = new ArrayList<>();
+ // The set of sections that we allow fast-scrolling to (includes non-merged sections)
private List<FastScrollSectionInfo> mFastScrollerSections = new ArrayList<>();
+ // The set of predicted app component names
private List<ComponentName> mPredictedAppComponents = new ArrayList<>();
+ // The set of predicted apps resolved from the component names and the current set of apps
private List<AppInfo> mPredictedApps = new ArrayList<>();
private HashMap<CharSequence, String> mCachedSectionNames = new HashMap<>();
private RecyclerView.Adapter mAdapter;
@@ -191,16 +195,16 @@
private AlphabeticIndexCompat mIndexer;
private AppNameComparator mAppNameComparator;
private MergeAlgorithm mMergeAlgorithm;
- private FilterChangedCallback mFilterChangedCallback;
+ private AdapterChangedCallback mAdapterChangedCallback;
private int mNumAppsPerRow;
private int mNumPredictedAppsPerRow;
- public AlphabeticalAppsList(Context context, FilterChangedCallback cb, int numAppsPerRow,
+ public AlphabeticalAppsList(Context context, AdapterChangedCallback auCb, int numAppsPerRow,
int numPredictedAppsPerRow) {
mContext = context;
mIndexer = new AlphabeticIndexCompat(context);
mAppNameComparator = new AppNameComparator(context);
- mFilterChangedCallback = cb;
+ mAdapterChangedCallback = auCb;
setNumAppsPerRow(numAppsPerRow, numPredictedAppsPerRow);
}
@@ -248,7 +252,7 @@
* Returns the current filtered list of applications broken down into their sections.
*/
public List<AdapterItem> getAdapterItems() {
- return mSectionedFilteredApps;
+ return mAdapterItems;
}
/**
@@ -278,11 +282,7 @@
public void setFilter(Filter f) {
if (mFilter != f) {
mFilter = f;
- onAppsUpdated();
- mAdapter.notifyDataSetChanged();
- if (mFilterChangedCallback != null){
- mFilterChangedCallback.onFilterChanged();
- }
+ updateAdapterItems();
}
}
@@ -294,7 +294,6 @@
mPredictedAppComponents.clear();
mPredictedAppComponents.addAll(apps);
onAppsUpdated();
- mAdapter.notifyDataSetChanged();
}
/**
@@ -311,7 +310,6 @@
mApps.clear();
mApps.addAll(apps);
onAppsUpdated();
- mAdapter.notifyDataSetChanged();
}
/**
@@ -320,10 +318,9 @@
public void addApps(List<AppInfo> apps) {
// We add it in place, in alphabetical order
for (AppInfo info : apps) {
- addApp(info);
+ mApps.add(info);
}
onAppsUpdated();
- mAdapter.notifyDataSetChanged();
}
/**
@@ -335,11 +332,10 @@
if (index != -1) {
mApps.set(index, info);
} else {
- addApp(info);
+ mApps.add(info);
}
}
onAppsUpdated();
- mAdapter.notifyDataSetChanged();
}
/**
@@ -353,7 +349,6 @@
}
}
onAppsUpdated();
- mAdapter.notifyDataSetChanged();
}
/**
@@ -373,34 +368,68 @@
}
/**
- * Implementation to actually add an app to the alphabetic list, but does not notify.
- */
- private void addApp(AppInfo info) {
- int index = Collections.binarySearch(mApps, info, mAppNameComparator.getAppInfoComparator());
- if (index < 0) {
- mApps.add(-(index + 1), info);
- }
- }
-
- /**
* Updates internals when the set of apps are updated.
*/
private void onAppsUpdated() {
// Sort the list of apps
Collections.sort(mApps, mAppNameComparator.getAppInfoComparator());
- // Prepare to update the list of sections, filtered apps, etc.
- mFilteredApps.clear();
- mSections.clear();
- mSectionedFilteredApps.clear();
- mFastScrollerSections.clear();
+ // As a special case for some languages (currently only Simplified Chinese), we may need to
+ // coalesce sections
+ Locale curLocale = mContext.getResources().getConfiguration().locale;
+ TreeMap<String, ArrayList<AppInfo>> sectionMap = null;
+ 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
+ sectionMap = new TreeMap<>(mAppNameComparator.getSectionNameComparator());
+ for (AppInfo info : mApps) {
+ // Add the section to the cache
+ String sectionName = getAndUpdateCachedSectionName(info.title);
+
+ // Add it to the mapping
+ ArrayList<AppInfo> sectionApps = sectionMap.get(sectionName);
+ if (sectionApps == null) {
+ sectionApps = new ArrayList<>();
+ sectionMap.put(sectionName, sectionApps);
+ }
+ sectionApps.add(info);
+ }
+
+ // Add each of the section apps to the list in order
+ List<AppInfo> allApps = new ArrayList<>(mApps.size());
+ for (Map.Entry<String, ArrayList<AppInfo>> entry : sectionMap.entrySet()) {
+ allApps.addAll(entry.getValue());
+ }
+ mApps = allApps;
+ } else {
+ // Just compute the section headers for use below
+ for (AppInfo info : mApps) {
+ // Add the section to the cache
+ getAndUpdateCachedSectionName(info.title);
+ }
+ }
+
+ // Recompose the set of adapter items from the current set of apps
+ 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.
+ */
+ private void updateAdapterItems() {
SectionInfo lastSectionInfo = null;
String lastSectionName = null;
FastScrollSectionInfo lastFastScrollerSectionInfo = null;
int position = 0;
int appIndex = 0;
- List<AppInfo> allApps = new ArrayList<>();
+ // Prepare to update the list of sections, filtered apps, etc.
+ mFilteredApps.clear();
+ mFastScrollerSections.clear();
+ mAdapterItems.clear();
+ mSections.clear();
// Process the predicted app components
mPredictedApps.clear();
@@ -421,61 +450,16 @@
if (!mPredictedApps.isEmpty()) {
// Create a new spacer for the prediction bar
AdapterItem sectionItem = AdapterItem.asPredictionBarSpacer(position++);
- mSectionedFilteredApps.add(sectionItem);
+ mAdapterItems.add(sectionItem);
}
}
- // As a special case for some languages (currently only Simplified Chinese), we may need to
- // coalesce sections
- Locale curLocale = mContext.getResources().getConfiguration().locale;
- TreeMap<String, ArrayList<AppInfo>> sectionMap = null;
- 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
- sectionMap = new TreeMap<>(mAppNameComparator.getSectionNameComparator());
- for (AppInfo info : mApps) {
- // Add the section to the cache
- String sectionName = mCachedSectionNames.get(info.title);
- if (sectionName == null) {
- sectionName = mIndexer.computeSectionName(info.title);
- mCachedSectionNames.put(info.title, sectionName);
- }
-
- // Add it to the mapping
- ArrayList<AppInfo> sectionApps = sectionMap.get(sectionName);
- if (sectionApps == null) {
- sectionApps = new ArrayList<>();
- sectionMap.put(sectionName, sectionApps);
- }
- sectionApps.add(info);
- }
-
- // Add it to the list
- for (Map.Entry<String, ArrayList<AppInfo>> entry : sectionMap.entrySet()) {
- allApps.addAll(entry.getValue());
- }
- } else {
- // Just compute the section headers for use below
- for (AppInfo info : mApps) {
- // Add the section to the cache
- String sectionName = mCachedSectionNames.get(info.title);
- if (sectionName == null) {
- sectionName = mIndexer.computeSectionName(info.title);
- mCachedSectionNames.put(info.title, sectionName);
- }
- }
- // Add it to the list
- allApps.addAll(mApps);
- }
-
// Recreate the filtered and sectioned apps (for convenience for the grid layout) from the
// ordered set of sections
- int numApps = allApps.size();
+ int numApps = mApps.size();
for (int i = 0; i < numApps; i++) {
- AppInfo info = allApps.get(i);
- // The section name was computed above so this should be find
- String sectionName = mCachedSectionNames.get(info.title);
+ AppInfo info = mApps.get(i);
+ String sectionName = getAndUpdateCachedSectionName(info.title);
// Check if we want to retain this app
if (mFilter != null && !mFilter.retainApp(info, sectionName)) {
@@ -494,7 +478,7 @@
// Create a new section item to break the flow of items in the list
if (!hasFilter()) {
AdapterItem sectionItem = AdapterItem.asSectionBreak(position++, lastSectionInfo);
- mSectionedFilteredApps.add(sectionItem);
+ mAdapterItems.add(sectionItem);
}
}
@@ -505,12 +489,21 @@
lastSectionInfo.firstAppItem = appItem;
lastFastScrollerSectionInfo.appItem = appItem;
}
- mSectionedFilteredApps.add(appItem);
+ mAdapterItems.add(appItem);
mFilteredApps.add(info);
}
// Merge multiple sections together as requested by the merge strategy for this device
mergeSections();
+
+ // Refresh the recycler view
+ if (mAdapter != null) {
+ mAdapter.notifyDataSetChanged();
+ }
+
+ if (mAdapterChangedCallback != null) {
+ mAdapterChangedCallback.onAdapterItemsChanged();
+ }
}
/**
@@ -531,20 +524,20 @@
SectionInfo nextSection = mSections.remove(i + 1);
// Remove the next section break
- mSectionedFilteredApps.remove(nextSection.sectionBreakItem);
- int pos = mSectionedFilteredApps.indexOf(section.firstAppItem);
+ mAdapterItems.remove(nextSection.sectionBreakItem);
+ int pos = mAdapterItems.indexOf(section.firstAppItem);
// Point the section for these new apps to the merged section
int nextPos = pos + section.numApps;
for (int j = nextPos; j < (nextPos + nextSection.numApps); j++) {
- AdapterItem item = mSectionedFilteredApps.get(j);
+ AdapterItem item = mAdapterItems.get(j);
item.sectionInfo = section;
item.sectionAppIndex += section.numApps;
}
// Update the following adapter items of the removed section item
- pos = mSectionedFilteredApps.indexOf(nextSection.firstAppItem);
- for (int j = pos; j < mSectionedFilteredApps.size(); j++) {
- AdapterItem item = mSectionedFilteredApps.get(j);
+ pos = mAdapterItems.indexOf(nextSection.firstAppItem);
+ for (int j = pos; j < mAdapterItems.size(); j++) {
+ AdapterItem item = mAdapterItems.get(j);
item.position--;
}
section.numApps += nextSection.numApps;
@@ -560,4 +553,17 @@
}
}
}
+
+ /**
+ * Returns the cached section name for the given title, recomputing and updating the cache if
+ * the title has no cached section name.
+ */
+ private String getAndUpdateCachedSectionName(CharSequence title) {
+ String sectionName = mCachedSectionNames.get(title);
+ if (sectionName == null) {
+ sectionName = mIndexer.computeSectionName(title);
+ mCachedSectionNames.put(title, sectionName);
+ }
+ return sectionName;
+ }
}
diff --git a/src/com/android/launcher3/AppsContainerView.java b/src/com/android/launcher3/AppsContainerView.java
index 76f3c88..612c19c 100644
--- a/src/com/android/launcher3/AppsContainerView.java
+++ b/src/com/android/launcher3/AppsContainerView.java
@@ -136,7 +136,7 @@
*/
public class AppsContainerView extends BaseContainerView implements DragSource, Insettable,
TextWatcher, TextView.OnEditorActionListener, LauncherTransitionable,
- AlphabeticalAppsList.FilterChangedCallback, AppsGridAdapter.PredictionBarSpacerCallbacks,
+ AlphabeticalAppsList.AdapterChangedCallback, AppsGridAdapter.PredictionBarSpacerCallbacks,
View.OnTouchListener, View.OnClickListener, View.OnLongClickListener,
ViewTreeObserver.OnPreDrawListener {
@@ -686,7 +686,7 @@
}
@Override
- public void onFilterChanged() {
+ public void onAdapterItemsChanged() {
updatePredictionBarVisibility();
}