summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/text/FontConfig.java149
-rw-r--r--core/java/android/text/flags/custom_locale_fallback.aconfig8
-rw-r--r--core/tests/coretests/Android.bp1
-rw-r--r--core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java135
-rw-r--r--graphics/java/android/graphics/FontListParser.java4
-rw-r--r--graphics/java/android/graphics/fonts/FontCustomizationParser.java46
-rw-r--r--graphics/java/android/graphics/fonts/SystemFonts.java97
-rw-r--r--services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java9
9 files changed, 436 insertions, 17 deletions
diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java
index c5857347fd45..94c8eaf13ccb 100644
--- a/core/java/android/text/FontConfig.java
+++ b/core/java/android/text/FontConfig.java
@@ -29,6 +29,7 @@ import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.fonts.FontFamily.Builder.VariableFontFamilyType;
import android.graphics.fonts.FontStyle;
import android.graphics.fonts.FontVariationAxis;
+import android.icu.util.ULocale;
import android.os.Build;
import android.os.LocaleList;
import android.os.Parcel;
@@ -39,6 +40,7 @@ import java.lang.annotation.Retention;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Locale;
import java.util.Objects;
@@ -58,6 +60,7 @@ public final class FontConfig implements Parcelable {
private final @NonNull List<FontFamily> mFamilies;
private final @NonNull List<Alias> mAliases;
private final @NonNull List<NamedFamilyList> mNamedFamilyLists;
+ private final @NonNull List<Customization.LocaleFallback> mLocaleFallbackCustomizations;
private final long mLastModifiedTimeMillis;
private final int mConfigVersion;
@@ -71,10 +74,12 @@ public final class FontConfig implements Parcelable {
*/
public FontConfig(@NonNull List<FontFamily> families, @NonNull List<Alias> aliases,
@NonNull List<NamedFamilyList> namedFamilyLists,
+ @NonNull List<Customization.LocaleFallback> localeFallbackCustomizations,
long lastModifiedTimeMillis, @IntRange(from = 0) int configVersion) {
mFamilies = families;
mAliases = aliases;
mNamedFamilyLists = namedFamilyLists;
+ mLocaleFallbackCustomizations = localeFallbackCustomizations;
mLastModifiedTimeMillis = lastModifiedTimeMillis;
mConfigVersion = configVersion;
}
@@ -84,7 +89,8 @@ public final class FontConfig implements Parcelable {
*/
public FontConfig(@NonNull List<FontFamily> families, @NonNull List<Alias> aliases,
long lastModifiedTimeMillis, @IntRange(from = 0) int configVersion) {
- this(families, aliases, Collections.emptyList(), lastModifiedTimeMillis, configVersion);
+ this(families, aliases, Collections.emptyList(), Collections.emptyList(),
+ lastModifiedTimeMillis, configVersion);
}
@@ -113,6 +119,18 @@ public final class FontConfig implements Parcelable {
}
/**
+ * Returns a locale fallback customizations.
+ *
+ * This field is used for creating the system fallback in the system server. This field is
+ * always empty in the application process.
+ *
+ * @hide
+ */
+ public @NonNull List<Customization.LocaleFallback> getLocaleFallbackCustomizations() {
+ return mLocaleFallbackCustomizations;
+ }
+
+ /**
* Returns the last modified time in milliseconds.
*
* This is a value of {@link System#currentTimeMillis()} when the system font configuration was
@@ -169,7 +187,9 @@ public final class FontConfig implements Parcelable {
source.readTypedList(familyLists, NamedFamilyList.CREATOR);
long lastModifiedDate = source.readLong();
int configVersion = source.readInt();
- return new FontConfig(families, aliases, familyLists, lastModifiedDate, configVersion);
+ return new FontConfig(families, aliases, familyLists,
+ Collections.emptyList(), // Don't need to pass customization to API caller.
+ lastModifiedDate, configVersion);
}
@Override
@@ -813,4 +833,129 @@ public final class FontConfig implements Parcelable {
+ '}';
}
}
+
+ /** @hide */
+ public static class Customization {
+ private Customization() {} // Singleton
+
+ /**
+ * A class that represents customization of locale fallback
+ *
+ * This class represents a vendor customization of new-locale-family.
+ *
+ * <pre>
+ * <family customizationType="new-locale-family" operation="prepend" lang="ja-JP">
+ * <font weight="400" style="normal">MyAlternativeFont.ttf
+ * <axis tag="wght" stylevalue="400"/>
+ * </font>
+ * </family>
+ * </pre>
+ *
+ * The operation can be one of prepend, replace or append. The operation prepend means that
+ * the new font family is inserted just before the original font family. The original font
+ * family is still in the fallback. The operation replace means that the original font
+ * family is replaced with new font family. The original font family is removed from the
+ * fallback. The operation append means that the new font family is inserted just after the
+ * original font family. The original font family is still in the fallback.
+ *
+ * The lang attribute is a BCP47 compliant language tag. The font fallback mainly uses ISO
+ * 15924 script code for matching. If the script code is missing, most likely script code
+ * will be used.
+ */
+ public static class LocaleFallback {
+ private final Locale mLocale;
+ private final int mOperation;
+ private final FontFamily mFamily;
+ private final String mScript;
+
+ public static final int OPERATION_PREPEND = 0;
+ public static final int OPERATION_APPEND = 1;
+ public static final int OPERATION_REPLACE = 2;
+
+ /** @hide */
+ @Retention(SOURCE)
+ @IntDef(prefix = { "OPERATION_" }, value = {
+ OPERATION_PREPEND,
+ OPERATION_APPEND,
+ OPERATION_REPLACE
+ })
+ public @interface Operation {}
+
+
+ public LocaleFallback(@NonNull Locale locale, @Operation int operation,
+ @NonNull FontFamily family) {
+ mLocale = locale;
+ mOperation = operation;
+ mFamily = family;
+ mScript = resolveScript(locale);
+ }
+
+ /**
+ * A customization target locale.
+ * @return a locale
+ */
+ public @NonNull Locale getLocale() {
+ return mLocale;
+ }
+
+ /**
+ * An operation to be applied to the original font family.
+ *
+ * The operation can be one of {@link #OPERATION_PREPEND}, {@link #OPERATION_REPLACE} or
+ * {@link #OPERATION_APPEND}.
+ *
+ * The operation prepend ({@link #OPERATION_PREPEND}) means that the new font family is
+ * inserted just before the original font family. The original font family is still in
+ * the fallback.
+ *
+ * The operation replace ({@link #OPERATION_REPLACE}) means that the original font
+ * family is replaced with new font family. The original font family is removed from the
+ * fallback.
+ *
+ * The operation append ({@link #OPERATION_APPEND}) means that the new font family is
+ * inserted just after the original font family. The original font family is still in
+ * the fallback.
+ *
+ * @return an operation.
+ */
+ public @Operation int getOperation() {
+ return mOperation;
+ }
+
+ /**
+ * Returns a family to be inserted or replaced to the fallback.
+ *
+ * @return a family
+ */
+ public @NonNull FontFamily getFamily() {
+ return mFamily;
+ }
+
+ /**
+ * Returns a script of the locale. If the script is missing in the given locale, the
+ * most likely locale is returned.
+ */
+ public @NonNull String getScript() {
+ return mScript;
+ }
+
+ @Override
+ public String toString() {
+ return "LocaleFallback{"
+ + "mLocale=" + mLocale
+ + ", mOperation=" + mOperation
+ + ", mFamily=" + mFamily
+ + '}';
+ }
+ }
+ }
+
+ /** @hide */
+ public static String resolveScript(Locale locale) {
+ String script = locale.getScript();
+ if (script != null && !script.isEmpty()) {
+ return script;
+ }
+ return ULocale.addLikelySubtags(ULocale.forLocale(locale)).getScript();
+ }
}
diff --git a/core/java/android/text/flags/custom_locale_fallback.aconfig b/core/java/android/text/flags/custom_locale_fallback.aconfig
new file mode 100644
index 000000000000..04e64f96454b
--- /dev/null
+++ b/core/java/android/text/flags/custom_locale_fallback.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.text.flags"
+
+flag {
+ name: "custom_locale_fallback"
+ namespace: "text"
+ description: "A feature flag that adds custom locale fallback to the vendor customization XML. This enables vendors to add their locale specific fonts, e.g. Japanese font."
+ bug: ""
+}
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 81dab0833af1..2993a0e63228 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -64,6 +64,7 @@ android_test {
"servicestests-utils",
"device-time-shell-utils",
"testables",
+ "com.android.text.flags-aconfig-java",
],
libs: [
diff --git a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
index a8a5059eea20..3f78396e3a70 100644
--- a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
+++ b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
@@ -16,6 +16,8 @@
package android.graphics;
+import static com.android.text.flags.Flags.FLAG_CUSTOM_LOCALE_FALLBACK;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -30,6 +32,9 @@ import android.graphics.fonts.FontFamily;
import android.graphics.fonts.SystemFonts;
import android.graphics.text.PositionedGlyphs;
import android.graphics.text.TextRunShaper;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.text.FontConfig;
import android.util.ArrayMap;
@@ -39,6 +44,7 @@ import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.xmlpull.v1.XmlPullParserException;
@@ -107,6 +113,10 @@ public class TypefaceSystemFallbackTest {
GLYPH_2EM_WIDTH = paint.measureText("a");
}
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() {
final AssetManager am =
@@ -877,6 +887,130 @@ public class TypefaceSystemFallbackTest {
assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0.0f);
}
+ private static void assertA3emFontIsUsed(Typeface typeface) {
+ final Paint paint = new Paint();
+ assertNotNull(typeface);
+ paint.setTypeface(typeface);
+ assertTrue("a3em font must be used", GLYPH_3EM_WIDTH == paint.measureText("a")
+ && GLYPH_1EM_WIDTH == paint.measureText("b")
+ && GLYPH_1EM_WIDTH == paint.measureText("c"));
+ }
+
+ private static void assertB3emFontIsUsed(Typeface typeface) {
+ final Paint paint = new Paint();
+ assertNotNull(typeface);
+ paint.setTypeface(typeface);
+ assertTrue("b3em font must be used", GLYPH_1EM_WIDTH == paint.measureText("a")
+ && GLYPH_3EM_WIDTH == paint.measureText("b")
+ && GLYPH_1EM_WIDTH == paint.measureText("c"));
+ }
+
+ private static String getBaseXml(String font, String lang) {
+ final String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+ + "<familyset>"
+ + " <family>"
+ + " <font weight='400' style='normal'>no_coverage.ttf</font>"
+ + " </family>"
+ + " <family name='named-family'>"
+ + " <font weight='400' style='normal'>no_coverage.ttf</font>"
+ + " </family>"
+ + " <family lang='%s'>"
+ + " <font weight='400' style='normal'>%s</font>"
+ + " </family>"
+ + "</familyset>";
+ return String.format(xml, lang, font);
+ }
+
+ private static String getCustomizationXml(String font, String op, String lang) {
+ final String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+ + "<fonts-modification version='1'>"
+ + " <family customizationType='new-locale-family' operation='%s' lang='%s'>"
+ + " <font weight='400' style='normal' fallbackFor='named-family'>%s</font>"
+ + " </family>"
+ + "</fonts-modification>";
+ return String.format(xml, op, lang, font);
+ }
+
+ @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK)
+ @Test
+ public void testBuildSystemFallback__Customization_locale_prepend() {
+ final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
+ final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>();
+
+ buildSystemFallback(
+ getBaseXml("a3em.ttf", "ja-JP"),
+ getCustomizationXml("b3em.ttf", "prepend", "ja-JP"),
+ fontMap, fallbackMap);
+ Typeface typeface = fontMap.get("named-family");
+
+ // operation "prepend" places font before the original font, thus b3em is used.
+ assertB3emFontIsUsed(typeface);
+ }
+
+ @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK)
+ @Test
+ public void testBuildSystemFallback__Customization_locale_replace() {
+ final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
+ final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>();
+
+ buildSystemFallback(
+ getBaseXml("a3em.ttf", "ja-JP"),
+ getCustomizationXml("b3em.ttf", "replace", "ja-JP"),
+ fontMap, fallbackMap);
+ Typeface typeface = fontMap.get("named-family");
+
+ // operation "replace" removes the original font, thus b3em font is used.
+ assertB3emFontIsUsed(typeface);
+ }
+
+ @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK)
+ @Test
+ public void testBuildSystemFallback__Customization_locale_append() {
+ final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
+ final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>();
+
+ buildSystemFallback(
+ getBaseXml("a3em.ttf", "ja-JP"),
+ getCustomizationXml("b3em.ttf", "append", "ja-JP"),
+ fontMap, fallbackMap);
+ Typeface typeface = fontMap.get("named-family");
+
+ // operation "append" comes next to the original font, so the original "a3em" is used.
+ assertA3emFontIsUsed(typeface);
+ }
+
+ @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK)
+ @Test
+ public void testBuildSystemFallback__Customization_locale_ScriptMismatch() {
+ final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
+ final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>();
+
+ buildSystemFallback(
+ getBaseXml("a3em.ttf", "ja-JP"),
+ getCustomizationXml("b3em.ttf", "replace", "ko-KR"),
+ fontMap, fallbackMap);
+ Typeface typeface = fontMap.get("named-family");
+
+ // Since the script doesn't match, the customization is ignored.
+ assertA3emFontIsUsed(typeface);
+ }
+
+ @RequiresFlagsEnabled(FLAG_CUSTOM_LOCALE_FALLBACK)
+ @Test
+ public void testBuildSystemFallback__Customization_locale_SubscriptMatch() {
+ final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
+ final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>();
+
+ buildSystemFallback(
+ getBaseXml("a3em.ttf", "ja-JP"),
+ getCustomizationXml("b3em.ttf", "replace", "ko-Hani-KR"),
+ fontMap, fallbackMap);
+ Typeface typeface = fontMap.get("named-family");
+
+ // Hani script is supported by Japanese, Jpan.
+ assertB3emFontIsUsed(typeface);
+ }
+
@Test(expected = IllegalArgumentException.class)
public void testBuildSystemFallback__Customization_new_named_family_no_name_exception() {
final String oemXml = "<?xml version='1.0' encoding='UTF-8'?>"
@@ -902,7 +1036,6 @@ public class TypefaceSystemFallbackTest {
readFontCustomization(oemXml);
}
-
@Test
public void testBuildSystemFallback_UpdatableFont() {
final String xml = "<?xml version='1.0' encoding='UTF-8'?>"
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index 735bc180c015..52b0b95d3e76 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -236,7 +236,9 @@ public class FontListParser {
}
}
- return new FontConfig(families, filtered, resultNamedFamilies, lastModifiedDate,
+ return new FontConfig(families, filtered, resultNamedFamilies,
+ customization.getLocaleFamilyCustomizations(),
+ lastModifiedDate,
configVersion);
}
diff --git a/graphics/java/android/graphics/fonts/FontCustomizationParser.java b/graphics/java/android/graphics/fonts/FontCustomizationParser.java
index b458dd9021d0..6e04a2f5e405 100644
--- a/graphics/java/android/graphics/fonts/FontCustomizationParser.java
+++ b/graphics/java/android/graphics/fonts/FontCustomizationParser.java
@@ -22,6 +22,7 @@ import static android.text.FontConfig.NamedFamilyList;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.FontListParser;
+import android.text.FontConfig;
import android.util.Xml;
import org.xmlpull.v1.XmlPullParser;
@@ -34,6 +35,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
/**
@@ -52,14 +54,19 @@ public class FontCustomizationParser {
private final List<Alias> mAdditionalAliases;
+ private final List<FontConfig.Customization.LocaleFallback> mLocaleFamilyCustomizations;
+
public Result() {
mAdditionalNamedFamilies = Collections.emptyMap();
+ mLocaleFamilyCustomizations = Collections.emptyList();
mAdditionalAliases = Collections.emptyList();
}
public Result(Map<String, NamedFamilyList> additionalNamedFamilies,
+ List<FontConfig.Customization.LocaleFallback> localeFamilyCustomizations,
List<Alias> additionalAliases) {
mAdditionalNamedFamilies = additionalNamedFamilies;
+ mLocaleFamilyCustomizations = localeFamilyCustomizations;
mAdditionalAliases = additionalAliases;
}
@@ -70,6 +77,10 @@ public class FontCustomizationParser {
public List<Alias> getAdditionalAliases() {
return mAdditionalAliases;
}
+
+ public List<FontConfig.Customization.LocaleFallback> getLocaleFamilyCustomizations() {
+ return mLocaleFamilyCustomizations;
+ }
}
/**
@@ -89,7 +100,9 @@ public class FontCustomizationParser {
}
private static Result validateAndTransformToResult(
- List<NamedFamilyList> families, List<Alias> aliases) {
+ List<NamedFamilyList> families,
+ List<FontConfig.Customization.LocaleFallback> outLocaleFamilies,
+ List<Alias> aliases) {
HashMap<String, NamedFamilyList> namedFamily = new HashMap<>();
for (int i = 0; i < families.size(); ++i) {
final NamedFamilyList family = families.get(i);
@@ -105,7 +118,7 @@ public class FontCustomizationParser {
+ "requires fallackTarget attribute");
}
}
- return new Result(namedFamily, aliases);
+ return new Result(namedFamily, outLocaleFamilies, aliases);
}
private static Result readFamilies(
@@ -115,12 +128,13 @@ public class FontCustomizationParser {
) throws XmlPullParserException, IOException {
List<NamedFamilyList> families = new ArrayList<>();
List<Alias> aliases = new ArrayList<>();
+ List<FontConfig.Customization.LocaleFallback> outLocaleFamilies = new ArrayList<>();
parser.require(XmlPullParser.START_TAG, null, "fonts-modification");
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) continue;
String tag = parser.getName();
if (tag.equals("family")) {
- readFamily(parser, fontDir, families, updatableFontMap);
+ readFamily(parser, fontDir, families, outLocaleFamilies, updatableFontMap);
} else if (tag.equals("family-list")) {
readFamilyList(parser, fontDir, families, updatableFontMap);
} else if (tag.equals("alias")) {
@@ -129,13 +143,14 @@ public class FontCustomizationParser {
FontListParser.skip(parser);
}
}
- return validateAndTransformToResult(families, aliases);
+ return validateAndTransformToResult(families, outLocaleFamilies, aliases);
}
private static void readFamily(
@NonNull XmlPullParser parser,
@NonNull String fontDir,
@NonNull List<NamedFamilyList> out,
+ @NonNull List<FontConfig.Customization.LocaleFallback> outCustomization,
@Nullable Map<String, File> updatableFontMap)
throws XmlPullParserException, IOException {
final String customizationType = parser.getAttributeValue(null, "customizationType");
@@ -148,6 +163,29 @@ public class FontCustomizationParser {
if (fontFamily != null) {
out.add(fontFamily);
}
+ } else if (customizationType.equals("new-locale-family")) {
+ final String lang = parser.getAttributeValue(null, "lang");
+ final String op = parser.getAttributeValue(null, "operation");
+ final int intOp;
+ if (op.equals("append")) {
+ intOp = FontConfig.Customization.LocaleFallback.OPERATION_APPEND;
+ } else if (op.equals("prepend")) {
+ intOp = FontConfig.Customization.LocaleFallback.OPERATION_PREPEND;
+ } else if (op.equals("replace")) {
+ intOp = FontConfig.Customization.LocaleFallback.OPERATION_REPLACE;
+ } else {
+ throw new IllegalArgumentException("Unknown operation=" + op);
+ }
+
+ final FontConfig.FontFamily family = FontListParser.readFamily(
+ parser, fontDir, updatableFontMap, false);
+
+ // For ignoring the customization, consume the new-locale-family element but don't
+ // register any customizations.
+ if (com.android.text.flags.Flags.customLocaleFallback()) {
+ outCustomization.add(new FontConfig.Customization.LocaleFallback(
+ Locale.forLanguageTag(lang), intOp, family));
+ }
} else {
throw new IllegalArgumentException("Unknown customizationType=" + customizationType);
}
diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java
index d4e35b30c8d0..618aa5b5019c 100644
--- a/graphics/java/android/graphics/fonts/SystemFonts.java
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -16,10 +16,15 @@
package android.graphics.fonts;
+import static android.text.FontConfig.Customization.LocaleFallback.OPERATION_APPEND;
+import static android.text.FontConfig.Customization.LocaleFallback.OPERATION_PREPEND;
+import static android.text.FontConfig.Customization.LocaleFallback.OPERATION_REPLACE;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.FontListParser;
import android.graphics.Typeface;
+import android.os.LocaleList;
import android.text.FontConfig;
import android.util.ArrayMap;
import android.util.Log;
@@ -38,6 +43,7 @@ import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Set;
@@ -119,7 +125,6 @@ public final class SystemFonts {
}
}
-
final FontFamily defaultFamily = defaultFonts.isEmpty() ? null : createFontFamily(
defaultFonts, languageTags, variant, xmlFamily.getVariableFontFamilyType(), false,
cache);
@@ -300,11 +305,11 @@ public final class SystemFonts {
} catch (IOException e) {
Log.e(TAG, "Failed to open/read system font configurations.", e);
return new FontConfig(Collections.emptyList(), Collections.emptyList(),
- Collections.emptyList(), 0, 0);
+ Collections.emptyList(), Collections.emptyList(), 0, 0);
} catch (XmlPullParserException e) {
Log.e(TAG, "Failed to parse the system font configuration.", e);
return new FontConfig(Collections.emptyList(), Collections.emptyList(),
- Collections.emptyList(), 0, 0);
+ Collections.emptyList(), Collections.emptyList(), 0, 0);
}
}
@@ -328,6 +333,8 @@ public final class SystemFonts {
ArrayMap<String, ByteBuffer> outBufferCache) {
final ArrayMap<String, NativeFamilyListSet> fallbackListMap = new ArrayMap<>();
+ final List<FontConfig.Customization.LocaleFallback> localeFallbacks =
+ fontConfig.getLocaleFallbackCustomizations();
final List<FontConfig.NamedFamilyList> namedFamilies = fontConfig.getNamedFamilyLists();
for (int i = 0; i < namedFamilies.size(); ++i) {
@@ -336,10 +343,54 @@ public final class SystemFonts {
}
// Then, add fallback fonts to the fallback map.
+ final List<FontConfig.Customization.LocaleFallback> customizations = new ArrayList<>();
final List<FontConfig.FontFamily> xmlFamilies = fontConfig.getFontFamilies();
+ final SparseIntArray seenCustomization = new SparseIntArray();
for (int i = 0; i < xmlFamilies.size(); i++) {
final FontConfig.FontFamily xmlFamily = xmlFamilies.get(i);
- pushFamilyToFallback(xmlFamily, fallbackListMap, outBufferCache);
+
+ customizations.clear();
+ for (int j = 0; j < localeFallbacks.size(); ++j) {
+ if (seenCustomization.get(j, -1) != -1) {
+ continue; // The customization is already applied.
+ }
+ FontConfig.Customization.LocaleFallback localeFallback = localeFallbacks.get(j);
+ if (scriptMatch(xmlFamily.getLocaleList(), localeFallback.getScript())) {
+ customizations.add(localeFallback);
+ seenCustomization.put(j, 1);
+ }
+ }
+
+ if (customizations.isEmpty()) {
+ pushFamilyToFallback(xmlFamily, fallbackListMap, outBufferCache);
+ } else {
+ for (int j = 0; j < customizations.size(); ++j) {
+ FontConfig.Customization.LocaleFallback localeFallback = customizations.get(j);
+ if (localeFallback.getOperation() == OPERATION_PREPEND) {
+ pushFamilyToFallback(localeFallback.getFamily(), fallbackListMap,
+ outBufferCache);
+ }
+ }
+ boolean isReplaced = false;
+ for (int j = 0; j < customizations.size(); ++j) {
+ FontConfig.Customization.LocaleFallback localeFallback = customizations.get(j);
+ if (localeFallback.getOperation() == OPERATION_REPLACE) {
+ pushFamilyToFallback(localeFallback.getFamily(), fallbackListMap,
+ outBufferCache);
+ isReplaced = true;
+ }
+ }
+ if (!isReplaced) { // If nothing is replaced, push the original one.
+ pushFamilyToFallback(xmlFamily, fallbackListMap, outBufferCache);
+ }
+ for (int j = 0; j < customizations.size(); ++j) {
+ FontConfig.Customization.LocaleFallback localeFallback = customizations.get(j);
+ if (localeFallback.getOperation() == OPERATION_APPEND) {
+ pushFamilyToFallback(localeFallback.getFamily(), fallbackListMap,
+ outBufferCache);
+ }
+ }
+ }
}
// Build the font map and fallback map.
@@ -365,4 +416,42 @@ public final class SystemFonts {
Typeface.initSystemDefaultTypefaces(fallbackMap, fontConfig.getAliases(), result);
return result;
}
+
+ private static boolean scriptMatch(LocaleList localeList, String targetScript) {
+ if (localeList == null || localeList.isEmpty()) {
+ return false;
+ }
+ for (int i = 0; i < localeList.size(); ++i) {
+ Locale locale = localeList.get(i);
+ if (locale == null) {
+ continue;
+ }
+ String baseScript = FontConfig.resolveScript(locale);
+ if (baseScript.equals(targetScript)) {
+ return true;
+ }
+
+ // Subtag match
+ if (targetScript.equals("Bopo") && baseScript.equals("Hanb")) {
+ // Hanb is Han with Bopomofo.
+ return true;
+ } else if (targetScript.equals("Hani")) {
+ if (baseScript.equals("Hanb") || baseScript.equals("Hans")
+ || baseScript.equals("Hant") || baseScript.equals("Kore")
+ || baseScript.equals("Jpan")) {
+ // Han id suppoted by Taiwanese, Traditional Chinese, Simplified Chinese, Korean
+ // and Japanese.
+ return true;
+ }
+ } else if (targetScript.equals("Hira") || targetScript.equals("Hrkt")
+ || targetScript.equals("Kana")) {
+ if (baseScript.equals("Jpan") || baseScript.equals("Hrkt")) {
+ // Hiragana, Hiragana-Katakana, Katakana is supported by Japanese and
+ // Hiragana-Katakana script.
+ return true;
+ }
+ }
+ }
+ return false;
+ }
}
diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
index cd3d2f031455..0f40ca082663 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -619,8 +619,8 @@ final class UpdatableFontDir {
}
return new FontConfig(
- config.getFontFamilies(), config.getAliases(), mergedFamilies, mLastModifiedMillis,
- mConfigVersion);
+ config.getFontFamilies(), config.getAliases(), mergedFamilies,
+ config.getLocaleFallbackCustomizations(), mLastModifiedMillis, mConfigVersion);
}
private PersistentSystemFontConfig.Config readPersistentConfig() {
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
index 184c976b86bf..3de167e72ba0 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
@@ -336,7 +336,8 @@ public final class UpdatableFontDirTest {
return new FontConfig(Collections.emptyList(),
Collections.emptyList(),
Collections.singletonList(new FontConfig.NamedFamilyList(
- Collections.singletonList(family), "sans-serif")), 0, 1);
+ Collections.singletonList(family), "sans-serif")),
+ Collections.emptyList(), 0, 1);
};
UpdatableFontDir dirForPreparation = new UpdatableFontDir(
@@ -499,7 +500,8 @@ public final class UpdatableFontDirTest {
Collections.emptyList(),
Collections.emptyList(),
Collections.singletonList(new FontConfig.NamedFamilyList(
- Collections.singletonList(family), "sans-serif")), 0, 1);
+ Collections.singletonList(family), "sans-serif")),
+ Collections.emptyList(), 0, 1);
});
dir.loadFontFileMap();
@@ -651,7 +653,8 @@ public final class UpdatableFontDirTest {
FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE);
return new FontConfig(Collections.emptyList(), Collections.emptyList(),
Collections.singletonList(new FontConfig.NamedFamilyList(
- Collections.singletonList(family), "sans-serif")), 0, 1);
+ Collections.singletonList(family), "sans-serif")),
+ Collections.emptyList(), 0, 1);
});
dir.loadFontFileMap();