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

Commit 60fbc8e3 authored by Yohei Yukawa's avatar Yohei Yukawa
Browse files

Introduce SuggestionSpan#getLocaleObject().

This is a series of CLs to promote Java Locale object as a primary data
object to represent locale information in Android APIs.

With this CL, developers can get Locale information from SuggestionSpan
without semantically losing the original data.  Possible use cases would
be:
 - Emoji subtag (CLDR Ticket #9063)
 - Stable and reliable 3-letter country codes handling
 - Custom attributes based on private extensions.

Note that this CL does not change the current behavior of
SuggestionSpan#getLocale() as is, even if it is created with a malformed
Locale object:

  new SuggestionSpan(new Locale(" a ", " b c ", " e"), new String[0], 0)
          .getLocale() -> " a  b c  e".

Bug: 22858221
Change-Id: I96dfd4f819a236ee2e6dbd3e12903d1214223cb0
parent a1dcb875
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -38206,7 +38206,8 @@ package android.text.style {
    ctor public SuggestionSpan(android.os.Parcel);
    method public int describeContents();
    method public int getFlags();
    method public java.lang.String getLocale();
    method public deprecated java.lang.String getLocale();
    method public java.util.Locale getLocaleObject();
    method public int getSpanTypeId();
    method public java.lang.String[] getSuggestions();
    method public void setFlags(int);
+2 −1
Original line number Diff line number Diff line
@@ -40557,7 +40557,8 @@ package android.text.style {
    ctor public SuggestionSpan(android.os.Parcel);
    method public int describeContents();
    method public int getFlags();
    method public java.lang.String getLocale();
    method public deprecated java.lang.String getLocale();
    method public java.util.Locale getLocaleObject();
    method public int getSpanTypeId();
    method public java.lang.String[] getSuggestions();
    method public void setFlags(int);
+2 −1
Original line number Diff line number Diff line
@@ -38222,7 +38222,8 @@ package android.text.style {
    ctor public SuggestionSpan(android.os.Parcel);
    method public int describeContents();
    method public int getFlags();
    method public java.lang.String getLocale();
    method public deprecated java.lang.String getLocale();
    method public java.util.Locale getLocaleObject();
    method public int getSpanTypeId();
    method public java.lang.String[] getSuggestions();
    method public void setFlags(int);
+48 −12
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package android.text.style;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
@@ -84,7 +86,15 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {

    private int mFlags;
    private final String[] mSuggestions;
    private final String mLocaleString;
    /**
     * Kept for compatibility for apps that rely on invalid locale strings e.g.
     * {@code new Locale(" an ", " i n v a l i d ", "data")}, which cannot be handled by
     * {@link #mLanguageTag}.
     */
    @NonNull
    private final String mLocaleStringForCompatibility;
    @NonNull
    private final String mLanguageTag;
    private final String mNotificationTargetClassName;
    private final String mNotificationTargetPackageName;
    private final int mHashCode;
@@ -130,14 +140,18 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
        final int N = Math.min(SUGGESTIONS_MAX_SIZE, suggestions.length);
        mSuggestions = Arrays.copyOf(suggestions, N);
        mFlags = flags;
        final Locale sourceLocale;
        if (locale != null) {
            mLocaleString = locale.toString();
            sourceLocale = locale;
        } else if (context != null) {
            mLocaleString = context.getResources().getConfiguration().locale.toString();
            // TODO: Consider to context.getResources().getResolvedLocale() instead.
            sourceLocale = context.getResources().getConfiguration().locale;
        } else {
            Log.e("SuggestionSpan", "No locale or context specified in SuggestionSpan constructor");
            mLocaleString = "";
            sourceLocale = null;
        }
        mLocaleStringForCompatibility = sourceLocale == null ? "" : sourceLocale.toString();
        mLanguageTag = sourceLocale == null ? "" : sourceLocale.toLanguageTag();

        if (context != null) {
            mNotificationTargetPackageName = context.getPackageName();
@@ -150,7 +164,8 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
        } else {
            mNotificationTargetClassName = "";
        }
        mHashCode = hashCodeInternal(mSuggestions, mLocaleString, mNotificationTargetClassName);
        mHashCode = hashCodeInternal(mSuggestions, mLanguageTag, mLocaleStringForCompatibility,
                mNotificationTargetClassName);

        initStyle(context);
    }
@@ -194,7 +209,8 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
    public SuggestionSpan(Parcel src) {
        mSuggestions = src.readStringArray();
        mFlags = src.readInt();
        mLocaleString = src.readString();
        mLocaleStringForCompatibility = src.readString();
        mLanguageTag = src.readString();
        mNotificationTargetClassName = src.readString();
        mNotificationTargetPackageName = src.readString();
        mHashCode = src.readInt();
@@ -214,10 +230,29 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
    }

    /**
     * @return the locale of the suggestions
     * @deprecated use {@link #getLocaleObject()} instead.
     * @return the locale of the suggestions. An empty string is returned if no locale is specified.
     */
    @NonNull
    @Deprecated
    public String getLocale() {
        return mLocaleString;
        return mLocaleStringForCompatibility;
    }

    /**
     * Returns a well-formed BCP 47 language tag representation of the suggestions, as a
     * {@link Locale} object.
     *
     * <p><b>Caveat</b>: The returned object is guaranteed to be a  a well-formed BCP 47 language tag
     * representation.  For example, this method can return an empty locale rather than returning a
     * malformed data when this object is initialized with an malformed {@link Locale} object, e.g.
     * {@code new Locale(" a ", " b c d ", " "}.</p>
     *
     * @return the locale of the suggestions. {@code null} is returned if no locale is specified.
     */
    @Nullable
    public Locale getLocaleObject() {
        return mLanguageTag.isEmpty() ? null : Locale.forLanguageTag(mLanguageTag);
    }

    /**
@@ -255,7 +290,8 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
    public void writeToParcelInternal(Parcel dest, int flags) {
        dest.writeStringArray(mSuggestions);
        dest.writeInt(mFlags);
        dest.writeString(mLocaleString);
        dest.writeString(mLocaleStringForCompatibility);
        dest.writeString(mLanguageTag);
        dest.writeString(mNotificationTargetClassName);
        dest.writeString(mNotificationTargetPackageName);
        dest.writeInt(mHashCode);
@@ -290,10 +326,10 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
        return mHashCode;
    }

    private static int hashCodeInternal(String[] suggestions, String locale,
            String notificationTargetClassName) {
    private static int hashCodeInternal(String[] suggestions, @NonNull String languageTag,
            @NonNull String localeStringForCompatibility, String notificationTargetClassName) {
        return Arrays.hashCode(new Object[] {Long.valueOf(SystemClock.uptimeMillis()), suggestions,
                locale, notificationTargetClassName});
                languageTag, localeStringForCompatibility, notificationTargetClassName});
    }

    public static final Parcelable.Creator<SuggestionSpan> CREATOR =