Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 868d19b9 authored by Yohei Yukawa's avatar Yohei Yukawa
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
parent f1b40f65
Loading
Loading
Loading
Loading
+6 −2
Original line number Diff line number Diff line
@@ -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;
+6 −2
Original line number Diff line number Diff line
@@ -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;
+6 −2
Original line number Diff line number Diff line
@@ -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;
+46 −11
Original line number Diff line number Diff line
@@ -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 @@ import java.util.Locale;
 *
 * @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 @@ import java.util.Locale;
 */
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 @@ public final class InputMethodSubtype implements Parcelable {
    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;
@@ -170,6 +174,15 @@ public final class InputMethodSubtype implements Parcelable {
        }
        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.
         */
@@ -271,6 +284,7 @@ public final class InputMethodSubtype implements Parcelable {
        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 @@ public final class InputMethodSubtype implements Parcelable {
        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 final class InputMethodSubtype implements Parcelable {
    /**
     * @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);
    }

@@ -477,6 +510,7 @@ public final class InputMethodSubtype implements Parcelable {
            }
            return (subtype.hashCode() == hashCode())
                    && (subtype.getLocale().equals(getLocale()))
                    && (subtype.getLanguageTag().equals(getLanguageTag()))
                    && (subtype.getMode().equals(getMode()))
                    && (subtype.getExtraValue().equals(getExtraValue()))
                    && (subtype.isAuxiliary() == isAuxiliary())
@@ -497,6 +531,7 @@ public final class InputMethodSubtype implements Parcelable {
        dest.writeInt(mSubtypeNameResId);
        dest.writeInt(mSubtypeIconResId);
        dest.writeString(mSubtypeLocale);
        dest.writeString(mSubtypeLanguageTag);
        dest.writeString(mSubtypeMode);
        dest.writeString(mSubtypeExtraValue);
        dest.writeInt(mIsAuxiliary ? 1 : 0);
+2 −0
Original line number Diff line number Diff line
@@ -116,6 +116,8 @@ public final class SpellCheckerInfo implements Parcelable {
                                    .SpellChecker_Subtype_label, 0),
                            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
Loading