Add family-list tag element to fonts_customization.xml

family-list can be used as a named font family definition.
Multiple families can be used as a fallback for the named
family.

Bug: 249787583
Test: atest TypefaceSystemFallbackTest FontListParserTest
Test: atest UpdatableFontDirTest UpdatableSystemFontTest
Test: atest GtsFontHostTestCases FontManagerTest
Change-Id: Ic459a533ac4b5081660c0a4a7519ef7e87a6b628
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 566ac45..c5b7a71 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -16178,6 +16178,7 @@
     method @IntRange(from=0) public int getConfigVersion();
     method @NonNull public java.util.List<android.text.FontConfig.FontFamily> getFontFamilies();
     method public long getLastModifiedTimeMillis();
+    method @NonNull public java.util.List<android.text.FontConfig.NamedFamilyList> getNamedFamilyLists();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.text.FontConfig> CREATOR;
   }
@@ -16207,7 +16208,7 @@
     method public int describeContents();
     method @NonNull public java.util.List<android.text.FontConfig.Font> getFontList();
     method @NonNull public android.os.LocaleList getLocaleList();
-    method @Nullable public String getName();
+    method @Deprecated @Nullable public String getName();
     method public int getVariant();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.text.FontConfig.FontFamily> CREATOR;
@@ -16216,6 +16217,14 @@
     field public static final int VARIANT_ELEGANT = 2; // 0x2
   }
 
+  public static final class FontConfig.NamedFamilyList implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.List<android.text.FontConfig.FontFamily> getFamilies();
+    method @NonNull public String getName();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.text.FontConfig.NamedFamilyList> CREATOR;
+  }
+
 }
 
 package android.util {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 5e02e72..5238c5d 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2706,6 +2706,7 @@
     method @IntRange(from=0) public int getConfigVersion();
     method @NonNull public java.util.List<android.text.FontConfig.FontFamily> getFontFamilies();
     method public long getLastModifiedTimeMillis();
+    method @NonNull public java.util.List<android.text.FontConfig.NamedFamilyList> getNamedFamilyLists();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.text.FontConfig> CREATOR;
   }
@@ -2735,7 +2736,7 @@
     method public int describeContents();
     method @NonNull public java.util.List<android.text.FontConfig.Font> getFontList();
     method @NonNull public android.os.LocaleList getLocaleList();
-    method @Nullable public String getName();
+    method @Deprecated @Nullable public String getName();
     method public int getVariant();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.text.FontConfig.FontFamily> CREATOR;
@@ -2744,6 +2745,14 @@
     field public static final int VARIANT_ELEGANT = 2; // 0x2
   }
 
+  public static final class FontConfig.NamedFamilyList implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.List<android.text.FontConfig.FontFamily> getFamilies();
+    method @NonNull public String getName();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.text.FontConfig.NamedFamilyList> CREATOR;
+  }
+
   public static final class Selection.MemoryTextWatcher implements android.text.TextWatcher {
     ctor public Selection.MemoryTextWatcher();
     method public void afterTextChanged(android.text.Editable);
diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java
index 32b3bc6..cb488b0 100644
--- a/core/java/android/text/FontConfig.java
+++ b/core/java/android/text/FontConfig.java
@@ -36,6 +36,7 @@
 import java.io.File;
 import java.lang.annotation.Retention;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 
@@ -55,6 +56,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 long mLastModifiedTimeMillis;
     private final int mConfigVersion;
 
@@ -67,14 +69,25 @@
      * @hide Only system server can create this instance and passed via IPC.
      */
     public FontConfig(@NonNull List<FontFamily> families, @NonNull List<Alias> aliases,
+            @NonNull List<NamedFamilyList> namedFamilyLists,
             long lastModifiedTimeMillis, @IntRange(from = 0) int configVersion) {
         mFamilies = families;
         mAliases = aliases;
+        mNamedFamilyLists = namedFamilyLists;
         mLastModifiedTimeMillis = lastModifiedTimeMillis;
         mConfigVersion = configVersion;
     }
 
     /**
+     * @hide Keep this constructor for reoborectric.
+     */
+    public FontConfig(@NonNull List<FontFamily> families, @NonNull List<Alias> aliases,
+            long lastModifiedTimeMillis, @IntRange(from = 0) int configVersion) {
+        this(families, aliases, Collections.emptyList(), lastModifiedTimeMillis, configVersion);
+    }
+
+
+    /**
      * Returns the ordered list of font families available in the system.
      *
      * @return a list of font families.
@@ -94,6 +107,10 @@
         return mAliases;
     }
 
+    public @NonNull List<NamedFamilyList> getNamedFamilyLists() {
+        return mNamedFamilyLists;
+    }
+
     /**
      * Returns the last modified time in milliseconds.
      *
@@ -133,8 +150,9 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeParcelableList(mFamilies, flags);
-        dest.writeParcelableList(mAliases, flags);
+        dest.writeTypedList(mFamilies, flags);
+        dest.writeTypedList(mAliases, flags);
+        dest.writeTypedList(mNamedFamilyLists, flags);
         dest.writeLong(mLastModifiedTimeMillis);
         dest.writeInt(mConfigVersion);
     }
@@ -142,13 +160,15 @@
     public static final @NonNull Creator<FontConfig> CREATOR = new Creator<FontConfig>() {
         @Override
         public FontConfig createFromParcel(Parcel source) {
-            List<FontFamily> families = source.readParcelableList(new ArrayList<>(),
-                    FontFamily.class.getClassLoader(), android.text.FontConfig.FontFamily.class);
-            List<Alias> aliases = source.readParcelableList(new ArrayList<>(),
-                    Alias.class.getClassLoader(), android.text.FontConfig.Alias.class);
+            final List<FontFamily> families = new ArrayList<>();
+            source.readTypedList(families, FontFamily.CREATOR);
+            final List<Alias> aliases = new ArrayList<>();
+            source.readTypedList(aliases, Alias.CREATOR);
+            final List<NamedFamilyList> familyLists = new ArrayList<>();
+            source.readTypedList(familyLists, NamedFamilyList.CREATOR);
             long lastModifiedDate = source.readLong();
             int configVersion = source.readInt();
-            return new FontConfig(families, aliases, lastModifiedDate, configVersion);
+            return new FontConfig(families, aliases, familyLists, lastModifiedDate, configVersion);
         }
 
         @Override
@@ -506,7 +526,6 @@
      */
     public static final class FontFamily implements Parcelable {
         private final @NonNull List<Font> mFonts;
-        private final @Nullable String mName;
         private final @NonNull LocaleList mLocaleList;
         private final @Variant int mVariant;
 
@@ -547,10 +566,9 @@
          *
          * @hide Only system server can create this instance and passed via IPC.
          */
-        public FontFamily(@NonNull List<Font> fonts, @Nullable String name,
-                @NonNull LocaleList localeList, @Variant int variant) {
+        public FontFamily(@NonNull List<Font> fonts, @NonNull LocaleList localeList,
+                @Variant int variant) {
             mFonts = fonts;
-            mName = name;
             mLocaleList = localeList;
             mVariant = variant;
         }
@@ -577,9 +595,13 @@
          *
          * When the name of a {@link FontFamily} is null, it will be appended to all of the
          * {@code Fallback List}s.
+         *
+         * @deprecated From API 34, this function always returns null. All font families which have
+         *             name attribute will be reported as a {@link NamedFamilyList}.
          */
+        @Deprecated
         public @Nullable String getName() {
-            return mName;
+            return null;
         }
 
         /**
@@ -606,8 +628,7 @@
 
         @Override
         public void writeToParcel(@NonNull Parcel dest, int flags) {
-            dest.writeParcelableList(mFonts, flags);
-            dest.writeString8(mName);
+            dest.writeTypedList(mFonts, flags);
             dest.writeString8(mLocaleList.toLanguageTags());
             dest.writeInt(mVariant);
         }
@@ -616,13 +637,12 @@
 
             @Override
             public FontFamily createFromParcel(Parcel source) {
-                List<Font> fonts = source.readParcelableList(
-                        new ArrayList<>(), Font.class.getClassLoader(), android.text.FontConfig.Font.class);
-                String name = source.readString8();
+                List<Font> fonts = new ArrayList<>();
+                source.readTypedList(fonts, Font.CREATOR);
                 String langTags = source.readString8();
                 int variant = source.readInt();
 
-                return new FontFamily(fonts, name, LocaleList.forLanguageTags(langTags), variant);
+                return new FontFamily(fonts, LocaleList.forLanguageTags(langTags), variant);
             }
 
             @Override
@@ -659,23 +679,118 @@
             FontFamily that = (FontFamily) o;
             return mVariant == that.mVariant
                     && Objects.equals(mFonts, that.mFonts)
-                    && Objects.equals(mName, that.mName)
                     && Objects.equals(mLocaleList, that.mLocaleList);
         }
 
         @Override
         public int hashCode() {
-            return Objects.hash(mFonts, mName, mLocaleList, mVariant);
+            return Objects.hash(mFonts, mLocaleList, mVariant);
         }
 
         @Override
         public String toString() {
             return "FontFamily{"
                     + "mFonts=" + mFonts
-                    + ", mName='" + mName + '\''
                     + ", mLocaleList=" + mLocaleList
                     + ", mVariant=" + mVariant
                     + '}';
         }
     }
+
+    /**
+     * Represents list of font family in the system font configuration.
+     *
+     * In the fonts_customization.xml, it can define the list of FontFamily as a named family. The
+     * list of FontFamily is treated as a fallback list when drawing.
+     *
+     * @see android.graphics.fonts.FontFamily
+     */
+    public static final class NamedFamilyList implements Parcelable {
+        private final List<FontFamily> mFamilies;
+        private final String mName;
+
+        /** @hide */
+        public NamedFamilyList(@NonNull List<FontFamily> families, @NonNull String name) {
+            mFamilies = families;
+            mName = name;
+        }
+
+        /** @hide */
+        public NamedFamilyList(@NonNull FontFamily family) {
+            mFamilies = new ArrayList<>();
+            mFamilies.add(family);
+            mName = family.getName();
+        }
+
+        /**
+         * A list of font families.
+         *
+         * @return a list of font families.
+         */
+        public @NonNull List<FontFamily> getFamilies() {
+            return mFamilies;
+        }
+
+        /**
+         * Returns the name of the {@link FontFamily}.
+         *
+         * This name is used to create a new {@code Fallback List}.
+         *
+         * For example, if the {@link FontFamily} has the name "serif", then the system will create
+         * a “serif” {@code Fallback List} and it can be used by creating a Typeface via
+         * {@code Typeface.create("serif", Typeface.NORMAL);}
+         */
+        public @NonNull String getName() {
+            return mName;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@androidx.annotation.NonNull Parcel dest, int flags) {
+            dest.writeTypedList(mFamilies, flags);
+            dest.writeString8(mName);
+        }
+
+        public static final @NonNull Creator<NamedFamilyList> CREATOR = new Creator<>() {
+
+            @Override
+            public NamedFamilyList createFromParcel(Parcel source) {
+                final List<FontFamily> families = new ArrayList<>();
+                source.readTypedList(families, FontFamily.CREATOR);
+                String name = source.readString8();
+                return new NamedFamilyList(families, name);
+            }
+
+            @Override
+            public NamedFamilyList[] newArray(int size) {
+                return new NamedFamilyList[size];
+            }
+        };
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            NamedFamilyList that = (NamedFamilyList) o;
+            return Objects.equals(mFamilies, that.mFamilies) && Objects.equals(mName,
+                    that.mName);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mFamilies, mName);
+        }
+
+        @Override
+        public String toString() {
+            return "NamedFamilyList{"
+                    + "mFamilies=" + mFamilies
+                    + ", mName='" + mName + '\''
+                    + '}';
+        }
+    }
 }
