diff options
author | 2023-09-13 12:39:53 -0700 | |
---|---|---|
committer | 2023-10-17 14:54:25 -0700 | |
commit | 2ae6ed593dbc381a2616b472cd5795f86e604a6f (patch) | |
tree | 7fb404797ecb2da602205b58263aff24da7665a8 | |
parent | 1d9c80032b2cf5a40e08c758c743b5d90fe7441b (diff) |
Make resource multilocale depend on default attribute
Change-Id: I8ea32c3eee94435c4e2b6e6e1e09d30db50f268a
Test: Manual and automatic
Fixes: 117306409
-rw-r--r-- | core/api/current.txt | 2 | ||||
-rw-r--r-- | core/java/android/app/ContextImpl.java | 10 | ||||
-rw-r--r-- | core/java/android/app/LocaleConfig.java | 40 | ||||
-rw-r--r-- | core/java/android/app/ResourcesManager.java | 19 | ||||
-rw-r--r-- | core/java/android/content/res/ResourcesImpl.java | 65 | ||||
-rw-r--r-- | core/java/android/os/LocaleList.java | 10 | ||||
-rw-r--r-- | core/res/res/values/attrs.xml | 9 | ||||
-rw-r--r-- | core/res/res/values/public-staging.xml | 2 | ||||
-rw-r--r-- | core/tests/coretests/src/android/os/LocaleListTest.java | 45 | ||||
-rw-r--r-- | libs/androidfw/AssetManager2.cpp | 9 |
10 files changed, 164 insertions, 47 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index f6564ecdbec0..fc1bf368caa4 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -668,6 +668,7 @@ package android { field public static final int debuggable = 16842767; // 0x101000f field public static final int defaultFocusHighlightEnabled = 16844130; // 0x1010562 field public static final int defaultHeight = 16844021; // 0x10104f5 + field @FlaggedApi("android.content.res.default_locale") public static final int defaultLocale; field public static final int defaultToDeviceProtectedStorage = 16844036; // 0x1010504 field public static final int defaultValue = 16843245; // 0x10101ed field public static final int defaultWidth = 16844020; // 0x10104f4 @@ -6188,6 +6189,7 @@ package android.app { ctor public LocaleConfig(@NonNull android.os.LocaleList); method public int describeContents(); method @NonNull public static android.app.LocaleConfig fromContextIgnoringOverride(@NonNull android.content.Context); + method @FlaggedApi("android.content.res.default_locale") @Nullable public java.util.Locale getDefaultLocale(); method public int getStatus(); method @Nullable public android.os.LocaleList getSupportedLocales(); method public void writeToParcel(@NonNull android.os.Parcel, int); diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index a538247998e6..08c18c8b7448 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -3483,12 +3483,10 @@ class ContextImpl extends Context { // only do this if the user already has more than one preferred locale if (r.getConfiguration().getLocales().size() > 1) { - LocaleConfig lc = LocaleConfig.fromContextIgnoringOverride(this); - mResourcesManager.setLocaleList(lc != null - && lc.getSupportedLocales() != null - && !lc.getSupportedLocales().isEmpty() - ? lc.getSupportedLocales() - : null); + LocaleConfig lc = getUserId() < 0 + ? LocaleConfig.fromContextIgnoringOverride(this) + : new LocaleConfig(this); + mResourcesManager.setLocaleConfig(lc); } } diff --git a/core/java/android/app/LocaleConfig.java b/core/java/android/app/LocaleConfig.java index 0857c9655e8d..794495c608c1 100644 --- a/core/java/android/app/LocaleConfig.java +++ b/core/java/android/app/LocaleConfig.java @@ -16,9 +16,11 @@ package android.app; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.res.Resources; @@ -31,6 +33,7 @@ import android.util.AttributeSet; import android.util.Slog; import android.util.Xml; +import com.android.internal.R; import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParserException; @@ -40,7 +43,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import java.util.Collections; -import java.util.LinkedHashSet; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Set; @@ -67,6 +70,8 @@ public class LocaleConfig implements Parcelable { public static final String TAG_LOCALE_CONFIG = "locale-config"; public static final String TAG_LOCALE = "locale"; private LocaleList mLocales; + + private Locale mDefaultLocale; private int mStatus = STATUS_NOT_SPECIFIED; /** @@ -195,8 +200,17 @@ public class LocaleConfig implements Parcelable { XmlUtils.beginDocument(parser, TAG_LOCALE_CONFIG); int outerDepth = parser.getDepth(); AttributeSet attrs = Xml.asAttributeSet(parser); - // LinkedHashSet to preserve insertion order - Set<String> localeNames = new LinkedHashSet<>(); + + String defaultLocale = null; + if (android.content.res.Flags.defaultLocale()) { + TypedArray att = res.obtainAttributes( + attrs, com.android.internal.R.styleable.LocaleConfig); + defaultLocale = att.getString( + R.styleable.LocaleConfig_defaultLocale); + att.recycle(); + } + + Set<String> localeNames = new HashSet<>(); while (XmlUtils.nextElementWithin(parser, outerDepth)) { if (TAG_LOCALE.equals(parser.getName())) { final TypedArray attributes = res.obtainAttributes( @@ -211,6 +225,15 @@ public class LocaleConfig implements Parcelable { } mStatus = STATUS_SUCCESS; mLocales = LocaleList.forLanguageTags(String.join(",", localeNames)); + if (defaultLocale != null) { + if (localeNames.contains(defaultLocale)) { + mDefaultLocale = Locale.forLanguageTag(defaultLocale); + } else { + Slog.w(TAG, "Default locale specified that is not contained in the list: " + + defaultLocale); + mStatus = STATUS_PARSING_FAILED; + } + } } /** @@ -226,6 +249,17 @@ public class LocaleConfig implements Parcelable { } /** + * Returns the default locale if specified, otherwise null + * + * @return The default Locale or null + */ + @SuppressLint("UseIcu") + @FlaggedApi(android.content.res.Flags.FLAG_DEFAULT_LOCALE) + public @Nullable Locale getDefaultLocale() { + return mDefaultLocale; + } + + /** * Get the status of reading the resource file where the LocaleConfig was stored. * * <p>Distinguish "the application didn't provide the resource file" from "the application diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index 1ecb5d33fba2..6009c29ae53c 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -120,9 +120,9 @@ public class ResourcesManager { private final ReferenceQueue<Resources> mResourcesReferencesQueue = new ReferenceQueue<>(); /** - * The list of locales the app declares it supports. + * The localeConfig of the app. */ - private LocaleList mLocaleList = LocaleList.getEmptyLocaleList(); + private LocaleConfig mLocaleConfig = new LocaleConfig(LocaleList.getEmptyLocaleList()); private static class ApkKey { public final String path; @@ -1612,18 +1612,19 @@ public class ResourcesManager { } /** - * Returns the LocaleList current set + * Returns the LocaleConfig current set */ - public LocaleList getLocaleList() { - return mLocaleList; + public LocaleConfig getLocaleConfig() { + return mLocaleConfig; } /** - * Sets the LocaleList of app's supported locales + * Sets the LocaleConfig of the app */ - public void setLocaleList(LocaleList localeList) { - if ((localeList != null) && !localeList.isEmpty()) { - mLocaleList = localeList; + public void setLocaleConfig(LocaleConfig localeConfig) { + if ((localeConfig != null) && (localeConfig.getSupportedLocales() != null) + && !localeConfig.getSupportedLocales().isEmpty()) { + mLocaleConfig = localeConfig; } } diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java index 5cc3b92da305..c7790bd96c62 100644 --- a/core/java/android/content/res/ResourcesImpl.java +++ b/core/java/android/content/res/ResourcesImpl.java @@ -27,6 +27,8 @@ import android.annotation.PluralsRes; import android.annotation.RawRes; import android.annotation.StyleRes; import android.annotation.StyleableRes; +import android.app.LocaleConfig; +import android.app.ResourcesManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo.Config; @@ -426,38 +428,59 @@ public class ResourcesImpl { String[] selectedLocales = null; String defaultLocale = null; + LocaleConfig lc = ResourcesManager.getInstance().getLocaleConfig(); if ((configChanges & ActivityInfo.CONFIG_LOCALE) != 0) { if (locales.size() > 1) { - String[] availableLocales; - // The LocaleList has changed. We must query the AssetManager's - // available Locales and figure out the best matching Locale in the new - // LocaleList. - availableLocales = mAssets.getNonSystemLocales(); - if (LocaleList.isPseudoLocalesOnly(availableLocales)) { - // No app defined locales, so grab the system locales. - availableLocales = mAssets.getLocales(); + if (Flags.defaultLocale() && (lc.getDefaultLocale() != null)) { + Locale[] intersection = + locales.getIntersection(lc.getSupportedLocales()); + mConfiguration.setLocales(new LocaleList(intersection)); + selectedLocales = new String[intersection.length]; + for (int i = 0; i < intersection.length; i++) { + selectedLocales[i] = + adjustLanguageTag(intersection[i].toLanguageTag()); + } + defaultLocale = + adjustLanguageTag(lc.getDefaultLocale().toLanguageTag()); + } else { + String[] availableLocales; + // The LocaleList has changed. We must query the AssetManager's + // available Locales and figure out the best matching Locale in the new + // LocaleList. + availableLocales = mAssets.getNonSystemLocales(); if (LocaleList.isPseudoLocalesOnly(availableLocales)) { - availableLocales = null; + // No app defined locales, so grab the system locales. + availableLocales = mAssets.getLocales(); + if (LocaleList.isPseudoLocalesOnly(availableLocales)) { + availableLocales = null; + } } - } - if (availableLocales != null) { - final Locale bestLocale = locales.getFirstMatchWithEnglishSupported( - availableLocales); - if (bestLocale != null) { - selectedLocales = new String[]{ - adjustLanguageTag(bestLocale.toLanguageTag())}; - if (!bestLocale.equals(locales.get(0))) { - mConfiguration.setLocales( - new LocaleList(bestLocale, locales)); + if (availableLocales != null) { + final Locale bestLocale = locales.getFirstMatchWithEnglishSupported( + availableLocales); + if (bestLocale != null) { + selectedLocales = new String[]{ + adjustLanguageTag(bestLocale.toLanguageTag())}; + if (!bestLocale.equals(locales.get(0))) { + mConfiguration.setLocales( + new LocaleList(bestLocale, locales)); + } } } } } } if (selectedLocales == null) { - selectedLocales = new String[]{ - adjustLanguageTag(locales.get(0).toLanguageTag())}; + if (Flags.defaultLocale() && (lc.getDefaultLocale() != null)) { + selectedLocales = new String[locales.size()]; + for (int i = 0; i < locales.size(); i++) { + selectedLocales[i] = adjustLanguageTag(locales.get(i).toLanguageTag()); + } + } else { + selectedLocales = new String[]{ + adjustLanguageTag(locales.get(0).toLanguageTag())}; + } } if (mConfiguration.densityDpi != Configuration.DENSITY_DPI_UNDEFINED) { diff --git a/core/java/android/os/LocaleList.java b/core/java/android/os/LocaleList.java index 82cdd280a0f3..d7e440b66e13 100644 --- a/core/java/android/os/LocaleList.java +++ b/core/java/android/os/LocaleList.java @@ -153,21 +153,21 @@ public final class LocaleList implements Parcelable { /** * Find the intersection between this LocaleList and another - * @return a String array of the Locales in both LocaleLists + * @return an array of the Locales in both LocaleLists * {@hide} */ @NonNull - public String[] getIntersection(@NonNull LocaleList other) { - List<String> intersection = new ArrayList<>(); + public Locale[] getIntersection(@NonNull LocaleList other) { + List<Locale> intersection = new ArrayList<>(); for (Locale l1 : mList) { for (Locale l2 : other.mList) { if (matchesLanguageAndScript(l2, l1)) { - intersection.add(l1.toLanguageTag()); + intersection.add(l1); break; } } } - return intersection.toArray(new String[0]); + return intersection.toArray(new Locale[0]); } /** diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 04fd70a96201..3496994fe173 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -10090,6 +10090,15 @@ <!-- Perceptual luminance of a color, in accessibility friendly color space. From 0 to 100. --> <attr name="lStar" format="float"/> + <!-- The attributes of the {@code <locale-config>} tag. --> + <!-- @FlaggedApi("android.content.res.default_locale") --> + <declare-styleable name="LocaleConfig"> + <!-- The <a href="https://www.rfc-editor.org/rfc/bcp/bcp47.txt">IETF BCP47 language tag</a> + the strings in values/strings.xml (the default strings in the directory with no locale + qualifier) are in. --> + <attr name="defaultLocale" format="string"/> + </declare-styleable> + <!-- The attributes of the {@code <locale>} tag within {@code <locale-config>}. --> <declare-styleable name="LocaleConfig_Locale"> <!-- The <a href="https://www.rfc-editor.org/rfc/bcp/bcp47.txt">IETF BCP47 language tag</a> diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml index adc7fe0922aa..4cd4f638e191 100644 --- a/core/res/res/values/public-staging.xml +++ b/core/res/res/values/public-staging.xml @@ -110,6 +110,8 @@ <eat-comment/> <staging-public-group type="attr" first-id="0x01bd0000"> + <!-- @FlaggedApi("android.content.res.default_locale") --> + <public name="defaultLocale"/> </staging-public-group> <staging-public-group type="id" first-id="0x01bc0000"> diff --git a/core/tests/coretests/src/android/os/LocaleListTest.java b/core/tests/coretests/src/android/os/LocaleListTest.java index 1f00a7a12c54..88fc8267f5b2 100644 --- a/core/tests/coretests/src/android/os/LocaleListTest.java +++ b/core/tests/coretests/src/android/os/LocaleListTest.java @@ -81,4 +81,49 @@ public class LocaleListTest extends TestCase { // restore the original values LocaleList.setDefault(originalLocaleList, originalLocaleIndex); } + + @SmallTest + public void testIntersection() { + LocaleList localesWithN = new LocaleList( + Locale.ENGLISH, + Locale.FRENCH, + Locale.GERMAN, + Locale.ITALIAN, + Locale.JAPANESE, + Locale.KOREAN, + Locale.CHINESE, + Locale.SIMPLIFIED_CHINESE, + Locale.TRADITIONAL_CHINESE, + Locale.FRANCE, + Locale.GERMANY, + Locale.JAPAN, + Locale.CANADA, + Locale.CANADA_FRENCH); + LocaleList localesWithE = new LocaleList( + Locale.ENGLISH, + Locale.FRENCH, + Locale.GERMAN, + Locale.JAPANESE, + Locale.KOREAN, + Locale.CHINESE, + Locale.SIMPLIFIED_CHINESE, + Locale.TRADITIONAL_CHINESE, + Locale.FRANCE, + Locale.GERMANY, + Locale.CANADA_FRENCH); + LocaleList localesWithNAndE = new LocaleList( + Locale.ENGLISH, + Locale.FRENCH, + Locale.GERMAN, + Locale.JAPANESE, + Locale.KOREAN, + Locale.CHINESE, + Locale.SIMPLIFIED_CHINESE, + Locale.TRADITIONAL_CHINESE, + Locale.FRANCE, + Locale.GERMANY, + Locale.CANADA_FRENCH); + + assertEquals(localesWithNAndE, new LocaleList(localesWithE.getIntersection(localesWithN))); + } } diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp index 7f226939ffaa..d056248273b2 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -785,7 +785,7 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntry( has_locale = true; } - // if we don't have a result yet + // if we don't have a result yet if (!final_result || // or this config is better before the locale than the existing result result->config.isBetterThanBeforeLocale(final_result->config, desired_config) || @@ -863,9 +863,12 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal( // We can skip calling ResTable_config::match() if the caller does not care for the // configuration to match or if we're using the list of types that have already had their - // configuration matched. + // configuration matched. The exception to this is when the user has multiple locales set + // because the filtered list will then have values from multiple locales and we will need to + // call match() to make sure the current entry matches the config we are currently checking. const ResTable_config& this_config = type_entry->config; - if (!(use_filtered || ignore_configuration || this_config.match(desired_config))) { + if (!((use_filtered && (configurations_.size() == 1)) + || ignore_configuration || this_config.match(desired_config))) { continue; } |