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