diff --git a/core/tests/coretests/assets/fonts/a3em.ttf b/core/tests/coretests/assets/fonts/a3em.ttf
index a601ce2..4c1ad1b 100644
--- a/core/tests/coretests/assets/fonts/a3em.ttf
+++ b/core/tests/coretests/assets/fonts/a3em.ttf
Binary files differ
diff --git a/core/tests/coretests/assets/fonts/a3em.ttx b/core/tests/coretests/assets/fonts/a3em.ttx
index d3b9e16..d7da0da 100644
--- a/core/tests/coretests/assets/fonts/a3em.ttx
+++ b/core/tests/coretests/assets/fonts/a3em.ttx
@@ -156,7 +156,7 @@
       Sample Font
     </namerecord>
     <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
-      SampleFont-Regular
+      a3em
     </namerecord>
     <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
       Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/core/tests/coretests/assets/fonts/b3em.ttf b/core/tests/coretests/assets/fonts/b3em.ttf
index 63948a2..b18d4bb 100644
--- a/core/tests/coretests/assets/fonts/b3em.ttf
+++ b/core/tests/coretests/assets/fonts/b3em.ttf
Binary files differ
diff --git a/core/tests/coretests/assets/fonts/b3em.ttx b/core/tests/coretests/assets/fonts/b3em.ttx
index b5a77ef..217393a 100644
--- a/core/tests/coretests/assets/fonts/b3em.ttx
+++ b/core/tests/coretests/assets/fonts/b3em.ttx
@@ -156,7 +156,7 @@
       Sample Font
     </namerecord>
     <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
-      SampleFont-Regular
+      b3em
     </namerecord>
     <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
       Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/core/tests/coretests/assets/fonts/c3em.ttf b/core/tests/coretests/assets/fonts/c3em.ttf
index badc3e2..83a5db2 100644
--- a/core/tests/coretests/assets/fonts/c3em.ttf
+++ b/core/tests/coretests/assets/fonts/c3em.ttf
Binary files differ
diff --git a/core/tests/coretests/assets/fonts/c3em.ttx b/core/tests/coretests/assets/fonts/c3em.ttx
index f5ed8e5..7535bea 100644
--- a/core/tests/coretests/assets/fonts/c3em.ttx
+++ b/core/tests/coretests/assets/fonts/c3em.ttx
@@ -156,7 +156,7 @@
       Sample Font
     </namerecord>
     <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
-      SampleFont-Regular
+      c3em
     </namerecord>
     <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
       Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/core/tests/coretests/assets/fonts/fallback.ttf b/core/tests/coretests/assets/fonts/fallback.ttf
new file mode 100644
index 0000000..1ba8639
--- /dev/null
+++ b/core/tests/coretests/assets/fonts/fallback.ttf
Binary files differ
diff --git a/core/tests/coretests/assets/fonts/fallback.ttx b/core/tests/coretests/assets/fonts/fallback.ttx
new file mode 100644
index 0000000..8e4b3e6
--- /dev/null
+++ b/core/tests/coretests/assets/fonts/fallback.ttx
@@ -0,0 +1,205 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+  <GlyphOrder>
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="1em"/>
+  </GlyphOrder>
+
+  <head>
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x640cdb2f"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="1000"/>
+    <created value="Fri Mar 17 07:26:00 2017"/>
+    <macStyle value="00000000 00000000"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="1.0"/>
+    <ascent value="1000"/>
+    <descent value="-200"/>
+    <lineGap value="0"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+  </hhea>
+
+  <maxp>
+    <tableVersion value="0x10000"/>
+    <maxZones value="0"/>
+    <maxTwilightPoints value="0"/>
+    <maxStorage value="0"/>
+    <maxFunctionDefs value="0"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="0"/>
+    <maxSizeOfInstructions value="0"/>
+    <maxComponentElements value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="400"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 01000000"/>
+    <usFirstCharIndex value="32"/>
+    <usLastCharIndex value="122"/>
+    <sTypoAscender value="800"/>
+    <sTypoDescender value="-200"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="500" lsb="93"/>
+    <mtx name="1em" width="1000" lsb="93"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_4 platformID="3" platEncID="10" language="0">
+      <map code="0x0061" name="1em" /> <!-- "a" -->
+      <map code="0x0062" name="1em" /> <!-- "b" -->
+      <map code="0x0063" name="1em" /> <!-- "c" -->
+      <map code="0x0064" name="1em" /> <!-- "d" -->
+      <map code="0x0065" name="1em" /> <!-- "e" -->
+      <map code="0x0066" name="1em" /> <!-- "f" -->
+      <map code="0x0067" name="1em" /> <!-- "g" -->
+      <map code="0x0068" name="1em" /> <!-- "h" -->
+      <map code="0x0069" name="1em" /> <!-- "i" -->
+      <map code="0x006a" name="1em" /> <!-- "j" -->
+      <map code="0x006b" name="1em" /> <!-- "k" -->
+      <map code="0x006c" name="1em" /> <!-- "l" -->
+      <map code="0x006d" name="1em" /> <!-- "m" -->
+      <map code="0x006e" name="1em" /> <!-- "n" -->
+      <map code="0x006f" name="1em" /> <!-- "o" -->
+      <map code="0x0070" name="1em" /> <!-- "p" -->
+      <map code="0x0071" name="1em" /> <!-- "q" -->
+      <map code="0x0072" name="1em" /> <!-- "r" -->
+      <map code="0x0073" name="1em" /> <!-- "s" -->
+      <map code="0x0074" name="1em" /> <!-- "t" -->
+      <map code="0x0075" name="1em" /> <!-- "u" -->
+      <map code="0x0076" name="1em" /> <!-- "v" -->
+      <map code="0x0077" name="1em" /> <!-- "w" -->
+      <map code="0x0078" name="1em" /> <!-- "x" -->
+      <map code="0x0079" name="1em" /> <!-- "y" -->
+      <map code="0x007a" name="1em" /> <!-- "z" -->
+    </cmap_format_4>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" />
+  </glyf>
+
+  <name>
+    <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+      Copyright (C) 2017 The Android Open Source Project
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      fallback
+    </namerecord>
+    <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+      Licensed under the Apache License, Version 2.0 (the "License");
+      you may not use this file except in compliance with the License.
+      Unless required by applicable law or agreed to in writing, software
+      distributed under the License is distributed on an "AS IS" BASIS
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+      See the License for the specific language governing permissions and
+      limitations under the License.
+    </namerecord>
+    <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+      http://www.apache.org/licenses/LICENSE-2.0
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/core/tests/coretests/assets/fonts/fallback_capital.ttf b/core/tests/coretests/assets/fonts/fallback_capital.ttf
new file mode 100644
index 0000000..073761f
--- /dev/null
+++ b/core/tests/coretests/assets/fonts/fallback_capital.ttf
Binary files differ
diff --git a/core/tests/coretests/assets/fonts/fallback_capital.ttx b/core/tests/coretests/assets/fonts/fallback_capital.ttx
new file mode 100644
index 0000000..710c95e
--- /dev/null
+++ b/core/tests/coretests/assets/fonts/fallback_capital.ttx
@@ -0,0 +1,205 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+  <GlyphOrder>
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="1em"/>
+  </GlyphOrder>
+
+  <head>
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x640cdb2f"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="1000"/>
+    <created value="Fri Mar 17 07:26:00 2017"/>
+    <macStyle value="00000000 00000000"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="1.0"/>
+    <ascent value="1000"/>
+    <descent value="-200"/>
+    <lineGap value="0"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+  </hhea>
+
+  <maxp>
+    <tableVersion value="0x10000"/>
+    <maxZones value="0"/>
+    <maxTwilightPoints value="0"/>
+    <maxStorage value="0"/>
+    <maxFunctionDefs value="0"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="0"/>
+    <maxSizeOfInstructions value="0"/>
+    <maxComponentElements value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="400"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 01000000"/>
+    <usFirstCharIndex value="32"/>
+    <usLastCharIndex value="122"/>
+    <sTypoAscender value="800"/>
+    <sTypoDescender value="-200"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="500" lsb="93"/>
+    <mtx name="1em" width="1000" lsb="93"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_4 platformID="3" platEncID="10" language="0">
+      <map code="0x0041" name="1em" /> <!-- "A" -->
+      <map code="0x0042" name="1em" /> <!-- "B" -->
+      <map code="0x0043" name="1em" /> <!-- "C" -->
+      <map code="0x0044" name="1em" /> <!-- "D" -->
+      <map code="0x0045" name="1em" /> <!-- "E" -->
+      <map code="0x0046" name="1em" /> <!-- "F" -->
+      <map code="0x0047" name="1em" /> <!-- "G" -->
+      <map code="0x0048" name="1em" /> <!-- "H" -->
+      <map code="0x0049" name="1em" /> <!-- "I" -->
+      <map code="0x004a" name="1em" /> <!-- "J" -->
+      <map code="0x004b" name="1em" /> <!-- "K" -->
+      <map code="0x004c" name="1em" /> <!-- "L" -->
+      <map code="0x004d" name="1em" /> <!-- "M" -->
+      <map code="0x004e" name="1em" /> <!-- "N" -->
+      <map code="0x004f" name="1em" /> <!-- "O" -->
+      <map code="0x0050" name="1em" /> <!-- "P" -->
+      <map code="0x0051" name="1em" /> <!-- "Q" -->
+      <map code="0x0052" name="1em" /> <!-- "R" -->
+      <map code="0x0053" name="1em" /> <!-- "S" -->
+      <map code="0x0054" name="1em" /> <!-- "T" -->
+      <map code="0x0055" name="1em" /> <!-- "U" -->
+      <map code="0x0056" name="1em" /> <!-- "V" -->
+      <map code="0x0057" name="1em" /> <!-- "W" -->
+      <map code="0x0058" name="1em" /> <!-- "X" -->
+      <map code="0x0059" name="1em" /> <!-- "Y" -->
+      <map code="0x005a" name="1em" /> <!-- "Z" -->
+    </cmap_format_4>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="1em" xMin="0" yMin="0" xMax="0" yMax="0" />
+  </glyf>
+
+  <name>
+    <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+      Copyright (C) 2017 The Android Open Source Project
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      Sample Font
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      fallback_capital
+    </namerecord>
+    <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+      Licensed under the Apache License, Version 2.0 (the "License");
+      you may not use this file except in compliance with the License.
+      Unless required by applicable law or agreed to in writing, software
+      distributed under the License is distributed on an "AS IS" BASIS
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+      See the License for the specific language governing permissions and
+      limitations under the License.
+    </namerecord>
+    <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+      http://www.apache.org/licenses/LICENSE-2.0
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/core/tests/coretests/src/android/graphics/FontListParserTest.java b/core/tests/coretests/src/android/graphics/FontListParserTest.java
index 479e52a..d46f762 100644
--- a/core/tests/coretests/src/android/graphics/FontListParserTest.java
+++ b/core/tests/coretests/src/android/graphics/FontListParserTest.java
@@ -47,6 +47,7 @@
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
 @SmallTest
