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
This commit is contained in:
Winson Chung
2015-05-20 16:07:46 -07:00
parent 324d78358c
commit 3abcd5ba1b
2 changed files with 105 additions and 99 deletions
@@ -5,14 +5,10 @@ import android.content.Context;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.util.Log; import android.util.Log;
import com.android.launcher3.compat.AlphabeticIndexCompat; 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 com.android.launcher3.model.AppNameComparator;
import java.text.Collator;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@@ -121,10 +117,10 @@ public class AlphabeticalAppsList {
} }
/** /**
* A callback to notify of changes to the filter. * Callback to notify when the set of adapter items have changed.
*/ */
public interface FilterChangedCallback { public interface AdapterChangedCallback {
void onFilterChanged(); void onAdapterItemsChanged();
} }
/** /**
@@ -178,12 +174,20 @@ public class AlphabeticalAppsList {
private static final int MAX_NUM_MERGES_PHONE = 2; private static final int MAX_NUM_MERGES_PHONE = 2;
private Context mContext; private Context mContext;
// The set of apps from the system not including predictions
private List<AppInfo> mApps = new ArrayList<>(); private List<AppInfo> mApps = new ArrayList<>();
// The set of filtered apps with the current filter
private List<AppInfo> mFilteredApps = new ArrayList<>(); 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<>(); 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<>(); private List<FastScrollSectionInfo> mFastScrollerSections = new ArrayList<>();
// The set of predicted app component names
private List<ComponentName> mPredictedAppComponents = new ArrayList<>(); 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 List<AppInfo> mPredictedApps = new ArrayList<>();
private HashMap<CharSequence, String> mCachedSectionNames = new HashMap<>(); private HashMap<CharSequence, String> mCachedSectionNames = new HashMap<>();
private RecyclerView.Adapter mAdapter; private RecyclerView.Adapter mAdapter;
@@ -191,16 +195,16 @@ public class AlphabeticalAppsList {
private AlphabeticIndexCompat mIndexer; private AlphabeticIndexCompat mIndexer;
private AppNameComparator mAppNameComparator; private AppNameComparator mAppNameComparator;
private MergeAlgorithm mMergeAlgorithm; private MergeAlgorithm mMergeAlgorithm;
private FilterChangedCallback mFilterChangedCallback; private AdapterChangedCallback mAdapterChangedCallback;
private int mNumAppsPerRow; private int mNumAppsPerRow;
private int mNumPredictedAppsPerRow; private int mNumPredictedAppsPerRow;
public AlphabeticalAppsList(Context context, FilterChangedCallback cb, int numAppsPerRow, public AlphabeticalAppsList(Context context, AdapterChangedCallback auCb, int numAppsPerRow,
int numPredictedAppsPerRow) { int numPredictedAppsPerRow) {
mContext = context; mContext = context;
mIndexer = new AlphabeticIndexCompat(context); mIndexer = new AlphabeticIndexCompat(context);
mAppNameComparator = new AppNameComparator(context); mAppNameComparator = new AppNameComparator(context);
mFilterChangedCallback = cb; mAdapterChangedCallback = auCb;
setNumAppsPerRow(numAppsPerRow, numPredictedAppsPerRow); setNumAppsPerRow(numAppsPerRow, numPredictedAppsPerRow);
} }
@@ -248,7 +252,7 @@ public class AlphabeticalAppsList {
* Returns the current filtered list of applications broken down into their sections. * Returns the current filtered list of applications broken down into their sections.
*/ */
public List<AdapterItem> getAdapterItems() { public List<AdapterItem> getAdapterItems() {
return mSectionedFilteredApps; return mAdapterItems;
} }
/** /**
@@ -278,11 +282,7 @@ public class AlphabeticalAppsList {
public void setFilter(Filter f) { public void setFilter(Filter f) {
if (mFilter != f) { if (mFilter != f) {
mFilter = f; mFilter = f;
onAppsUpdated(); updateAdapterItems();
mAdapter.notifyDataSetChanged();
if (mFilterChangedCallback != null){
mFilterChangedCallback.onFilterChanged();
}
} }
} }
@@ -294,7 +294,6 @@ public class AlphabeticalAppsList {
mPredictedAppComponents.clear(); mPredictedAppComponents.clear();
mPredictedAppComponents.addAll(apps); mPredictedAppComponents.addAll(apps);
onAppsUpdated(); onAppsUpdated();
mAdapter.notifyDataSetChanged();
} }
/** /**
@@ -311,7 +310,6 @@ public class AlphabeticalAppsList {
mApps.clear(); mApps.clear();
mApps.addAll(apps); mApps.addAll(apps);
onAppsUpdated(); onAppsUpdated();
mAdapter.notifyDataSetChanged();
} }
/** /**
@@ -320,10 +318,9 @@ public class AlphabeticalAppsList {
public void addApps(List<AppInfo> apps) { public void addApps(List<AppInfo> apps) {
// We add it in place, in alphabetical order // We add it in place, in alphabetical order
for (AppInfo info : apps) { for (AppInfo info : apps) {
addApp(info); mApps.add(info);
} }
onAppsUpdated(); onAppsUpdated();
mAdapter.notifyDataSetChanged();
} }
/** /**
@@ -335,11 +332,10 @@ public class AlphabeticalAppsList {
if (index != -1) { if (index != -1) {
mApps.set(index, info); mApps.set(index, info);
} else { } else {
addApp(info); mApps.add(info);
} }
} }
onAppsUpdated(); onAppsUpdated();
mAdapter.notifyDataSetChanged();
} }
/** /**
@@ -353,7 +349,6 @@ public class AlphabeticalAppsList {
} }
} }
onAppsUpdated(); onAppsUpdated();
mAdapter.notifyDataSetChanged();
} }
/** /**
@@ -372,16 +367,6 @@ public class AlphabeticalAppsList {
return -1; return -1;
} }
/**
* 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. * Updates internals when the set of apps are updated.
*/ */
@@ -389,18 +374,62 @@ public class AlphabeticalAppsList {
// Sort the list of apps // Sort the list of apps
Collections.sort(mApps, mAppNameComparator.getAppInfoComparator()); Collections.sort(mApps, mAppNameComparator.getAppInfoComparator());
// Prepare to update the list of sections, filtered apps, etc. // As a special case for some languages (currently only Simplified Chinese), we may need to
mFilteredApps.clear(); // coalesce sections
mSections.clear(); Locale curLocale = mContext.getResources().getConfiguration().locale;
mSectionedFilteredApps.clear(); TreeMap<String, ArrayList<AppInfo>> sectionMap = null;
mFastScrollerSections.clear(); 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; SectionInfo lastSectionInfo = null;
String lastSectionName = null; String lastSectionName = null;
FastScrollSectionInfo lastFastScrollerSectionInfo = null; FastScrollSectionInfo lastFastScrollerSectionInfo = null;
int position = 0; int position = 0;
int appIndex = 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 // Process the predicted app components
mPredictedApps.clear(); mPredictedApps.clear();
@@ -421,61 +450,16 @@ public class AlphabeticalAppsList {
if (!mPredictedApps.isEmpty()) { if (!mPredictedApps.isEmpty()) {
// Create a new spacer for the prediction bar // Create a new spacer for the prediction bar
AdapterItem sectionItem = AdapterItem.asPredictionBarSpacer(position++); 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 // Recreate the filtered and sectioned apps (for convenience for the grid layout) from the
// ordered set of sections // ordered set of sections
int numApps = allApps.size(); int numApps = mApps.size();
for (int i = 0; i < numApps; i++) { for (int i = 0; i < numApps; i++) {
AppInfo info = allApps.get(i); AppInfo info = mApps.get(i);
// The section name was computed above so this should be find String sectionName = getAndUpdateCachedSectionName(info.title);
String sectionName = mCachedSectionNames.get(info.title);
// Check if we want to retain this app // Check if we want to retain this app
if (mFilter != null && !mFilter.retainApp(info, sectionName)) { if (mFilter != null && !mFilter.retainApp(info, sectionName)) {
@@ -494,7 +478,7 @@ public class AlphabeticalAppsList {
// Create a new section item to break the flow of items in the list // Create a new section item to break the flow of items in the list
if (!hasFilter()) { if (!hasFilter()) {
AdapterItem sectionItem = AdapterItem.asSectionBreak(position++, lastSectionInfo); AdapterItem sectionItem = AdapterItem.asSectionBreak(position++, lastSectionInfo);
mSectionedFilteredApps.add(sectionItem); mAdapterItems.add(sectionItem);
} }
} }
@@ -505,12 +489,21 @@ public class AlphabeticalAppsList {
lastSectionInfo.firstAppItem = appItem; lastSectionInfo.firstAppItem = appItem;
lastFastScrollerSectionInfo.appItem = appItem; lastFastScrollerSectionInfo.appItem = appItem;
} }
mSectionedFilteredApps.add(appItem); mAdapterItems.add(appItem);
mFilteredApps.add(info); mFilteredApps.add(info);
} }
// Merge multiple sections together as requested by the merge strategy for this device // Merge multiple sections together as requested by the merge strategy for this device
mergeSections(); mergeSections();
// Refresh the recycler view
if (mAdapter != null) {
mAdapter.notifyDataSetChanged();
}
if (mAdapterChangedCallback != null) {
mAdapterChangedCallback.onAdapterItemsChanged();
}
} }
/** /**
@@ -531,20 +524,20 @@ public class AlphabeticalAppsList {
SectionInfo nextSection = mSections.remove(i + 1); SectionInfo nextSection = mSections.remove(i + 1);
// Remove the next section break // Remove the next section break
mSectionedFilteredApps.remove(nextSection.sectionBreakItem); mAdapterItems.remove(nextSection.sectionBreakItem);
int pos = mSectionedFilteredApps.indexOf(section.firstAppItem); int pos = mAdapterItems.indexOf(section.firstAppItem);
// Point the section for these new apps to the merged section // Point the section for these new apps to the merged section
int nextPos = pos + section.numApps; int nextPos = pos + section.numApps;
for (int j = nextPos; j < (nextPos + nextSection.numApps); j++) { for (int j = nextPos; j < (nextPos + nextSection.numApps); j++) {
AdapterItem item = mSectionedFilteredApps.get(j); AdapterItem item = mAdapterItems.get(j);
item.sectionInfo = section; item.sectionInfo = section;
item.sectionAppIndex += section.numApps; item.sectionAppIndex += section.numApps;
} }
// Update the following adapter items of the removed section item // Update the following adapter items of the removed section item
pos = mSectionedFilteredApps.indexOf(nextSection.firstAppItem); pos = mAdapterItems.indexOf(nextSection.firstAppItem);
for (int j = pos; j < mSectionedFilteredApps.size(); j++) { for (int j = pos; j < mAdapterItems.size(); j++) {
AdapterItem item = mSectionedFilteredApps.get(j); AdapterItem item = mAdapterItems.get(j);
item.position--; item.position--;
} }
section.numApps += nextSection.numApps; section.numApps += nextSection.numApps;
@@ -560,4 +553,17 @@ public class AlphabeticalAppsList {
} }
} }
} }
/**
* 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;
}
} }
@@ -136,7 +136,7 @@ final class HeaderElevationControllerVL implements HeaderElevationController {
*/ */
public class AppsContainerView extends BaseContainerView implements DragSource, Insettable, public class AppsContainerView extends BaseContainerView implements DragSource, Insettable,
TextWatcher, TextView.OnEditorActionListener, LauncherTransitionable, TextWatcher, TextView.OnEditorActionListener, LauncherTransitionable,
AlphabeticalAppsList.FilterChangedCallback, AppsGridAdapter.PredictionBarSpacerCallbacks, AlphabeticalAppsList.AdapterChangedCallback, AppsGridAdapter.PredictionBarSpacerCallbacks,
View.OnTouchListener, View.OnClickListener, View.OnLongClickListener, View.OnTouchListener, View.OnClickListener, View.OnLongClickListener,
ViewTreeObserver.OnPreDrawListener { ViewTreeObserver.OnPreDrawListener {
@@ -686,7 +686,7 @@ public class AppsContainerView extends BaseContainerView implements DragSource,
} }
@Override @Override
public void onFilterChanged() { public void onAdapterItemsChanged() {
updatePredictionBarVisibility(); updatePredictionBarVisibility();
} }