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