@@ -59,14 +60,12 @@
                 + "<family name='sans-serif'>"
                 + "  <font>test.ttf</font>"
                 + "</family>";
-        FontConfig.FontFamily expected = new FontConfig.FontFamily(
-                Arrays.asList(
-                        new FontConfig.Font(new File("test.ttf"), null, "test",
-                                new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
-                                0, "", null)),
-                "sans-serif", LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT);
-
-        FontConfig.FontFamily family = readFamily(xml);
+        FontConfig.NamedFamilyList expected = new FontConfig.NamedFamilyList(
+                Collections.singletonList(new FontConfig.FontFamily(
+                    Arrays.asList(new FontConfig.Font(new File("test.ttf"), null, "test",
+                        new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null)),
+                    LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)), "sans-serif");
+        FontConfig.NamedFamilyList family = readNamedFamily(xml);
         assertThat(family).isEqualTo(expected);
     }
 
@@ -85,7 +84,7 @@
                         new FontConfig.Font(new File("test.ttf"), null, "test",
                                 new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
                                 0, "", "serif")),
-                null, LocaleList.forLanguageTags("en"), VARIANT_DEFAULT);
+                LocaleList.forLanguageTags("en"), VARIANT_DEFAULT);
 
         FontConfig.FontFamily family = readFamily(xml);
         assertThat(family).isEqualTo(expected);
@@ -102,7 +101,7 @@
                         new FontConfig.Font(new File("test.ttf"), null, "test",
                                 new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
                                 0, "", null)),
-                null, LocaleList.forLanguageTags("en"), VARIANT_COMPACT);
+                LocaleList.forLanguageTags("en"), VARIANT_COMPACT);
 
         FontConfig.FontFamily family = readFamily(xml);
         assertThat(family).isEqualTo(expected);
@@ -119,7 +118,7 @@
                         new FontConfig.Font(new File("test.ttf"), null, "test",
                                 new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
                                 0, "", null)),
-                null, LocaleList.forLanguageTags("en"), VARIANT_ELEGANT);
+                LocaleList.forLanguageTags("en"), VARIANT_ELEGANT);
 
         FontConfig.FontFamily family = readFamily(xml);
         assertThat(family).isEqualTo(expected);
@@ -133,19 +132,16 @@
                 + "  <font weight='100'>weight.ttf</font>"
                 + "  <font style='italic'>italic.ttf</font>"
                 + "</family>";
-        FontConfig.FontFamily expected = new FontConfig.FontFamily(
-                Arrays.asList(
-                        new FontConfig.Font(new File("normal.ttf"), null, "test",
-                                new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
-                                0, "", null),
-                        new FontConfig.Font(new File("weight.ttf"), null, "test",
-                                new FontStyle(100, FONT_SLANT_UPRIGHT),
-                                0, "", null),
-                        new FontConfig.Font(new File("italic.ttf"), null, "test",
-                                new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_ITALIC),
-                                0, "", null)),
-                "sans-serif", LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT);
-        FontConfig.FontFamily family = readFamily(xml);
+        FontConfig.NamedFamilyList expected = new FontConfig.NamedFamilyList(
+                Collections.singletonList(new FontConfig.FontFamily(Arrays.asList(
+                      new FontConfig.Font(new File("normal.ttf"), null, "test",
+                        new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null),
+                      new FontConfig.Font(new File("weight.ttf"), null, "test",
+                        new FontStyle(100, FONT_SLANT_UPRIGHT), 0, "", null),
+                      new FontConfig.Font(new File("italic.ttf"), null, "test",
+                        new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_ITALIC), 0, "", null)),
+                    LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)), "sans-serif");
+        FontConfig.NamedFamilyList family = readNamedFamily(xml);
         assertThat(family).isEqualTo(expected);
     }
 
@@ -162,16 +158,17 @@
                 + "    <axis tag='wght' stylevalue='700' />"
                 + "  </font>"
                 + "</family>";
-        FontConfig.FontFamily expected = new FontConfig.FontFamily(
-                Arrays.asList(
+        FontConfig.NamedFamilyList expected = new FontConfig.NamedFamilyList(
+                Collections.singletonList(new FontConfig.FontFamily(Arrays.asList(
                         new FontConfig.Font(new File("test-VF.ttf"), null, "test",
                                 new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
                                 0, "'wdth' 100.0,'wght' 200.0", null),
                         new FontConfig.Font(new File("test-VF.ttf"), null, "test",
                                 new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
                                 0, "'wdth' 400.0,'wght' 700.0", null)),
-                "sans-serif", LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT);
-        FontConfig.FontFamily family = readFamily(xml);
+                        LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)),
+                "sans-serif");
+        FontConfig.NamedFamilyList family = readNamedFamily(xml);
         assertThat(family).isEqualTo(expected);
     }
 
@@ -182,16 +179,17 @@
                 + "  <font index='0'>test.ttc</font>"
                 + "  <font index='1'>test.ttc</font>"
                 + "</family>";
-        FontConfig.FontFamily expected = new FontConfig.FontFamily(
-                Arrays.asList(
+        FontConfig.NamedFamilyList expected = new FontConfig.NamedFamilyList(
+                Collections.singletonList(new FontConfig.FontFamily(Arrays.asList(
                         new FontConfig.Font(new File("test.ttc"), null, "test",
                                 new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
                                 0, "", null),
                         new FontConfig.Font(new File("test.ttc"), null, "test",
                                 new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
                                 1, "", null)),
-                "sans-serif", LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT);
-        FontConfig.FontFamily family = readFamily(xml);
+                                LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)),
+                "sans-serif");
+        FontConfig.NamedFamilyList family = readNamedFamily(xml);
         assertThat(family).isEqualTo(expected);
     }
 
@@ -202,16 +200,14 @@
                 + "  <font index='0' postScriptName='foo'>test.ttc</font>"
                 + "  <font index='1'>test.ttc</font>"
                 + "</family>";
-        FontConfig.FontFamily expected = new FontConfig.FontFamily(
-                Arrays.asList(
-                        new FontConfig.Font(new File("test.ttc"), null, "foo",
-                                new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
-                                0, "", null),
-                        new FontConfig.Font(new File("test.ttc"), null, "test",
-                                new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
-                                1, "", null)),
-                "sans-serif", LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT);
-        FontConfig.FontFamily family = readFamily(xml);
+        FontConfig.NamedFamilyList expected = new FontConfig.NamedFamilyList(
+                Collections.singletonList(new FontConfig.FontFamily(Arrays.asList(
+                      new FontConfig.Font(new File("test.ttc"), null, "foo",
+                        new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 0, "", null),
+                      new FontConfig.Font(new File("test.ttc"), null, "test",
+                        new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT), 1, "", null)),
+                    LocaleList.getEmptyLocaleList(), VARIANT_DEFAULT)), "sans-serif");
+        FontConfig.NamedFamilyList family = readNamedFamily(xml);
         assertThat(family).isEqualTo(expected);
     }
 
@@ -396,4 +392,14 @@
         parser.nextTag();
         return FontListParser.readFamily(parser, "", null, true);
     }
