summaryrefslogtreecommitdiffstats
path: root/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
diff options
context:
space:
mode:
authorWinson Chung <winsonc@google.com>2015-05-22 11:12:27 -0700
committerWinson Chung <winsonc@google.com>2015-05-22 12:21:40 -0700
commit5f4e0fdd2e4edeb9211e2dcd1c99497f175731f8 (patch)
tree3abefdc96cf11c695db912016598157f94a6cca4 /src/com/android/launcher3/allapps/AlphabeticalAppsList.java
parentc6205603efe1f2987caf96504c87d720a25b5a94 (diff)
downloadandroid_packages_apps_Trebuchet-5f4e0fdd2e4edeb9211e2dcd1c99497f175731f8.tar.gz
android_packages_apps_Trebuchet-5f4e0fdd2e4edeb9211e2dcd1c99497f175731f8.tar.bz2
android_packages_apps_Trebuchet-5f4e0fdd2e4edeb9211e2dcd1c99497f175731f8.zip
Moving all apps code into sub package.
- Renaming resources, dimens, etc to be more consistent - Removing old AppsCustomize resources and other unused code Change-Id: I15ce35e7cb7a9b9344fc7103963e4e4c9e45d89a
Diffstat (limited to 'src/com/android/launcher3/allapps/AlphabeticalAppsList.java')
-rw-r--r--src/com/android/launcher3/allapps/AlphabeticalAppsList.java577
1 files changed, 577 insertions, 0 deletions
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
new file mode 100644
index 000000000..3d1503d46
--- /dev/null
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -0,0 +1,577 @@
+package com.android.launcher3.allapps;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.compat.AlphabeticIndexCompat;
+import com.android.launcher3.model.AppNameComparator;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * The alphabetically sorted list of applications.
+ */
+public class AlphabeticalAppsList {
+
+ public static final String TAG = "AlphabeticalAppsList";
+ private static final boolean DEBUG = false;
+
+ /**
+ * Info about a section in the alphabetic list
+ */
+ public static class SectionInfo {
+ // The number of applications in this section
+ public int numApps;
+ // The section break AdapterItem for this section
+ public AdapterItem sectionBreakItem;
+ // The first app AdapterItem for this section
+ public AdapterItem firstAppItem;
+ }
+
+ /**
+ * 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 String sectionName;
+ // To map the touch (from 0..1) to the index in the app list to jump to in the fast
+ // scroller, we use the fraction in range (0..1) of the app index / total app count.
+ public float appRangeFraction;
+ // The AdapterItem to scroll to for this section
+ public AdapterItem appItem;
+
+ public FastScrollSectionInfo(String sectionName, float appRangeFraction) {
+ this.sectionName = sectionName;
+ this.appRangeFraction = appRangeFraction;
+ }
+ }
+
+ /**
+ * Info about a particular adapter item (can be either section or app)
+ */
+ public static class AdapterItem {
+ /** Common properties */
+ // The index of this adapter item in the list
+ public int position;
+ // The type of this item
+ public int viewType;
+
+ /** Section & App properties */
+ // The section for this item
+ public SectionInfo sectionInfo;
+
+ /** App-only properties */
+ // The section name of this app. Note that there can be multiple items with different
+ // sectionNames in the same section
+ public String sectionName = null;
+ // The index of this app in the section
+ public int sectionAppIndex = -1;
+ // The associated AppInfo for the app
+ public AppInfo appInfo = null;
+ // The index of this app not including sections
+ public int appIndex = -1;
+
+ public static AdapterItem asSectionBreak(int pos, SectionInfo section) {
+ AdapterItem item = new AdapterItem();
+ item.viewType = AllAppsGridAdapter.SECTION_BREAK_VIEW_TYPE;
+ item.position = pos;
+ item.sectionInfo = section;
+ section.sectionBreakItem = item;
+ return item;
+ }
+
+ public static AdapterItem asPredictionBarSpacer(int pos) {
+ AdapterItem item = new AdapterItem();
+ item.viewType = AllAppsGridAdapter.PREDICTION_BAR_SPACER_TYPE;
+ item.position = pos;
+ return item;
+ }
+
+ public static AdapterItem asApp(int pos, SectionInfo section, String sectionName,
+ int sectionAppIndex, AppInfo appInfo, int appIndex) {
+ AdapterItem item = new AdapterItem();
+ item.viewType = AllAppsGridAdapter.ICON_VIEW_TYPE;
+ item.position = pos;
+ item.sectionInfo = section;
+ item.sectionName = sectionName;
+ item.sectionAppIndex = sectionAppIndex;
+ item.appInfo = appInfo;
+ item.appIndex = appIndex;
+ return item;
+ }
+ }
+
+ /**
+ * A filter interface to limit the set of applications in the apps list.
+ */
+ public interface Filter {
+ boolean retainApp(AppInfo info, String sectionName);
+ }
+
+ /**
+ * Callback to notify when the set of adapter items have changed.
+ */
+ public interface AdapterChangedCallback {
+ void onAdapterItemsChanged();
+ }
+
+ /**
+ * Common interface for different merging strategies.
+ */
+ private interface MergeAlgorithm {
+ boolean continueMerging(int sectionAppCount, int numAppsPerRow, int mergeCount);
+ }
+
+ /**
+ * The logic we use to merge sections on tablets.
+ */
+ private static class TabletMergeAlgorithm implements MergeAlgorithm {
+
+ @Override
+ public boolean continueMerging(int sectionAppCount, int numAppsPerRow, int mergeCount) {
+ // Merge EVERYTHING
+ return true;
+ }
+ }
+
+ /**
+ * The logic we use to merge sections on phones.
+ */
+ private static class PhoneMergeAlgorithm implements MergeAlgorithm {
+
+ private int mMinAppsPerRow;
+ private int mMinRowsInMergedSection;
+ private int mMaxAllowableMerges;
+
+ public PhoneMergeAlgorithm(int minAppsPerRow, int minRowsInMergedSection, int maxNumMerges) {
+ mMinAppsPerRow = minAppsPerRow;
+ mMinRowsInMergedSection = minRowsInMergedSection;
+ mMaxAllowableMerges = maxNumMerges;
+ }
+
+ @Override
+ public boolean continueMerging(int sectionAppCount, int numAppsPerRow, int mergeCount) {
+ // Continue merging if the number of hanging apps on the final row is less than some
+ // fixed number (ragged), the merged rows has yet to exceed some minimum row count,
+ // and while the number of merged sections is less than some fixed number of merges
+ int rows = sectionAppCount / numAppsPerRow;
+ int cols = sectionAppCount % numAppsPerRow;
+ return (0 < cols && cols < mMinAppsPerRow) &&
+ rows < mMinRowsInMergedSection &&
+ mergeCount < mMaxAllowableMerges;
+ }
+ }
+
+ private static final int MIN_ROWS_IN_MERGED_SECTION_PHONE = 3;
+ private static final int MAX_NUM_MERGES_PHONE = 2;
+
+ private Launcher mLauncher;
+
+ // 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<>();
+ // 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;
+ private Filter mFilter;
+ private AlphabeticIndexCompat mIndexer;
+ private AppNameComparator mAppNameComparator;
+ private MergeAlgorithm mMergeAlgorithm;
+ private AdapterChangedCallback mAdapterChangedCallback;
+ private int mNumAppsPerRow;
+ private int mNumPredictedAppsPerRow;
+
+ public AlphabeticalAppsList(Context context, int numAppsPerRow, int numPredictedAppsPerRow) {
+ mLauncher = (Launcher) context;
+ mIndexer = new AlphabeticIndexCompat(context);
+ mAppNameComparator = new AppNameComparator(context);
+ setNumAppsPerRow(numAppsPerRow, numPredictedAppsPerRow);
+ }
+
+ /**
+ * Sets the apps updated callback.
+ */
+ public void setAdapterChangedCallback(AdapterChangedCallback cb) {
+ mAdapterChangedCallback = cb;
+ }
+
+ /**
+ * Sets the number of apps per row. Used only for AppsContainerView.SECTIONED_GRID_COALESCED.
+ */
+ public void setNumAppsPerRow(int numAppsPerRow, int numPredictedAppsPerRow) {
+ // Update the merge algorithm
+ DeviceProfile grid = mLauncher.getDeviceProfile();
+ if (grid.isPhone) {
+ mMergeAlgorithm = new PhoneMergeAlgorithm((int) Math.ceil(numAppsPerRow / 2f),
+ MIN_ROWS_IN_MERGED_SECTION_PHONE, MAX_NUM_MERGES_PHONE);
+ } else {
+ mMergeAlgorithm = new TabletMergeAlgorithm();
+ }
+
+ mNumAppsPerRow = numAppsPerRow;
+ mNumPredictedAppsPerRow = numPredictedAppsPerRow;
+
+ onAppsUpdated();
+ }
+
+ /**
+ * Sets the adapter to notify when this dataset changes.
+ */
+ public void setAdapter(RecyclerView.Adapter adapter) {
+ mAdapter = adapter;
+ }
+
+ /**
+ * Returns sections of all the current filtered applications.
+ */
+ public List<SectionInfo> getSections() {
+ return mSections;
+ }
+
+ /**
+ * 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 number of applications in this list.
+ */
+ public int getSize() {
+ return mFilteredApps.size();
+ }
+
+ /**
+ * Returns whether there are is a filter set.
+ */
+ public boolean hasFilter() {
+ return (mFilter != null);
+ }
+
+ /**
+ * Returns whether there are no filtered results.
+ */
+ public boolean hasNoFilteredResults() {
+ return (mFilter != null) && mFilteredApps.isEmpty();
+ }
+
+ /**
+ * Sets the current filter for this list of apps.
+ */
+ public void setFilter(Filter f) {
+ if (mFilter != f) {
+ mFilter = f;
+ updateAdapterItems();
+ }
+ }
+
+ /**
+ * Sets the current set of predicted apps. Since this can be called before we get the full set
+ * of applications, we should merge the results only in onAppsUpdated() which is idempotent.
+ */
+ public void setPredictedApps(List<ComponentName> apps) {
+ mPredictedAppComponents.clear();
+ mPredictedAppComponents.addAll(apps);
+ onAppsUpdated();
+ }
+
+ /**
+ * Returns the current set of predicted apps.
+ */
+ public List<AppInfo> getPredictedApps() {
+ return mPredictedApps;
+ }
+
+ /**
+ * Sets the current set of apps.
+ */
+ public void setApps(List<AppInfo> apps) {
+ mApps.clear();
+ mApps.addAll(apps);
+ onAppsUpdated();
+ }
+
+ /**
+ * Adds new apps to the list.
+ */
+ public void addApps(List<AppInfo> apps) {
+ // We add it in place, in alphabetical order
+ for (AppInfo info : apps) {
+ mApps.add(info);
+ }
+ onAppsUpdated();
+ }
+
+ /**
+ * Updates existing apps in the list
+ */
+ public void updateApps(List<AppInfo> apps) {
+ for (AppInfo info : apps) {
+ int index = mApps.indexOf(info);
+ if (index != -1) {
+ mApps.set(index, info);
+ } else {
+ mApps.add(info);
+ }
+ }
+ onAppsUpdated();
+ }
+
+ /**
+ * Removes some apps from the list.
+ */
+ public void removeApps(List<AppInfo> apps) {
+ for (AppInfo info : apps) {
+ int removeIndex = findAppByComponent(mApps, info);
+ if (removeIndex != -1) {
+ mApps.remove(removeIndex);
+ }
+ }
+ onAppsUpdated();
+ }
+
+ /**
+ * Finds the index of an app given a target AppInfo.
+ */
+ private int findAppByComponent(List<AppInfo> apps, AppInfo targetInfo) {
+ ComponentName targetComponent = targetInfo.intent.getComponent();
+ int length = apps.size();
+ for (int i = 0; i < length; ++i) {
+ AppInfo info = apps.get(i);
+ if (info.user.equals(targetInfo.user)
+ && info.intent.getComponent().equals(targetComponent)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Updates internals when the set of apps are updated.
+ */
+ private void onAppsUpdated() {
+ // Sort the list of apps
+ Collections.sort(mApps, mAppNameComparator.getAppInfoComparator());
+
+ // As a special case for some languages (currently only Simplified Chinese), we may need to
+ // coalesce sections
+ Locale curLocale = mLauncher.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;
+
+ // 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();
+ if (mPredictedAppComponents != null && !mPredictedAppComponents.isEmpty() && !hasFilter()) {
+ for (ComponentName cn : mPredictedAppComponents) {
+ for (AppInfo info : mApps) {
+ if (cn.equals(info.componentName)) {
+ mPredictedApps.add(info);
+ break;
+ }
+ }
+ // Stop at the number of predicted apps
+ if (mPredictedApps.size() == mNumPredictedAppsPerRow) {
+ break;
+ }
+ }
+
+ if (!mPredictedApps.isEmpty()) {
+ // Create a new spacer for the prediction bar
+ AdapterItem sectionItem = AdapterItem.asPredictionBarSpacer(position++);
+ mAdapterItems.add(sectionItem);
+ }
+ }
+
+ // Recreate the filtered and sectioned apps (for convenience for the grid layout) from the
+ // ordered set of sections
+ int numApps = mApps.size();
+ for (int i = 0; i < numApps; i++) {
+ 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)) {
+ continue;
+ }
+
+ // Create a new section if the section names do not match
+ if (lastSectionInfo == null || !sectionName.equals(lastSectionName)) {
+ lastSectionName = sectionName;
+ lastSectionInfo = new SectionInfo();
+ lastFastScrollerSectionInfo = new FastScrollSectionInfo(sectionName,
+ (float) appIndex / numApps);
+ mSections.add(lastSectionInfo);
+ mFastScrollerSections.add(lastFastScrollerSectionInfo);
+
+ // Create a new section item to break the flow of items in the list
+ if (!hasFilter()) {
+ AdapterItem sectionItem = AdapterItem.asSectionBreak(position++, lastSectionInfo);
+ mAdapterItems.add(sectionItem);
+ }
+ }
+
+ // Create an app item
+ AdapterItem appItem = AdapterItem.asApp(position++, lastSectionInfo, sectionName,
+ lastSectionInfo.numApps++, info, appIndex++);
+ if (lastSectionInfo.firstAppItem == null) {
+ lastSectionInfo.firstAppItem = appItem;
+ lastFastScrollerSectionInfo.appItem = 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();
+ }
+ }
+
+ /**
+ * Merges multiple sections to reduce visual raggedness.
+ */
+ private void mergeSections() {
+ // Go through each section and try and merge some of the sections
+ if (AllAppsContainerView.GRID_MERGE_SECTIONS && !hasFilter()) {
+ int sectionAppCount = 0;
+ for (int i = 0; i < mSections.size(); i++) {
+ SectionInfo section = mSections.get(i);
+ sectionAppCount = section.numApps;
+ int mergeCount = 1;
+
+ // Merge rows based on the current strategy
+ while (mMergeAlgorithm.continueMerging(sectionAppCount, mNumAppsPerRow, mergeCount) &&
+ (i + 1) < mSections.size()) {
+ SectionInfo nextSection = mSections.remove(i + 1);
+
+ // Remove the next section break
+ 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 = mAdapterItems.get(j);
+ item.sectionInfo = section;
+ item.sectionAppIndex += section.numApps;
+ }
+
+ // Update the following adapter items of the removed section item
+ pos = mAdapterItems.indexOf(nextSection.firstAppItem);
+ for (int j = pos; j < mAdapterItems.size(); j++) {
+ AdapterItem item = mAdapterItems.get(j);
+ item.position--;
+ }
+ section.numApps += nextSection.numApps;
+ sectionAppCount += nextSection.numApps;
+
+ if (DEBUG) {
+ Log.d(TAG, "Merging: " + nextSection.firstAppItem.sectionName +
+ " to " + section.firstAppItem.sectionName +
+ " mergedNumRows: " + (sectionAppCount / mNumAppsPerRow));
+ }
+ mergeCount++;
+ }
+ }
+ }
+ }
+
+ /**
+ * 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;
+ }
+}