Permalink
Browse files

Use BCP-47 LanguageTag in IME/Spell-Checker.

The primary goal of this CL is to make it clear that BCP-47 is the
expected format to annotate locale information for each
{InputMethod, SpellCkecker}Subtype.  In order to avoid possible
compatibility issues, this CL introduce a new "languageTag" attribute
instead of reusing existing "imeSubtypeMode" and "subtypeLocale"
attributes.

For IME developers, this CL changes nothing unless "languageTag"
attribute is specified.  To summarize:

  A: If only legacy locale-string is specified
     (existing IMEs/Spell-Checkers fall into this category):
    -> The system uses locale-string.

  B: If only LanguageTag is specified:
    -> The system uses LanguageTag.

  C: If both locale-string and languageTag are specified:
    -> The system uses LanguageTag.  Legacy locale-string is ignored.

For application developers, there should be some follow-ups CLs because
even with this CL most likely they would still have to take care of
previous versions of Android where:
  - Locale#forLanguageTag()              (N/A in API Level 20 and prior)
  - Locale#toLanguageTag()               (N/A in API Level 20 and prior)
  - InputMethodSubtype#getLocale()       (Deprecated in N)
  - SpellCheckerSubtype#getLocale()      (Deprecated in N)
  - InputMethodSubtype#getLanguageTag()  (N/A in M and prior)
  - SpellCheckerSubtype#getLanguageTag() (N/A in M and prior)
One idea would be is in the official support library to provide a utility
method that takes care of above tasks and just returns a Locale object.
If we had a utility method in the support library, probably not
returning a Locale object from #getLanguageTag() would make sense.

From performance point of view both existing legacy locale-string
attribute and new LanguageTag attribute are just String objects that
travel from XML manifest to system services to applications via IPCs.
Hence there are no performance implications except for having one more
String objects.

Bug: 22858221
Change-Id: I6db107ad2afc7709167f7c4e5d24bd589ac8bd70
  • Loading branch information...