+
+    private FontConfig.NamedFamilyList readNamedFamily(String xml)
+            throws IOException, XmlPullParserException {
+        ByteArrayInputStream buffer = new ByteArrayInputStream(
+                xml.getBytes(StandardCharsets.UTF_8));
+        XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(buffer, "UTF-8");
+        parser.nextTag();
+        return FontListParser.readNamedFamily(parser, "", null, true);
+    }
 }
diff --git a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
index 3df2e90..a8a5059 100644
--- a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
+++ b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
@@ -28,6 +28,8 @@
 import android.graphics.fonts.FontCustomizationParser;
 import android.graphics.fonts.FontFamily;
 import android.graphics.fonts.SystemFonts;
+import android.graphics.text.PositionedGlyphs;
+import android.graphics.text.TextRunShaper;
 import android.text.FontConfig;
 import android.util.ArrayMap;
 
@@ -64,6 +66,8 @@
         "b3em.ttf",  // Supports "a","b","c". The width of "b" is 3em,  others are 1em.
         "c3em.ttf",  // Supports "a","b","c". The width of "c" is 3em,  others are 1em.
         "all2em.ttf",  // Supports "a,","b","c". All of them have the same width of 2em.
+        "fallback.ttf",  // SUpports all small alphabets.
+        "fallback_capital.ttf",  // SUpports all capital alphabets.
         "no_coverage.ttf",  // This font doesn't support any characters.
     };
     private static final String TEST_FONTS_XML;
@@ -165,7 +169,10 @@
 
         Map<String, File> updatableFontMap = new HashMap<>();
         for (File file : new File(TEST_UPDATABLE_FONT_DIR).listFiles()) {
-            updatableFontMap.put(file.getName(), file);
+            final String fileName = file.getName();
+            final int periodIndex = fileName.lastIndexOf(".");
+            final String psName = fileName.substring(0, periodIndex);
+            updatableFontMap.put(psName, file);
         }
 
         FontConfig fontConfig;
@@ -710,6 +717,47 @@
         assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0.0f);
     }
 
