Trim all whitespace from titles and labels.

Bug: 20953160

Change-Id: I1610df5e445a4139522226f68fa6439926bc70c6
diff --git a/src/com/android/launcher3/AlphabeticalAppsList.java b/src/com/android/launcher3/AlphabeticalAppsList.java
index 70e36a7..f075e41 100644
--- a/src/com/android/launcher3/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/AlphabeticalAppsList.java
@@ -29,8 +29,7 @@
         mAppNameComparator = new Comparator<AppInfo>() {
             public final int compare(AppInfo a, AppInfo b) {
                 // Order by the title
-                int result = collator.compare(a.title.toString().trim(),
-                        b.title.toString().trim());
+                int result = collator.compare(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);
@@ -349,7 +348,7 @@
         int appIndex = 0;
         int numApps = mApps.size();
         for (AppInfo info : mApps) {
-            String sectionName = mIndexer.computeSectionName(info.title.toString().trim());
+            String sectionName = mIndexer.computeSectionName(info.title);
 
             // Check if we want to retain this app
             if (mFilter != null && !mFilter.retainApp(info, sectionName)) {
diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java
index 7c6b066..58a57a1 100644
--- a/src/com/android/launcher3/AppInfo.java
+++ b/src/com/android/launcher3/AppInfo.java
@@ -105,7 +105,7 @@
     public AppInfo(AppInfo info) {
         super(info);
         componentName = info.componentName;
-        title = info.title.toString();
+        title = Utilities.trim(info.title);
         intent = new Intent(info.intent);
         flags = info.flags;
         firstInstallTime = info.firstInstallTime;
@@ -114,7 +114,7 @@
 
     @Override
     public String toString() {
-        return "ApplicationInfo(title=" + title.toString() + " id=" + this.id
+        return "ApplicationInfo(title=" + title + " id=" + this.id
                 + " type=" + this.itemType + " container=" + this.container
                 + " screen=" + screenId + " cellX=" + cellX + " cellY=" + cellY
                 + " spanX=" + spanX + " spanY=" + spanY + " dropPos=" + Arrays.toString(dropPos)
diff --git a/src/com/android/launcher3/AppsContainerView.java b/src/com/android/launcher3/AppsContainerView.java
index 993f9c8..aa6c059 100644
--- a/src/com/android/launcher3/AppsContainerView.java
+++ b/src/com/android/launcher3/AppsContainerView.java
@@ -36,6 +36,7 @@
 import com.android.launcher3.util.Thunk;
 
 import java.util.List;
+import java.util.regex.Pattern;
 
 
 /**
@@ -56,6 +57,8 @@
     private static final int FADE_OUT_DURATION = 100;
     private static final int SEARCH_TRANSLATION_X_DP = 18;
 
+    private static final Pattern SPLIT_PATTERN = Pattern.compile("[\\s|\\p{javaSpaceChar}]+");
+
     @Thunk Launcher mLauncher;
     @Thunk AlphabeticalAppsList mApps;
     private AppsGridAdapter mAdapter;
@@ -430,23 +433,24 @@
 
     @Override
     public void afterTextChanged(final Editable s) {
-        if (s.toString().isEmpty()) {
+        String queryText = s.toString();
+        if (queryText.isEmpty()) {
             mApps.setFilter(null);
         } else {
             String formatStr = getResources().getString(R.string.apps_view_no_search_results);
-            mAdapter.setEmptySearchText(String.format(formatStr, s.toString()));
+            mAdapter.setEmptySearchText(String.format(formatStr, queryText));
 
-            final String filterText = s.toString().toLowerCase().replaceAll("\\s+", "");
+            final String queryTextLower = queryText.toLowerCase();
             mApps.setFilter(new AlphabeticalAppsList.Filter() {
                 @Override
                 public boolean retainApp(AppInfo info, String sectionName) {
-                    String title = info.title.toString();
-                    if (sectionName.toLowerCase().contains(filterText)) {
+                    if (sectionName.toLowerCase().contains(queryTextLower)) {
                         return true;
                     }
-                    String[] words = title.toLowerCase().split("\\s+");
+                    String title = info.title.toString();
+                    String[] words = SPLIT_PATTERN.split(title.toLowerCase());
                     for (int i = 0; i < words.length; i++) {
-                        if (words[i].startsWith(filterText)) {
+                        if (words[i].startsWith(queryTextLower)) {
                             return true;
                         }
                     }
diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java
index a282805..7a3ae39 100644
--- a/src/com/android/launcher3/Folder.java
+++ b/src/com/android/launcher3/Folder.java
@@ -296,13 +296,14 @@
         mFolderName.setHint(sHintText);
         // Convert to a string here to ensure that no other state associated with the text field
         // gets saved.
-        String newTitle = mFolderName.getText().toString();
+        CharSequence newTitle = mFolderName.getText();
         mInfo.setTitle(newTitle);
         LauncherModel.updateItemInDatabase(mLauncher, mInfo);
 
         if (commit) {
             sendCustomAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
-                    String.format(getContext().getString(R.string.folder_renamed), newTitle));
+                    String.format(getContext().getString(R.string.folder_renamed),
+                            newTitle.toString()));
         }
         // In order to clear the focus from the text field, we set the focus on ourself. This
         // ensures that every time the field is clicked, focus is gained, giving reliable behavior.
diff --git a/src/com/android/launcher3/FolderIcon.java b/src/com/android/launcher3/FolderIcon.java
index f5836c2..b161b1c 100644
--- a/src/com/android/launcher3/FolderIcon.java
+++ b/src/com/android/launcher3/FolderIcon.java
@@ -710,7 +710,7 @@
     }
 
     public void onTitleChanged(CharSequence title) {
-        mFolderName.setText(title.toString());
+        mFolderName.setText(title);
         setContentDescription(String.format(getContext().getString(R.string.folder_name_format),
                 title));
     }
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index 80b1564..9675371 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -87,7 +87,7 @@
     }
 
     public void setTitle(CharSequence title) {
-        this.title = title;
+        this.title = Utilities.trim(title);
         for (int i = 0; i < listeners.size(); i++) {
             listeners.get(i).onTitleChanged(title);
         }
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index 6c2aa39..0596fbe 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -400,7 +400,7 @@
         UserHandleCompat user = info == null ? application.user : info.getUser();
         CacheEntry entry = cacheLocked(application.componentName, info, user,
                 false, useLowResIcon);
-        application.title = entry.title;
+        application.title = Utilities.trim(entry.title);
         application.iconBitmap = getNonNullIcon(entry, user);
         application.contentDescription = entry.contentDescription;
         application.usingLowResIcon = entry.isLowResIcon;
@@ -413,7 +413,7 @@
         CacheEntry entry = cacheLocked(application.componentName, null, application.user,
                 false, application.usingLowResIcon);
         if (entry.icon != null && !isDefaultIcon(entry.icon, application.user)) {
-            application.title = entry.title;
+            application.title = Utilities.trim(entry.title);
             application.iconBitmap = entry.icon;
             application.contentDescription = entry.contentDescription;
             application.usingLowResIcon = entry.isLowResIcon;
@@ -464,7 +464,7 @@
             UserHandleCompat user, boolean usePkgIcon, boolean useLowResIcon) {
         CacheEntry entry = cacheLocked(component, info, user, usePkgIcon, useLowResIcon);
         shortcutInfo.setIcon(getNonNullIcon(entry, user));
-        shortcutInfo.title = entry.title;
+        shortcutInfo.title = Utilities.trim(entry.title);
         shortcutInfo.usingFallbackIcon = isDefaultIcon(entry.icon, user);
         shortcutInfo.usingLowResIcon = entry.isLowResIcon;
     }
@@ -477,7 +477,7 @@
             PackageItemInfo infoOut) {
         CacheEntry entry = getEntryForPackageLocked(packageName, user, useLowResIcon);
         infoOut.iconBitmap = getNonNullIcon(entry, user);
-        infoOut.title = entry.title;
+        infoOut.title = Utilities.trim(entry.title);
         infoOut.usingLowResIcon = entry.isLowResIcon;
         infoOut.contentDescription = entry.contentDescription;
     }
@@ -530,7 +530,7 @@
             }
 
             if (TextUtils.isEmpty(entry.title) && info != null) {
-                entry.title = info.getLabel().toString();
+                entry.title = info.getLabel();
                 entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
             }
         }
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index 23bcc85..115598f 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -247,7 +247,7 @@
             try {
                 PackageManager pm = context.getPackageManager();
                 ActivityInfo info = pm.getActivityInfo(intent.getComponent(), 0);
-                name = info.loadLabel(pm).toString();
+                name = info.loadLabel(pm);
             } catch (PackageManager.NameNotFoundException nnfe) {
                 return "";
             }
diff --git a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
index bb4580c..af680f2 100644
--- a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
@@ -66,7 +66,7 @@
 
     public String getLabel(PackageManager packageManager) {
         if (isCustomWidget) {
-            return label.toString().trim();
+            return Utilities.trim(label);
         }
         return super.loadLabel(packageManager);
     }
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index e81c8c2..3987c02 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -969,7 +969,7 @@
                         break;
                 }
 
-                folderInfo.title = c.getString(titleIndex);
+                folderInfo.title = Utilities.trim(c.getString(titleIndex));
                 folderInfo.id = id;
                 folderInfo.container = c.getInt(containerIndex);
                 folderInfo.screenId = c.getInt(screenIndex);
@@ -2144,7 +2144,7 @@
                                 id = c.getLong(idIndex);
                                 FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);
 
-                                folderInfo.title = c.getString(titleIndex);
+                                folderInfo.title = Utilities.trim(c.getString(titleIndex));
                                 folderInfo.id = id;
                                 container = c.getInt(containerIndex);
                                 folderInfo.container = container;
@@ -3199,7 +3199,7 @@
                                 if (appInfo != null && Intent.ACTION_MAIN.equals(si.intent.getAction())
                                         && si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
                                     si.updateIcon(mIconCache);
-                                    si.title = appInfo.title.toString();
+                                    si.title = Utilities.trim(appInfo.title);
                                     si.contentDescription = appInfo.contentDescription;
                                     infoUpdated = true;
                                 }
@@ -3428,18 +3428,17 @@
         if ((promiseType & ShortcutInfo.FLAG_RESTORED_ICON) != 0) {
             String title = (cursor != null) ? cursor.getString(titleIndex) : null;
             if (!TextUtils.isEmpty(title)) {
-                info.title = title;
+                info.title = Utilities.trim(title);
             }
         } else if  ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) {
             if (TextUtils.isEmpty(info.title)) {
-                info.title = (cursor != null) ? cursor.getString(titleIndex) : "";
+                info.title = (cursor != null) ? Utilities.trim(cursor.getString(titleIndex)) : "";
             }
         } else {
             throw new InvalidParameterException("Invalid restoreType " + promiseType);
         }
 
-        info.contentDescription = mUserManager.getBadgedLabelForUser(
-                info.title.toString(), info.user);
+        info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
         info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
         info.promisedIntent = intent;
         info.status = promiseType;
@@ -3501,7 +3500,7 @@
 
         // from the db
         if (TextUtils.isEmpty(info.title) && c != null) {
-            info.title =  c.getString(titleIndex);
+            info.title =  Utilities.trim(c.getString(titleIndex));
         }
 
         // fall back to the class name of the activity
@@ -3511,8 +3510,7 @@
 
         info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
         info.user = user;
-        info.contentDescription = mUserManager.getBadgedLabelForUser(
-                info.title.toString(), info.user);
+        info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
         if (lai != null) {
             info.flags = AppInfo.initFlags(lai);
         }
@@ -3578,7 +3576,7 @@
 
         // TODO: If there's an explicit component and we can't install that, delete it.
 
-        info.title = c.getString(titleIndex);
+        info.title = Utilities.trim(c.getString(titleIndex));
 
         int iconType = c.getInt(iconTypeIndex);
         switch (iconType) {
@@ -3656,9 +3654,8 @@
         }
         info.setIcon(icon);
 
-        info.title = name;
-        info.contentDescription = mUserManager.getBadgedLabelForUser(
-                info.title.toString(), info.user);
+        info.title = Utilities.trim(name);
+        info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user);
         info.intent = intent;
         info.customIcon = customIcon;
         info.iconResource = iconResource;
@@ -3699,16 +3696,16 @@
                 labelA = mLabelCache.get(a);
             } else {
                 labelA = (a instanceof LauncherAppWidgetProviderInfo)
-                        ? mManager.loadLabel((LauncherAppWidgetProviderInfo) a)
-                        : ((ResolveInfo) a).loadLabel(mPackageManager).toString().trim();
+                        ? Utilities.trim(mManager.loadLabel((LauncherAppWidgetProviderInfo) a))
+                        : Utilities.trim(((ResolveInfo) a).loadLabel(mPackageManager));
                 mLabelCache.put(a, labelA);
             }
             if (mLabelCache.containsKey(b)) {
                 labelB = mLabelCache.get(b);
             } else {
                 labelB = (b instanceof LauncherAppWidgetProviderInfo)
-                        ? mManager.loadLabel((LauncherAppWidgetProviderInfo) b)
-                        : ((ResolveInfo) b).loadLabel(mPackageManager).toString().trim();
+                        ? Utilities.trim(mManager.loadLabel((LauncherAppWidgetProviderInfo) b))
+                        : Utilities.trim(((ResolveInfo) b).loadLabel(mPackageManager));
                 mLabelCache.put(b, labelB);
             }
             return mCollator.compare(labelA, labelB);
diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java
index 6354fcd..8be4872 100644
--- a/src/com/android/launcher3/ShortcutInfo.java
+++ b/src/com/android/launcher3/ShortcutInfo.java
@@ -153,7 +153,7 @@
             Bitmap icon, UserHandleCompat user) {
         this();
         this.intent = intent;
-        this.title = title;
+        this.title = Utilities.trim(title);
         this.contentDescription = contentDescription;
         mIcon = icon;
         this.user = user;
@@ -161,7 +161,7 @@
 
     public ShortcutInfo(Context context, ShortcutInfo info) {
         super(info);
-        title = info.title.toString();
+        title = Utilities.trim(info.title);
         intent = new Intent(info.intent);
         if (info.iconResource != null) {
             iconResource = new Intent.ShortcutIconResource();
@@ -179,7 +179,7 @@
     /** TODO: Remove this.  It's only called by ApplicationInfo.makeShortcut. */
     public ShortcutInfo(AppInfo info) {
         super(info);
-        title = info.title.toString();
+        title = Utilities.trim(info.title);
         intent = new Intent(info.intent);
         customIcon = false;
         flags = info.flags;
@@ -281,7 +281,7 @@
     public static ShortcutInfo fromActivityInfo(LauncherActivityInfoCompat info, Context context) {
         final ShortcutInfo shortcut = new ShortcutInfo();
         shortcut.user = info.getUser();
-        shortcut.title = info.getLabel().toString();
+        shortcut.title = Utilities.trim(info.getLabel());
         shortcut.contentDescription = UserManagerCompat.getInstance(context)
                 .getBadgedLabelForUser(info.getLabel(), info.getUser());
         shortcut.customIcon = false;
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 2dbf078..2981747 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -54,6 +54,8 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Comparator;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * Various utilities shared amongst the Launcher's classes.
@@ -67,6 +69,9 @@
     private static final Rect sOldBounds = new Rect();
     private static final Canvas sCanvas = new Canvas();
 
+    private static final Pattern sTrimPattern =
+            Pattern.compile("^[\\s|\\p{javaSpaceChar}]*(.*)[\\s|\\p{javaSpaceChar}]*$");
+
     static {
         sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
                 Paint.FILTER_BITMAP_FLAG));
@@ -616,4 +621,14 @@
 
         return false;
     }
+
+    /**
+     * Trims the string, removing all whitespace at the beginning and end of the string.
+     * Non-breaking whitespaces are also removed.
+     */
+    public static String trim(CharSequence s) {
+        // Just strip any sequence of whitespace or java space characters from the beginning and end
+        Matcher m = sTrimPattern.matcher(s);
+        return m.replaceAll("$1");
+    }
 }
diff --git a/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java b/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
index 42e9e3c..6f89d0e 100644
--- a/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
+++ b/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
@@ -145,7 +145,7 @@
             if (info instanceof ShortcutInfo) {
                 return mContext.getString(R.string.create_folder_with, info.title);
             } else if (info instanceof FolderInfo) {
-                if (TextUtils.isEmpty(info.title.toString().trim())) {
+                if (TextUtils.isEmpty(info.title)) {
                     // Find the first item in the folder.
                     FolderInfo folder = (FolderInfo) info;
                     ShortcutInfo firstItem = null;
diff --git a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
index f890706..18cdc81 100644
--- a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
+++ b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java
@@ -1,6 +1,7 @@
 package com.android.launcher3.compat;
 
 import android.content.Context;
+import com.android.launcher3.Utilities;
 
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Method;
@@ -102,10 +103,11 @@
     /**
      * Computes the section name for an given string {@param s}.
      */
-    public String computeSectionName(String s) {
+    public String computeSectionName(CharSequence cs) {
+        String s = Utilities.trim(cs);
         String sectionName = getBucketLabel(getBucketIndex(s));
-        if (sectionName.trim().isEmpty() && s.length() > 0) {
-            boolean startsWithDigit = Character.isDigit(Character.codePointAt(s.trim(), 0));
+        if (Utilities.trim(sectionName).isEmpty() && s.length() > 0) {
+            boolean startsWithDigit = Character.isDigit(s.codePointAt(0));
             if (startsWithDigit) {
                 // Digit section
                 return "#";
diff --git a/src/com/android/launcher3/compat/AppWidgetManagerCompatV16.java b/src/com/android/launcher3/compat/AppWidgetManagerCompatV16.java
index 767f16f..967b53b 100644
--- a/src/com/android/launcher3/compat/AppWidgetManagerCompatV16.java
+++ b/src/com/android/launcher3/compat/AppWidgetManagerCompatV16.java
@@ -46,7 +46,7 @@
 
     @Override
     public String loadLabel(LauncherAppWidgetProviderInfo info) {
-        return info.label.trim();
+        return Utilities.trim(info.label);
     }
 
     @Override
diff --git a/src/com/android/launcher3/widget/PackageItemInfo.java b/src/com/android/launcher3/widget/PackageItemInfo.java
index 1a1de55..8f45a77 100644
--- a/src/com/android/launcher3/widget/PackageItemInfo.java
+++ b/src/com/android/launcher3/widget/PackageItemInfo.java
@@ -49,7 +49,7 @@
 
     @Override
     public String toString() {
-        return "PackageItemInfo(title=" + title.toString() + " id=" + this.id
+        return "PackageItemInfo(title=" + title + " id=" + this.id
                 + " type=" + this.itemType + " container=" + this.container
                 + " screen=" + screenId + " cellX=" + cellX + " cellY=" + cellY
                 + " spanX=" + spanX + " spanY=" + spanY + " dropPos=" + Arrays.toString(dropPos)
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 2df170e..27b7e6d 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -222,10 +222,9 @@
      * Helper method to get the string info of the tag.
      */
     private String getTagToString() {
-        if (getTag() instanceof PendingAddWidgetInfo) {
-            return ((PendingAddWidgetInfo)getTag()).toString();
-        } else if (getTag() instanceof PendingAddShortcutInfo) {
-            return ((PendingAddShortcutInfo)getTag()).toString();
+        if (getTag() instanceof PendingAddWidgetInfo ||
+                getTag() instanceof PendingAddShortcutInfo) {
+            return getTag().toString();
         }
         return "";
     }