From 1484b9e764aa3dae183a8462d9141c4d78a40242 Mon Sep 17 00:00:00 2001 From: Yohei Yukawa Date: Mon, 24 Oct 2022 23:11:35 -0700 Subject: [PATCH] Add InputMethodSubtype#getCanonicalizedLanguageTag() This is a preparation for implementing Subtype-keyed physical keyboard layout switching. This CL introduces an @hide method InputMethodSubtype#getCanonicalizedLanguageTag() so that later CLs can be written by obtaining canonicalized BCP 47 Language Tags from InputMethodSubtype. See test cases to see how it works. Bug: 252816846 Test: atest FrameworksCoreTests:InputMethodSubtypeTest Change-Id: I5276642ba83fb708e95591da9bc7fefe2810b170 --- .../view/inputmethod/InputMethodSubtype.java | 41 +++++++++++++++++++ .../inputmethod/InputMethodSubtypeTest.java | 32 +++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java index 121839bc9ea64..96b6b437a64b9 100644 --- a/core/java/android/view/inputmethod/InputMethodSubtype.java +++ b/core/java/android/view/inputmethod/InputMethodSubtype.java @@ -16,6 +16,7 @@ package android.view.inputmethod; +import android.annotation.AnyThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; @@ -23,6 +24,7 @@ import android.content.pm.ApplicationInfo; import android.content.res.Configuration; import android.icu.text.DisplayContext; import android.icu.text.LocaleDisplayNames; +import android.icu.util.ULocale; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; @@ -89,6 +91,14 @@ public final class InputMethodSubtype implements Parcelable { private volatile Locale mCachedLocaleObj; private volatile HashMap mExtraValueHashMapCache; + /** + * A volatile cache to optimize {@link #getCanonicalizedLanguageTag()}. + * + *

{@code null} means that the initial evaluation is not yet done.

+ */ + @Nullable + private volatile String mCachedCanonicalizedLanguageTag; + /** * InputMethodSubtypeBuilder is a builder class of InputMethodSubtype. * This class is designed to be used with @@ -391,6 +401,37 @@ public final class InputMethodSubtype implements Parcelable { } } + /** + * Returns a canonicalized BCP 47 Language Tag initialized with {@link #getLocaleObject()}. + * + *

This has an internal cache mechanism. Subsequent calls are in general cheap and fast.

+ * + * @return a canonicalized BCP 47 Language Tag initialized with {@link #getLocaleObject()}. An + * empty string if {@link #getLocaleObject()} returns {@code null} or an empty + * {@link Locale} object. + * @hide + */ + @AnyThread + @NonNull + public String getCanonicalizedLanguageTag() { + final String cachedValue = mCachedCanonicalizedLanguageTag; + if (cachedValue != null) { + return cachedValue; + } + + String result = null; + final Locale locale = getLocaleObject(); + if (locale != null) { + final String langTag = locale.toLanguageTag(); + if (!TextUtils.isEmpty(langTag)) { + result = ULocale.createCanonical(ULocale.forLanguageTag(langTag)).toLanguageTag(); + } + } + result = TextUtils.emptyIfNull(result); + mCachedCanonicalizedLanguageTag = result; + return result; + } + /** * @return The mode of the subtype. */ diff --git a/core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeTest.java b/core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeTest.java index 5ff357a884837..bd292f04ba96e 100644 --- a/core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeTest.java +++ b/core/tests/coretests/src/android/view/inputmethod/InputMethodSubtypeTest.java @@ -20,8 +20,10 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import android.annotation.Nullable; import android.os.Parcel; import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder; @@ -125,6 +127,36 @@ public class InputMethodSubtypeTest { assertEquals("he", clonedSubtypeHe.getLocale()); } + @Test + public void testCanonicalizedLanguageTagObjectCache() { + final InputMethodSubtype subtype = createSubtypeUsingLanguageTag("en-US"); + // Verify that the returned object is cached and any subsequent call should return the same + // object, which is strictly guaranteed if the method gets called only on a single thread. + assertSame(subtype.getCanonicalizedLanguageTag(), subtype.getCanonicalizedLanguageTag()); + } + + @Test + public void testCanonicalizedLanguageTag() { + verifyCanonicalizedLanguageTag("en", "en"); + verifyCanonicalizedLanguageTag("en-US", "en-US"); + verifyCanonicalizedLanguageTag("en-Latn-US-t-k0-qwerty", "en-Latn-US-t-k0-qwerty"); + + verifyCanonicalizedLanguageTag("en-us", "en-US"); + verifyCanonicalizedLanguageTag("EN-us", "en-US"); + + verifyCanonicalizedLanguageTag(null, ""); + verifyCanonicalizedLanguageTag("", ""); + + verifyCanonicalizedLanguageTag("und", "und"); + verifyCanonicalizedLanguageTag("apparently invalid language tag!!!", "und"); + } + + private void verifyCanonicalizedLanguageTag( + @Nullable String languageTag, @Nullable String expectedLanguageTag) { + final InputMethodSubtype subtype = createSubtypeUsingLanguageTag(languageTag); + assertEquals(subtype.getCanonicalizedLanguageTag(), expectedLanguageTag); + } + private static InputMethodSubtype cloneViaParcel(final InputMethodSubtype original) { Parcel parcel = null; try {