Merge "Add subtypeId for SpellCheckerSubtype."
diff --git a/api/current.txt b/api/current.txt
index feb43da..ca176b6 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -43255,7 +43255,7 @@
   }
 
   public final class SpellCheckerSubtype implements android.os.Parcelable {
-    ctor public SpellCheckerSubtype(int, java.lang.String, java.lang.String);
+    ctor public deprecated SpellCheckerSubtype(int, java.lang.String, java.lang.String);
     method public boolean containsExtraValueKey(java.lang.String);
     method public int describeContents();
     method public java.lang.CharSequence getDisplayName(android.content.Context, java.lang.String, android.content.pm.ApplicationInfo);
diff --git a/api/system-current.txt b/api/system-current.txt
index bc7983a..dcb0852 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -45594,7 +45594,7 @@
   }
 
   public final class SpellCheckerSubtype implements android.os.Parcelable {
-    ctor public SpellCheckerSubtype(int, java.lang.String, java.lang.String);
+    ctor public deprecated SpellCheckerSubtype(int, java.lang.String, java.lang.String);
     method public boolean containsExtraValueKey(java.lang.String);
     method public int describeContents();
     method public java.lang.CharSequence getDisplayName(android.content.Context, java.lang.String, android.content.pm.ApplicationInfo);
diff --git a/api/test-current.txt b/api/test-current.txt
index 3bcfd67..da02754 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -43257,7 +43257,7 @@
   }
 
   public final class SpellCheckerSubtype implements android.os.Parcelable {
-    ctor public SpellCheckerSubtype(int, java.lang.String, java.lang.String);
+    ctor public deprecated SpellCheckerSubtype(int, java.lang.String, java.lang.String);
     method public boolean containsExtraValueKey(java.lang.String);
     method public int describeContents();
     method public java.lang.CharSequence getDisplayName(android.content.Context, java.lang.String, android.content.pm.ApplicationInfo);
diff --git a/core/java/android/view/textservice/SpellCheckerInfo.java b/core/java/android/view/textservice/SpellCheckerInfo.java
index 2167862..491de78 100644
--- a/core/java/android/view/textservice/SpellCheckerInfo.java
+++ b/core/java/android/view/textservice/SpellCheckerInfo.java
@@ -117,7 +117,9 @@
                             a.getString(com.android.internal.R.styleable
                                     .SpellChecker_Subtype_subtypeLocale),
                             a.getString(com.android.internal.R.styleable
-                                    .SpellChecker_Subtype_subtypeExtraValue));
+                                    .SpellChecker_Subtype_subtypeExtraValue),
+                            a.getInt(com.android.internal.R.styleable
+                                    .SpellChecker_Subtype_subtypeId, 0));
                     mSubtypes.add(subtype);
                 }
             }
