Merge "Introduce system font enumeration API"
diff --git a/api/current.txt b/api/current.txt
index 872d0ec..88d7825 100755
--- a/api/current.txt
+++ b/api/current.txt
@@ -15218,6 +15218,9 @@
 
   public final class Font {
     method public android.graphics.fonts.FontVariationAxis[] getAxes();
+    method public java.nio.ByteBuffer getBuffer();
+    method public java.io.File getFile();
+    method public android.os.LocaleList getLocaleList();
     method public int getTtcIndex();
     method public int getWeight();
     method public boolean isItalic();
@@ -15266,6 +15269,10 @@
     method public static java.lang.String toFontVariationSettings(android.graphics.fonts.FontVariationAxis[]);
   }
 
+  public class SystemFonts {
+    method public static java.util.Set<android.graphics.fonts.Font> getAvailableFonts();
+  }
+
 }
 
 package android.graphics.pdf {
diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java
index 9e0fee33..19c55d5 100644
--- a/core/java/android/text/FontConfig.java
+++ b/core/java/android/text/FontConfig.java
@@ -181,7 +181,8 @@
     public static final class Family {
         private final @NonNull String mName;
         private final @NonNull Font[] mFonts;
-        private final @NonNull String[] mLanguages;
+        // Comma separated BCP47 complient locale strings
+        private final @NonNull String mLanguages;
 
         /** @hide */
         @Retention(SOURCE)
@@ -219,7 +220,7 @@
         // See frameworks/minikin/include/minikin/FontFamily.h
         private final @Variant int mVariant;
 
-        public Family(@NonNull String name, @NonNull Font[] fonts, @NonNull String[] languages,
+        public Family(@NonNull String name, @NonNull Font[] fonts, @NonNull String languages,
                 @Variant int variant) {
             mName = name;
             mFonts = fonts;
@@ -244,9 +245,9 @@
         }
 
         /**
-         * Returns the languages for this family. May be null.
+         * Returns the comma separated BCP47 complient languages for this family. May be null.
          */
-        public @Nullable String[] getLanguages() {
+        public @NonNull String getLanguages() {
             return mLanguages;
         }
 
diff --git a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
index dd34f1f..1c4039b 100644
--- a/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
+++ b/core/tests/coretests/src/android/graphics/TypefaceSystemFallbackTest.java
@@ -23,6 +23,7 @@
 
 import android.content.Context;
 import android.content.res.AssetManager;
+import android.graphics.fonts.Font;
 import android.graphics.fonts.FontFamily;
 import android.graphics.fonts.SystemFonts;
 import android.support.test.InstrumentationRegistry;
@@ -43,6 +44,7 @@
 import java.nio.charset.Charset;
 import java.nio.file.Files;
 import java.nio.file.StandardCopyOption;
+import java.util.HashSet;
 import java.util.Locale;
 
 @SmallTest
@@ -110,13 +112,14 @@
 
     private static void buildSystemFallback(String xml,
             ArrayMap<String, Typeface> fontMap, ArrayMap<String, FontFamily[]> fallbackMap) {
+        final HashSet<Font> availableFonts = new HashSet<>();
         try (FileOutputStream fos = new FileOutputStream(TEST_FONTS_XML)) {
             fos.write(xml.getBytes(Charset.forName("UTF-8")));
         } catch (IOException e) {
             throw new RuntimeException(e);
         }
         final FontConfig.Alias[] aliases = SystemFonts.buildSystemFallback(TEST_FONTS_XML,
-                TEST_FONT_DIR, fallbackMap);
+                TEST_FONT_DIR, fallbackMap, availableFonts);
         Typeface.initSystemDefaultTypefaces(fontMap, fallbackMap, aliases);
     }
 
@@ -124,9 +127,10 @@
     public void testBuildSystemFallback() {
         final ArrayMap<String, Typeface> fontMap = new ArrayMap<>();
         final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>();
+        final HashSet<Font> availableFonts = new HashSet<>();
 
         final FontConfig.Alias[] aliases = SystemFonts.buildSystemFallback(SYSTEM_FONTS_XML,
-                SYSTEM_FONT_DIR, fallbackMap);
+                SYSTEM_FONT_DIR, fallbackMap, availableFonts);
 
         assertNotNull(aliases);
         assertFalse(fallbackMap.isEmpty());
@@ -487,7 +491,7 @@
                 + "  <family lang='de'>"
                 + "    <font weight='400' style='normal'>a3em.ttf</font>"
                 + "  </family>"
-                + "  <family lang='it fr'>"
+                + "  <family lang='it,fr'>"
                 + "    <font weight='400' style='normal'>b3em.ttf</font>"
                 + "  </family>"
                 + "</familyset>";
diff --git a/core/tests/coretests/src/android/text/FontFallbackSetup.java b/core/tests/coretests/src/android/text/FontFallbackSetup.java
index 6c34043..355be61 100644
--- a/core/tests/coretests/src/android/text/FontFallbackSetup.java
+++ b/core/tests/coretests/src/android/text/FontFallbackSetup.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.res.AssetManager;
 import android.graphics.Typeface;
+import android.graphics.fonts.Font;
 import android.graphics.fonts.FontFamily;
 import android.graphics.fonts.SystemFonts;
 import android.support.test.InstrumentationRegistry;
@@ -32,6 +33,7 @@
 import java.nio.charset.Charset;
 import java.nio.file.Files;
 import java.nio.file.StandardCopyOption;
+import java.util.HashSet;
 
 public class FontFallbackSetup implements AutoCloseable {
     private final String[] mTestFontFiles;
@@ -74,8 +76,9 @@
         }
 
         final ArrayMap<String, FontFamily[]> fallbackMap = new ArrayMap<>();
+        final HashSet<Font> availableFonts = new HashSet<>();
         final FontConfig.Alias[] aliases = SystemFonts.buildSystemFallback(testFontsXml,
-                mTestFontsDir, fallbackMap);
+                mTestFontsDir, fallbackMap, availableFonts);
         Typeface.initSystemDefaultTypefaces(mFontMap, fallbackMap, aliases);
     }
 
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index be9347a..c84c035 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -137,7 +137,7 @@
         <font weight="400" style="normal" fallbackFor="serif">NotoSerifArmenian-Regular.otf</font>
         <font weight="700" style="normal" fallbackFor="serif">NotoSerifArmenian-Bold.otf</font>
     </family>
-    <family lang="und-Geor und-Geok">
+    <family lang="und-Geor,und-Geok">
         <font weight="400" style="normal">NotoSansGeorgian-Regular.otf</font>
         <font weight="500" style="normal">NotoSansGeorgian-Medium.otf</font>
         <font weight="700" style="normal">NotoSansGeorgian-Bold.otf</font>
@@ -538,7 +538,7 @@
         <font weight="400" style="normal" index="2">NotoSansCJK-Regular.ttc</font>
         <font weight="400" style="normal" index="2" fallbackFor="serif">NotoSerifCJK-Regular.ttc</font>
     </family>
-    <family lang="zh-Hant zh-Bopo">
+    <family lang="zh-Hant,zh-Bopo">
         <font weight="400" style="normal" index="3">NotoSansCJK-Regular.ttc</font>
         <font weight="400" style="normal" index="3" fallbackFor="serif">NotoSerifCJK-Regular.ttc</font>
     </family>
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index e3e8380..82435d5 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -74,8 +74,7 @@
     private static FontConfig.Family readFamily(XmlPullParser parser)
             throws XmlPullParserException, IOException {
         final String name = parser.getAttributeValue(null, "name");
-        final String lang = parser.getAttributeValue(null, "lang");
-        final String[] langs = lang == null ? null : lang.split("\\s+");
+        final String lang = parser.getAttributeValue("", "lang");
         final String variant = parser.getAttributeValue(null, "variant");
         final List<FontConfig.Font> fonts = new ArrayList<FontConfig.Font>();
         while (parser.next() != XmlPullParser.END_TAG) {
@@ -95,7 +94,7 @@
                 intVariant = FontConfig.Family.VARIANT_ELEGANT;
             }
         }
-        return new FontConfig.Family(name, fonts.toArray(new FontConfig.Font[fonts.size()]), langs,
+        return new FontConfig.Family(name, fonts.toArray(new FontConfig.Font[fonts.size()]), lang,
                 intVariant);
     }
 
@@ -126,8 +125,8 @@
             }
         }
         String sanitizedName = FILENAME_WHITESPACE_PATTERN.matcher(filename).replaceAll("");
