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));
}
}