Use ICU to format plural strings

Test: manual
Fix: 199230208
Change-Id: I0b6fe9f8bb134a1479117c832575c63da2a07794
diff --git a/res/values/strings.xml b/res/values/strings.xml
index a6105eb..323c5ca 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -67,17 +67,15 @@
          button in a dialog. [CHAR_LIMIT=none] -->
     <string name="added_to_home_screen_accessibility_text"><xliff:g id="widget_name" example="Calendar month view">%1$s</xliff:g> widget added to home screen</string>
     <!-- Label for showing the number of widgets an app has in the full widgets picker.
-         [CHAR_LIMIT=25] -->
-    <plurals name="widgets_count">
-        <item quantity="one"><xliff:g id="widgets_count" example="1">%1$d</xliff:g> widget</item>
-        <item quantity="other"><xliff:g id="widgets_count" example="2">%1$d</xliff:g> widgets</item>
-    </plurals>
+         [CHAR_LIMIT=25][ICU SYNTAX] -->
+    <string name="widgets_count">
+        {count, plural, =1{# widget} other{# widgets}}
+    </string>
     <!-- Label for showing the number of shortcut an app has in the full widgets picker.
-         [CHAR_LIMIT=25] -->
-    <plurals name="shortcuts_count">
-        <item quantity="one"><xliff:g id="shortcuts_count" example="1">%1$d</xliff:g> shortcut</item>
-        <item quantity="other"><xliff:g id="shortcuts_count" example="2">%1$d</xliff:g> shortcuts</item>
-    </plurals>
+         [CHAR_LIMIT=25][ICU SYNTAX] -->
+    <string name="shortcuts_count">
+        {count, plural, =1{# shortcut} other{# shortcuts}}
+    </string>
     <!-- Label for showing both the number of widgets and shortcuts an app has in the full widgets
          picker. [CHAR_LIMIT=50] -->
     <string name="widgets_and_shortcuts_count"><xliff:g id="widgets_count" example="5 widgets">%1$s</xliff:g>, <xliff:g id="shortcuts_count" example="1 shortcut">%2$s</xliff:g></string>
@@ -212,11 +210,13 @@
     <!-- Accessibility -->
     <!-- The format string for when an app is temporarily disabled. -->
     <string name="disabled_app_label">Disabled <xliff:g id="app_name" example="Messenger">%1$s</xliff:g></string>
-    <!-- The format string for when an app has a notification dot (meaning it has associated notifications). -->
-    <plurals name="dotted_app_label">
-        <item quantity="one"><xliff:g id="app_name" example="Messenger">%1$s</xliff:g>, has <xliff:g id="notification_count" example="1">%2$d</xliff:g> notification</item>
-        <item quantity="other"><xliff:g id="app_name" example="Messenger">%1$s</xliff:g>, has <xliff:g id="notification_count" example="3">%2$d</xliff:g> notifications</item>
-    </plurals>
+    <!-- The format string for when an app has a notification dot (meaning it has associated notifications). [ICU_FORMAT]-->
+    <string name="dotted_app_label">
+        {count, plural, offset:1
+            =1      {{app_name} has # notification}
+            other   {{app_name} has # notifications}
+        }
+    </string>
     <skip />
 
     <!-- The format string for default page scroll text [CHAR_LIMIT=none] -->
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 54920e1..f6c58c4 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -32,6 +32,7 @@
 import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
+import android.icu.text.MessageFormat;
 import android.text.TextUtils.TruncateAt;
 import android.util.AttributeSet;
 import android.util.Property;
@@ -68,6 +69,8 @@
 import com.android.launcher3.views.IconLabelDotView;
 
 import java.text.NumberFormat;
+import java.util.HashMap;
+import java.util.Locale;
 
 /**
  * TextView that draws a bubble behind the text. We cannot use a LineBackgroundSpan
@@ -695,8 +698,8 @@
                             itemInfo.contentDescription));
                 } else if (hasDot()) {
                     int count = mDotInfo.getNotificationCount();
-                    setContentDescription(getContext().getResources().getQuantityString(
-                            R.plurals.dotted_app_label, count, itemInfo.contentDescription, count));
+                    setContentDescription(
+                            getAppLabelPluralString(itemInfo.contentDescription.toString(), count));
                 } else {
                     setContentDescription(itemInfo.contentDescription);
                 }
@@ -938,4 +941,14 @@
             setCompoundDrawables(null, newIcon, null, null);
         }
     }
+
+    private String getAppLabelPluralString(String appName, int notificationCount) {
+        MessageFormat icuCountFormat = new MessageFormat(
+                getResources().getString(R.string.dotted_app_label),
+                Locale.getDefault());
+        HashMap<String, Object> args = new HashMap();
+        args.put("app_name", appName);
+        args.put("count", notificationCount);
+        return icuCountFormat.format(args);
+    }
 }
diff --git a/src/com/android/launcher3/util/PluralMessageFormat.java b/src/com/android/launcher3/util/PluralMessageFormat.java
new file mode 100644
index 0000000..5e4ce8d
--- /dev/null
+++ b/src/com/android/launcher3/util/PluralMessageFormat.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util;
+
+import android.content.Context;
+import android.icu.text.MessageFormat;
+
+import androidx.annotation.StringRes;
+
+import java.util.HashMap;
+import java.util.Locale;
+
+/** A helper class to format common ICU plural strings. */
+public class PluralMessageFormat {
+
+    /**
+     * Returns a plural string from a ICU format message template, which takes "count" as an
+     * argument.
+     *
+     * <p>An example of ICU format message template provided by {@code stringId}:
+     * {count, plural, =1{# widget} other{# widgets}}
+     */
+    public static final String getIcuPluralString(Context context, @StringRes int stringId,
+            int count) {
+        MessageFormat icuCountFormat = new MessageFormat(
+                context.getResources().getString(stringId),
+                Locale.getDefault());
+        HashMap<String, Object> args = new HashMap();
+        args.put("count", count);
+        return icuCountFormat.format(args);
+    }
+}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
index ef2adbb..ebd2d10 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
@@ -39,6 +39,7 @@
 import com.android.launcher3.icons.cache.HandlerRunnable;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.model.data.PackageItemInfo;
+import com.android.launcher3.util.PluralMessageFormat;
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
 import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
@@ -217,18 +218,18 @@
 
         String subtitle;
         if (entry.widgetsCount > 0 && entry.shortcutsCount > 0) {
-            String widgetsCount = resources.getQuantityString(R.plurals.widgets_count,
-                    entry.widgetsCount, entry.widgetsCount);
-            String shortcutsCount = resources.getQuantityString(R.plurals.shortcuts_count,
-                    entry.shortcutsCount, entry.shortcutsCount);
+            String widgetsCount = PluralMessageFormat.getIcuPluralString(getContext(),
+                    R.string.widgets_count, entry.widgetsCount);
+            String shortcutsCount = PluralMessageFormat.getIcuPluralString(getContext(),
+                    R.string.shortcuts_count, entry.shortcutsCount);
             subtitle = resources.getString(R.string.widgets_and_shortcuts_count, widgetsCount,
                     shortcutsCount);
         } else if (entry.widgetsCount > 0) {
-            subtitle = resources.getQuantityString(R.plurals.widgets_count,
-                    entry.widgetsCount, entry.widgetsCount);
+            subtitle = PluralMessageFormat.getIcuPluralString(getContext(),
+                    R.string.widgets_count, entry.widgetsCount);
         } else {
-            subtitle = resources.getQuantityString(R.plurals.shortcuts_count,
-                    entry.shortcutsCount, entry.shortcutsCount);
+            subtitle = PluralMessageFormat.getIcuPluralString(getContext(),
+                    R.string.shortcuts_count, entry.shortcutsCount);
         }
         mSubtitle.setText(subtitle);
         mSubtitle.setVisibility(VISIBLE);