-        return new FontConfig.Font(sanitizedName, index,
-                axes.toArray(new FontVariationAxis[axes.size()]), weight, isItalic, fallbackFor);
+        return new FontConfig.Font(sanitizedName, index, axes.toArray(
+                new FontVariationAxis[axes.size()]), weight, isItalic, fallbackFor);
     }
 
     private static FontVariationAxis readAxis(XmlPullParser parser)
diff --git a/graphics/java/android/graphics/fonts/Font.java b/graphics/java/android/graphics/fonts/Font.java
index f0c5199..a99d297 100644
--- a/graphics/java/android/graphics/fonts/Font.java
+++ b/graphics/java/android/graphics/fonts/Font.java
@@ -21,6 +21,7 @@
 import android.annotation.Nullable;
 import android.content.res.AssetManager;
 import android.content.res.Resources;
+import android.os.LocaleList;
 import android.util.TypedValue;
 
 import com.android.internal.util.Preconditions;
@@ -107,6 +108,8 @@
                     nGetReleaseNativeFont(), 64);
 
         private @Nullable ByteBuffer mBuffer;
+        private @Nullable File mFile;
+        private @NonNull LocaleList mLocaleList = LocaleList.getEmptyLocaleList();
         private @IntRange(from = -1, to = 1000) int mWeight = NOT_SPECIFIED;
         private @IntRange(from = -1, to = 1) int mItalic = NOT_SPECIFIED;
         private @IntRange(from = 0) int mTtcIndex = 0;
