Reload fallback fonts when system language changes

When the system locale is changed, skia reloads its fallback fonts
using the best-matching configuration files for both framework and
vendor.

Bug: 5873170
Change-Id: Ie1d13cb404905ae2af05d8f20fbd857c96f39e4b
diff --git a/src/ports/FontHostConfiguration_android.cpp b/src/ports/FontHostConfiguration_android.cpp
index 475dc4a..d1164c8 100644
--- a/src/ports/FontHostConfiguration_android.cpp
+++ b/src/ports/FontHostConfiguration_android.cpp
@@ -16,8 +16,12 @@
 */
 
 #include "FontHostConfiguration_android.h"
-#include <expat.h>
+#include "SkString.h"
 #include "SkTDArray.h"
+#include <expat.h>
+#if !defined(SK_BUILD_FOR_ANDROID_NDK)
+    #include <cutils/properties.h>
+#endif
 
 #define SYSTEM_FONTS_FILE "/system/etc/system_fonts.xml"
 #define FALLBACK_FONTS_FILE "/system/etc/fallback_fonts.xml"
@@ -127,6 +131,65 @@
     }
 }
 
+#if !defined(SK_BUILD_FOR_ANDROID_NDK)
+/**
+ * Read the persistent locale.
+ */
+void getLocale(char* language, char* region)
+{
+    char propLang[PROPERTY_VALUE_MAX], propRegn[PROPERTY_VALUE_MAX];
+
+    property_get("persist.sys.language", propLang, "");
+    property_get("persist.sys.country", propRegn, "");
+    if (*propLang == 0 && *propRegn == 0) {
+        /* Set to ro properties, default is en_US */
+        property_get("ro.product.locale.language", propLang, "en");
+        property_get("ro.product.locale.region", propRegn, "US");
+    }
+    strncat(language, propLang, 2);
+    strncat(region, propRegn, 2);
+}
+#endif
+
+/**
+ * Use the current system locale (language and region) to open the best matching
+ * customization. For example, when the language is Japanese, the sequence might be:
+ *      /system/etc/fallback_fonts-ja-JP.xml
+ *      /system/etc/fallback_fonts-ja.xml
+ *      /system/etc/fallback_fonts.xml
+ */
+FILE* openLocalizedFile(const char* origname) {
+    FILE* file = 0;
+
+#if !defined(SK_BUILD_FOR_ANDROID_NDK)
+    SkString basename;
+    SkString filename;
+    char language[3] = "";
+    char region[3] = "";
+
+    basename.set(origname);
+    // Remove the .xml suffix. We'll add it back in a moment.
+    if (basename.endsWith(".xml")) {
+        basename.resize(basename.size()-4);
+    }
+    getLocale(language, region);
+    // Try first with language and region
+    filename.printf("%s-%s-%s.xml", basename.c_str(), language, region);
+    file = fopen(filename.c_str(), "r");
+    if (!file) {
+        // If not found, try next with just language
+        filename.printf("%s-%s.xml", basename.c_str(), language);
+        file = fopen(filename.c_str(), "r");
+    }
+#endif
+
+    if (!file) {
+        // If still not found, try just the original name
+        file = fopen(origname, "r");
+    }
+    return file;
+}
+
 /**
  * This function parses the given filename and stores the results in the given
  * families array.
@@ -136,7 +199,7 @@
     FamilyData *familyData = new FamilyData(&parser, families);
     XML_SetUserData(parser, familyData);
     XML_SetElementHandler(parser, startElementHandler, endElementHandler);
-    FILE *file = fopen(filename, "r");
+    FILE *file = openLocalizedFile(filename);
     // Some of the files we attempt to parse (in particular, /vendor/etc/fallback_fonts.xml)
     // are optional - failure here is okay because one of these optional files may not exist.
     if (file == NULL) {
@@ -154,15 +217,12 @@
     }
 }
 
-/**
- * Loads data on font families from various expected configuration files. The
- * resulting data is returned in the given fontFamilies array.
- */
-void getFontFamilies(SkTDArray<FontFamily*> &fontFamilies) {
-
-    SkTDArray<FontFamily*> fallbackFonts;
-    SkTDArray<FontFamily*> vendorFonts;
+void getSystemFontFamilies(SkTDArray<FontFamily*> &fontFamilies) {
     parseConfigFile(SYSTEM_FONTS_FILE, fontFamilies);
+}
+
+void getFallbackFontFamilies(SkTDArray<FontFamily*> &fallbackFonts) {
+    SkTDArray<FontFamily*> vendorFonts;
     parseConfigFile(FALLBACK_FONTS_FILE, fallbackFonts);
     parseConfigFile(VENDOR_FONTS_FILE, vendorFonts);
 
@@ -188,6 +248,18 @@
             currentOrder = order + 1;
         }
     }