1 parent f1b40f6 commit 868d19b93b1e20c802a001c7304f8bcac5fe5114 @yukawa yukawa committed Dec 7, 2015
View
@@ -728,6 +728,7 @@ package android {
field public static final int label = 16842753; // 0x1010001
field public static final int labelFor = 16843718; // 0x10103c6
field public static final int labelTextSize = 16843317; // 0x1010235
+ field public static final int languageTag = 16844041; // 0x1010509
field public static final int largeHeap = 16843610; // 0x101035a
field public static final int largeScreens = 16843398; // 0x1010286
field public static final int largestWidthLimitDp = 16843622; // 0x1010366
@@ -43183,7 +43184,8 @@ package android.view.inputmethod {
method public java.lang.String getExtraValue();
method public java.lang.String getExtraValueOf(java.lang.String);
method public int getIconResId();
- method public java.lang.String getLocale();
+ method public java.lang.String getLanguageTag();
+ method public deprecated java.lang.String getLocale();
method public java.lang.String getMode();
method public int getNameResId();
method public boolean isAsciiCapable();
@@ -43198,6 +43200,7 @@ package android.view.inputmethod {
method public android.view.inputmethod.InputMethodSubtype build();
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAsciiCapable(boolean);
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAuxiliary(boolean);
+ method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setLanguageTag(java.lang.String);
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setOverridesImplicitlyEnabledSubtype(boolean);
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeExtraValue(java.lang.String);
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeIconResId(int);
@@ -43261,7 +43264,8 @@ package android.view.textservice {
method public java.lang.CharSequence getDisplayName(android.content.Context, java.lang.String, android.content.pm.ApplicationInfo);
method public java.lang.String getExtraValue();
method public java.lang.String getExtraValueOf(java.lang.String);
- method public java.lang.String getLocale();
+ method public java.lang.String getLanguageTag();
+ method public deprecated java.lang.String getLocale();
method public int getNameResId();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.view.textservice.SpellCheckerSubtype> CREATOR;
@@ -822,6 +822,7 @@ package android {
field public static final int label = 16842753; // 0x1010001
field public static final int labelFor = 16843718; // 0x10103c6
field public static final int labelTextSize = 16843317; // 0x1010235
+ field public static final int languageTag = 16844041; // 0x1010509
field public static final int largeHeap = 16843610; // 0x101035a
field public static final int largeScreens = 16843398; // 0x1010286
field public static final int largestWidthLimitDp = 16843622; // 0x1010366
@@ -45522,7 +45523,8 @@ package android.view.inputmethod {
method public java.lang.String getExtraValue();
method public java.lang.String getExtraValueOf(java.lang.String);
method public int getIconResId();
- method public java.lang.String getLocale();
+ method public java.lang.String getLanguageTag();
+ method public deprecated java.lang.String getLocale();
method public java.lang.String getMode();
method public int getNameResId();
method public boolean isAsciiCapable();
@@ -45537,6 +45539,7 @@ package android.view.inputmethod {
method public android.view.inputmethod.InputMethodSubtype build();
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAsciiCapable(boolean);
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAuxiliary(boolean);
+ method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setLanguageTag(java.lang.String);
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setOverridesImplicitlyEnabledSubtype(boolean);
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeExtraValue(java.lang.String);
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeIconResId(int);
@@ -45600,7 +45603,8 @@ package android.view.textservice {
method public java.lang.CharSequence getDisplayName(android.content.Context, java.lang.String, android.content.pm.ApplicationInfo);
method public java.lang.String getExtraValue();
method public java.lang.String getExtraValueOf(java.lang.String);
- method public java.lang.String getLocale();
+ method public java.lang.String getLanguageTag();
+ method public deprecated java.lang.String getLocale();
method public int getNameResId();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.view.textservice.SpellCheckerSubtype> CREATOR;
@@ -728,6 +728,7 @@ package android {
field public static final int label = 16842753; // 0x1010001
field public static final int labelFor = 16843718; // 0x10103c6
field public static final int labelTextSize = 16843317; // 0x1010235
+ field public static final int languageTag = 16844041; // 0x1010509
field public static final int largeHeap = 16843610; // 0x101035a
field public static final int largeScreens = 16843398; // 0x1010286
field public static final int largestWidthLimitDp = 16843622; // 0x1010366
@@ -43185,7 +43186,8 @@ package android.view.inputmethod {
method public java.lang.String getExtraValue();
method public java.lang.String getExtraValueOf(java.lang.String);
method public int getIconResId();
- method public java.lang.String getLocale();
+ method public java.lang.String getLanguageTag();
+ method public deprecated java.lang.String getLocale();
method public java.lang.String getMode();
method public int getNameResId();
method public boolean isAsciiCapable();
@@ -43200,6 +43202,7 @@ package android.view.inputmethod {
method public android.view.inputmethod.InputMethodSubtype build();
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAsciiCapable(boolean);
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAuxiliary(boolean);
+ method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setLanguageTag(java.lang.String);
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setOverridesImplicitlyEnabledSubtype(boolean);
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeExtraValue(java.lang.String);
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeIconResId(int);
@@ -43263,7 +43266,8 @@ package android.view.textservice {
method public java.lang.CharSequence getDisplayName(android.content.Context, java.lang.String, android.content.pm.ApplicationInfo);
method public java.lang.String getExtraValue();
method public java.lang.String getExtraValueOf(java.lang.String);
- method public java.lang.String getLocale();
+ method public java.lang.String getLanguageTag();
+ method public deprecated java.lang.String getLocale();
method public int getNameResId();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.view.textservice.SpellCheckerSubtype> CREATOR;
@@ -16,6 +16,7 @@
package android.view.inputmethod;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -50,6 +51,7 @@
*
* @attr ref android.R.styleable#InputMethod_Subtype_label
* @attr ref android.R.styleable#InputMethod_Subtype_icon
+ * @attr ref android.R.styleable#InputMethod_Subtype_languageTag
* @attr ref android.R.styleable#InputMethod_Subtype_imeSubtypeLocale
* @attr ref android.R.styleable#InputMethod_Subtype_imeSubtypeMode
* @attr ref android.R.styleable#InputMethod_Subtype_imeSubtypeExtraValue
@@ -60,6 +62,7 @@
*/
public final class InputMethodSubtype implements Parcelable {
private static final String TAG = InputMethodSubtype.class.getSimpleName();
+ private static final String LANGUAGE_TAG_NONE = "";
private static final String EXTRA_VALUE_PAIR_SEPARATOR = ",";
private static final String EXTRA_VALUE_KEY_VALUE_SEPARATOR = "=";
// TODO: remove this
@@ -74,6 +77,7 @@
private final int mSubtypeNameResId;
private final int mSubtypeId;
private final String mSubtypeLocale;
+ private final String mSubtypeLanguageTag;
private final String mSubtypeMode;
private final String mSubtypeExtraValue;
private volatile HashMap<String, String> mExtraValueHashMapCache;
@@ -171,6 +175,15 @@ public InputMethodSubtypeBuilder setSubtypeLocale(String subtypeLocale) {
private String mSubtypeLocale = "";
/**
+ * @param languageTag is the BCP-47 Language Tag supported by this subtype.
+ */
+ public InputMethodSubtypeBuilder setLanguageTag(String languageTag) {
+ mSubtypeLanguageTag = languageTag == null ? LANGUAGE_TAG_NONE : languageTag;
+ return this;
+ }
+ private String mSubtypeLanguageTag = LANGUAGE_TAG_NONE;
+
+ /**
* @param subtypeMode is the mode supported by this subtype.
*/
public InputMethodSubtypeBuilder setSubtypeMode(String subtypeMode) {
@@ -271,6 +284,7 @@ private InputMethodSubtype(InputMethodSubtypeBuilder builder) {
mSubtypeNameResId = builder.mSubtypeNameResId;
mSubtypeIconResId = builder.mSubtypeIconResId;
mSubtypeLocale = builder.mSubtypeLocale;
+ mSubtypeLanguageTag = builder.mSubtypeLanguageTag;
mSubtypeMode = builder.mSubtypeMode;
mSubtypeExtraValue = builder.mSubtypeExtraValue;
mIsAuxiliary = builder.mIsAuxiliary;
@@ -291,6 +305,8 @@ private InputMethodSubtype(InputMethodSubtypeBuilder builder) {
s = source.readString();
mSubtypeLocale = s != null ? s : "";
s = source.readString();
+ mSubtypeLanguageTag = s != null ? s : LANGUAGE_TAG_NONE;
+ s = source.readString();
mSubtypeMode = s != null ? s : "";
s = source.readString();
mSubtypeExtraValue = s != null ? s : "";
@@ -318,21 +334,38 @@ public int getIconResId() {
/**
* @return The locale of the subtype. This method returns the "locale" string parameter passed
* to the constructor.
+ *
+ * @deprecated Use {@link #getLanguageTag()} instead.
*/
+ @Deprecated
+ @NonNull
public String getLocale() {
return mSubtypeLocale;
}
/**
- * @return The normalized {@link Locale} object of the subtype. The returned locale may or may
- * not equal to "locale" string parameter passed to the constructor.
+ * @return the BCP-47 Language Tag of the subtype. Returns an empty string when no Language Tag
+ * is specified.
*
- * <p>TODO: Consider to make this a public API.</p>
+ * @see Locale#forLanguageTag(String)
+ */
+ @NonNull
+ public String getLanguageTag() {
+ return mSubtypeLanguageTag;
+ }
+
+ /**
+ * @return {@link Locale} constructed from {@link #getLanguageTag()}. If the Language Tag is not
+ * specified, then try to construct from {@link #getLocale()}
+ *
+ * <p>TODO: Consider to make this a public API, or move this to support lib.</p>
* @hide
*/
@Nullable
public Locale getLocaleObject() {
- // TODO: Move the following method from InputMethodUtils to InputMethodSubtype.
+ if (!TextUtils.isEmpty(mSubtypeLanguageTag)) {
+ return Locale.forLanguageTag(mSubtypeLanguageTag);
+ }
return InputMethodUtils.constructLocaleFromString(mSubtypeLocale);
}
@@ -476,13 +509,14 @@ public boolean equals(Object o) {
return (subtype.hashCode() == hashCode());
}
return (subtype.hashCode() == hashCode())
- && (subtype.getLocale().equals(getLocale()))
- && (subtype.getMode().equals(getMode()))
- && (subtype.getExtraValue().equals(getExtraValue()))
- && (subtype.isAuxiliary() == isAuxiliary())
- && (subtype.overridesImplicitlyEnabledSubtype()
- == overridesImplicitlyEnabledSubtype())
- && (subtype.isAsciiCapable() == isAsciiCapable());
+ && (subtype.getLocale().equals(getLocale()))
+ && (subtype.getLanguageTag().equals(getLanguageTag()))
+ && (subtype.getMode().equals(getMode()))
+ && (subtype.getExtraValue().equals(getExtraValue()))
+ && (subtype.isAuxiliary() == isAuxiliary())
+ && (subtype.overridesImplicitlyEnabledSubtype()
+ == overridesImplicitlyEnabledSubtype())
+ && (subtype.isAsciiCapable() == isAsciiCapable());
}
return false;
}
@@ -497,6 +531,7 @@ public void writeToParcel(Parcel dest, int parcelableFlags) {
dest.writeInt(mSubtypeNameResId);
dest.writeInt(mSubtypeIconResId);
dest.writeString(mSubtypeLocale);
+ dest.writeString(mSubtypeLanguageTag);
dest.writeString(mSubtypeMode);
dest.writeString(mSubtypeExtraValue);
dest.writeInt(mIsAuxiliary ? 1 : 0);
@@ -117,6 +117,8 @@ public SpellCheckerInfo(Context context, ResolveInfo service)
a.getString(com.android.internal.R.styleable
.SpellChecker_Subtype_subtypeLocale),
a.getString(com.android.internal.R.styleable
+ .SpellChecker_Subtype_languageTag),
+ a.getString(com.android.internal.R.styleable
.SpellChecker_Subtype_subtypeExtraValue),
a.getInt(com.android.internal.R.styleable
.SpellChecker_Subtype_subtypeId, 0));
@@ -18,6 +18,7 @@
import com.android.internal.inputmethod.InputMethodUtils;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -40,6 +41,7 @@
* @see SpellCheckerInfo
*
* @attr ref android.R.styleable#SpellChecker_Subtype_label
+ * @attr ref android.R.styleable#SpellChecker_Subtype_languageTag
* @attr ref android.R.styleable#SpellChecker_Subtype_subtypeLocale
* @attr ref android.R.styleable#SpellChecker_Subtype_subtypeExtraValue
* @attr ref android.R.styleable#SpellChecker_Subtype_subtypeId
@@ -49,11 +51,13 @@
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 static final String SUBTYPE_LANGUAGE_TAG_NONE = "";
private final int mSubtypeId;
private final int mSubtypeHashCode;
private final int mSubtypeNameResId;
private final String mSubtypeLocale;
+ private final String mSubtypeLanguageTag;
private final String mSubtypeExtraValue;
private HashMap<String, String> mExtraValueHashMapCache;
@@ -66,14 +70,17 @@
*
* @param nameId The name of the subtype
* @param locale The locale supported by the subtype
+ * @param languageTag The BCP-47 Language Tag associated with this 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, int subtypeId) {
+ public SpellCheckerSubtype(int nameId, String locale, String languageTag, String extraValue,
+ int subtypeId) {
mSubtypeNameResId = nameId;
mSubtypeLocale = locale != null ? locale : "";
+ mSubtypeLanguageTag = languageTag != null ? languageTag : SUBTYPE_LANGUAGE_TAG_NONE;
mSubtypeExtraValue = extraValue != null ? extraValue : "";
mSubtypeId = subtypeId;
mSubtypeHashCode = mSubtypeId != SUBTYPE_ID_NONE ?
@@ -91,7 +98,7 @@ public SpellCheckerSubtype(int nameId, String locale, String extraValue, int sub
* to instantiate {@link SpellCheckerSubtype} object.
*/
public SpellCheckerSubtype(int nameId, String locale, String extraValue) {
- this(nameId, locale, extraValue, SUBTYPE_ID_NONE);
+ this(nameId, locale, SUBTYPE_LANGUAGE_TAG_NONE, extraValue, SUBTYPE_ID_NONE);
}
SpellCheckerSubtype(Parcel source) {
@@ -100,6 +107,8 @@ public SpellCheckerSubtype(int nameId, String locale, String extraValue) {
s = source.readString();
mSubtypeLocale = s != null ? s : "";
s = source.readString();
+ mSubtypeLanguageTag = s != null ? s : "";
+ s = source.readString();
mSubtypeExtraValue = s != null ? s : "";
mSubtypeId = source.readInt();
mSubtypeHashCode = mSubtypeId != SUBTYPE_ID_NONE ?
@@ -115,12 +124,27 @@ public int getNameResId() {
/**
* @return the locale of the subtype
+ *
+ * @deprecated Use {@link #getLanguageTag()} instead.
*/
+ @Deprecated
+ @NonNull
public String getLocale() {
return mSubtypeLocale;
}
/**
+ * @return the BCP-47 Language Tag of the subtype. Returns an empty string when no Language Tag
+ * is specified.
+ *
+ * @see Locale#forLanguageTag(String)
+ */
+ @NonNull
+ public String getLanguageTag() {
+ return mSubtypeLanguageTag;
+ }
+
+ /**
* @return the extra value of the subtype
*/
public String getExtraValue() {
@@ -182,20 +206,24 @@ public boolean equals(Object o) {
return (subtype.hashCode() == hashCode())
&& (subtype.getNameResId() == getNameResId())
&& (subtype.getLocale().equals(getLocale()))
+ && (subtype.getLanguageTag().equals(getLanguageTag()))
&& (subtype.getExtraValue().equals(getExtraValue()));
}
return false;
}
/**
- * @return The normalized {@link Locale} object of the subtype. The returned locale may or may
- * not equal to "locale" string parameter passed to the constructor.
+ * @return {@link Locale} constructed from {@link #getLanguageTag()}. If the Language Tag is not
+ * specified, then try to construct from {@link #getLocale()}
*
- * <p>TODO: Consider to make this a public API.</p>
+ * <p>TODO: Consider to make this a public API, or move this to support lib.</p>
* @hide
*/
@Nullable
public Locale getLocaleObject() {
+ if (!TextUtils.isEmpty(mSubtypeLanguageTag)) {
+ return Locale.forLanguageTag(mSubtypeLanguageTag);
+ }
return InputMethodUtils.constructLocaleFromString(mSubtypeLocale);
}
@@ -234,6 +262,7 @@ public int describeContents() {
public void writeToParcel(Parcel dest, int parcelableFlags) {
dest.writeInt(mSubtypeNameResId);
dest.writeString(mSubtypeLocale);
+ dest.writeString(mSubtypeLanguageTag);
dest.writeString(mSubtypeExtraValue);
dest.writeInt(mSubtypeId);
}
Oops, something went wrong.

0 comments on commit 868d19b

Please sign in to comment.