diff options
author | Winson Chung <winsonc@google.com> | 2015-05-11 22:12:38 -0700 |
---|---|---|
committer | Winson Chung <winsonc@google.com> | 2015-05-12 21:19:32 +0000 |
commit | a3499dc019a677efb008d926b2cd9a5ad0bc0ca0 (patch) | |
tree | f4a15e24a57e149f53f9964c8f99978580baaaf6 | |
parent | f6d7f4f21c4d3efa08084d3caaa1a19660ca5777 (diff) | |
download | android_packages_apps_Trebuchet-a3499dc019a677efb008d926b2cd9a5ad0bc0ca0.tar.gz android_packages_apps_Trebuchet-a3499dc019a677efb008d926b2cd9a5ad0bc0ca0.tar.bz2 android_packages_apps_Trebuchet-a3499dc019a677efb008d926b2cd9a5ad0bc0ca0.zip |
Tweaking section processing for different languages
- Ensuring that apps with non-letter/digit characters are ordered last in the misc bucket
- Removing duplicate latin-alphabet sections for Simplified Chinese
- Adding more appropriate misc bucket label for Japanese
Bug 21022854
Change-Id: I62c7b219820ef88787fcfa83f1bd4202f16f9c0c
-rw-r--r-- | src/com/android/launcher3/AlphabeticalAppsList.java | 117 | ||||
-rw-r--r-- | src/com/android/launcher3/AppsGridAdapter.java | 17 | ||||
-rw-r--r-- | src/com/android/launcher3/DeviceProfile.java | 7 | ||||
-rw-r--r-- | src/com/android/launcher3/compat/AlphabeticIndexCompat.java | 27 |
4 files changed, 122 insertions, 46 deletions
diff --git a/src/com/android/launcher3/AlphabeticalAppsList.java b/src/com/android/launcher3/AlphabeticalAppsList.java index de4edcb7b..dc75637e5 100644 --- a/src/com/android/launcher3/AlphabeticalAppsList.java +++ b/src/com/android/launcher3/AlphabeticalAppsList.java @@ -14,23 +14,28 @@ import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.TreeMap; /** * A private class to manage access to an app name comparator. */ class AppNameComparator { - private UserManagerCompat mUserManager; - private Comparator<AppInfo> mAppNameComparator; + private final UserManagerCompat mUserManager; + private final Collator mCollator; + private final Comparator<AppInfo> mAppInfoComparator; + private final Comparator<String> mSectionNameComparator; private HashMap<UserHandleCompat, Long> mUserSerialCache = new HashMap<>(); public AppNameComparator(Context context) { - final Collator collator = Collator.getInstance(); + mCollator = Collator.getInstance(); mUserManager = UserManagerCompat.getInstance(context); - mAppNameComparator = new Comparator<AppInfo>() { + mAppInfoComparator = new Comparator<AppInfo>() { public final int compare(AppInfo a, AppInfo b) { - // Order by the title - int result = collator.compare(a.title.toString(), b.title.toString()); + // Order by the title in the current locale + int result = compareTitles(a.title.toString(), b.title.toString()); if (result == 0) { // If two apps have the same title, then order by the component name result = a.componentName.compareTo(b.componentName); @@ -49,15 +54,45 @@ class AppNameComparator { return result; } }; + mSectionNameComparator = new Comparator<String>() { + @Override + public int compare(String o1, String o2) { + return compareTitles(o1, o2); + } + }; } /** * Returns a locale-aware comparator that will alphabetically order a list of applications. */ - public Comparator<AppInfo> getComparator() { + public Comparator<AppInfo> getAppInfoComparator() { // Clear the user serial cache so that we get serials as needed in the comparator mUserSerialCache.clear(); - return mAppNameComparator; + return mAppInfoComparator; + } + + /** + * Returns a locale-aware comparator that will alphabetically order a list of section names. + */ + public Comparator<String> getSectionNameComparator() { + return mSectionNameComparator; + } + + /** + * Compares two titles with the same return value semantics as Comparator. + */ + private int compareTitles(String titleA, String titleB) { + // Ensure that we de-prioritize any titles that don't start with a linguistic letter or digit + boolean aStartsWithLetter = Character.isLetterOrDigit(titleA.codePointAt(0)); + boolean bStartsWithLetter = Character.isLetterOrDigit(titleB.codePointAt(0)); + if (aStartsWithLetter && !bStartsWithLetter) { + return -1; + } else if (!aStartsWithLetter && bStartsWithLetter) { + return 1; + } + + // Order by the title in the current locale + return mCollator.compare(titleA, titleB); } /** @@ -219,6 +254,7 @@ public class AlphabeticalAppsList { private static final int MIN_ROWS_IN_MERGED_SECTION_PHONE = 3; private static final int MAX_NUM_MERGES_PHONE = 2; + private Context mContext; private List<AppInfo> mApps = new ArrayList<>(); private List<AppInfo> mFilteredApps = new ArrayList<>(); private List<AdapterItem> mSectionedFilteredApps = new ArrayList<>(); @@ -234,6 +270,7 @@ public class AlphabeticalAppsList { private int mNumAppsPerRow; public AlphabeticalAppsList(Context context, int numAppsPerRow) { + mContext = context; mIndexer = new AlphabeticIndexCompat(context); mAppNameComparator = new AppNameComparator(context); setNumAppsPerRow(numAppsPerRow); @@ -400,7 +437,7 @@ public class AlphabeticalAppsList { * 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.getComparator()); + int index = Collections.binarySearch(mApps, info, mAppNameComparator.getAppInfoComparator()); if (index < 0) { mApps.add(-(index + 1), info); } @@ -411,7 +448,44 @@ public class AlphabeticalAppsList { */ private void onAppsUpdated() { // Sort the list of apps - Collections.sort(mApps, mAppNameComparator.getComparator()); + Collections.sort(mApps, mAppNameComparator.getAppInfoComparator()); + + // 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); + } + } 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); + } + } + } // Prepare to update the list of sections, filtered apps, etc. mFilteredApps.clear(); @@ -444,23 +518,22 @@ public class AlphabeticalAppsList { } // Add all the other apps to the combined list - allApps.addAll(mApps); + if (localeRequiresSectionSorting) { + for (Map.Entry<String, ArrayList<AppInfo>> entry : sectionMap.entrySet()) { + allApps.addAll(entry.getValue()); + } + } else { + allApps.addAll(mApps); + } // Recreate the filtered and sectioned apps (for convenience for the grid layout) from the - // combined list + // ordered set of sections int numApps = allApps.size(); for (int i = 0; i < numApps; i++) { boolean isPredictedApp = i < numPredictedApps; AppInfo info = allApps.get(i); - String sectionName = ""; - if (!isPredictedApp) { - // Only cache section names from non-predicted apps - sectionName = mCachedSectionNames.get(info.title); - if (sectionName == null) { - sectionName = mIndexer.computeSectionName(info.title); - mCachedSectionNames.put(info.title, sectionName); - } - } + // The section name was computed above so this should be find + String sectionName = isPredictedApp ? "" : mCachedSectionNames.get(info.title); // Check if we want to retain this app if (mFilter != null && !mFilter.retainApp(info, sectionName)) { @@ -478,7 +551,7 @@ public class AlphabeticalAppsList { mFastScrollerSections.add(lastFastScrollerSectionInfo); // Create a new section item to break the flow of items in the list - if (!AppsContainerView.GRID_HIDE_SECTION_HEADERS && !hasFilter()) { + if (!hasFilter()) { AdapterItem sectionItem = AdapterItem.asSectionBreak(position++, lastSectionInfo); mSectionedFilteredApps.add(sectionItem); } diff --git a/src/com/android/launcher3/AppsGridAdapter.java b/src/com/android/launcher3/AppsGridAdapter.java index 563044916..a6902d5d3 100644 --- a/src/com/android/launcher3/AppsGridAdapter.java +++ b/src/com/android/launcher3/AppsGridAdapter.java @@ -63,11 +63,7 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> { if (mApps.getAdapterItems().get(position).isSectionHeader) { // Section break spans full width - if (AppsContainerView.GRID_HIDE_SECTION_HEADERS) { - return 0; - } else { - return mAppsPerRow; - } + return mAppsPerRow; } else { return 1; } @@ -290,10 +286,8 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> { mTouchListener = touchListener; mIconClickListener = iconClickListener; mIconLongClickListener = iconLongClickListener; - if (!AppsContainerView.GRID_HIDE_SECTION_HEADERS) { - mStartMargin = res.getDimensionPixelSize(R.dimen.apps_grid_view_start_margin); - mSectionHeaderOffset = res.getDimensionPixelSize(R.dimen.apps_grid_section_y_offset); - } + mStartMargin = res.getDimensionPixelSize(R.dimen.apps_grid_view_start_margin); + mSectionHeaderOffset = res.getDimensionPixelSize(R.dimen.apps_grid_section_y_offset); mPaddingStart = res.getDimensionPixelSize(R.dimen.apps_container_inset); mSectionTextPaint = new Paint(); @@ -342,10 +336,7 @@ class AppsGridAdapter extends RecyclerView.Adapter<AppsGridAdapter.ViewHolder> { */ public RecyclerView.ItemDecoration getItemDecoration() { // We don't draw any headers when we are uncomfortably dense - if (!AppsContainerView.GRID_HIDE_SECTION_HEADERS) { - return mItemDecoration; - } - return null; + return mItemDecoration; } /** diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index 7a6e9a20a..3bbf0e7d8 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -437,13 +437,6 @@ public class DeviceProfile { } public boolean updateAppsViewNumCols(Resources res, int containerWidth) { - if (AppsContainerView.GRID_HIDE_SECTION_HEADERS) { - if (appsViewNumCols != allAppsNumCols) { - appsViewNumCols = allAppsNumCols; - return true; - } - return false; - } int appsViewLeftMarginPx = res.getDimensionPixelSize(R.dimen.apps_grid_view_start_margin); int availableAppsWidthPx = (containerWidth > 0) ? containerWidth : availableWidthPx; diff --git a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java index 18cdc81f3..ec1fb669f 100644 --- a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java +++ b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java @@ -52,12 +52,15 @@ class BaseAlphabeticIndex { */ public class AlphabeticIndexCompat extends BaseAlphabeticIndex { + private static final String MID_DOT = "\u2219"; + private Object mAlphabeticIndex; private Method mAddLabelsMethod; private Method mSetMaxLabelCountMethod; private Method mGetBucketIndexMethod; private Method mGetBucketLabelMethod; private boolean mHasValidAlphabeticIndex; + private String mDefaultMiscLabel; public AlphabeticIndexCompat(Context context) { super(); @@ -72,12 +75,20 @@ public class AlphabeticIndexCompat extends BaseAlphabeticIndex { mAlphabeticIndex = ctor.newInstance(curLocale); try { // Ensure we always have some base English locale buckets - if (!curLocale.getLanguage().equals(new Locale("en").getLanguage())) { + if (!curLocale.getLanguage().equals(Locale.ENGLISH.getLanguage())) { mAddLabelsMethod.invoke(mAlphabeticIndex, Locale.ENGLISH); } } catch (Exception e) { e.printStackTrace(); } + if (curLocale.getLanguage().equals(Locale.JAPANESE.getLanguage())) { + // Japanese character 他 ("misc") + mDefaultMiscLabel = "\u4ed6"; + // TODO(winsonc, omakoto): We need to handle Japanese sections better, especially the kanji + } else { + // Dot + mDefaultMiscLabel = MID_DOT; + } mHasValidAlphabeticIndex = true; } catch (Exception e) { mHasValidAlphabeticIndex = false; @@ -107,13 +118,21 @@ public class AlphabeticIndexCompat extends BaseAlphabeticIndex { String s = Utilities.trim(cs); String sectionName = getBucketLabel(getBucketIndex(s)); if (Utilities.trim(sectionName).isEmpty() && s.length() > 0) { - boolean startsWithDigit = Character.isDigit(s.codePointAt(0)); + int c = s.codePointAt(0); + boolean startsWithDigit = Character.isDigit(c); if (startsWithDigit) { // Digit section return "#"; } else { - // Unknown section - return "\u2022"; + boolean startsWithLetter = Character.isLetter(c); + if (startsWithLetter) { + return mDefaultMiscLabel; + } else { + // In languages where these differ, this ensures that we differentiate + // between the misc section in the native language and a misc section + // for everything else. + return MID_DOT; + } } } return sectionName; |