+}
+
+/**
+ * Loads data on font families from various expected configuration files. The
+ * resulting data is returned in the given fontFamilies array.
+ */
+void getFontFamilies(SkTDArray<FontFamily*> &fontFamilies) {
+    SkTDArray<FontFamily*> fallbackFonts;
+
+    getSystemFontFamilies(fontFamilies);
+    getFallbackFontFamilies(fallbackFonts);
+
     // Append all fallback fonts to system fonts
     for (int i = 0; i < fallbackFonts.count(); ++i) {
         *fontFamilies.append() = fallbackFonts[i];
diff --git a/src/ports/FontHostConfiguration_android.h b/src/ports/FontHostConfiguration_android.h
index 010f0ef..2441f0e 100644
--- a/src/ports/FontHostConfiguration_android.h
+++ b/src/ports/FontHostConfiguration_android.h
@@ -39,4 +39,21 @@
  */
 void getFontFamilies(SkTDArray<FontFamily*> &fontFamilies);
 
+/**
+ * Parse only the core system font configuration file and return the results in
+ * an array of FontFamily structures.
+ */
+void getSystemFontFamilies(SkTDArray<FontFamily*> &fontFamilies);
+
+
+/**
+ * Parse the fallback and vendor system font configuration files and return the
+ * results in an array of FontFamily structures.
+ */
+void getFallbackFontFamilies(SkTDArray<FontFamily*> &fallbackFonts);
+
+#if !defined(SK_BUILD_FOR_ANDROID_NDK)
+    void getLocale(char* language, char* region);
+#endif
+
 #endif /* FONTHOSTCONFIGURATION_ANDROID_H_ */
diff --git a/src/ports/SkFontHost_android.cpp b/src/ports/SkFontHost_android.cpp
index 1856cff..f2893a8 100644
--- a/src/ports/SkFontHost_android.cpp
+++ b/src/ports/SkFontHost_android.cpp
@@ -16,6 +16,7 @@
 */
 
 #include "SkFontHost.h"
+#include "SkGraphics.h"
 #include "SkDescriptor.h"
 #include "SkMMapStream.h"
 #include "SkPaint.h"
@@ -25,6 +26,7 @@
 #include "SkTSearch.h"
 #include "FontHostConfiguration_android.h"
 #include <stdio.h>
+#include <string.h>
 
 #define FONT_CACHE_MEMORY_BUDGET    (768 * 1024)
 
@@ -72,6 +74,7 @@
 // this is the mutex that protects gFamilyHead and GetNameList()
 SK_DECLARE_STATIC_MUTEX(gFamilyHeadAndNameListMutex);
 static FamilyRec* gFamilyHead;
+static SkTDArray<NameFamilyPair> gFallbackFilenameList;
 
 static NameFamilyPairList& GetNameList() {
     /*
@@ -264,7 +267,6 @@
     : SkTypeface(style, sk_atomic_inc(&gUniqueFontID) + 1, isFixedWidth) {
         fIsSysFont = sysFont;
 
-
         // our caller has acquired the gFamilyHeadAndNameListMutex so this is safe
         FamilyRec* rec = NULL;
         if (familyMember) {
@@ -423,18 +425,72 @@
 static FontInitRec *gSystemFonts;
 static size_t gNumSystemFonts = 0;
 
-#define SYSTEM_FONTS_FILE "/system/etc/system_fonts.cfg"
-
 // these globals are assigned (once) by load_system_fonts()
 static FamilyRec* gDefaultFamily;
 static SkTypeface* gDefaultNormal;
 static char** gDefaultNames = NULL;
 static uint32_t *gFallbackFonts;
 
+static void dump_globals() {
+    SkDebugf("gDefaultNormal=%p id=%u refCnt=%d", gDefaultNormal,
+             gDefaultNormal ? gDefaultNormal->uniqueID() : 0,
+             gDefaultNormal ? gDefaultNormal->getRefCnt() : 0);
+
+    if (gDefaultFamily) {
+        SkDebugf("gDefaultFamily=%p fFaces={%u,%u,%u,%u} refCnt={%d,%d,%d,%d}",
+                 gDefaultFamily,
+                 gDefaultFamily->fFaces[0] ? gDefaultFamily->fFaces[0]->uniqueID() : 0,
+                 gDefaultFamily->fFaces[1] ? gDefaultFamily->fFaces[1]->uniqueID() : 0,
+                 gDefaultFamily->fFaces[2] ? gDefaultFamily->fFaces[2]->uniqueID() : 0,
+                 gDefaultFamily->fFaces[3] ? gDefaultFamily->fFaces[3]->uniqueID() : 0,
+                 gDefaultFamily->fFaces[0] ? gDefaultFamily->fFaces[0]->getRefCnt() : 0,
+                 gDefaultFamily->fFaces[1] ? gDefaultFamily->fFaces[1]->getRefCnt() : 0,
+                 gDefaultFamily->fFaces[2] ? gDefaultFamily->fFaces[2]->getRefCnt() : 0,
+                 gDefaultFamily->fFaces[3] ? gDefaultFamily->fFaces[3]->getRefCnt() : 0);
+    } else {
+        SkDebugf("gDefaultFamily=%p", gDefaultFamily);
+    }
+
+    SkDebugf("gNumSystemFonts=%d gSystemFonts=%p gFallbackFonts=%p",
+             gNumSystemFonts, gSystemFonts, gFallbackFonts);
+
+    for (size_t i = 0; i < gNumSystemFonts; ++i) {
+        SkDebugf("gSystemFonts[%d] fileName=%s", i, gSystemFonts[i].fFileName);
+        size_t namesIndex = 0;
+        if (gSystemFonts[i].fNames)
+            for (const char* fontName = gSystemFonts[i].fNames[namesIndex];
+                    fontName != 0;
+                    fontName = gSystemFonts[i].fNames[++namesIndex]) {
+                SkDebugf("       name[%u]=%s", namesIndex, fontName);
+            }
+    }
+
+    if (gFamilyHead) {
+        FamilyRec* rec = gFamilyHead;
+        int i=0;
+        while (rec) {
+            SkDebugf("gFamilyHead[%d]=%p fFaces={%u,%u,%u,%u} refCnt={%d,%d,%d,%d}",
+                     i++, rec,
+                     rec->fFaces[0] ? rec->fFaces[0]->uniqueID() : 0,
+                     rec->fFaces[1] ? rec->fFaces[1]->uniqueID() : 0,
+                     rec->fFaces[2] ? rec->fFaces[2]->uniqueID() : 0,
+                     rec->fFaces[3] ? rec->fFaces[3]->uniqueID() : 0,
+                     rec->fFaces[0] ? rec->fFaces[0]->getRefCnt() : 0,
+                     rec->fFaces[1] ? rec->fFaces[1]->getRefCnt() : 0,
+                     rec->fFaces[2] ? rec->fFaces[2]->getRefCnt() : 0,
+                     rec->fFaces[3] ? rec->fFaces[3]->getRefCnt() : 0);
+            rec = rec->fNext;
+        }
+    } else {
+        SkDebugf("gFamilyHead=%p", gFamilyHead);
+    }
+
+}
+
+
 /*  Load info from a configuration file that populates the system/fallback font structures
 */
 static void load_font_info() {
-//    load_font_info_xml("/system/etc/system_fonts.xml");
     SkTDArray<FontFamily*> fontFamilies;
     getFontFamilies(fontFamilies);
 
@@ -495,9 +551,9 @@
  *
  *  gFamilyHeadAndNameListMutex must already be acquired.
  */
-static void load_system_fonts() {
-    // check if we've already be called
-    if (NULL != gDefaultNormal) {
+static void init_system_fonts() {
+    // check if we've already been called
+    if (gDefaultNormal) {
         return;
     }
 
@@ -565,6 +621,65 @@
     gFallbackFonts[fallbackCount] = 0;
 }
 
+static size_t find_uniqueID(const char* filename) {
+    // uniqueID is the index, offset by one, of the associated element in gSystemFonts[]
+    // return 0 if not found
+    const FontInitRec* rec = gSystemFonts;
+    for (size_t i = 0; i < gNumSystemFonts; i++) {
+        if (strcmp(rec[i].fFileName, filename) == 0) {
+            return i+1;
+        }
+    }
+    return 0;
+}
+
+static void reload_fallback_fonts() {
+    SkGraphics::PurgeFontCache();
+
+    SkTDArray<FontFamily*> fallbackFamilies;
+    getFallbackFontFamilies(fallbackFamilies);
+
+    for (int i = 0; i < fallbackFamilies.count(); ++i) {
+        FontFamily *family = fallbackFamilies[i];
+
+        for (int j = 0; j < family->fFileNames.count(); ++j) {
+            if (family->fFileNames[j]) {
+                size_t uniqueID = find_uniqueID(family->fFileNames[j]);
+                if (uniqueID != gFallbackFonts[i])
+                    gFallbackFonts[i] = uniqueID;
+                break;  // The fallback set contains only the first font of each family
+            }
+        }
+    }
+}
+
+static void load_system_fonts() {
+#if !defined(SK_BUILD_FOR_ANDROID_NDK)
+    static char prevLanguage[3];
+    static char prevRegion[3];
+    char language[3] = "";
+    char region[3] = "";
+
+    getLocale(language, region);
+
+    if (!gDefaultNormal) {
+        strncpy(prevLanguage, language, 2);
+        strncpy(prevRegion, region, 2);
+        init_system_fonts();
+    } else if (strncmp(language, prevLanguage, 2) || strncmp(region, prevRegion, 2)) {
+        strncpy(prevLanguage, language, 2);
+        strncpy(prevRegion, region, 2);
+        reload_fallback_fonts();
+    }
+#else
+    if (!gDefaultNormal) {
+        init_system_fonts();
+        reload_fallback_fonts();
+    }
+#endif
+}
+
+
 ///////////////////////////////////////////////////////////////////////////////
 
 void SkFontHost::Serialize(const SkTypeface* face, SkWStream* stream) {
@@ -683,7 +798,7 @@
         tf = find_best_face(gDefaultFamily, style);
     }
 
-    // we ref(), since the symantic is to return a new instance
+    // we ref(), since the semantic is to return a new instance
     tf->ref();
     return tf;
 }
@@ -732,6 +847,7 @@
 
     SkASSERT(origTypeface != 0);
     SkASSERT(currTypeface != 0);
+    SkASSERT(gFallbackFonts);
 
     // Our fallback list always stores the id of the plain in each fallback
     // family, so we transform currFontID to its plain equivalent.