• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.graphics.fonts;
18 
19 import static com.android.server.graphics.fonts.FontManagerService.SystemFontException;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.graphics.fonts.FontManager;
24 import android.graphics.fonts.FontUpdateRequest;
25 import android.graphics.fonts.SystemFonts;
26 import android.os.FileUtils;
27 import android.os.LocaleList;
28 import android.system.ErrnoException;
29 import android.system.Os;
30 import android.text.FontConfig;
31 import android.util.ArrayMap;
32 import android.util.AtomicFile;
33 import android.util.Base64;
34 import android.util.Slog;
35 
36 import org.xmlpull.v1.XmlPullParserException;
37 
38 import java.io.File;
39 import java.io.FileDescriptor;
40 import java.io.FileInputStream;
41 import java.io.FileOutputStream;
42 import java.io.IOException;
43 import java.security.SecureRandom;
44 import java.util.ArrayList;
45 import java.util.HashMap;
46 import java.util.List;
47 import java.util.Map;
48 import java.util.Objects;
49 import java.util.function.Function;
50 import java.util.function.Supplier;
51 
52 /**
53  * Manages set of updatable font files.
54  *
55  * <p>This class is not thread safe.
56  */
57 final class UpdatableFontDir {
58 
59     private static final String TAG = "UpdatableFontDir";
60     private static final String RANDOM_DIR_PREFIX = "~~";
61 
62     /** Interface to mock font file access in tests. */
63     interface FontFileParser {
getPostScriptName(File file)64         String getPostScriptName(File file) throws IOException;
65 
buildFontFileName(File file)66         String buildFontFileName(File file) throws IOException;
67 
getRevision(File file)68         long getRevision(File file) throws IOException;
69 
tryToCreateTypeface(File file)70         void tryToCreateTypeface(File file) throws Throwable;
71     }
72 
73     /** Interface to mock fs-verity in tests. */
74     interface FsverityUtil {
hasFsverity(String path)75         boolean hasFsverity(String path);
76 
setUpFsverity(String path, byte[] pkcs7Signature)77         void setUpFsverity(String path, byte[] pkcs7Signature) throws IOException;
78 
rename(File src, File dest)79         boolean rename(File src, File dest);
80     }
81 
82     /** Data class to hold font file path and revision. */
83     private static final class FontFileInfo {
84         private final File mFile;
85         private final String mPsName;
86         private final long mRevision;
87 
FontFileInfo(File file, String psName, long revision)88         FontFileInfo(File file, String psName, long revision) {
89             mFile = file;
90             mPsName = psName;
91             mRevision = revision;
92         }
93 
getFile()94         public File getFile() {
95             return mFile;
96         }
97 
getPostScriptName()98         public String getPostScriptName() {
99             return mPsName;
100         }
101 
102         /** Returns the unique randomized font dir containing this font file. */
getRandomizedFontDir()103         public File getRandomizedFontDir() {
104             return mFile.getParentFile();
105         }
106 
getRevision()107         public long getRevision() {
108             return mRevision;
109         }
110 
111         @Override
toString()112         public String toString() {
113             return "FontFileInfo{mFile=" + mFile
114                     + ", psName=" + mPsName
115                     + ", mRevision=" + mRevision + '}';
116         }
117     }
118 
119     /**
120      * Root directory for storing updated font files. Each font file is stored in a unique
121      * randomized dir. The font file path would be {@code mFilesDir/~~{randomStr}/{fontFileName}}.
122      */
123     private final File mFilesDir;
124     private final FontFileParser mParser;
125     private final FsverityUtil mFsverityUtil;
126     private final AtomicFile mConfigFile;
127     private final Supplier<Long> mCurrentTimeSupplier;
128     private final Function<Map<String, File>, FontConfig> mConfigSupplier;
129 
130     private long mLastModifiedMillis;
131     private int mConfigVersion;
132 
133     /**
134      * A mutable map containing mapping from font file name (e.g. "NotoColorEmoji.ttf") to {@link
135      * FontFileInfo}. All files in this map are validated, and have higher revision numbers than
136      * corresponding font files returned by {@link #mConfigSupplier}.
137      */
138     private final ArrayMap<String, FontFileInfo> mFontFileInfoMap = new ArrayMap<>();
139 
UpdatableFontDir(File filesDir, FontFileParser parser, FsverityUtil fsverityUtil, File configFile)140     UpdatableFontDir(File filesDir, FontFileParser parser, FsverityUtil fsverityUtil,
141             File configFile) {
142         this(filesDir, parser, fsverityUtil, configFile,
143                 System::currentTimeMillis,
144                 (map) -> SystemFonts.getSystemFontConfig(map, 0, 0)
145         );
146     }
147 
148     // For unit testing
UpdatableFontDir(File filesDir, FontFileParser parser, FsverityUtil fsverityUtil, File configFile, Supplier<Long> currentTimeSupplier, Function<Map<String, File>, FontConfig> configSupplier)149     UpdatableFontDir(File filesDir, FontFileParser parser, FsverityUtil fsverityUtil,
150             File configFile, Supplier<Long> currentTimeSupplier,
151             Function<Map<String, File>, FontConfig> configSupplier) {
152         mFilesDir = filesDir;
153         mParser = parser;
154         mFsverityUtil = fsverityUtil;
155         mConfigFile = new AtomicFile(configFile);
156         mCurrentTimeSupplier = currentTimeSupplier;
157         mConfigSupplier = configSupplier;
158     }
159 
160     /**
161      * Loads fonts from file system, validate them, and delete obsolete font files.
162      * Note that this method may be called by multiple times in integration tests via {@link
163      * FontManagerService#restart()}.
164      */
loadFontFileMap()165     /* package */ void loadFontFileMap() {
166         mFontFileInfoMap.clear();
167         mLastModifiedMillis = 0;
168         mConfigVersion = 1;
169         boolean success = false;
170         try {
171             PersistentSystemFontConfig.Config config = readPersistentConfig();
172             mLastModifiedMillis = config.lastModifiedMillis;
173 
174             File[] dirs = mFilesDir.listFiles();
175             if (dirs == null) {
176                 // mFilesDir should be created by init script.
177                 Slog.e(TAG, "Could not read: " + mFilesDir);
178                 return;
179             }
180             FontConfig fontConfig = null;
181             for (File dir : dirs) {
182                 if (!dir.getName().startsWith(RANDOM_DIR_PREFIX)) {
183                     Slog.e(TAG, "Unexpected dir found: " + dir);
184                     return;
185                 }
186                 if (!config.updatedFontDirs.contains(dir.getName())) {
187                     Slog.i(TAG, "Deleting obsolete dir: " + dir);
188                     FileUtils.deleteContentsAndDir(dir);
189                     continue;
190                 }
191                 File[] files = dir.listFiles();
192                 if (files == null || files.length != 1) {
193                     Slog.e(TAG, "Unexpected files in dir: " + dir);
194                     return;
195                 }
196                 FontFileInfo fontFileInfo = validateFontFile(files[0]);
197                 if (fontConfig == null) {
198                     fontConfig = getSystemFontConfig();
199                 }
200                 addFileToMapIfSameOrNewer(fontFileInfo, fontConfig, true /* deleteOldFile */);
201             }
202             success = true;
203         } catch (Throwable t) {
204             // If something happened during loading system fonts, clear all contents in finally
205             // block. Here, just dumping errors.
206             Slog.e(TAG, "Failed to load font mappings.", t);
207         } finally {
208             // Delete all files just in case if we find a problematic file.
209             if (!success) {
210                 mFontFileInfoMap.clear();
211                 mLastModifiedMillis = 0;
212                 FileUtils.deleteContents(mFilesDir);
213             }
214         }
215     }
216 
217     /**
218      * Applies multiple {@link FontUpdateRequest}s in transaction.
219      * If one of the request fails, the fonts and config are rolled back to the previous state
220      * before this method is called.
221      */
update(List<FontUpdateRequest> requests)222     public void update(List<FontUpdateRequest> requests) throws SystemFontException {
223         for (FontUpdateRequest request : requests) {
224             switch (request.getType()) {
225                 case FontUpdateRequest.TYPE_UPDATE_FONT_FILE:
226                     Objects.requireNonNull(request.getFd());
227                     Objects.requireNonNull(request.getSignature());
228                     break;
229                 case FontUpdateRequest.TYPE_UPDATE_FONT_FAMILY:
230                     Objects.requireNonNull(request.getFontFamily());
231                     Objects.requireNonNull(request.getFontFamily().getName());
232                     break;
233             }
234         }
235         // Backup the mapping for rollback.
236         ArrayMap<String, FontFileInfo> backupMap = new ArrayMap<>(mFontFileInfoMap);
237         PersistentSystemFontConfig.Config curConfig = readPersistentConfig();
238         Map<String, FontUpdateRequest.Family> familyMap = new HashMap<>();
239         for (int i = 0; i < curConfig.fontFamilies.size(); ++i) {
240             FontUpdateRequest.Family family = curConfig.fontFamilies.get(i);
241             familyMap.put(family.getName(), family);
242         }
243 
244         long backupLastModifiedDate = mLastModifiedMillis;
245         boolean success = false;
246         try {
247             for (FontUpdateRequest request : requests) {
248                 switch (request.getType()) {
249                     case FontUpdateRequest.TYPE_UPDATE_FONT_FILE:
250                         installFontFile(
251                                 request.getFd().getFileDescriptor(), request.getSignature());
252                         break;
253                     case FontUpdateRequest.TYPE_UPDATE_FONT_FAMILY:
254                         FontUpdateRequest.Family family = request.getFontFamily();
255                         familyMap.put(family.getName(), family);
256                         break;
257                 }
258             }
259 
260             // Before processing font family update, check all family points the available fonts.
261             for (FontUpdateRequest.Family family : familyMap.values()) {
262                 if (resolveFontFiles(family) == null) {
263                     throw new SystemFontException(
264                             FontManager.RESULT_ERROR_FONT_NOT_FOUND,
265                             "Required fonts are not available");
266                 }
267             }
268 
269             // Write config file.
270             mLastModifiedMillis = mCurrentTimeSupplier.get();
271 
272             PersistentSystemFontConfig.Config newConfig = new PersistentSystemFontConfig.Config();
273             newConfig.lastModifiedMillis = mLastModifiedMillis;
274             for (FontFileInfo info : mFontFileInfoMap.values()) {
275                 newConfig.updatedFontDirs.add(info.getRandomizedFontDir().getName());
276             }
277             newConfig.fontFamilies.addAll(familyMap.values());
278             writePersistentConfig(newConfig);
279             mConfigVersion++;
280             success = true;
281         } finally {
282             if (!success) {
283                 mFontFileInfoMap.clear();
284                 mFontFileInfoMap.putAll(backupMap);
285                 mLastModifiedMillis = backupLastModifiedDate;
286             }
287         }
288     }
289 
290     /**
291      * Installs a new font file, or updates an existing font file.
292      *
293      * <p>The new font will be immediately available for new Zygote-forked processes through
294      * {@link #getPostScriptMap()}. Old font files will be kept until next system server reboot,
295      * because existing Zygote-forked processes have paths to old font files.
296      *
297      * @param fd             A file descriptor to the font file.
298      * @param pkcs7Signature A PKCS#7 detached signature to enable fs-verity for the font file.
299      * @throws SystemFontException if error occurs.
300      */
installFontFile(FileDescriptor fd, byte[] pkcs7Signature)301     private void installFontFile(FileDescriptor fd, byte[] pkcs7Signature)
302             throws SystemFontException {
303         File newDir = getRandomDir(mFilesDir);
304         if (!newDir.mkdir()) {
305             throw new SystemFontException(
306                     FontManager.RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE,
307                     "Failed to create font directory.");
308         }
309         try {
310             // Make newDir executable so that apps can access font file inside newDir.
311             Os.chmod(newDir.getAbsolutePath(), 0711);
312         } catch (ErrnoException e) {
313             throw new SystemFontException(
314                     FontManager.RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE,
315                     "Failed to change mode to 711", e);
316         }
317         boolean success = false;
318         try {
319             File tempNewFontFile = new File(newDir, "font.ttf");
320             try (FileOutputStream out = new FileOutputStream(tempNewFontFile)) {
321                 FileUtils.copy(fd, out.getFD());
322             } catch (IOException e) {
323                 throw new SystemFontException(
324                         FontManager.RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE,
325                         "Failed to write font file to storage.", e);
326             }
327             try {
328                 // Do not parse font file before setting up fs-verity.
329                 // setUpFsverity throws IOException if failed.
330                 mFsverityUtil.setUpFsverity(tempNewFontFile.getAbsolutePath(),
331                         pkcs7Signature);
332             } catch (IOException e) {
333                 throw new SystemFontException(
334                         FontManager.RESULT_ERROR_VERIFICATION_FAILURE,
335                         "Failed to setup fs-verity.", e);
336             }
337             String fontFileName;
338             try {
339                 fontFileName = mParser.buildFontFileName(tempNewFontFile);
340             } catch (IOException e) {
341                 throw new SystemFontException(
342                         FontManager.RESULT_ERROR_INVALID_FONT_FILE,
343                         "Failed to read PostScript name from font file", e);
344             }
345             if (fontFileName == null) {
346                 throw new SystemFontException(
347                         FontManager.RESULT_ERROR_INVALID_FONT_NAME,
348                         "Failed to read PostScript name from font file");
349             }
350             File newFontFile = new File(newDir, fontFileName);
351             if (!mFsverityUtil.rename(tempNewFontFile, newFontFile)) {
352                 throw new SystemFontException(
353                         FontManager.RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE,
354                         "Failed to move verified font file.");
355             }
356             try {
357                 // Make the font file readable by apps.
358                 Os.chmod(newFontFile.getAbsolutePath(), 0644);
359             } catch (ErrnoException e) {
360                 throw new SystemFontException(
361                         FontManager.RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE,
362                         "Failed to change mode to 711", e);
363             }
364             FontFileInfo fontFileInfo = validateFontFile(newFontFile);
365 
366             // Try to create Typeface and treat as failure something goes wrong.
367             try {
368                 mParser.tryToCreateTypeface(fontFileInfo.getFile());
369             } catch (Throwable t) {
370                 throw new SystemFontException(
371                         FontManager.RESULT_ERROR_INVALID_FONT_FILE,
372                         "Failed to create Typeface from file", t);
373             }
374 
375             FontConfig fontConfig = getSystemFontConfig();
376             if (!addFileToMapIfSameOrNewer(fontFileInfo, fontConfig, false)) {
377                 throw new SystemFontException(
378                         FontManager.RESULT_ERROR_DOWNGRADING,
379                         "Downgrading font file is forbidden.");
380             }
381             success = true;
382         } finally {
383             if (!success) {
384                 FileUtils.deleteContentsAndDir(newDir);
385             }
386         }
387     }
388 
389     /**
390      * Given {@code parent}, returns {@code parent/~~[randomStr]}.
391      * Makes sure that {@code parent/~~[randomStr]} directory doesn't exist.
392      * Notice that this method doesn't actually create any directory.
393      */
getRandomDir(File parent)394     private static File getRandomDir(File parent) {
395         SecureRandom random = new SecureRandom();
396         byte[] bytes = new byte[16];
397         File dir;
398         do {
399             random.nextBytes(bytes);
400             String dirName = RANDOM_DIR_PREFIX
401                     + Base64.encodeToString(bytes, Base64.URL_SAFE | Base64.NO_WRAP);
402             dir = new File(parent, dirName);
403         } while (dir.exists());
404         return dir;
405     }
406 
lookupFontFileInfo(String psName)407     private FontFileInfo lookupFontFileInfo(String psName) {
408         return mFontFileInfoMap.get(psName);
409     }
410 
putFontFileInfo(FontFileInfo info)411     private void putFontFileInfo(FontFileInfo info) {
412         mFontFileInfoMap.put(info.getPostScriptName(), info);
413     }
414 
415     /**
416      * Add the given {@link FontFileInfo} to {@link #mFontFileInfoMap} if its font revision is
417      * equal to or higher than the revision of currently used font file (either in
418      * {@link #mFontFileInfoMap} or {@code fontConfig}).
419      */
addFileToMapIfSameOrNewer(FontFileInfo fontFileInfo, FontConfig fontConfig, boolean deleteOldFile)420     private boolean addFileToMapIfSameOrNewer(FontFileInfo fontFileInfo, FontConfig fontConfig,
421             boolean deleteOldFile) {
422         FontFileInfo existingInfo = lookupFontFileInfo(fontFileInfo.getPostScriptName());
423         final boolean shouldAddToMap;
424         if (existingInfo == null) {
425             // We got a new updatable font. We need to check if it's newer than preinstalled fonts.
426             // Note that getPreinstalledFontRevision() returns -1 if there is no preinstalled font
427             // with 'name'.
428             long preInstalledRev = getPreinstalledFontRevision(fontFileInfo, fontConfig);
429             shouldAddToMap = preInstalledRev <= fontFileInfo.getRevision();
430         } else {
431             shouldAddToMap = existingInfo.getRevision() <= fontFileInfo.getRevision();
432         }
433         if (shouldAddToMap) {
434             if (deleteOldFile && existingInfo != null) {
435                 FileUtils.deleteContentsAndDir(existingInfo.getRandomizedFontDir());
436             }
437             putFontFileInfo(fontFileInfo);
438         } else {
439             if (deleteOldFile) {
440                 FileUtils.deleteContentsAndDir(fontFileInfo.getRandomizedFontDir());
441             }
442         }
443         return shouldAddToMap;
444     }
445 
getPreinstalledFontRevision(FontFileInfo info, FontConfig fontConfig)446     private long getPreinstalledFontRevision(FontFileInfo info, FontConfig fontConfig) {
447         String psName = info.getPostScriptName();
448         FontConfig.Font targetFont = null;
449         for (int i = 0; i < fontConfig.getFontFamilies().size(); i++) {
450             FontConfig.FontFamily family = fontConfig.getFontFamilies().get(i);
451             for (int j = 0; j < family.getFontList().size(); ++j) {
452                 FontConfig.Font font = family.getFontList().get(j);
453                 if (font.getPostScriptName().equals(psName)) {
454                     targetFont = font;
455                     break;
456                 }
457             }
458         }
459         if (targetFont == null) {
460             return -1;
461         }
462 
463         File preinstalledFontFile = targetFont.getOriginalFile() != null
464                 ? targetFont.getOriginalFile() : targetFont.getFile();
465         if (!preinstalledFontFile.exists()) {
466             return -1;
467         }
468         long revision = getFontRevision(preinstalledFontFile);
469         if (revision == -1) {
470             Slog.w(TAG, "Invalid preinstalled font file");
471         }
472         return revision;
473     }
474 
475     /**
476      * Checks the fs-verity protection status of the given font file, validates the file name, and
477      * returns a {@link FontFileInfo} on success. This method does not check if the font revision
478      * is higher than the currently used font.
479      */
480     @NonNull
validateFontFile(File file)481     private FontFileInfo validateFontFile(File file) throws SystemFontException {
482         if (!mFsverityUtil.hasFsverity(file.getAbsolutePath())) {
483             throw new SystemFontException(
484                     FontManager.RESULT_ERROR_VERIFICATION_FAILURE,
485                     "Font validation failed. Fs-verity is not enabled: " + file);
486         }
487         final String psName;
488         try {
489             psName = mParser.getPostScriptName(file);
490         } catch (IOException e) {
491             throw new SystemFontException(
492                     FontManager.RESULT_ERROR_INVALID_FONT_NAME,
493                     "Font validation failed. Could not read PostScript name name: " + file);
494         }
495         long revision = getFontRevision(file);
496         if (revision == -1) {
497             throw new SystemFontException(
498                     FontManager.RESULT_ERROR_INVALID_FONT_FILE,
499                     "Font validation failed. Could not read font revision: " + file);
500         }
501         return new FontFileInfo(file, psName, revision);
502     }
503     /** Returns the non-negative font revision of the given font file, or -1. */
getFontRevision(File file)504     private long getFontRevision(File file) {
505         try {
506             return mParser.getRevision(file);
507         } catch (IOException e) {
508             Slog.e(TAG, "Failed to read font file", e);
509             return -1;
510         }
511     }
512 
513     @Nullable
resolveFontFiles(FontUpdateRequest.Family fontFamily)514     private FontConfig.FontFamily resolveFontFiles(FontUpdateRequest.Family fontFamily) {
515         List<FontUpdateRequest.Font> fontList = fontFamily.getFonts();
516         List<FontConfig.Font> resolvedFonts = new ArrayList<>(fontList.size());
517         for (int i = 0; i < fontList.size(); i++) {
518             FontUpdateRequest.Font font = fontList.get(i);
519             FontFileInfo info = mFontFileInfoMap.get(font.getPostScriptName());
520             if (info == null) {
521                 Slog.e(TAG, "Failed to lookup font file that has " + font.getPostScriptName());
522                 return null;
523             }
524             resolvedFonts.add(new FontConfig.Font(info.mFile, null, info.getPostScriptName(),
525                     font.getFontStyle(), font.getIndex(), font.getFontVariationSettings(), null));
526         }
527         return new FontConfig.FontFamily(resolvedFonts, fontFamily.getName(),
528                 LocaleList.getEmptyLocaleList(), FontConfig.FontFamily.VARIANT_DEFAULT);
529     }
530 
getPostScriptMap()531     Map<String, File> getPostScriptMap() {
532         Map<String, File> map = new ArrayMap<>();
533         for (int i = 0; i < mFontFileInfoMap.size(); ++i) {
534             FontFileInfo info = mFontFileInfoMap.valueAt(i);
535             map.put(info.getPostScriptName(), info.getFile());
536         }
537         return map;
538     }
539 
getSystemFontConfig()540     /* package */ FontConfig getSystemFontConfig() {
541         FontConfig config = mConfigSupplier.apply(getPostScriptMap());
542         PersistentSystemFontConfig.Config persistentConfig = readPersistentConfig();
543         List<FontUpdateRequest.Family> families = persistentConfig.fontFamilies;
544 
545         List<FontConfig.FontFamily> mergedFamilies =
546                 new ArrayList<>(config.getFontFamilies().size() + families.size());
547         // We should keep the first font family (config.getFontFamilies().get(0)) because it's used
548         // as a fallback font. See SystemFonts.java.
549         mergedFamilies.addAll(config.getFontFamilies());
550         // When building Typeface, a latter font family definition will override the previous font
551         // family definition with the same name. An exception is config.getFontFamilies.get(0),
552         // which will be used as a fallback font without being overridden.
553         for (int i = 0; i < families.size(); ++i) {
554             FontConfig.FontFamily family = resolveFontFiles(families.get(i));
555             if (family != null) {
556                 mergedFamilies.add(family);
557             }
558         }
559 
560         return new FontConfig(
561                 mergedFamilies, config.getAliases(), mLastModifiedMillis, mConfigVersion);
562     }
563 
readPersistentConfig()564     private PersistentSystemFontConfig.Config readPersistentConfig() {
565         PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config();
566         try (FileInputStream fis = mConfigFile.openRead()) {
567             PersistentSystemFontConfig.loadFromXml(fis, config);
568         } catch (IOException | XmlPullParserException e) {
569             // The font config file is missing on the first boot. Just do nothing.
570         }
571         return config;
572     }
573 
writePersistentConfig(PersistentSystemFontConfig.Config config)574     private void writePersistentConfig(PersistentSystemFontConfig.Config config)
575             throws SystemFontException {
576         FileOutputStream fos = null;
577         try {
578             fos = mConfigFile.startWrite();
579             PersistentSystemFontConfig.writeToXml(fos, config);
580             mConfigFile.finishWrite(fos);
581         } catch (IOException e) {
582             if (fos != null) {
583                 mConfigFile.failWrite(fos);
584             }
585             throw new SystemFontException(
586                     FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG,
587                     "Failed to write config XML.", e);
588         }
589     }
590 
getConfigVersion()591     /* package */ int getConfigVersion() {
592         return mConfigVersion;
593     }
594 
getFontFamilyMap()595     public Map<String, FontConfig.FontFamily> getFontFamilyMap() {
596         PersistentSystemFontConfig.Config curConfig = readPersistentConfig();
597         Map<String, FontConfig.FontFamily> familyMap = new HashMap<>();
598         for (int i = 0; i < curConfig.fontFamilies.size(); ++i) {
599             FontUpdateRequest.Family family = curConfig.fontFamilies.get(i);
600             FontConfig.FontFamily resolvedFamily = resolveFontFiles(family);
601             if (resolvedFamily != null) {
602                 familyMap.put(family.getName(), resolvedFamily);
603             }
604         }
605         return familyMap;
606     }
607 
deleteAllFiles(File filesDir, File configFile)608     /* package */ static void deleteAllFiles(File filesDir, File configFile) {
609         // As this method is called in safe mode, try to delete all files even though an exception
610         // is thrown.
611         try {
612             new AtomicFile(configFile).delete();
613         } catch (Throwable t) {
614             Slog.w(TAG, "Failed to delete " + configFile);
615         }
616         try {
617             FileUtils.deleteContents(filesDir);
618         } catch (Throwable t) {
619             Slog.w(TAG, "Failed to delete " + filesDir);
620         }
621     }
622 }
623