Make resource multilocale depend on default attribute
Change-Id: I8ea32c3eee94435c4e2b6e6e1e09d30db50f268a
Test: Manual and automatic
Fixes: 117306409
diff --git a/core/api/current.txt b/core/api/current.txt
index f6564ec..fc1bf36 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -668,6 +668,7 @@
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 @@
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 a538247..08c18c8 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -3483,12 +3483,10 @@
// 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 0857c96..794495c 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.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.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 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 @@
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 @@
}
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 @@
}
/**
+ * 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 1ecb5d3..6009c29 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -120,9 +120,9 @@
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 @@
}
/**
- * 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 5cc3b92..c7790bd 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -27,6 +27,8 @@
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 @@
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 (LocaleList.isPseudoLocalesOnly(availableLocales)) {
- availableLocales = null;
+ 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)) {
+ // 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 82cdd28..d7e440b 100644
--- a/core/java/android/os/LocaleList.java
+++ b/core/java/android/os/LocaleList.java
@@ -153,21 +153,21 @@
/**
* 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 04fd70a..3496994 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 adc7fe0..4cd4f63 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 1f00a7a..88fc826 100644
--- a/core/tests/coretests/src/android/os/LocaleListTest.java
+++ b/core/tests/coretests/src/android/os/LocaleListTest.java
@@ -81,4 +81,49 @@
// 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 7f22693..d056248 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -785,7 +785,7 @@
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 @@
// 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;
}