diff --git a/core/java/android/view/textservice/SpellCheckerSubtype.java b/core/java/android/view/textservice/SpellCheckerSubtype.java
index 77fd002..f2b03cc 100644
--- a/core/java/android/view/textservice/SpellCheckerSubtype.java
+++ b/core/java/android/view/textservice/SpellCheckerSubtype.java
@@ -36,12 +36,21 @@
 /**
  * This class is used to specify meta information of a subtype contained in a spell checker.
  * Subtype can describe locale (e.g. en_US, fr_FR...) used for settings.
+ *
+ * @see SpellCheckerInfo
+ *
+ * @attr ref android.R.styleable#SpellChecker_Subtype_label
+ * @attr ref android.R.styleable#SpellChecker_Subtype_subtypeLocale
+ * @attr ref android.R.styleable#SpellChecker_Subtype_subtypeExtraValue
+ * @attr ref android.R.styleable#SpellChecker_Subtype_subtypeId
  */
 public final class SpellCheckerSubtype implements Parcelable {
     private static final String TAG = SpellCheckerSubtype.class.getSimpleName();
     private static final String EXTRA_VALUE_PAIR_SEPARATOR = ",";
     private static final String EXTRA_VALUE_KEY_VALUE_SEPARATOR = "=";
+    private static final int SUBTYPE_ID_NONE = 0;
 
+    private final int mSubtypeId;
     private final int mSubtypeHashCode;
     private final int mSubtypeNameResId;
     private final String mSubtypeLocale;
@@ -49,16 +58,40 @@
     private HashMap<String, String> mExtraValueHashMapCache;
 
     /**
-     * Constructor
+     * Constructor.
+     *
+     * <p>There is no public API that requires developers to instantiate custom
+     * {@link SpellCheckerSubtype} object.  Hence so far there is no need to make this constructor
+     * available in public API.</p>
+     *
      * @param nameId The name of the subtype
      * @param locale The locale supported by the subtype
      * @param extraValue The extra value of the subtype
+     * @param subtypeId The subtype ID that is supposed to be stable during package update.
+     *
+     * @hide
      */
-    public SpellCheckerSubtype(int nameId, String locale, String extraValue) {
+    public SpellCheckerSubtype(int nameId, String locale, String extraValue, int subtypeId) {
         mSubtypeNameResId = nameId;
         mSubtypeLocale = locale != null ? locale : "";
         mSubtypeExtraValue = extraValue != null ? extraValue : "";
-        mSubtypeHashCode = hashCodeInternal(mSubtypeLocale, mSubtypeExtraValue);
+        mSubtypeId = subtypeId;
+        mSubtypeHashCode = mSubtypeId != SUBTYPE_ID_NONE ?
+                mSubtypeId : hashCodeInternal(mSubtypeLocale, mSubtypeExtraValue);
+    }
+
+    /**
+     * Constructor.
+     * @param nameId The name of the subtype
+     * @param locale The locale supported by the subtype
+     * @param extraValue The extra value of the subtype
+     *
+     * @deprecated There is no public API that requires developers to directly instantiate custom
+     * {@link SpellCheckerSubtype} objects right now.  Hence only the system is expected to be able
+     * to instantiate {@link SpellCheckerSubtype} object.
+     */
+    public SpellCheckerSubtype(int nameId, String locale, String extraValue) {
+        this(nameId, locale, extraValue, SUBTYPE_ID_NONE);
     }
 
     SpellCheckerSubtype(Parcel source) {
@@ -68,7 +101,9 @@
         mSubtypeLocale = s != null ? s : "";
         s = source.readString();
         mSubtypeExtraValue = s != null ? s : "";
-        mSubtypeHashCode = hashCodeInternal(mSubtypeLocale, mSubtypeExtraValue);
+        mSubtypeId = source.readInt();
+        mSubtypeHashCode = mSubtypeId != SUBTYPE_ID_NONE ?
+                mSubtypeId : hashCodeInternal(mSubtypeLocale, mSubtypeExtraValue);
     }
 
     /**
@@ -141,10 +176,13 @@
     public boolean equals(Object o) {
         if (o instanceof SpellCheckerSubtype) {
             SpellCheckerSubtype subtype = (SpellCheckerSubtype) o;
+            if (subtype.mSubtypeId != SUBTYPE_ID_NONE || mSubtypeId != SUBTYPE_ID_NONE) {
+                return (subtype.hashCode() == hashCode());
+            }
             return (subtype.hashCode() == hashCode())
-                && (subtype.getNameResId() == getNameResId())
-                && (subtype.getLocale().equals(getLocale()))
-                && (subtype.getExtraValue().equals(getExtraValue()));
+                    && (subtype.getNameResId() == getNameResId())
+                    && (subtype.getLocale().equals(getLocale()))
+                    && (subtype.getExtraValue().equals(getExtraValue()));
         }
         return false;
     }
@@ -197,6 +235,7 @@
         dest.writeInt(mSubtypeNameResId);
         dest.writeString(mSubtypeLocale);
         dest.writeString(mSubtypeExtraValue);
+        dest.writeInt(mSubtypeId);
     }
 
     public static final Parcelable.Creator<SpellCheckerSubtype> CREATOR
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 34a66d0..9bca3d6 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3095,6 +3095,13 @@
         <!-- The extra value of the subtype. This string can be any string and will be passed to
              the SpellChecker.  -->
         <attr name="subtypeExtraValue" format="string" />
+        <!-- The unique id for the subtype. The text service (spell checker) framework keeps track
+             of enabled subtypes by ID. When the spell checker package gets upgraded, enabled IDs
+             will stay enabled even if other attributes are different. If the ID is unspecified or
+             or explicitly specified to 0 in XML resources,
+             {@code Arrays.hashCode(new Object[] {subtypeLocale, extraValue}) will be used instead.
+              -->
+        <attr name="subtypeId" />
     </declare-styleable>
 
     <!-- Use <code>accessibility-service</code> as the root tag of the XML resource that
diff --git a/core/tests/coretests/src/android/view/textservice/SpellCheckerSubtypeTest.java b/core/tests/coretests/src/android/view/textservice/SpellCheckerSubtypeTest.java
index 157c815..73fdb10 100644
--- a/core/tests/coretests/src/android/view/textservice/SpellCheckerSubtypeTest.java
+++ b/core/tests/coretests/src/android/view/textservice/SpellCheckerSubtypeTest.java
@@ -29,12 +29,15 @@
  * TODO: Most of part can be, and probably should be, moved to CTS.
  */
 public class SpellCheckerSubtypeTest extends InstrumentationTestCase {
+    private static final int SUBTYPE_SUBTYPE_ID_NONE = 0;
     private static final String SUBTYPE_SUBTYPE_LOCALE_STRING_A = "en_GB";
     private static final int SUBTYPE_NAME_RES_ID_A = 0x12345;
     private static final String SUBTYPE_EXTRA_VALUE_A = "Key1=Value1,Key2=Value2";
+    private static final int SUBTYPE_SUBTYPE_ID_A = 42;
     private static final String SUBTYPE_SUBTYPE_LOCALE_STRING_B = "en_IN";
     private static final int SUBTYPE_NAME_RES_ID_B = 0x54321;
     private static final String SUBTYPE_EXTRA_VALUE_B = "Key3=Value3,Key4=Value4";
+    private static final int SUBTYPE_SUBTYPE_ID_B = -42;
 
     private static int defaultHashCodeAlgorithm(String locale, String extraValue) {
         return Arrays.hashCode(new Object[] {locale, extraValue});
@@ -55,10 +58,9 @@
     }
 
     @SmallTest
-    public void testSubtype() throws Exception {
+    public void testSubtypeWithNoSubtypeId() throws Exception {
         final SpellCheckerSubtype subtype = new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A,
-                SUBTYPE_SUBTYPE_LOCALE_STRING_A, SUBTYPE_EXTRA_VALUE_A);
-
+                SUBTYPE_SUBTYPE_LOCALE_STRING_A, SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE);
         assertEquals(SUBTYPE_NAME_RES_ID_A, subtype.getNameResId());
         assertEquals(SUBTYPE_SUBTYPE_LOCALE_STRING_A, subtype.getLocale());
         assertEquals("Value1", subtype.getExtraValueOf("Key1"));
@@ -80,6 +82,26 @@
                 clonedSubtype.hashCode());
     }
 
+    public void testSubtypeWithSubtypeId() throws Exception {
+        final SpellCheckerSubtype subtype = new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A,
+                SUBTYPE_SUBTYPE_LOCALE_STRING_A, SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A);
+
+        assertEquals(SUBTYPE_NAME_RES_ID_A, subtype.getNameResId());
+        assertEquals(SUBTYPE_SUBTYPE_LOCALE_STRING_A, subtype.getLocale());
+        assertEquals("Value1", subtype.getExtraValueOf("Key1"));
+        assertEquals("Value2", subtype.getExtraValueOf("Key2"));
+        // Similar to "SubtypeId" in InputMethodSubtype, "SubtypeId" in SpellCheckerSubtype enables
+        // developers to specify a stable and consistent ID for each subtype.
+        assertEquals(SUBTYPE_SUBTYPE_ID_A, subtype.hashCode());
+
+        final SpellCheckerSubtype clonedSubtype = cloneViaParcel(subtype);
+        assertEquals(SUBTYPE_NAME_RES_ID_A, clonedSubtype.getNameResId());
+        assertEquals(SUBTYPE_SUBTYPE_LOCALE_STRING_A, clonedSubtype.getLocale());
+        assertEquals("Value1", clonedSubtype.getExtraValueOf("Key1"));
+        assertEquals("Value2", clonedSubtype.getExtraValueOf("Key2"));
+        assertEquals(SUBTYPE_SUBTYPE_ID_A, clonedSubtype.hashCode());
+    }
+
     @SmallTest
     public void testGetLocaleObject() throws Exception {
         assertEquals(new Locale("en"), new SpellCheckerSubtype(
@@ -130,5 +152,54 @@
                         SUBTYPE_EXTRA_VALUE_A),
                 new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
                         SUBTYPE_EXTRA_VALUE_B));
