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 return; 200 } 201 byte[] signature; 202 try { 203 signature = Files.readAllBytes(Paths.get(signatureFile.getAbsolutePath())); 204 } catch (IOException e) { 205 Slog.e(TAG, "Failed to read signature file."); 206 return; 207 } 208 209 File[] files = dir.listFiles(); 210 if (files == null || files.length != 2) { 211 Slog.e(TAG, "Unexpected files in dir: " + dir); 212 return; 213 } 214 215 File fontFile; 216 if (files[0].equals(signatureFile)) { 217 fontFile = files[1]; 218 } else { 219 fontFile = files[0]; 220 } 221 222 FontFileInfo fontFileInfo = validateFontFile(fontFile, signature); 223 if (fontConfig == null) { 224 // Use preinstalled font config for checking revision number. 225 fontConfig = mConfigSupplier.apply(Collections.emptyMap()); 226 } 227 addFileToMapIfSameOrNewer(fontFileInfo, fontConfig, true /* deleteOldFile */); 228 } 229 230 // Treat as error if post script name of font family was not installed. 231 for (int i = 0; i < config.fontFamilies.size(); ++i) { 232 FontUpdateRequest.Family family = config.fontFamilies.get(i); 233 for (int j = 0; j < family.getFonts().size(); ++j) { 234 FontUpdateRequest.Font font = family.getFonts().get(j); 235 if (mFontFileInfoMap.containsKey(font.getPostScriptName())) { 236 continue; 237 } 238 239 if (fontConfig == null) { 240 fontConfig = mConfigSupplier.apply(Collections.emptyMap()); 241 } 242 243 if (getFontByPostScriptName(font.getPostScriptName(), fontConfig) != null) { 244 continue; 245 } 246 247 Slog.e(TAG, "Unknown font that has PostScript name " 248 + font.getPostScriptName() + " is requested in FontFamily " 249 + family.getName()); 250 return; 251 } 252 } 253 254 success = true; 255 } catch (Throwable t) { 256 // If something happened during loading system fonts, clear all contents in finally 257 // block. Here, just dumping errors. 258 Slog.e(TAG, "Failed to load font mappings.", t); 259 } finally { 260 // Delete all files just in case if we find a problematic file. 261 if (!success) { 262 mFontFileInfoMap.clear(); 263 mLastModifiedMillis = 0; 264 FileUtils.deleteContents(mFilesDir); 265 mConfigFile.delete(); 266 } 267 } 268 } 269 270 /** 271 * Applies multiple {@link FontUpdateRequest}s in transaction. 272 * If one of the request fails, the fonts and config are rolled back to the previous state 273 * before this method is called. 274 */ update(List<FontUpdateRequest> requests)275 public void update(List<FontUpdateRequest> requests) throws SystemFontException { 276 for (FontUpdateRequest request : requests) { 277 switch (request.getType()) { 278 case FontUpdateRequest.TYPE_UPDATE_FONT_FILE: 279 Objects.requireNonNull(request.getFd()); 280 Objects.requireNonNull(request.getSignature()); 281 break; 282 case FontUpdateRequest.TYPE_UPDATE_FONT_FAMILY: 283 Objects.requireNonNull(request.getFontFamily()); 284 Objects.requireNonNull(request.getFontFamily().getName()); 285 break; 286 } 287 } 288 // Backup the mapping for rollback. 289 ArrayMap<String, FontFileInfo> backupMap = new ArrayMap<>(mFontFileInfoMap); 290 PersistentSystemFontConfig.Config curConfig = readPersistentConfig(); 291 Map<String, FontUpdateRequest.Family> familyMap = new HashMap<>(); 292 for (int i = 0; i < curConfig.fontFamilies.size(); ++i) { 293 FontUpdateRequest.Family family = curConfig.fontFamilies.get(i); 294 familyMap.put(family.getName(), family); 295 } 296 297 long backupLastModifiedDate = mLastModifiedMillis; 298 boolean success = false; 299 try { 300 for (FontUpdateRequest request : requests) { 301 switch (request.getType()) { 302 case FontUpdateRequest.TYPE_UPDATE_FONT_FILE: 303 installFontFile( 304 request.getFd().getFileDescriptor(), request.getSignature()); 305 break; 306 case FontUpdateRequest.TYPE_UPDATE_FONT_FAMILY: 307 FontUpdateRequest.Family family = request.getFontFamily(); 308 familyMap.put(family.getName(), family); 309 break; 310 } 311 } 312 313 // Before processing font family update, check all family points the available fonts. 314 for (FontUpdateRequest.Family family : familyMap.values()) { 315 if (resolveFontFilesForNamedFamily(family) == null) { 316 throw new SystemFontException( 317 FontManager.RESULT_ERROR_FONT_NOT_FOUND, 318 "Required fonts are not available"); 319 } 320 } 321 322 // Write config file. 323 mLastModifiedMillis = mCurrentTimeSupplier.get(); 324 325 PersistentSystemFontConfig.Config newConfig = new PersistentSystemFontConfig.Config(); 326 newConfig.lastModifiedMillis = mLastModifiedMillis; 327 for (FontFileInfo info : mFontFileInfoMap.values()) { 328 newConfig.updatedFontDirs.add(info.getRandomizedFontDir().getName()); 329 } 330 newConfig.fontFamilies.addAll(familyMap.values()); 331 writePersistentConfig(newConfig); 332 mConfigVersion++; 333 success = true; 334 } finally { 335 if (!success) { 336 mFontFileInfoMap.clear(); 337 mFontFileInfoMap.putAll(backupMap); 338 mLastModifiedMillis = backupLastModifiedDate; 339 } 340 } 341 } 342 343 /** 344 * Installs a new font file, or updates an existing font file. 345 * 346 * <p>The new font will be immediately available for new Zygote-forked processes through 347 * {@link #getPostScriptMap()}. Old font files will be kept until next system server reboot, 348 * because existing Zygote-forked processes have paths to old font files. 349 * 350 * @param fd A file descriptor to the font file. 351 * @param pkcs7Signature A PKCS#7 detached signature to enable fs-verity for the font file. 352 * @throws SystemFontException if error occurs. 353 */ installFontFile(FileDescriptor fd, byte[] pkcs7Signature)354 private void installFontFile(FileDescriptor fd, byte[] pkcs7Signature) 355 throws SystemFontException { 356 File newDir = getRandomDir(mFilesDir); 357 if (!newDir.mkdir()) { 358 throw new SystemFontException( 359 FontManager.RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE, 360 "Failed to create font directory."); 361 } 362 try { 363 // Make newDir executable so that apps can access font file inside newDir. 364 Os.chmod(newDir.getAbsolutePath(), 0711); 365 } catch (ErrnoException e) { 366 throw new SystemFontException( 367 FontManager.RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE, 368 "Failed to change mode to 711", e); 369 } 370 boolean success = false; 371 try { 372 File tempNewFontFile = new File(newDir, "font.ttf"); 373 try (FileOutputStream out = new FileOutputStream(tempNewFontFile)) { 374 FileUtils.copy(fd, out.getFD()); 375 } catch (IOException e) { 376 throw new SystemFontException( 377 FontManager.RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE, 378 "Failed to write font file to storage.", e); 379 } 380 try { 381 // Do not parse font file before setting up fs-verity. 382 // setUpFsverity throws IOException if failed. 383 mFsverityUtil.setUpFsverity(tempNewFontFile.getAbsolutePath()); 384 } catch (IOException e) { 385 throw new SystemFontException( 386 FontManager.RESULT_ERROR_VERIFICATION_FAILURE, 387 "Failed to setup fs-verity.", e); 388 } 389 String fontFileName; 390 try { 391 fontFileName = mParser.buildFontFileName(tempNewFontFile); 392 } catch (IOException e) { 393 throw new SystemFontException( 394 FontManager.RESULT_ERROR_INVALID_FONT_FILE, 395 "Failed to read PostScript name from font file", e); 396 } 397 if (fontFileName == null) { 398 throw new SystemFontException( 399 FontManager.RESULT_ERROR_INVALID_FONT_NAME, 400 "Failed to read PostScript name from font file"); 401 } 402 File newFontFile = new File(newDir, fontFileName); 403 if (!mFsverityUtil.rename(tempNewFontFile, newFontFile)) { 404 throw new SystemFontException( 405 FontManager.RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE, 406 "Failed to move verified font file."); 407 } 408 try { 409 // Make the font file readable by apps. 410 Os.chmod(newFontFile.getAbsolutePath(), 0644); 411 } catch (ErrnoException e) { 412 throw new SystemFontException( 413 FontManager.RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE, 414 "Failed to change font file mode to 644", e); 415 } 416 File signatureFile = new File(newDir, FONT_SIGNATURE_FILE); 417 try (FileOutputStream out = new FileOutputStream(signatureFile)) { 418 out.write(pkcs7Signature); 419 } catch (IOException e) { 420 // TODO: Do we need new error code for signature write failure? 421 throw new SystemFontException( 422 FontManager.RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE, 423 "Failed to write font signature file to storage.", e); 424 } 425 try { 426 Os.chmod(signatureFile.getAbsolutePath(), 0600); 427 } catch (ErrnoException e) { 428 throw new SystemFontException( 429 FontManager.RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE, 430 "Failed to change the signature file mode to 600", e); 431 } 432 FontFileInfo fontFileInfo = validateFontFile(newFontFile, pkcs7Signature); 433 434 // Try to create Typeface and treat as failure something goes wrong. 435 try { 436 mParser.tryToCreateTypeface(fontFileInfo.getFile()); 437 } catch (Throwable t) { 438 throw new SystemFontException( 439 FontManager.RESULT_ERROR_INVALID_FONT_FILE, 440 "Failed to create Typeface from file", t); 441 } 442 443 FontConfig fontConfig = getSystemFontConfig(); 444 if (!addFileToMapIfSameOrNewer(fontFileInfo, fontConfig, false)) { 445 throw new SystemFontException( 446 FontManager.RESULT_ERROR_DOWNGRADING, 447 "Downgrading font file is forbidden."); 448 } 449 success = true; 450 } finally { 451 if (!success) { 452 FileUtils.deleteContentsAndDir(newDir); 453 } 454 } 455 } 456 457 /** 458 * Given {@code parent}, returns {@code parent/~~[randomStr]}. 459 * Makes sure that {@code parent/~~[randomStr]} directory doesn't exist. 460 * Notice that this method doesn't actually create any directory. 461 */ getRandomDir(File parent)462 private static File getRandomDir(File parent) { 463 SecureRandom random = new SecureRandom(); 464 byte[] bytes = new byte[16]; 465 File dir; 466 do { 467 random.nextBytes(bytes); 468 String dirName = RANDOM_DIR_PREFIX 469 + Base64.encodeToString(bytes, Base64.URL_SAFE | Base64.NO_WRAP); 470 dir = new File(parent, dirName); 471 } while (dir.exists()); 472 return dir; 473 } 474 lookupFontFileInfo(String psName)475 private FontFileInfo lookupFontFileInfo(String psName) { 476 return mFontFileInfoMap.get(psName); 477 } 478 putFontFileInfo(FontFileInfo info)479 private void putFontFileInfo(FontFileInfo info) { 480 mFontFileInfoMap.put(info.getPostScriptName(), info); 481 } 482 483 /** 484 * Add the given {@link FontFileInfo} to {@link #mFontFileInfoMap} if its font revision is 485 * equal to or higher than the revision of currently used font file (either in 486 * {@link #mFontFileInfoMap} or {@code fontConfig}). 487 */ addFileToMapIfSameOrNewer(FontFileInfo fontFileInfo, FontConfig fontConfig, boolean deleteOldFile)488 private boolean addFileToMapIfSameOrNewer(FontFileInfo fontFileInfo, FontConfig fontConfig, 489 boolean deleteOldFile) { 490 FontFileInfo existingInfo = lookupFontFileInfo(fontFileInfo.getPostScriptName()); 491 final boolean shouldAddToMap; 492 if (existingInfo == null) { 493 // We got a new updatable font. We need to check if it's newer than preinstalled fonts. 494 // Note that getPreinstalledFontRevision() returns -1 if there is no preinstalled font 495 // with 'name'. 496 long preInstalledRev = getPreinstalledFontRevision(fontFileInfo, fontConfig); 497 shouldAddToMap = preInstalledRev <= fontFileInfo.getRevision(); 498 } else { 499 shouldAddToMap = existingInfo.getRevision() <= fontFileInfo.getRevision(); 500 } 501 if (shouldAddToMap) { 502 if (deleteOldFile && existingInfo != null) { 503 FileUtils.deleteContentsAndDir(existingInfo.getRandomizedFontDir()); 504 } 505 putFontFileInfo(fontFileInfo); 506 } else { 507 if (deleteOldFile) { 508 FileUtils.deleteContentsAndDir(fontFileInfo.getRandomizedFontDir()); 509 } 510 } 511 return shouldAddToMap; 512 } 513 getFontByPostScriptName(String psName, FontConfig fontConfig)514 private FontConfig.Font getFontByPostScriptName(String psName, FontConfig fontConfig) { 515 FontConfig.Font targetFont = null; 516 for (int i = 0; i < fontConfig.getFontFamilies().size(); i++) { 517 FontConfig.FontFamily family = fontConfig.getFontFamilies().get(i); 518 for (int j = 0; j < family.getFontList().size(); ++j) { 519 FontConfig.Font font = family.getFontList().get(j); 520 if (font.getPostScriptName().equals(psName)) { 521 targetFont = font; 522 break; 523 } 524 } 525 } 526 for (int i = 0; i < fontConfig.getNamedFamilyLists().size(); ++i) { 527 FontConfig.NamedFamilyList namedFamilyList = fontConfig.getNamedFamilyLists().get(i); 528 for (int j = 0; j < namedFamilyList.getFamilies().size(); ++j) { 529 FontConfig.FontFamily family = namedFamilyList.getFamilies().get(j); 530 for (int k = 0; k < family.getFontList().size(); ++k) { 531 FontConfig.Font font = family.getFontList().get(k); 532 if (font.getPostScriptName().equals(psName)) { 533 targetFont = font; 534 break; 535 } 536 } 537 } 538 } 539 return targetFont; 540 } 541 getPreinstalledFontRevision(FontFileInfo info, FontConfig fontConfig)542 private long getPreinstalledFontRevision(FontFileInfo info, FontConfig fontConfig) { 543 String psName = info.getPostScriptName(); 544 FontConfig.Font targetFont = getFontByPostScriptName(psName, fontConfig); 545 546 if (targetFont == null) { 547 return -1; 548 } 549 550 File preinstalledFontFile = targetFont.getOriginalFile() != null 551 ? targetFont.getOriginalFile() : targetFont.getFile(); 552 if (!preinstalledFontFile.exists()) { 553 return -1; 554 } 555 long revision = getFontRevision(preinstalledFontFile); 556 if (revision == -1) { 557 Slog.w(TAG, "Invalid preinstalled font file"); 558 } 559 return revision; 560 } 561 562 /** 563 * Checks the fs-verity protection status of the given font file, validates the file name, and 564 * returns a {@link FontFileInfo} on success. This method does not check if the font revision 565 * is higher than the currently used font. 566 */ 567 @NonNull validateFontFile(File file, byte[] pkcs7Signature)568 private FontFileInfo validateFontFile(File file, byte[] pkcs7Signature) 569 throws SystemFontException { 570 if (!mFsverityUtil.isFromTrustedProvider(file.getAbsolutePath(), pkcs7Signature)) { 571 throw new SystemFontException( 572 FontManager.RESULT_ERROR_VERIFICATION_FAILURE, 573 "Font validation failed. Fs-verity is not enabled: " + file); 574 } 575 final String psName; 576 try { 577 psName = mParser.getPostScriptName(file); 578 } catch (IOException e) { 579 throw new SystemFontException( 580 FontManager.RESULT_ERROR_INVALID_FONT_NAME, 581 "Font validation failed. Could not read PostScript name name: " + file); 582 } 583 long revision = getFontRevision(file); 584 if (revision == -1) { 585 throw new SystemFontException( 586 FontManager.RESULT_ERROR_INVALID_FONT_FILE, 587 "Font validation failed. Could not read font revision: " + file); 588 } 589 return new FontFileInfo(file, psName, revision); 590 } 591 /** Returns the non-negative font revision of the given font file, or -1. */ getFontRevision(File file)592 private long getFontRevision(File file) { 593 try { 594 return mParser.getRevision(file); 595 } catch (IOException e) { 596 Slog.e(TAG, "Failed to read font file", e); 597 return -1; 598 } 599 } 600 resolveFontFilesForNamedFamily( FontUpdateRequest.Family fontFamily)601 private FontConfig.NamedFamilyList resolveFontFilesForNamedFamily( 602 FontUpdateRequest.Family fontFamily) { 603 List<FontUpdateRequest.Font> fontList = fontFamily.getFonts(); 604 List<FontConfig.Font> resolvedFonts = new ArrayList<>(fontList.size()); 605 for (int i = 0; i < fontList.size(); i++) { 606 FontUpdateRequest.Font font = fontList.get(i); 607 FontFileInfo info = mFontFileInfoMap.get(font.getPostScriptName()); 608 if (info == null) { 609 Slog.e(TAG, "Failed to lookup font file that has " + font.getPostScriptName()); 610 return null; 611 } 612 resolvedFonts.add(new FontConfig.Font(info.mFile, null, info.getPostScriptName(), 613 font.getFontStyle(), font.getIndex(), font.getFontVariationSettings(), 614 null /* family name */, FontConfig.Font.VAR_TYPE_AXES_NONE)); 615 } 616 FontConfig.FontFamily family = new FontConfig.FontFamily(resolvedFonts, 617 LocaleList.getEmptyLocaleList(), FontConfig.FontFamily.VARIANT_DEFAULT); 618 return new FontConfig.NamedFamilyList(Collections.singletonList(family), 619 fontFamily.getName()); 620 } 621 getPostScriptMap()622 Map<String, File> getPostScriptMap() { 623 Map<String, File> map = new ArrayMap<>(); 624 for (int i = 0; i < mFontFileInfoMap.size(); ++i) { 625 FontFileInfo info = mFontFileInfoMap.valueAt(i); 626 map.put(info.getPostScriptName(), info.getFile()); 627 } 628 return map; 629 } 630 getSystemFontConfig()631 /* package */ FontConfig getSystemFontConfig() { 632 FontConfig config = mConfigSupplier.apply(getPostScriptMap()); 633 PersistentSystemFontConfig.Config persistentConfig = readPersistentConfig(); 634 List<FontUpdateRequest.Family> families = persistentConfig.fontFamilies; 635 636 List<FontConfig.NamedFamilyList> mergedFamilies = 637 new ArrayList<>(config.getNamedFamilyLists().size() + families.size()); 638 // We should keep the first font family (config.getFontFamilies().get(0)) because it's used 639 // as a fallback font. See SystemFonts.java. 640 mergedFamilies.addAll(config.getNamedFamilyLists()); 641 // When building Typeface, a latter font family definition will override the previous font 642 // family definition with the same name. An exception is config.getFontFamilies.get(0), 643 // which will be used as a fallback font without being overridden. 644 for (int i = 0; i < families.size(); ++i) { 645 FontConfig.NamedFamilyList family = resolveFontFilesForNamedFamily(families.get(i)); 646 if (family != null) { 647 mergedFamilies.add(family); 648 } 649 } 650 651 return new FontConfig( 652 config.getFontFamilies(), config.getAliases(), mergedFamilies, 653 config.getLocaleFallbackCustomizations(), mLastModifiedMillis, mConfigVersion); 654 } 655 readPersistentConfig()656 private PersistentSystemFontConfig.Config readPersistentConfig() { 657 PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config(); 658 try (FileInputStream fis = mConfigFile.openRead()) { 659 PersistentSystemFontConfig.loadFromXml(fis, config); 660 } catch (IOException | XmlPullParserException e) { 661 // The font config file is missing on the first boot. Just do nothing. 662 } 663 return config; 664 } 665 writePersistentConfig(PersistentSystemFontConfig.Config config)666 private void writePersistentConfig(PersistentSystemFontConfig.Config config) 667 throws SystemFontException { 668 FileOutputStream fos = null; 669 try { 670 fos = mConfigFile.startWrite(); 671 PersistentSystemFontConfig.writeToXml(fos, config); 672 mConfigFile.finishWrite(fos); 673 } catch (IOException e) { 674 if (fos != null) { 675 mConfigFile.failWrite(fos); 676 } 677 throw new SystemFontException( 678 FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG, 679 "Failed to write config XML.", e); 680 } 681 } 682 getConfigVersion()683 /* package */ int getConfigVersion() { 684 return mConfigVersion; 685 } 686 getFontFamilyMap()687 public Map<String, FontConfig.NamedFamilyList> getFontFamilyMap() { 688 PersistentSystemFontConfig.Config curConfig = readPersistentConfig(); 689 Map<String, FontConfig.NamedFamilyList> familyMap = new HashMap<>(); 690 for (int i = 0; i < curConfig.fontFamilies.size(); ++i) { 691 FontUpdateRequest.Family family = curConfig.fontFamilies.get(i); 692 FontConfig.NamedFamilyList resolvedFamily = resolveFontFilesForNamedFamily(family); 693 if (resolvedFamily != null) { 694 familyMap.put(family.getName(), resolvedFamily); 695 } 696 } 697 return familyMap; 698 } 699 deleteAllFiles(File filesDir, File configFile)700 /* package */ static void deleteAllFiles(File filesDir, File configFile) { 701 // As this method is called in safe mode, try to delete all files even though an exception 702 // is thrown. 703 try { 704 new AtomicFile(configFile).delete(); 705 } catch (Throwable t) { 706 Slog.w(TAG, "Failed to delete " + configFile); 707 } 708 try { 709 FileUtils.deleteContents(filesDir); 710 } catch (Throwable t) { 711 Slog.w(TAG, "Failed to delete " + filesDir); 712 } 713 } 714 } 715