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