+
+        // If subtype ID is 0 (== SUBTYPE_SUBTYPE_ID_NONE), we keep the same behavior.
+        assertEquals(
+                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
+                        SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE),
+                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
+                        SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE));
+        assertNotEqual(
+                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
+                        SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE),
+                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_B, SUBTYPE_SUBTYPE_LOCALE_STRING_B,
+                        SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE));
+        assertNotEqual(
+                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
+                        SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE),
+                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_B,
+                        SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE));
+        assertNotEqual(
+                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
+                        SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_NONE),
+                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
+                        SUBTYPE_EXTRA_VALUE_B, SUBTYPE_SUBTYPE_ID_NONE));
+
+        // If subtype ID is not 0, we test the equality based only on the subtype ID.
+        assertEquals(
+                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
+                        SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A),
+                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
+                        SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A));
+        assertEquals(
+                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
+                        SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A),
+                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_B, SUBTYPE_SUBTYPE_LOCALE_STRING_B,
+                        SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A));
+        assertEquals(
+                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
+                        SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A),
+                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_B,
+                        SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A));
+        assertEquals(
+                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
+                        SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A),
+                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
+                        SUBTYPE_EXTRA_VALUE_B, SUBTYPE_SUBTYPE_ID_A));
+        assertNotEqual(
+                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
+                        SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_A),
+                new SpellCheckerSubtype(SUBTYPE_NAME_RES_ID_A, SUBTYPE_SUBTYPE_LOCALE_STRING_A,
+                        SUBTYPE_EXTRA_VALUE_A, SUBTYPE_SUBTYPE_ID_B));
     }
 }