+    private String getFontName(Paint paint, String text) {
+        PositionedGlyphs glyphs = TextRunShaper.shapeTextRun(
+                text, 0, text.length(), 0, text.length(), 0f, 0f, false, paint);
+        assertEquals(1, glyphs.glyphCount());
+        return glyphs.getFont(0).getFile().getName();
+    }
+
+    @Test
+    public void testBuildSystemFallback__Customization_new_named_familyList() {
+        final String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+                + "<familyset>"
+                + "  <family name='sans-serif'>"
+                + "    <font weight='400' style='normal'>fallback_capital.ttf</font>"
+                + "  </family>"
+                + "</familyset>";
+        final String oemXml = "<?xml version='1.0' encoding='UTF-8'?>"
+                + "<fonts-modification version='1'>"
+                + "  <family-list customizationType='new-named-family' name='google-sans'>"
+                + "    <family>"
+                + "      <font weight='400' style='normal'>b3em.ttf</font>"
+                + "    </family>"
+                + "    <family>"
+                + "      <font weight='400' style='normal'>fallback.ttf</font>"
+                + "    </family>"
+                + "  </family-list>"
+                + "</fonts-modification>";
+        final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
+        final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>();
+
+        buildSystemFallback(xml, oemXml, fontMap, fallbackMap);
+
+        final Paint paint = new Paint();
+
+        Typeface testTypeface = fontMap.get("google-sans");
+        assertNotNull(testTypeface);
+        paint.setTypeface(testTypeface);
+        assertEquals("b3em.ttf", getFontName(paint, "a"));
+        assertEquals("fallback.ttf", getFontName(paint, "x"));
+        assertEquals("fallback_capital.ttf", getFontName(paint, "A"));
+    }
+
     @Test
     public void testBuildSystemFallback__Customization_new_named_family_override() {
         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 4bb16c6..674246a 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -16,6 +16,8 @@
 
 package android.graphics;
 
+import static android.text.FontConfig.NamedFamilyList;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -36,6 +38,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -46,6 +49,7 @@
  * @hide
  */
 public class FontListParser {
+    private static final String TAG = "FontListParser";
 
     // XML constants for FontFamily.
     private static final String ATTR_NAME = "name";
@@ -148,27 +152,60 @@
             boolean allowNonExistingFile)
             throws XmlPullParserException, IOException {
         List<FontConfig.FontFamily> families = new ArrayList<>();
+        List<FontConfig.NamedFamilyList> resultNamedFamilies = new ArrayList<>();
         List<FontConfig.Alias> aliases = new ArrayList<>(customization.getAdditionalAliases());
 
-        Map<String, FontConfig.FontFamily> oemNamedFamilies =
+        Map<String, NamedFamilyList> oemNamedFamilies =
                 customization.getAdditionalNamedFamilies();
 
+        boolean firstFamily = true;
         parser.require(XmlPullParser.START_TAG, null, "familyset");
         while (keepReading(parser)) {
             if (parser.getEventType() != XmlPullParser.START_TAG) continue;
             String tag = parser.getName();
             if (tag.equals("family")) {
-                FontConfig.FontFamily family = readFamily(parser, fontDir, updatableFontMap,
-                        allowNonExistingFile);
-                if (family == null) {
+                final String name = parser.getAttributeValue(null, "name");
+                if (name == null) {
+                    FontConfig.FontFamily family = readFamily(parser, fontDir, updatableFontMap,
+                            allowNonExistingFile);
+                    if (family == null) {
+                        continue;
+                    }
+                    families.add(family);
+
+                } else {
+                    FontConfig.NamedFamilyList namedFamilyList = readNamedFamily(
+                            parser, fontDir, updatableFontMap, allowNonExistingFile);
+                    if (namedFamilyList == null) {
+                        continue;
+                    }
+                    if (!oemNamedFamilies.containsKey(name)) {
+                        // The OEM customization overrides system named family. Skip if OEM
+                        // customization XML defines the same named family.
+                        resultNamedFamilies.add(namedFamilyList);
+                    }
+                    if (firstFamily) {
+                        // The first font family is used as a fallback family as well.
+                        families.addAll(namedFamilyList.getFamilies());
+                    }
+                }
+                firstFamily = false;
+            } else if (tag.equals("family-list")) {
+                FontConfig.NamedFamilyList namedFamilyList = readNamedFamilyList(
+                        parser, fontDir, updatableFontMap, allowNonExistingFile);
+                if (namedFamilyList == null) {
                     continue;
                 }
-                String name = family.getName();
-                if (name == null || !oemNamedFamilies.containsKey(name)) {
+                if (!oemNamedFamilies.containsKey(namedFamilyList.getName())) {
                     // The OEM customization overrides system named family. Skip if OEM
                     // customization XML defines the same named family.
-                    families.add(family);
+                    resultNamedFamilies.add(namedFamilyList);
                 }
+                if (firstFamily) {
+                    // The first font family is used as a fallback family as well.
+                    families.addAll(namedFamilyList.getFamilies());
+                }
+                firstFamily = false;
             } else if (tag.equals("alias")) {
                 aliases.add(readAlias(parser));
             } else {
@@ -176,12 +213,12 @@
             }
         }
 
-        families.addAll(oemNamedFamilies.values());
+        resultNamedFamilies.addAll(oemNamedFamilies.values());
 
         // Filters aliases that point to non-existing families.
         Set<String> namedFamilies = new ArraySet<>();
-        for (int i = 0; i < families.size(); ++i) {
-            String name = families.get(i).getName();
+        for (int i = 0; i < resultNamedFamilies.size(); ++i) {
+            String name = resultNamedFamilies.get(i).getName();
             if (name != null) {
                 namedFamilies.add(name);
             }
@@ -194,7 +231,8 @@
             }
         }
 
-        return new FontConfig(families, filtered, lastModifiedDate, configVersion);
+        return new FontConfig(families, filtered, resultNamedFamilies, lastModifiedDate,
+                configVersion);
     }
 
     private static boolean keepReading(XmlPullParser parser)
@@ -215,7 +253,6 @@
     public static @Nullable FontConfig.FontFamily readFamily(XmlPullParser parser, String fontDir,
             @Nullable Map<String, File> updatableFontMap, boolean allowNonExistingFile)
             throws XmlPullParserException, IOException {
-        final String name = parser.getAttributeValue(null, "name");
         final String lang = parser.getAttributeValue("", "lang");
         final String variant = parser.getAttributeValue(null, "variant");
         final String ignore = parser.getAttributeValue(null, "ignore");
@@ -246,7 +283,68 @@
         if (skip || fonts.isEmpty()) {
             return null;
         }
-        return new FontConfig.FontFamily(fonts, name, LocaleList.forLanguageTags(lang), intVariant);
+        return new FontConfig.FontFamily(fonts, LocaleList.forLanguageTags(lang), intVariant);
+    }
+
+    private static void throwIfAttributeExists(String attrName, XmlPullParser parser) {
+        if (parser.getAttributeValue(null, attrName) != null) {
+            throw new IllegalArgumentException(attrName + " cannot be used in FontFamily inside "
+                    + " family or family-list with name attribute.");
+        }
+    }
+
+    /**
+     * Read a font family with name attribute as a single element family-list element.
+     */
+    public static @Nullable FontConfig.NamedFamilyList readNamedFamily(
+            @NonNull XmlPullParser parser, @NonNull String fontDir,
+            @Nullable Map<String, File> updatableFontMap, boolean allowNonExistingFile)
+            throws XmlPullParserException, IOException {
+        final String name = parser.getAttributeValue(null, "name");
+        throwIfAttributeExists("lang", parser);
+        throwIfAttributeExists("variant", parser);
+        throwIfAttributeExists("ignore", parser);
+
+        final FontConfig.FontFamily family = readFamily(parser, fontDir, updatableFontMap,
+                allowNonExistingFile);
+        if (family == null) {
+            return null;
+        }
+        return new NamedFamilyList(Collections.singletonList(family), name);
+    }
+
+    /**
+     * Read a family-list element
+     */
+    public static @Nullable FontConfig.NamedFamilyList readNamedFamilyList(
+            @NonNull XmlPullParser parser, @NonNull String fontDir,
+            @Nullable Map<String, File> updatableFontMap, boolean allowNonExistingFile)
+            throws XmlPullParserException, IOException {
+        final String name = parser.getAttributeValue(null, "name");
+        final List<FontConfig.FontFamily> familyList = new ArrayList<>();
+        while (keepReading(parser)) {
+            if (parser.getEventType() != XmlPullParser.START_TAG) continue;
+            final String tag = parser.getName();
+            if (tag.equals("family")) {
+                throwIfAttributeExists("name", parser);
+                throwIfAttributeExists("lang", parser);
+                throwIfAttributeExists("variant", parser);
+                throwIfAttributeExists("ignore", parser);
+
+                final FontConfig.FontFamily family = readFamily(parser, fontDir, updatableFontMap,
+                        allowNonExistingFile);
+                if (family != null) {
+                    familyList.add(family);
+                }
+            } else {
+                skip(parser);
+            }
+        }
+
+        if (familyList.isEmpty()) {
+            return null;
+        }
+        return new FontConfig.NamedFamilyList(familyList, name);
     }
 
     /** Matches leading and trailing XML whitespace. */
diff --git a/graphics/java/android/graphics/fonts/FontCustomizationParser.java b/graphics/java/android/graphics/fonts/FontCustomizationParser.java
index df47f73..b458dd9 100644
--- a/graphics/java/android/graphics/fonts/FontCustomizationParser.java
+++ b/graphics/java/android/graphics/fonts/FontCustomizationParser.java
@@ -17,7 +17,7 @@
 package android.graphics.fonts;
 
 import static android.text.FontConfig.Alias;
-import static android.text.FontConfig.FontFamily;
+import static android.text.FontConfig.NamedFamilyList;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -42,11 +42,14 @@
  * @hide
  */
 public class FontCustomizationParser {
+    private static final String TAG = "FontCustomizationParser";
+
     /**
      * Represents a customization XML
      */
     public static class Result {
-        private final Map<String, FontFamily> mAdditionalNamedFamilies;
+        private final Map<String, NamedFamilyList> mAdditionalNamedFamilies;
+
         private final List<Alias> mAdditionalAliases;
 
         public Result() {
@@ -54,13 +57,13 @@
             mAdditionalAliases = Collections.emptyList();
         }
 
-        public Result(Map<String, FontFamily> additionalNamedFamilies,
+        public Result(Map<String, NamedFamilyList> additionalNamedFamilies,
                 List<Alias> additionalAliases) {
             mAdditionalNamedFamilies = additionalNamedFamilies;
             mAdditionalAliases = additionalAliases;
         }
 
-        public Map<String, FontFamily> getAdditionalNamedFamilies() {
+        public Map<String, NamedFamilyList> getAdditionalNamedFamilies() {
             return mAdditionalNamedFamilies;
         }
 
@@ -85,20 +88,24 @@
         return readFamilies(parser, fontDir, updatableFontMap);
     }
 
-    private static Map<String, FontFamily> validateAndTransformToMap(List<FontFamily> families) {
-        HashMap<String, FontFamily> namedFamily = new HashMap<>();
+    private static Result validateAndTransformToResult(
+            List<NamedFamilyList> families, List<Alias> aliases) {
+        HashMap<String, NamedFamilyList> namedFamily = new HashMap<>();
         for (int i = 0; i < families.size(); ++i) {
-            final FontFamily family = families.get(i);
+            final NamedFamilyList family = families.get(i);
             final String name = family.getName();
-            if (name == null) {
-                throw new IllegalArgumentException("new-named-family requires name attribute");
-            }
-            if (namedFamily.put(name, family) != null) {
+            if (name != null) {
+                if (namedFamily.put(name, family) != null) {
+                    throw new IllegalArgumentException(
+                            "new-named-family requires unique name attribute");
+                }
+            } else {
                 throw new IllegalArgumentException(
-                        "new-named-family requires unique name attribute");
+                        "new-named-family requires name attribute or new-default-fallback-family"
+                                + "requires fallackTarget attribute");
             }
         }
-        return namedFamily;
+        return new Result(namedFamily, aliases);
     }
 
     private static Result readFamilies(
@@ -106,7 +113,7 @@
             @NonNull String fontDir,
             @Nullable Map<String, File> updatableFontMap
     ) throws XmlPullParserException, IOException {
-        List<FontFamily> families = new ArrayList<>();
+        List<NamedFamilyList> families = new ArrayList<>();
         List<Alias> aliases = new ArrayList<>();
         parser.require(XmlPullParser.START_TAG, null, "fonts-modification");
         while (parser.next() != XmlPullParser.END_TAG) {
@@ -114,19 +121,21 @@
             String tag = parser.getName();
             if (tag.equals("family")) {
                 readFamily(parser, fontDir, families, updatableFontMap);
+            } else if (tag.equals("family-list")) {
+                readFamilyList(parser, fontDir, families, updatableFontMap);
             } else if (tag.equals("alias")) {
                 aliases.add(FontListParser.readAlias(parser));
             } else {
                 FontListParser.skip(parser);
             }
         }
-        return new Result(validateAndTransformToMap(families), aliases);
+        return validateAndTransformToResult(families, aliases);
     }
 
     private static void readFamily(
             @NonNull XmlPullParser parser,
             @NonNull String fontDir,
-            @NonNull List<FontFamily> out,
+            @NonNull List<NamedFamilyList> out,
             @Nullable Map<String, File> updatableFontMap)
             throws XmlPullParserException, IOException {
         final String customizationType = parser.getAttributeValue(null, "customizationType");
@@ -134,7 +143,28 @@
             throw new IllegalArgumentException("customizationType must be specified");
         }
         if (customizationType.equals("new-named-family")) {
-            FontFamily fontFamily = FontListParser.readFamily(
+            NamedFamilyList fontFamily = FontListParser.readNamedFamily(
+                    parser, fontDir, updatableFontMap, false);
+            if (fontFamily != null) {
+                out.add(fontFamily);
+            }
+        } else {
+            throw new IllegalArgumentException("Unknown customizationType=" + customizationType);
+        }
+    }
+
+    private static void readFamilyList(
+            @NonNull XmlPullParser parser,
+            @NonNull String fontDir,
+            @NonNull List<NamedFamilyList> out,
+            @Nullable Map<String, File> updatableFontMap)
+            throws XmlPullParserException, IOException {
+        final String customizationType = parser.getAttributeValue(null, "customizationType");
+        if (customizationType == null) {
+            throw new IllegalArgumentException("customizationType must be specified");
+        }
+        if (customizationType.equals("new-named-family")) {
+            NamedFamilyList fontFamily = FontListParser.readNamedFamilyList(
                     parser, fontDir, updatableFontMap, false);
             if (fontFamily != null) {
                 out.add(fontFamily);
diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java
index a771a6e..bf79b1b 100644
--- a/graphics/java/android/graphics/fonts/FontFamily.java
+++ b/graphics/java/android/graphics/fonts/FontFamily.java
@@ -114,17 +114,19 @@
          * @return a font family
          */
         public @NonNull FontFamily build() {
-            return build("", FontConfig.FontFamily.VARIANT_DEFAULT, true /* isCustomFallback */);
+            return build("", FontConfig.FontFamily.VARIANT_DEFAULT, true /* isCustomFallback */,
+                    false /* isDefaultFallback */);
         }
 
         /** @hide */
         public @NonNull FontFamily build(@NonNull String langTags, int variant,
-                boolean isCustomFallback) {
+                boolean isCustomFallback, boolean isDefaultFallback) {
             final long builderPtr = nInitBuilder();
             for (int i = 0; i < mFonts.size(); ++i) {
                 nAddFont(builderPtr, mFonts.get(i).getNativePtr());
             }
-            final long ptr = nBuild(builderPtr, langTags, variant, isCustomFallback);
+            final long ptr = nBuild(builderPtr, langTags, variant, isCustomFallback,
+                    isDefaultFallback);
             final FontFamily family = new FontFamily(ptr);
             sFamilyRegistory.registerNativeAllocation(family, ptr);
             return family;
@@ -138,7 +140,7 @@
         @CriticalNative
         private static native void nAddFont(long builderPtr, long fontPtr);
         private static native long nBuild(long builderPtr, String langTags, int variant,
-                boolean isCustomFallback);
+                boolean isCustomFallback, boolean isDefaultFallback);
         @CriticalNative
         private static native long nGetReleaseNativeFamily();
     }
diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java
index 6278c0e..ec8b2d6 100644
--- a/graphics/java/android/graphics/fonts/SystemFonts.java
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -22,6 +22,7 @@
 import android.graphics.Typeface;
 import android.text.FontConfig;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
@@ -92,9 +93,8 @@
     }
 
     private static void pushFamilyToFallback(@NonNull FontConfig.FontFamily xmlFamily,
-            @NonNull ArrayMap<String, ArrayList<FontFamily>> fallbackMap,
+            @NonNull ArrayMap<String, NativeFamilyListSet> fallbackMap,
             @NonNull Map<String, ByteBuffer> cache) {
-
         final String languageTags = xmlFamily.getLocaleList().toLanguageTags();
         final int variant = xmlFamily.getVariant();
 
@@ -118,26 +118,29 @@
         }
 
         final FontFamily defaultFamily = defaultFonts.isEmpty() ? null : createFontFamily(
-                xmlFamily.getName(), defaultFonts, languageTags, variant, cache);
+                defaultFonts, languageTags, variant, false, cache);
 
         // Insert family into fallback map.
         for (int i = 0; i < fallbackMap.size(); i++) {
-            String name = fallbackMap.keyAt(i);
+            final String name = fallbackMap.keyAt(i);
+            final NativeFamilyListSet familyListSet = fallbackMap.valueAt(i);
+            if (familyListSet.seenXmlFamilies.contains(xmlFamily)) {
+                continue;
+            } else {
+                familyListSet.seenXmlFamilies.add(xmlFamily);
+            }
             final ArrayList<FontConfig.Font> fallback = specificFallbackFonts.get(name);
             if (fallback == null) {
-                String familyName = xmlFamily.getName();
-                if (defaultFamily != null
-                        // do not add myself to the fallback chain.
-                        && (familyName == null || !familyName.equals(name))) {
-                    fallbackMap.valueAt(i).add(defaultFamily);
+                if (defaultFamily != null) {
+                    familyListSet.familyList.add(defaultFamily);
                 }
             } else {
-                final FontFamily family = createFontFamily(
-                        xmlFamily.getName(), fallback, languageTags, variant, cache);
+                final FontFamily family = createFontFamily(fallback, languageTags, variant, false,
+                        cache);
                 if (family != null) {
-                    fallbackMap.valueAt(i).add(family);
+                    familyListSet.familyList.add(family);
                 } else if (defaultFamily != null) {
-                    fallbackMap.valueAt(i).add(defaultFamily);
+                    familyListSet.familyList.add(defaultFamily);
                 } else {
                     // There is no valid for for default fallback. Ignore.
                 }
@@ -145,10 +148,11 @@
         }
     }
 
-    private static @Nullable FontFamily createFontFamily(@NonNull String familyName,
+    private static @Nullable FontFamily createFontFamily(
             @NonNull List<FontConfig.Font> fonts,
             @NonNull String languageTags,
             @FontConfig.FontFamily.Variant int variant,
+            boolean isDefaultFallback,
             @NonNull Map<String, ByteBuffer> cache) {
         if (fonts.size() == 0) {
             return null;
@@ -188,23 +192,30 @@
                 b.addFont(font);
             }
         }
-        return b == null ? null : b.build(languageTags, variant, false /* isCustomFallback */);
+        return b == null ? null : b.build(languageTags, variant, false /* isCustomFallback */,
+                isDefaultFallback);
     }
 
-    private static void appendNamedFamily(@NonNull FontConfig.FontFamily xmlFamily,
+    private static void appendNamedFamilyList(@NonNull FontConfig.NamedFamilyList namedFamilyList,
             @NonNull ArrayMap<String, ByteBuffer> bufferCache,
-            @NonNull ArrayMap<String, ArrayList<FontFamily>> fallbackListMap) {
-        final String familyName = xmlFamily.getName();
-        final FontFamily family = createFontFamily(
-                familyName, xmlFamily.getFontList(),
-                xmlFamily.getLocaleList().toLanguageTags(), xmlFamily.getVariant(),
-                bufferCache);
-        if (family == null) {
-            return;
+            @NonNull ArrayMap<String, NativeFamilyListSet> fallbackListMap) {
+        final String familyName = namedFamilyList.getName();
+        final NativeFamilyListSet familyListSet = new NativeFamilyListSet();
+        final List<FontConfig.FontFamily> xmlFamilies = namedFamilyList.getFamilies();
+        for (int i = 0; i < xmlFamilies.size(); ++i) {
+            FontConfig.FontFamily xmlFamily = xmlFamilies.get(i);
+            final FontFamily family = createFontFamily(
+                    xmlFamily.getFontList(),
+                    xmlFamily.getLocaleList().toLanguageTags(), xmlFamily.getVariant(),
+                    true, // named family is always default
+                    bufferCache);
+            if (family == null) {
+                return;
+            }
+            familyListSet.familyList.add(family);
+            familyListSet.seenXmlFamilies.add(xmlFamily);
         }
-        final ArrayList<FontFamily> fallback = new ArrayList<>();
-        fallback.add(family);
-        fallbackListMap.put(familyName, fallback);
+        fallbackListMap.put(familyName, familyListSet);
     }
 
     /**
@@ -245,10 +256,12 @@
                                                 updatableFontMap, lastModifiedDate, configVersion);
         } catch (IOException e) {
             Log.e(TAG, "Failed to open/read system font configurations.", e);
-            return new FontConfig(Collections.emptyList(), Collections.emptyList(), 0, 0);
+            return new FontConfig(Collections.emptyList(), 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(), 0, 0);
+            return new FontConfig(Collections.emptyList(), Collections.emptyList(),
+                    Collections.emptyList(), 0, 0);
         }
     }
 
@@ -261,37 +274,36 @@
         return buildSystemFallback(fontConfig, new ArrayMap<>());
     }
 
+    private static final class NativeFamilyListSet {
+        public List<FontFamily> familyList = new ArrayList<>();
+        public Set<FontConfig.FontFamily> seenXmlFamilies = new ArraySet<>();
+    }
+
     /** @hide */
     @VisibleForTesting
     public static Map<String, FontFamily[]> buildSystemFallback(FontConfig fontConfig,
             ArrayMap<String, ByteBuffer> outBufferCache) {
-        final Map<String, FontFamily[]> fallbackMap = new ArrayMap<>();
-        final List<FontConfig.FontFamily> xmlFamilies = fontConfig.getFontFamilies();
 
-        final ArrayMap<String, ArrayList<FontFamily>> fallbackListMap = new ArrayMap<>();
-        // First traverse families which have a 'name' attribute to create fallback map.
-        for (final FontConfig.FontFamily xmlFamily : xmlFamilies) {
-            final String familyName = xmlFamily.getName();
-            if (familyName == null) {
-                continue;
-            }
-            appendNamedFamily(xmlFamily, outBufferCache, fallbackListMap);
+        final ArrayMap<String, NativeFamilyListSet> fallbackListMap = new ArrayMap<>();
+
+        final List<FontConfig.NamedFamilyList> namedFamilies = fontConfig.getNamedFamilyLists();
+        for (int i = 0; i < namedFamilies.size(); ++i) {
+            FontConfig.NamedFamilyList namedFamilyList = namedFamilies.get(i);
+            appendNamedFamilyList(namedFamilyList, outBufferCache, fallbackListMap);
         }
 
-        // Then, add fallback fonts to the each fallback map.
+        // Then, add fallback fonts to the fallback map.
+        final List<FontConfig.FontFamily> xmlFamilies = fontConfig.getFontFamilies();
         for (int i = 0; i < xmlFamilies.size(); i++) {
             final FontConfig.FontFamily xmlFamily = xmlFamilies.get(i);
-            // The first family (usually the sans-serif family) is always placed immediately
-            // after the primary family in the fallback.
-            if (i == 0 || xmlFamily.getName() == null) {
-                pushFamilyToFallback(xmlFamily, fallbackListMap, outBufferCache);
-            }
+            pushFamilyToFallback(xmlFamily, fallbackListMap, outBufferCache);
         }
 
         // Build the font map and fallback map.
+        final Map<String, FontFamily[]> fallbackMap = new ArrayMap<>();
         for (int i = 0; i < fallbackListMap.size(); i++) {
             final String fallbackName = fallbackListMap.keyAt(i);
-            final List<FontFamily> familyList = fallbackListMap.valueAt(i);
+            final List<FontFamily> familyList = fallbackListMap.valueAt(i).familyList;
             fallbackMap.put(fallbackName, familyList.toArray(new FontFamily[0]));
         }
 
diff --git a/libs/hwui/jni/FontFamily.cpp b/libs/hwui/jni/FontFamily.cpp
index c146ada..28e71d7 100644
--- a/libs/hwui/jni/FontFamily.cpp
+++ b/libs/hwui/jni/FontFamily.cpp
@@ -85,9 +85,9 @@
     if (builder->fonts.empty()) {
         return 0;
     }
-    std::shared_ptr<minikin::FontFamily> family =
-            minikin::FontFamily::create(builder->langId, builder->variant,
-                                        std::move(builder->fonts), true /* isCustomFallback */);
+    std::shared_ptr<minikin::FontFamily> family = minikin::FontFamily::create(
+            builder->langId, builder->variant, std::move(builder->fonts),
+            true /* isCustomFallback */, false /* isDefaultFallback */);
     if (family->getCoverage().length() == 0) {
         return 0;
     }
diff --git a/libs/hwui/jni/fonts/FontFamily.cpp b/libs/hwui/jni/fonts/FontFamily.cpp
index fbfc07e..897c4d7 100644
--- a/libs/hwui/jni/fonts/FontFamily.cpp
+++ b/libs/hwui/jni/fonts/FontFamily.cpp
@@ -57,7 +57,8 @@
 
 // Regular JNI
 static jlong FontFamily_Builder_build(JNIEnv* env, jobject clazz, jlong builderPtr,
-            jstring langTags, jint variant, jboolean isCustomFallback) {
+                                      jstring langTags, jint variant, jboolean isCustomFallback,
+                                      jboolean isDefaultFallback) {
     std::unique_ptr<NativeFamilyBuilder> builder(toBuilder(builderPtr));
     uint32_t localeId;
     if (langTags == nullptr) {
@@ -66,9 +67,9 @@
         ScopedUtfChars str(env, langTags);
         localeId = minikin::registerLocaleList(str.c_str());
     }
-    std::shared_ptr<minikin::FontFamily> family =
-            minikin::FontFamily::create(localeId, static_cast<minikin::FamilyVariant>(variant),
-                                        std::move(builder->fonts), isCustomFallback);
+    std::shared_ptr<minikin::FontFamily> family = minikin::FontFamily::create(
+            localeId, static_cast<minikin::FamilyVariant>(variant), std::move(builder->fonts),
+            isCustomFallback, isDefaultFallback);
     if (family->getCoverage().length() == 0) {
         // No coverage means minikin rejected given font for some reasons.
         jniThrowException(env, "java/lang/IllegalArgumentException",
@@ -116,10 +117,10 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 static const JNINativeMethod gFontFamilyBuilderMethods[] = {
-    { "nInitBuilder", "()J", (void*) FontFamily_Builder_initBuilder },
-    { "nAddFont", "(JJ)V", (void*) FontFamily_Builder_addFont },
-    { "nBuild", "(JLjava/lang/String;IZ)J", (void*) FontFamily_Builder_build },
-    { "nGetReleaseNativeFamily", "()J", (void*) FontFamily_Builder_GetReleaseFunc },
+        {"nInitBuilder", "()J", (void*)FontFamily_Builder_initBuilder},
+        {"nAddFont", "(JJ)V", (void*)FontFamily_Builder_addFont},
+        {"nBuild", "(JLjava/lang/String;IZZ)J", (void*)FontFamily_Builder_build},
+        {"nGetReleaseNativeFamily", "()J", (void*)FontFamily_Builder_GetReleaseFunc},
 };
 
 static const JNINativeMethod gFontFamilyMethods[] = {
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
index 4cd0d6e..2ac2833 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
@@ -163,19 +163,25 @@
         // Dump named font family first.
         List<FontConfig.FontFamily> families = fontConfig.getFontFamilies();
 
-        w.println("Named Font Families");
+        // Dump FontFamilyList
+        w.println("Named Family List");
         w.increaseIndent();
-        for (int i = 0; i < families.size(); ++i) {
-            final FontConfig.FontFamily family = families.get(i);
-
-            // Here, only dump the named family only.
-            if (family.getName() == null) continue;
-
-            w.println("Named Family (" + family.getName() + ")");
-            final List<FontConfig.Font> fonts = family.getFontList();
+        List<FontConfig.NamedFamilyList> namedFamilyLists = fontConfig.getNamedFamilyLists();
+        for (int i = 0; i < namedFamilyLists.size(); ++i) {
+            final FontConfig.NamedFamilyList namedFamilyList = namedFamilyLists.get(i);
+            w.println("Named Family (" + namedFamilyList.getName() + ")");
             w.increaseIndent();
-            for (int j = 0; j < fonts.size(); ++j) {
-                dumpSingleFontConfig(w, fonts.get(j));
+            final List<FontConfig.FontFamily> namedFamilies = namedFamilyList.getFamilies();
+            for (int j = 0; j < namedFamilies.size(); ++j) {
+                final FontConfig.FontFamily family = namedFamilies.get(j);
+
+                w.println("Family");
+                final List<FontConfig.Font> fonts = family.getFontList();
+                w.increaseIndent();
+                for (int k = 0; k < fonts.size(); ++k) {
+                    dumpSingleFontConfig(w, fonts.get(k));
+                }
+                w.decreaseIndent();
             }
             w.decreaseIndent();
         }
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 6f93608..a680f50 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -19,7 +19,6 @@
 import static com.android.server.graphics.fonts.FontManagerService.SystemFontException;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.graphics.fonts.FontManager;
 import android.graphics.fonts.FontUpdateRequest;
 import android.graphics.fonts.SystemFonts;
@@ -44,6 +43,7 @@
 import java.nio.file.Paths;
 import java.security.SecureRandom;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -286,7 +286,7 @@
 
             // Before processing font family update, check all family points the available fonts.
             for (FontUpdateRequest.Family family : familyMap.values()) {
-                if (resolveFontFiles(family) == null) {
+                if (resolveFontFilesForNamedFamily(family) == null) {
                     throw new SystemFontException(
                             FontManager.RESULT_ERROR_FONT_NOT_FOUND,
                             "Required fonts are not available");
@@ -498,6 +498,19 @@
                 }
             }
         }
+        for (int i = 0; i < fontConfig.getNamedFamilyLists().size(); ++i) {
+            FontConfig.NamedFamilyList namedFamilyList = fontConfig.getNamedFamilyLists().get(i);
+            for (int j = 0; j < namedFamilyList.getFamilies().size(); ++j) {
+                FontConfig.FontFamily family = namedFamilyList.getFamilies().get(j);
+                for (int k = 0; k < family.getFontList().size(); ++k) {
+                    FontConfig.Font font = family.getFontList().get(k);
+                    if (font.getPostScriptName().equals(psName)) {
+                        targetFont = font;
+                        break;
+                    }
+                }
+            }
+        }
         if (targetFont == null) {
             return -1;
         }
@@ -553,8 +566,8 @@
         }
     }
 
-    @Nullable
-    private FontConfig.FontFamily resolveFontFiles(FontUpdateRequest.Family fontFamily) {
+    private FontConfig.NamedFamilyList resolveFontFilesForNamedFamily(
+            FontUpdateRequest.Family fontFamily) {
         List<FontUpdateRequest.Font> fontList = fontFamily.getFonts();
         List<FontConfig.Font> resolvedFonts = new ArrayList<>(fontList.size());
         for (int i = 0; i < fontList.size(); i++) {
@@ -567,8 +580,10 @@
             resolvedFonts.add(new FontConfig.Font(info.mFile, null, info.getPostScriptName(),
                     font.getFontStyle(), font.getIndex(), font.getFontVariationSettings(), null));
         }
-        return new FontConfig.FontFamily(resolvedFonts, fontFamily.getName(),
+        FontConfig.FontFamily family = new FontConfig.FontFamily(resolvedFonts,
                 LocaleList.getEmptyLocaleList(), FontConfig.FontFamily.VARIANT_DEFAULT);
+        return new FontConfig.NamedFamilyList(Collections.singletonList(family),
+                fontFamily.getName());
     }
 
     Map<String, File> getPostScriptMap() {
@@ -585,23 +600,24 @@
         PersistentSystemFontConfig.Config persistentConfig = readPersistentConfig();
         List<FontUpdateRequest.Family> families = persistentConfig.fontFamilies;
 
-        List<FontConfig.FontFamily> mergedFamilies =
-                new ArrayList<>(config.getFontFamilies().size() + families.size());
+        List<FontConfig.NamedFamilyList> mergedFamilies =
+                new ArrayList<>(config.getNamedFamilyLists().size() + families.size());
         // We should keep the first font family (config.getFontFamilies().get(0)) because it's used
         // as a fallback font. See SystemFonts.java.
-        mergedFamilies.addAll(config.getFontFamilies());
+        mergedFamilies.addAll(config.getNamedFamilyLists());
         // When building Typeface, a latter font family definition will override the previous font
         // family definition with the same name. An exception is config.getFontFamilies.get(0),
         // which will be used as a fallback font without being overridden.
         for (int i = 0; i < families.size(); ++i) {
-            FontConfig.FontFamily family = resolveFontFiles(families.get(i));
+            FontConfig.NamedFamilyList family = resolveFontFilesForNamedFamily(families.get(i));
             if (family != null) {
                 mergedFamilies.add(family);
             }
         }
 
         return new FontConfig(
-                mergedFamilies, config.getAliases(), mLastModifiedMillis, mConfigVersion);
+                config.getFontFamilies(), config.getAliases(), mergedFamilies, mLastModifiedMillis,
+                mConfigVersion);
     }
 
     private PersistentSystemFontConfig.Config readPersistentConfig() {
@@ -635,12 +651,12 @@
         return mConfigVersion;
     }
 
-    public Map<String, FontConfig.FontFamily> getFontFamilyMap() {
+    public Map<String, FontConfig.NamedFamilyList> getFontFamilyMap() {
         PersistentSystemFontConfig.Config curConfig = readPersistentConfig();
-        Map<String, FontConfig.FontFamily> familyMap = new HashMap<>();
+        Map<String, FontConfig.NamedFamilyList> familyMap = new HashMap<>();
         for (int i = 0; i < curConfig.fontFamilies.size(); ++i) {
             FontUpdateRequest.Family family = curConfig.fontFamilies.get(i);
-            FontConfig.FontFamily resolvedFamily = resolveFontFiles(family);
+            FontConfig.NamedFamilyList resolvedFamily = resolveFontFilesForNamedFamily(family);
             if (resolvedFamily != null) {
                 familyMap.put(family.getName(), resolvedFamily);
             }
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 68e5ebf..e9a7d85 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
@@ -210,7 +210,8 @@
         assertThat(mUpdatableFontFilesDir.list()).hasLength(2);
         assertNamedFamilyExists(dir.getSystemFontConfig(), "foobar");
         assertThat(dir.getFontFamilyMap()).containsKey("foobar");
-        FontConfig.FontFamily foobar = dir.getFontFamilyMap().get("foobar");
+        assertThat(dir.getFontFamilyMap().get("foobar").getFamilies().size()).isEqualTo(1);
+        FontConfig.FontFamily foobar = dir.getFontFamilyMap().get("foobar").getFamilies().get(0);
         assertThat(foobar.getFontList()).hasSize(2);
         assertThat(foobar.getFontList().get(0).getFile())
                 .isEqualTo(dir.getPostScriptMap().get("foo"));
@@ -326,10 +327,12 @@
                     new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT), 0, null, null);
 
             FontConfig.FontFamily family = new FontConfig.FontFamily(
-                    Arrays.asList(fooFont, barFont), "sans-serif", null,
+                    Arrays.asList(fooFont, barFont), null,
                     FontConfig.FontFamily.VARIANT_DEFAULT);
-            return new FontConfig(Collections.singletonList(family),
-                    Collections.emptyList(), 0, 1);
+            return new FontConfig(Collections.emptyList(),
+                    Collections.emptyList(),
+                    Collections.singletonList(new FontConfig.NamedFamilyList(
+                            Collections.singletonList(family), "sans-serif")), 0, 1);
         };
 
         UpdatableFontDir dirForPreparation = new UpdatableFontDir(
@@ -411,7 +414,8 @@
         assertThat(dir.getPostScriptMap()).containsKey("foo");
         assertThat(mParser.getRevision(dir.getPostScriptMap().get("foo"))).isEqualTo(1);
         assertThat(dir.getFontFamilyMap()).containsKey("foobar");
-        FontConfig.FontFamily foobar = dir.getFontFamilyMap().get("foobar");
+        assertThat(dir.getFontFamilyMap().get("foobar").getFamilies().size()).isEqualTo(1);
+        FontConfig.FontFamily foobar = dir.getFontFamilyMap().get("foobar").getFamilies().get(0);
         assertThat(foobar.getFontList()).hasSize(1);
         assertThat(foobar.getFontList().get(0).getFile())
                 .isEqualTo(dir.getPostScriptMap().get("foo"));
@@ -485,10 +489,12 @@
                     file, null, "bar", new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT),
                     0, null, null);
             FontConfig.FontFamily family = new FontConfig.FontFamily(
-                    Collections.singletonList(font), "sans-serif", null,
-                    FontConfig.FontFamily.VARIANT_DEFAULT);
-            return new FontConfig(Collections.singletonList(family),
-                    Collections.emptyList(), 0, 1);
+                    Collections.singletonList(font), null, FontConfig.FontFamily.VARIANT_DEFAULT);
+            return new FontConfig(
+                    Collections.emptyList(),
+                    Collections.emptyList(),
+                    Collections.singletonList(new FontConfig.NamedFamilyList(
+                            Collections.singletonList(family), "sans-serif")), 0, 1);
         });
         dir.loadFontFileMap();
 
@@ -636,9 +642,10 @@
                     file, null, "test", new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT), 0, null,
                     null);
             FontConfig.FontFamily family = new FontConfig.FontFamily(
-                    Collections.singletonList(font), "sans-serif", null,
-                    FontConfig.FontFamily.VARIANT_DEFAULT);
-            return new FontConfig(Collections.singletonList(family), Collections.emptyList(), 0, 1);
+                    Collections.singletonList(font), null, FontConfig.FontFamily.VARIANT_DEFAULT);
+            return new FontConfig(Collections.emptyList(), Collections.emptyList(),
+                    Collections.singletonList(new FontConfig.NamedFamilyList(
+                            Collections.singletonList(family), "sans-serif")), 0, 1);
         });
         dir.loadFontFileMap();
 
@@ -873,13 +880,14 @@
                         + "</family>")));
         assertThat(dir.getPostScriptMap()).containsKey("test");
         assertThat(dir.getFontFamilyMap()).containsKey("test");
-        FontConfig.FontFamily test = dir.getFontFamilyMap().get("test");
+        assertThat(dir.getFontFamilyMap().get("test").getFamilies().size()).isEqualTo(1);
+        FontConfig.FontFamily test = dir.getFontFamilyMap().get("test").getFamilies().get(0);
         assertThat(test.getFontList()).hasSize(1);
         assertThat(test.getFontList().get(0).getFile())
                 .isEqualTo(dir.getPostScriptMap().get("test"));
     }
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     public void addFontFamily_noName() throws Exception {
         UpdatableFontDir dir = new UpdatableFontDir(
                 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
@@ -891,12 +899,7 @@
                 newAddFontFamilyRequest("<family lang='en'>"
                         + "  <font>test.ttf</font>"
                         + "</family>"));
-        try {
-            dir.update(requests);
-            fail("Expect NullPointerException");
-        } catch (NullPointerException e) {
-            // Expect
-        }
+        dir.update(requests);
     }
 
     @Test
@@ -955,17 +958,16 @@
         dir.loadFontFileMap();
         assertThat(dir.getSystemFontConfig().getFontFamilies()).isNotEmpty();
         FontConfig.FontFamily firstFontFamily = dir.getSystemFontConfig().getFontFamilies().get(0);
-        assertThat(firstFontFamily.getName()).isNotEmpty();
 
         dir.update(Arrays.asList(
                 newFontUpdateRequest("test.ttf,1,test", GOOD_SIGNATURE),
-                newAddFontFamilyRequest("<family name='" + firstFontFamily.getName() + "'>"
+                newAddFontFamilyRequest("<family name='sans-serif'>"
                         + "  <font>test.ttf</font>"
                         + "</family>")));
         FontConfig fontConfig = dir.getSystemFontConfig();
         assertThat(dir.getSystemFontConfig().getFontFamilies()).isNotEmpty();
         assertThat(fontConfig.getFontFamilies().get(0)).isEqualTo(firstFontFamily);
-        FontConfig.FontFamily updated = getLastFamily(fontConfig, firstFontFamily.getName());
+        FontConfig.FontFamily updated = getLastFamily(fontConfig, "sans-serif");
         assertThat(updated.getFontList()).hasSize(1);
         assertThat(updated.getFontList().get(0).getFile())
                 .isEqualTo(dir.getPostScriptMap().get("test"));
@@ -1005,7 +1007,9 @@
         mParser.setInput(is, "UTF-8");
         mParser.nextTag();
 
-        FontConfig.FontFamily fontFamily = FontListParser.readFamily(mParser, "", null, true);
+        FontConfig.NamedFamilyList namedFamilyList = FontListParser.readNamedFamily(
+                mParser, "", null, true);
+        FontConfig.FontFamily fontFamily = namedFamilyList.getFamilies().get(0);
         List<FontUpdateRequest.Font> fonts = new ArrayList<>();
         for (FontConfig.Font font : fontFamily.getFontList()) {
             String name = font.getFile().getName();
@@ -1014,7 +1018,8 @@
                     psName, font.getStyle(), font.getTtcIndex(), font.getFontVariationSettings());
             fonts.add(updateFont);
         }
-        FontUpdateRequest.Family family = new FontUpdateRequest.Family(fontFamily.getName(), fonts);
+        FontUpdateRequest.Family family = new FontUpdateRequest.Family(
+                namedFamilyList.getName(), fonts);
         return new FontUpdateRequest(family);
     }
 
@@ -1035,18 +1040,18 @@
 
     // Returns the last family with the given name, which will be used for creating Typeface.
     private static FontConfig.FontFamily getLastFamily(FontConfig fontConfig, String familyName) {
-        List<FontConfig.FontFamily> fontFamilies = fontConfig.getFontFamilies();
-        for (int i = fontFamilies.size() - 1; i >= 0; i--) {
-            if (familyName.equals(fontFamilies.get(i).getName())) {
-                return fontFamilies.get(i);
+        List<FontConfig.NamedFamilyList> namedFamilyLists = fontConfig.getNamedFamilyLists();
+        for (int i = namedFamilyLists.size() - 1; i >= 0; i--) {
+            if (familyName.equals(namedFamilyLists.get(i).getName())) {
+                return namedFamilyLists.get(i).getFamilies().get(0);
             }
         }
         return null;
     }
 
     private static void assertNamedFamilyExists(FontConfig fontConfig, String familyName) {
-        assertThat(fontConfig.getFontFamilies().stream()
-                .map(FontConfig.FontFamily::getName)
+        assertThat(fontConfig.getNamedFamilyLists().stream()
+                .map(FontConfig.NamedFamilyList::getName)
                 .collect(Collectors.toSet())).contains(familyName);
     }
 }
diff --git a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
index 650686f..fa5b7c1 100644
--- a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
+++ b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
@@ -69,6 +69,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.regex.Pattern;
+import java.util.stream.Stream;
 
 /**
  * Tests if fonts can be updated by {@link FontManager} API.
@@ -246,7 +247,10 @@
     @Test
     public void updateFontFamily() throws Exception {
         assertThat(updateNotoSerifAs("serif")).isEqualTo(FontManager.RESULT_SUCCESS);
-        FontConfig.FontFamily family = findFontFamilyOrThrow("serif");
+        final FontConfig.NamedFamilyList namedFamilyList = findFontFamilyOrThrow("serif");
+        assertThat(namedFamilyList.getFamilies().size()).isEqualTo(1);
+        final FontConfig.FontFamily family = namedFamilyList.getFamilies().get(0);
+
         assertThat(family.getFontList()).hasSize(2);
         assertThat(family.getFontList().get(0).getPostScriptName())
                 .isEqualTo(NOTO_SERIF_REGULAR_POSTSCRIPT_NAME);
@@ -265,7 +269,10 @@
     public void updateFontFamily_asNewFont() throws Exception {
         assertThat(updateNotoSerifAs("UpdatableSystemFontTest-serif"))
                 .isEqualTo(FontManager.RESULT_SUCCESS);
-        FontConfig.FontFamily family = findFontFamilyOrThrow("UpdatableSystemFontTest-serif");
+        final FontConfig.NamedFamilyList namedFamilyList =
+                findFontFamilyOrThrow("UpdatableSystemFontTest-serif");
+        assertThat(namedFamilyList.getFamilies().size()).isEqualTo(1);
+        final FontConfig.FontFamily family = namedFamilyList.getFamilies().get(0);
         assertThat(family.getFontList()).hasSize(2);
         assertThat(family.getFontList().get(0).getPostScriptName())
                 .isEqualTo(NOTO_SERIF_REGULAR_POSTSCRIPT_NAME);
@@ -434,9 +441,15 @@
     private String getFontPath(String psName) {
         FontConfig fontConfig =
                 SystemUtil.runWithShellPermissionIdentity(mFontManager::getFontConfig);
-        return fontConfig.getFontFamilies().stream()
+        final List<FontConfig.FontFamily> namedFamilies = fontConfig.getNamedFamilyLists().stream()
+                .flatMap(namedFamily -> namedFamily.getFamilies().stream()).toList();
+
+        return Stream.concat(fontConfig.getFontFamilies().stream(), namedFamilies.stream())
                 .flatMap(family -> family.getFontList().stream())
-                .filter(font -> psName.equals(font.getPostScriptName()))
+                .filter(font -> {
+                    Log.e("Debug", "PsName = " + font.getPostScriptName());
+                    return psName.equals(font.getPostScriptName());
+                })
                 // Return the last match, because the latter family takes precedence if two families
                 // have the same name.
                 .reduce((first, second) -> second)
@@ -445,10 +458,10 @@
                 .getAbsolutePath();
     }
 
-    private FontConfig.FontFamily findFontFamilyOrThrow(String familyName) {
+    private FontConfig.NamedFamilyList findFontFamilyOrThrow(String familyName) {
         FontConfig fontConfig =
                 SystemUtil.runWithShellPermissionIdentity(mFontManager::getFontConfig);
-        return fontConfig.getFontFamilies().stream()
+        return fontConfig.getNamedFamilyLists().stream()
                 .filter(family -> familyName.equals(family.getName()))
                 // Return the last match, because the latter family takes precedence if two families
                 // have the same name.