diff options
author | Winson Chung <winsonc@google.com> | 2015-05-22 11:12:27 -0700 |
---|---|---|
committer | Winson Chung <winsonc@google.com> | 2015-05-22 12:21:40 -0700 |
commit | 5f4e0fdd2e4edeb9211e2dcd1c99497f175731f8 (patch) | |
tree | 3abefdc96cf11c695db912016598157f94a6cca4 /src/com/android/launcher3/allapps/AlphabeticalAppsList.java | |
parent | c6205603efe1f2987caf96504c87d720a25b5a94 (diff) | |
download | android_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.java | 577 |
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; + } +} |