@@ -131,6 +134,19 @@
         }
 
         /**
+         * Construct a builder with a byte buffer and file path.
+         *
+         * This method is intended to be called only from SystemFonts.
+         * @hide
+         */
+        public Builder(@NonNull ByteBuffer buffer, @NonNull File path,
+                @NonNull LocaleList localeList) {
+            this(buffer);
+            mFile = path;
+            mLocaleList = localeList;
+        }
+
+        /**
          * Constructs a builder with a file path.
          *
          * @param path a file path to the font file
@@ -143,6 +159,7 @@
             } catch (IOException e) {
                 mException = e;
             }
+            mFile = path;
         }
 
         /**
@@ -394,7 +411,8 @@
                 }
             }
             final long ptr = nBuild(builderPtr, mBuffer, mWeight, italic, mTtcIndex);
-            final Font font = new Font(ptr, mBuffer, mWeight, italic, mTtcIndex, mAxes);
+            final Font font = new Font(ptr, mBuffer, mFile, mWeight, italic, mTtcIndex, mAxes,
+                    mLocaleList);
             sFontRegistory.registerNativeAllocation(font, ptr);
             return font;
         }
@@ -422,23 +440,48 @@
 
     private final long mNativePtr;  // address of the shared ptr of minikin::Font
     private final @NonNull ByteBuffer mBuffer;
+    private final @Nullable File mFile;
     private final @IntRange(from = 0, to = 1000) int mWeight;
     private final boolean mItalic;
     private final @IntRange(from = 0) int mTtcIndex;
     private final @Nullable FontVariationAxis[] mAxes;
+    private final @NonNull LocaleList mLocaleList;
 
     /**
      * Use Builder instead
      */
-    private Font(long nativePtr, @NonNull ByteBuffer buffer,
+    private Font(long nativePtr, @NonNull ByteBuffer buffer, @Nullable File file,
             @IntRange(from = 0, to = 1000) int weight, boolean italic,
-            @IntRange(from = 0) int ttcIndex, @Nullable FontVariationAxis[] axes) {
+            @IntRange(from = 0) int ttcIndex, @Nullable FontVariationAxis[] axes,
+            @NonNull LocaleList localeList) {
         mBuffer = buffer;
+        mFile = file;
         mWeight = weight;
         mItalic = italic;
         mNativePtr = nativePtr;
         mTtcIndex = ttcIndex;
         mAxes = axes;
+        mLocaleList = localeList;
+    }
+
+    /**
+     * Retuns a font file buffer.
+     *
+     * @return a font buffer
+     */
+    public @NonNull ByteBuffer getBuffer() {
+        return mBuffer;
+    }
+
+    /**
+     * Returns a file path of this font.
+     *
+     * This returns null if this font is not created from regular file.
+     *
+     * @return a file path of the font
+     */
+    public @Nullable File getFile() {
+        return mFile;
     }
 
     /**
@@ -484,6 +527,16 @@
         return mAxes == null ? null : mAxes.clone();
     }
 
+    /**
+     * Get a locale list of this font.
+     *
+     * This is always empty if this font is not a system font.
+     * @return a locale list
+     */
+    public @NonNull LocaleList getLocaleList() {
+        return mLocaleList;
+    }
+
     /** @hide */
     public long getNativePtr() {
         return mNativePtr;
@@ -504,11 +557,19 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mWeight, mItalic, mTtcIndex, mAxes, mBuffer);
+        return Objects.hash(mWeight, mItalic, mTtcIndex, Arrays.hashCode(mAxes), mBuffer);
     }
 
     @Override
     public String toString() {
-        return "Font {weight=" + mWeight + ", italic=" + mItalic + "}";
+        return "Font {"
+            + "path=" + mFile
+            + ", weight=" + mWeight
+            + ", italic=" + mItalic
+            + ", ttcIndex=" + mTtcIndex
+            + ", axes=" + FontVariationAxis.toFontVariationSettings(mAxes)
+            + ", localeList=" + mLocaleList.toLanguageTags()
+            + ", buffer=" + mBuffer
+            + "}";
     }
 }
diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java
index c914ece..3bcdc31 100644
--- a/graphics/java/android/graphics/fonts/FontFamily.java
+++ b/graphics/java/android/graphics/fonts/FontFamily.java
@@ -18,9 +18,7 @@
 
 import android.annotation.IntRange;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.text.FontConfig;
-import android.text.TextUtils;
 
 import com.android.internal.util.Preconditions;
 
@@ -110,25 +108,16 @@
          * @return a font family
          */
         public @NonNull FontFamily build() {
-            return build(null, FontConfig.Family.VARIANT_DEFAULT);
+            return build("", FontConfig.Family.VARIANT_DEFAULT);
         }
 
         /** @hide */
-        public @NonNull FontFamily build(@Nullable String[] langTags, int variant) {
+        public @NonNull FontFamily build(@NonNull String langTags, int variant) {
             final long builderPtr = nInitBuilder();
             for (int i = 0; i < mFonts.size(); ++i) {
                 nAddFont(builderPtr, mFonts.get(i).getNativePtr());
             }
-            final String langString;
-            if (langTags == null || langTags.length == 0) {
-                langString = null;
-            } else if (langTags.length == 1) {
-                langString = langTags[0];
-            } else {
-                langString = TextUtils.join(",", langTags);
-            }
-
-            final long ptr = nBuild(builderPtr, langString, variant);
+            final long ptr = nBuild(builderPtr, langTags, variant);
             final FontFamily family = new FontFamily(mFonts, ptr);
             sFamilyRegistory.registerNativeAllocation(family, ptr);
             return family;
diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java
index 26b4ec5..1c957b8 100644
--- a/graphics/java/android/graphics/fonts/SystemFonts.java
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.graphics.FontListParser;
+import android.os.LocaleList;
 import android.text.FontConfig;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -28,6 +29,7 @@
 
 import org.xmlpull.v1.XmlPullParserException;
 
+import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.nio.ByteBuffer;
@@ -36,12 +38,13 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Provides the system font configurations.
- * @hide
  */
 public class SystemFonts {
     private static final String TAG = "SystemFonts";
@@ -49,8 +52,19 @@
 
     private SystemFonts() {}  // Do not instansiate.
 
-    static final Map<String, FontFamily[]> sSystemFallbackMap;
-    static final FontConfig.Alias[] sAliases;
+    private static final Map<String, FontFamily[]> sSystemFallbackMap;
+    private static final FontConfig.Alias[] sAliases;
+    private static final Set<Font> sAvailableFonts;
+
+    /**
+     * Returns all available font files in the system.
+     *
+     * Note: The order of this font doesn't indicates anything.
+     * @return an array of system fonts
+     */
+    public static @NonNull Set<Font> getAvailableFonts() {
+        return sAvailableFonts;
+    }
 
     /**
      * Returns fallback list for the given family name.
@@ -58,6 +72,7 @@
      * If no fallback found for the given family name, returns fallback for the default family.
      *
      * @param familyName family name, e.g. "serif"
+     * @hide
      */
     public static @NonNull FontFamily[] getSystemFallback(@Nullable String familyName) {
         final FontFamily[] families = sSystemFallbackMap.get(familyName);
@@ -68,6 +83,7 @@
      * Returns raw system fallback map.
      *
      * This method is intended to be used only by Typeface static initializer.
+     * @hide
      */
     public static @NonNull Map<String, FontFamily[]> getRawSystemFallbackMap() {
         return sSystemFallbackMap;
@@ -77,6 +93,7 @@
      * Returns a list of aliases.
      *
      * This method is intended to be used only by Typeface static initializer.
+     * @hide
      */
     public static @NonNull FontConfig.Alias[] getAliases() {
         return sAliases;
@@ -96,9 +113,10 @@
     private static void pushFamilyToFallback(@NonNull FontConfig.Family xmlFamily,
             @NonNull ArrayMap<String, ArrayList<FontFamily>> fallbackMap,
             @NonNull Map<String, ByteBuffer> cache,
-            @NonNull String fontDir) {
+            @NonNull String fontDir,
+            @NonNull HashSet<Font> availableFonts) {
 
-        final String[] languageTags = xmlFamily.getLanguages();
+        final String languageTags = xmlFamily.getLanguages();
         final int variant = xmlFamily.getVariant();
 
         final ArrayList<FontConfig.Font> defaultFonts = new ArrayList<>();
@@ -120,7 +138,8 @@
         }
 
         final FontFamily defaultFamily = defaultFonts.isEmpty() ? null : createFontFamily(
-                xmlFamily.getName(), defaultFonts, languageTags, variant, cache, fontDir);
+                xmlFamily.getName(), defaultFonts, languageTags, variant, cache, fontDir,
+                availableFonts);
 
         // Insert family into fallback map.
         for (int i = 0; i < fallbackMap.size(); i++) {
@@ -132,7 +151,8 @@
                 }
             } else {
                 final FontFamily family = createFontFamily(
-                        xmlFamily.getName(), fallback, languageTags, variant, cache, fontDir);
+                        xmlFamily.getName(), fallback, languageTags, variant, cache, fontDir,
+                        availableFonts);
                 if (family != null) {
                     fallbackMap.valueAt(i).add(family);
                 } else if (defaultFamily != null) {
@@ -146,15 +166,17 @@
 
     private static @Nullable FontFamily createFontFamily(@NonNull String familyName,
             @NonNull List<FontConfig.Font> fonts,
-            @NonNull String[] languageTags,
+            @NonNull String languageTags,
             @FontConfig.Family.Variant int variant,
             @NonNull Map<String, ByteBuffer> cache,
-            @NonNull String fontDir) {
+            @NonNull String fontDir,
+            @NonNull HashSet<Font> availableFonts) {
         if (fonts.size() == 0) {
             return null;
         }
 
         FontFamily.Builder b = null;
+        final LocaleList localeList = LocaleList.forLanguageTags(languageTags);
         for (int i = 0; i < fonts.size(); i++) {
             final FontConfig.Font fontConfig = fonts.get(i);
             final String fullPath = fontDir + fontConfig.getFontName();
@@ -172,7 +194,7 @@
 
             final Font font;
             try {
-                font = new Font.Builder(buffer)
+                font = new Font.Builder(buffer, new File(fullPath), localeList)
                         .setWeight(fontConfig.getWeight())
                         .setItalic(fontConfig.isItalic())
                         .setTtcIndex(fontConfig.getTtcIndex())
@@ -182,6 +204,7 @@
                 throw new RuntimeException(e);  // Never reaches here
             }
 
+            availableFonts.add(font);
             if (b == null) {
                 b = new FontFamily.Builder(font);
             } else {
@@ -204,7 +227,8 @@
     @VisibleForTesting
     public static FontConfig.Alias[] buildSystemFallback(@NonNull String xmlPath,
             @NonNull String fontDir,
-            @NonNull ArrayMap<String, FontFamily[]> fallbackMap) {
+            @NonNull ArrayMap<String, FontFamily[]> fallbackMap,
+            @NonNull HashSet<Font> availableFonts) {
         try {
             final FileInputStream fontsIn = new FileInputStream(xmlPath);
             final FontConfig fontConfig = FontListParser.parse(fontsIn);
@@ -221,7 +245,8 @@
                 }
                 final FontFamily family = createFontFamily(
                         xmlFamily.getName(), Arrays.asList(xmlFamily.getFonts()),
-                        xmlFamily.getLanguages(), xmlFamily.getVariant(), bufferCache, fontDir);
+                        xmlFamily.getLanguages(), xmlFamily.getVariant(), bufferCache, fontDir,
+                        availableFonts);
                 if (family == null) {
                     continue;
                 }
@@ -236,7 +261,8 @@
                 // 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, bufferCache, fontDir);
+                    pushFamilyToFallback(xmlFamily, fallbackListMap, bufferCache, fontDir,
+                            availableFonts);
                 }
             }
 
@@ -258,9 +284,11 @@
 
     static {
         final ArrayMap<String, FontFamily[]> systemFallbackMap = new ArrayMap<>();
+        final HashSet<Font> availableFonts = new HashSet<>();
         sAliases = buildSystemFallback("/system/etc/fonts.xml", "/system/fonts/",
-                systemFallbackMap);
+                systemFallbackMap, availableFonts);
         sSystemFallbackMap = Collections.unmodifiableMap(systemFallbackMap);
+        sAvailableFonts = Collections.unmodifiableSet(availableFonts);
     }
 
 }