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.google.common.truth.Truth.assertThat; 20 import static com.google.common.truth.Truth.assertWithMessage; 21 22 import static org.junit.Assert.fail; 23 24 import android.content.Context; 25 import android.graphics.FontListParser; 26 import android.graphics.fonts.FontManager; 27 import android.graphics.fonts.FontStyle; 28 import android.graphics.fonts.FontUpdateRequest; 29 import android.graphics.fonts.SystemFonts; 30 import android.os.FileUtils; 31 import android.os.ParcelFileDescriptor; 32 import android.platform.test.annotations.Presubmit; 33 import android.system.Os; 34 import android.text.FontConfig; 35 import android.util.Xml; 36 37 import androidx.test.InstrumentationRegistry; 38 import androidx.test.filters.SmallTest; 39 import androidx.test.runner.AndroidJUnit4; 40 41 import org.junit.After; 42 import org.junit.Before; 43 import org.junit.Test; 44 import org.junit.runner.RunWith; 45 import org.xmlpull.v1.XmlPullParser; 46 47 import java.io.ByteArrayInputStream; 48 import java.io.File; 49 import java.io.FileInputStream; 50 import java.io.FileOutputStream; 51 import java.io.IOException; 52 import java.io.InputStream; 53 import java.nio.charset.StandardCharsets; 54 import java.util.ArrayList; 55 import java.util.Arrays; 56 import java.util.Collections; 57 import java.util.HashSet; 58 import java.util.List; 59 import java.util.Map; 60 import java.util.Set; 61 import java.util.function.Function; 62 import java.util.function.Supplier; 63 import java.util.stream.Collectors; 64 65 @Presubmit 66 @SmallTest 67 @RunWith(AndroidJUnit4.class) 68 public final class UpdatableFontDirTest { 69 70 /** 71 * A {@link UpdatableFontDir.FontFileParser} for testing. Instead of using real font files, 72 * this test uses fake font files. A fake font file has its PostScript naem and revision as the 73 * file content. 74 */ 75 private static class FakeFontFileParser implements UpdatableFontDir.FontFileParser { 76 @Override getPostScriptName(File file)77 public String getPostScriptName(File file) throws IOException { 78 String content = FileUtils.readTextFile(file, 100, ""); 79 return content.split(",")[2]; 80 } 81 82 @Override buildFontFileName(File file)83 public String buildFontFileName(File file) throws IOException { 84 String content = FileUtils.readTextFile(file, 100, ""); 85 return content.split(",")[0]; 86 } 87 88 @Override getRevision(File file)89 public long getRevision(File file) throws IOException { 90 String content = FileUtils.readTextFile(file, 100, ""); 91 return Long.parseLong(content.split(",")[1]); 92 } 93 94 @Override tryToCreateTypeface(File file)95 public void tryToCreateTypeface(File file) throws Throwable { 96 } 97 } 98 99 // FakeFsverityUtil will successfully set up fake fs-verity if the signature is GOOD_SIGNATURE. 100 private static final String GOOD_SIGNATURE = "Good signature"; 101 102 /** A fake FsverityUtil to keep fake verity bit in memory. */ 103 private static class FakeFsverityUtil implements UpdatableFontDir.FsverityUtil { 104 private final Set<String> mHasFsverityPaths = new HashSet<>(); 105 remove(String name)106 public void remove(String name) { 107 mHasFsverityPaths.remove(name); 108 } 109 110 @Override isFromTrustedProvider(String path, byte[] signature)111 public boolean isFromTrustedProvider(String path, byte[] signature) { 112 if (!mHasFsverityPaths.contains(path)) { 113 return false; 114 } 115 String fakeSignature = new String(signature, StandardCharsets.UTF_8); 116 return GOOD_SIGNATURE.equals(fakeSignature); 117 } 118 119 @Override setUpFsverity(String path)120 public void setUpFsverity(String path) throws IOException { 121 mHasFsverityPaths.add(path); 122 } 123 124 @Override rename(File src, File dest)125 public boolean rename(File src, File dest) { 126 if (src.renameTo(dest)) { 127 mHasFsverityPaths.remove(src.getAbsolutePath()); 128 mHasFsverityPaths.add(dest.getAbsolutePath()); 129 return true; 130 } 131 return false; 132 } 133 } 134 135 private static final long CURRENT_TIME = 1234567890L; 136 137 private File mCacheDir; 138 private File mUpdatableFontFilesDir; 139 private File mConfigFile; 140 private List<File> mPreinstalledFontDirs; 141 private final Supplier<Long> mCurrentTimeSupplier = () -> CURRENT_TIME; 142 private final Function<Map<String, File>, FontConfig> mConfigSupplier = 143 (map) -> SystemFonts.getSystemFontConfig(map, 0, 0); 144 private FakeFontFileParser mParser; 145 private FakeFsverityUtil mFakeFsverityUtil; 146 147 @SuppressWarnings("ResultOfMethodCallIgnored") 148 @Before setUp()149 public void setUp() { 150 Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); 151 mCacheDir = new File(context.getCacheDir(), "UpdatableFontDirTest"); 152 FileUtils.deleteContentsAndDir(mCacheDir); 153 mCacheDir.mkdirs(); 154 mUpdatableFontFilesDir = new File(mCacheDir, "updatable_fonts"); 155 mUpdatableFontFilesDir.mkdir(); 156 mPreinstalledFontDirs = new ArrayList<>(); 157 mPreinstalledFontDirs.add(new File(mCacheDir, "system_fonts")); 158 mPreinstalledFontDirs.add(new File(mCacheDir, "product_fonts")); 159 for (File dir : mPreinstalledFontDirs) { 160 dir.mkdir(); 161 } 162 mConfigFile = new File(mCacheDir, "config.xml"); 163 mParser = new FakeFontFileParser(); 164 mFakeFsverityUtil = new FakeFsverityUtil(); 165 } 166 167 @After tearDown()168 public void tearDown() { 169 FileUtils.deleteContentsAndDir(mCacheDir); 170 } 171 172 @Test construct()173 public void construct() throws Exception { 174 long expectedModifiedDate = CURRENT_TIME / 2; 175 PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config(); 176 config.lastModifiedMillis = expectedModifiedDate; 177 writeConfig(config, mConfigFile); 178 UpdatableFontDir dirForPreparation = new UpdatableFontDir( 179 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil, 180 mConfigFile, mCurrentTimeSupplier, mConfigSupplier); 181 dirForPreparation.loadFontFileMap(); 182 assertThat(dirForPreparation.getSystemFontConfig().getLastModifiedTimeMillis()) 183 .isEqualTo(expectedModifiedDate); 184 dirForPreparation.update(Arrays.asList( 185 newFontUpdateRequest("foo.ttf,1,foo", GOOD_SIGNATURE), 186 newFontUpdateRequest("bar.ttf,2,bar", GOOD_SIGNATURE), 187 newFontUpdateRequest("foo.ttf,3,foo", GOOD_SIGNATURE), 188 newFontUpdateRequest("bar.ttf,4,bar", GOOD_SIGNATURE), 189 newAddFontFamilyRequest("<family name='foobar'>" 190 + " <font>foo.ttf</font>" 191 + " <font>bar.ttf</font>" 192 + "</family>"))); 193 // Verifies that getLastModifiedTimeMillis() returns the value of currentTimeMillis. 194 assertThat(dirForPreparation.getSystemFontConfig().getLastModifiedTimeMillis()) 195 .isEqualTo(CURRENT_TIME); 196 // Four font dirs are created. 197 assertThat(mUpdatableFontFilesDir.list()).hasLength(4); 198 assertThat(dirForPreparation.getSystemFontConfig().getLastModifiedTimeMillis()) 199 .isNotEqualTo(expectedModifiedDate); 200 201 UpdatableFontDir dir = new UpdatableFontDir( 202 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil, 203 mConfigFile, mCurrentTimeSupplier, mConfigSupplier); 204 dir.loadFontFileMap(); 205 assertThat(dir.getPostScriptMap()).containsKey("foo"); 206 assertThat(mParser.getRevision(dir.getPostScriptMap().get("foo"))).isEqualTo(3); 207 assertThat(dir.getPostScriptMap()).containsKey("bar"); 208 assertThat(mParser.getRevision(dir.getPostScriptMap().get("bar"))).isEqualTo(4); 209 // Outdated font dir should be deleted. 210 assertThat(mUpdatableFontFilesDir.list()).hasLength(2); 211 assertNamedFamilyExists(dir.getSystemFontConfig(), "foobar"); 212 assertThat(dir.getFontFamilyMap()).containsKey("foobar"); 213 assertThat(dir.getFontFamilyMap().get("foobar").getFamilies().size()).isEqualTo(1); 214 FontConfig.FontFamily foobar = dir.getFontFamilyMap().get("foobar").getFamilies().get(0); 215 assertThat(foobar.getFontList()).hasSize(2); 216 assertThat(foobar.getFontList().get(0).getFile()) 217 .isEqualTo(dir.getPostScriptMap().get("foo")); 218 assertThat(foobar.getFontList().get(1).getFile()) 219 .isEqualTo(dir.getPostScriptMap().get("bar")); 220 } 221 222 @Test construct_empty()223 public void construct_empty() { 224 UpdatableFontDir dir = new UpdatableFontDir( 225 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil, 226 mConfigFile, mCurrentTimeSupplier, mConfigSupplier); 227 dir.loadFontFileMap(); 228 assertThat(dir.getPostScriptMap()).isEmpty(); 229 assertThat(dir.getFontFamilyMap()).isEmpty(); 230 } 231 232 @Test construct_missingFsverity()233 public void construct_missingFsverity() throws Exception { 234 UpdatableFontDir dirForPreparation = new UpdatableFontDir( 235 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil, 236 mConfigFile, mCurrentTimeSupplier, mConfigSupplier); 237 dirForPreparation.loadFontFileMap(); 238 dirForPreparation.update(Arrays.asList( 239 newFontUpdateRequest("foo.ttf,1,foo", GOOD_SIGNATURE), 240 newFontUpdateRequest("bar.ttf,2,bar", GOOD_SIGNATURE), 241 newFontUpdateRequest("foo.ttf,3,foo", GOOD_SIGNATURE), 242 newFontUpdateRequest("bar.ttf,4,bar", GOOD_SIGNATURE), 243 newAddFontFamilyRequest("<family name='foobar'>" 244 + " <font>foo.ttf</font>" 245 + " <font>bar.ttf</font>" 246 + "</family>"))); 247 // Four font dirs are created. 248 assertThat(mUpdatableFontFilesDir.list()).hasLength(4); 249 250 mFakeFsverityUtil.remove( 251 dirForPreparation.getPostScriptMap().get("foo").getAbsolutePath()); 252 UpdatableFontDir dir = new UpdatableFontDir( 253 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil, 254 mConfigFile, mCurrentTimeSupplier, mConfigSupplier); 255 dir.loadFontFileMap(); 256 assertThat(dir.getPostScriptMap()).isEmpty(); 257 // All font dirs (including dir for "bar.ttf") should be deleted. 258 assertThat(mUpdatableFontFilesDir.list()).hasLength(0); 259 assertThat(dir.getFontFamilyMap()).isEmpty(); 260 } 261 262 @Test construct_fontNameMismatch()263 public void construct_fontNameMismatch() throws Exception { 264 UpdatableFontDir dirForPreparation = new UpdatableFontDir( 265 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil, 266 mConfigFile, mCurrentTimeSupplier, mConfigSupplier); 267 dirForPreparation.loadFontFileMap(); 268 dirForPreparation.update(Arrays.asList( 269 newFontUpdateRequest("foo.ttf,1,foo", GOOD_SIGNATURE), 270 newFontUpdateRequest("bar.ttf,2,bar", GOOD_SIGNATURE), 271 newFontUpdateRequest("foo.ttf,3,foo", GOOD_SIGNATURE), 272 newFontUpdateRequest("bar.ttf,4,bar", GOOD_SIGNATURE), 273 newAddFontFamilyRequest("<family name='foobar'>" 274 + " <font>foo.ttf</font>" 275 + " <font>bar.ttf</font>" 276 + "</family>"))); 277 // Four font dirs are created. 278 assertThat(mUpdatableFontFilesDir.list()).hasLength(4); 279 280 // Overwrite "foo.ttf" with wrong contents. 281 FileUtils.stringToFile(dirForPreparation.getPostScriptMap().get("foo"), "bar,4"); 282 283 UpdatableFontDir dir = new UpdatableFontDir( 284 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil, 285 mConfigFile, mCurrentTimeSupplier, mConfigSupplier); 286 dir.loadFontFileMap(); 287 assertThat(dir.getPostScriptMap()).isEmpty(); 288 // All font dirs (including dir for "bar.ttf") should be deleted. 289 assertThat(mUpdatableFontFilesDir.list()).hasLength(0); 290 assertThat(dir.getFontFamilyMap()).isEmpty(); 291 } 292 293 @Test construct_missingSignatureFile()294 public void construct_missingSignatureFile() throws Exception { 295 UpdatableFontDir dirForPreparation = new UpdatableFontDir( 296 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil, 297 mConfigFile, mCurrentTimeSupplier, mConfigSupplier); 298 dirForPreparation.loadFontFileMap(); 299 dirForPreparation.update(Arrays.asList( 300 newFontUpdateRequest("foo.ttf,1,foo", GOOD_SIGNATURE))); 301 assertThat(mUpdatableFontFilesDir.list()).hasLength(1); 302 303 // Remove signature file next to the font file. 304 File fontDir = dirForPreparation.getPostScriptMap().get("foo"); 305 File sigFile = new File(fontDir.getParentFile(), "font.fsv_sig"); 306 assertThat(sigFile.exists()).isTrue(); 307 sigFile.delete(); 308 309 UpdatableFontDir dir = new UpdatableFontDir( 310 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil, 311 mConfigFile, mCurrentTimeSupplier, mConfigSupplier); 312 dir.loadFontFileMap(); 313 // The font file should be removed and should not be loaded. 314 assertThat(dir.getPostScriptMap()).isEmpty(); 315 assertThat(mUpdatableFontFilesDir.list()).hasLength(0); 316 assertThat(dir.getFontFamilyMap()).isEmpty(); 317 } 318 319 @Test construct_olderThanPreinstalledFont()320 public void construct_olderThanPreinstalledFont() throws Exception { 321 Function<Map<String, File>, FontConfig> configSupplier = (map) -> { 322 FontConfig.Font fooFont = new FontConfig.Font( 323 new File(mPreinstalledFontDirs.get(0), "foo.ttf"), null, "foo", 324 new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT), 0, null, null); 325 FontConfig.Font barFont = new FontConfig.Font( 326 new File(mPreinstalledFontDirs.get(1), "bar.ttf"), null, "bar", 327 new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT), 0, null, null); 328 329 FontConfig.FontFamily family = new FontConfig.FontFamily( 330 Arrays.asList(fooFont, barFont), null, 331 FontConfig.FontFamily.VARIANT_DEFAULT); 332 return new FontConfig(Collections.emptyList(), 333 Collections.emptyList(), 334 Collections.singletonList(new FontConfig.NamedFamilyList( 335 Collections.singletonList(family), "sans-serif")), 0, 1); 336 }; 337 338 UpdatableFontDir dirForPreparation = new UpdatableFontDir( 339 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil, 340 mConfigFile, mCurrentTimeSupplier, configSupplier); 341 dirForPreparation.loadFontFileMap(); 342 dirForPreparation.update(Arrays.asList( 343 newFontUpdateRequest("foo.ttf,1,foo", GOOD_SIGNATURE), 344 newFontUpdateRequest("bar.ttf,2,bar", GOOD_SIGNATURE), 345 newFontUpdateRequest("foo.ttf,3,foo", GOOD_SIGNATURE), 346 newFontUpdateRequest("bar.ttf,4,bar", GOOD_SIGNATURE), 347 newAddFontFamilyRequest("<family name='foobar'>" 348 + " <font>foo.ttf</font>" 349 + " <font>bar.ttf</font>" 350 + "</family>"))); 351 // Four font dirs are created. 352 assertThat(mUpdatableFontFilesDir.list()).hasLength(4); 353 354 // Add preinstalled fonts. 355 FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(0), "foo.ttf"), "foo,5,foo"); 356 FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(1), "bar.ttf"), "bar,1,bar"); 357 FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(1), "bar.ttf"), "bar,2,bar"); 358 UpdatableFontDir dir = new UpdatableFontDir( 359 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil, 360 mConfigFile, mCurrentTimeSupplier, configSupplier); 361 dir.loadFontFileMap(); 362 // For foo.ttf, preinstalled font (revision 5) should be used. 363 assertThat(dir.getPostScriptMap()).doesNotContainKey("foo"); 364 // For bar.ttf, updated font (revision 4) should be used. 365 assertThat(dir.getPostScriptMap()).containsKey("bar"); 366 assertThat(mParser.getRevision(dir.getPostScriptMap().get("bar"))).isEqualTo(4); 367 // Outdated font dir should be deleted. 368 // We don't delete bar.ttf in this case, because it's normal that OTA updates preinstalled 369 // fonts. 370 assertThat(mUpdatableFontFilesDir.list()).hasLength(1); 371 // Font family depending on obsoleted font should be removed. 372 assertThat(dir.getFontFamilyMap()).isEmpty(); 373 } 374 375 @Test construct_failedToLoadConfig()376 public void construct_failedToLoadConfig() throws Exception { 377 UpdatableFontDir dir = new UpdatableFontDir( 378 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil, 379 new File("/dev/null"), mCurrentTimeSupplier, mConfigSupplier); 380 dir.loadFontFileMap(); 381 assertThat(dir.getPostScriptMap()).isEmpty(); 382 assertThat(dir.getFontFamilyMap()).isEmpty(); 383 } 384 385 @Test construct_afterBatchFailure()386 public void construct_afterBatchFailure() throws Exception { 387 UpdatableFontDir dirForPreparation = new UpdatableFontDir( 388 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil, 389 mConfigFile, mCurrentTimeSupplier, mConfigSupplier); 390 dirForPreparation.loadFontFileMap(); 391 dirForPreparation.update(Arrays.asList( 392 newFontUpdateRequest("foo.ttf,1,foo", GOOD_SIGNATURE), 393 newAddFontFamilyRequest("<family name='foobar'>" 394 + " <font>foo.ttf</font>" 395 + "</family>"))); 396 try { 397 dirForPreparation.update(Arrays.asList( 398 newFontUpdateRequest("foo.ttf,2,foo", GOOD_SIGNATURE), 399 newFontUpdateRequest("bar.ttf,2,bar", "Invalid signature"), 400 newAddFontFamilyRequest("<family name='foobar'>" 401 + " <font>foo.ttf</font>" 402 + " <font>bar.ttf</font>" 403 + "</family>"))); 404 fail("Batch update with invalid signature should fail"); 405 } catch (FontManagerService.SystemFontException e) { 406 // Expected 407 } 408 409 UpdatableFontDir dir = new UpdatableFontDir( 410 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil, 411 mConfigFile, mCurrentTimeSupplier, mConfigSupplier); 412 dir.loadFontFileMap(); 413 // The state should be rolled back as a whole if one of the update requests fail. 414 assertThat(dir.getPostScriptMap()).containsKey("foo"); 415 assertThat(mParser.getRevision(dir.getPostScriptMap().get("foo"))).isEqualTo(1); 416 assertThat(dir.getFontFamilyMap()).containsKey("foobar"); 417 assertThat(dir.getFontFamilyMap().get("foobar").getFamilies().size()).isEqualTo(1); 418 FontConfig.FontFamily foobar = dir.getFontFamilyMap().get("foobar").getFamilies().get(0); 419 assertThat(foobar.getFontList()).hasSize(1); 420 assertThat(foobar.getFontList().get(0).getFile()) 421 .isEqualTo(dir.getPostScriptMap().get("foo")); 422 } 423 424 @Test loadFontFileMap_twice()425 public void loadFontFileMap_twice() throws Exception { 426 UpdatableFontDir dir = new UpdatableFontDir( 427 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil, 428 mConfigFile, mCurrentTimeSupplier, mConfigSupplier); 429 dir.loadFontFileMap(); 430 dir.update(Collections.singletonList(newFontUpdateRequest("test.ttf,1,test", 431 GOOD_SIGNATURE))); 432 assertThat(dir.getPostScriptMap()).containsKey("test"); 433 File fontFile = dir.getPostScriptMap().get("test"); 434 dir.loadFontFileMap(); 435 assertThat(dir.getPostScriptMap().get("test")).isEqualTo(fontFile); 436 } 437 438 @Test installFontFile()439 public void installFontFile() throws Exception { 440 UpdatableFontDir dir = new UpdatableFontDir( 441 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil, 442 mConfigFile, mCurrentTimeSupplier, mConfigSupplier); 443 dir.loadFontFileMap(); 444 445 dir.update(Collections.singletonList(newFontUpdateRequest("test.ttf,1,test", 446 GOOD_SIGNATURE))); 447 assertThat(dir.getPostScriptMap()).containsKey("test"); 448 assertThat(mParser.getRevision(dir.getPostScriptMap().get("test"))).isEqualTo(1); 449 File fontFile = dir.getPostScriptMap().get("test"); 450 assertThat(Os.stat(fontFile.getAbsolutePath()).st_mode & 0777).isEqualTo(0644); 451 File fontDir = fontFile.getParentFile(); 452 assertThat(Os.stat(fontDir.getAbsolutePath()).st_mode & 0777).isEqualTo(0711); 453 } 454 455 @Test installFontFile_upgrade()456 public void installFontFile_upgrade() throws Exception { 457 UpdatableFontDir dir = new UpdatableFontDir( 458 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil, 459 mConfigFile, mCurrentTimeSupplier, mConfigSupplier); 460 dir.loadFontFileMap(); 461 462 dir.update(Collections.singletonList(newFontUpdateRequest("test.ttf,1,test", 463 GOOD_SIGNATURE))); 464 Map<String, File> mapBeforeUpgrade = dir.getPostScriptMap(); 465 dir.update(Collections.singletonList(newFontUpdateRequest("test.ttf,2,test", 466 GOOD_SIGNATURE))); 467 assertThat(dir.getPostScriptMap()).containsKey("test"); 468 assertThat(mParser.getRevision(dir.getPostScriptMap().get("test"))).isEqualTo(2); 469 assertThat(mapBeforeUpgrade).containsKey("test"); 470 assertWithMessage("Older fonts should not be deleted until next loadFontFileMap") 471 .that(mParser.getRevision(mapBeforeUpgrade.get("test"))).isEqualTo(1); 472 // Check that updatedFontDirs is pruned. 473 assertWithMessage("config.updatedFontDirs should only list latest active dirs") 474 .that(readConfig(mConfigFile).updatedFontDirs) 475 .containsExactly(dir.getPostScriptMap().get("test").getParentFile().getName()); 476 } 477 478 @Test installFontFile_systemFontHasPSNameDifferentFromFileName()479 public void installFontFile_systemFontHasPSNameDifferentFromFileName() throws Exception { 480 481 // Setup the environment that the system installed font file named "foo.ttf" has PostScript 482 // name "bar". 483 File file = new File(mPreinstalledFontDirs.get(0), "foo.ttf"); 484 FileUtils.stringToFile(file, "foo.ttf,1,bar"); 485 UpdatableFontDir dir = new UpdatableFontDir( 486 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil, 487 mConfigFile, mCurrentTimeSupplier, (map) -> { 488 FontConfig.Font font = new FontConfig.Font( 489 file, null, "bar", new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT), 490 0, null, null); 491 FontConfig.FontFamily family = new FontConfig.FontFamily( 492 Collections.singletonList(font), null, FontConfig.FontFamily.VARIANT_DEFAULT); 493 return new FontConfig( 494 Collections.emptyList(), 495 Collections.emptyList(), 496 Collections.singletonList(new FontConfig.NamedFamilyList( 497 Collections.singletonList(family), "sans-serif")), 0, 1); 498 }); 499 dir.loadFontFileMap(); 500 501 dir.update(Collections.singletonList(newFontUpdateRequest("bar.ttf,2,bar", 502 GOOD_SIGNATURE))); 503 assertThat(dir.getPostScriptMap()).containsKey("bar"); 504 assertThat(dir.getPostScriptMap().size()).isEqualTo(1); 505 assertThat(mParser.getRevision(dir.getPostScriptMap().get("bar"))).isEqualTo(2); 506 File fontFile = dir.getPostScriptMap().get("bar"); 507 assertThat(Os.stat(fontFile.getAbsolutePath()).st_mode & 0777).isEqualTo(0644); 508 File fontDir = fontFile.getParentFile(); 509 assertThat(Os.stat(fontDir.getAbsolutePath()).st_mode & 0777).isEqualTo(0711); 510 } 511 512 @Test installFontFile_sameVersion()513 public void installFontFile_sameVersion() throws Exception { 514 UpdatableFontDir dir = new UpdatableFontDir( 515 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil, 516 mConfigFile, mCurrentTimeSupplier, mConfigSupplier); 517 dir.loadFontFileMap(); 518 519 dir.update(Collections.singletonList(newFontUpdateRequest("test.ttf,1,test", 520 GOOD_SIGNATURE))); 521 dir.update(Collections.singletonList(newFontUpdateRequest("test.ttf,1,test", 522 GOOD_SIGNATURE))); 523 assertThat(dir.getPostScriptMap()).containsKey("test"); 524 assertThat(mParser.getRevision(dir.getPostScriptMap().get("test"))).isEqualTo(1); 525 } 526 527 @Test installFontFile_downgrade()528 public void installFontFile_downgrade() throws Exception { 529 UpdatableFontDir dir = new UpdatableFontDir( 530 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil, 531 mConfigFile, mCurrentTimeSupplier, mConfigSupplier); 532 dir.loadFontFileMap(); 533 534 dir.update(Collections.singletonList(newFontUpdateRequest("test.ttf,2,test", 535 GOOD_SIGNATURE))); 536 try { 537 dir.update(Collections.singletonList(newFontUpdateRequest("test.ttf,1,test", 538 GOOD_SIGNATURE))); 539 fail("Expect SystemFontException"); 540 } catch (FontManagerService.SystemFontException e) { 541 assertThat(e.getErrorCode()).isEqualTo(FontManager.RESULT_ERROR_DOWNGRADING); 542 } 543 assertThat(dir.getPostScriptMap()).containsKey("test"); 544 assertWithMessage("Font should not be downgraded to an older revision") 545 .that(mParser.getRevision(dir.getPostScriptMap().get("test"))).isEqualTo(2); 546 // Check that updatedFontDirs is not updated. 547 assertWithMessage("config.updatedFontDirs should only list latest active dirs") 548 .that(readConfig(mConfigFile).updatedFontDirs) 549 .containsExactly(dir.getPostScriptMap().get("test").getParentFile().getName()); 550 } 551 552 @Test installFontFile_multiple()553 public void installFontFile_multiple() throws Exception { 554 UpdatableFontDir dir = new UpdatableFontDir( 555 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil, 556 mConfigFile, mCurrentTimeSupplier, mConfigSupplier); 557 dir.loadFontFileMap(); 558 559 dir.update(Collections.singletonList(newFontUpdateRequest("foo.ttf,1,foo", 560 GOOD_SIGNATURE))); 561 dir.update(Collections.singletonList(newFontUpdateRequest("bar.ttf,2,bar", 562 GOOD_SIGNATURE))); 563 assertThat(dir.getPostScriptMap()).containsKey("foo"); 564 assertThat(mParser.getRevision(dir.getPostScriptMap().get("foo"))).isEqualTo(1); 565 assertThat(dir.getPostScriptMap()).containsKey("bar"); 566 assertThat(mParser.getRevision(dir.getPostScriptMap().get("bar"))).isEqualTo(2); 567 } 568 569 @Test installFontFile_batch()570 public void installFontFile_batch() throws Exception { 571 UpdatableFontDir dir = new UpdatableFontDir( 572 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil, 573 mConfigFile, mCurrentTimeSupplier, mConfigSupplier); 574 dir.loadFontFileMap(); 575 576 dir.update(Arrays.asList( 577 newFontUpdateRequest("foo.ttf,1,foo", GOOD_SIGNATURE), 578 newFontUpdateRequest("bar.ttf,2,bar", GOOD_SIGNATURE))); 579 assertThat(dir.getPostScriptMap()).containsKey("foo"); 580 assertThat(mParser.getRevision(dir.getPostScriptMap().get("foo"))).isEqualTo(1); 581 assertThat(dir.getPostScriptMap()).containsKey("bar"); 582 assertThat(mParser.getRevision(dir.getPostScriptMap().get("bar"))).isEqualTo(2); 583 } 584 585 @Test installFontFile_invalidSignature()586 public void installFontFile_invalidSignature() throws Exception { 587 UpdatableFontDir dir = new UpdatableFontDir( 588 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil, 589 mConfigFile, mCurrentTimeSupplier, mConfigSupplier); 590 dir.loadFontFileMap(); 591 592 try { 593 dir.update( 594 Collections.singletonList(newFontUpdateRequest("test.ttf,1,test", 595 "Invalid signature"))); 596 fail("Expect SystemFontException"); 597 } catch (FontManagerService.SystemFontException e) { 598 assertThat(e.getErrorCode()) 599 .isEqualTo(FontManager.RESULT_ERROR_VERIFICATION_FAILURE); 600 } 601 assertThat(dir.getPostScriptMap()).isEmpty(); 602 } 603 604 @Test installFontFile_preinstalled_upgrade()605 public void installFontFile_preinstalled_upgrade() throws Exception { 606 FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(0), "test.ttf"), 607 "test.ttf,1,test"); 608 UpdatableFontDir dir = new UpdatableFontDir( 609 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil, 610 mConfigFile, mCurrentTimeSupplier, mConfigSupplier); 611 dir.loadFontFileMap(); 612 613 dir.update(Collections.singletonList(newFontUpdateRequest("test.ttf,2,test", 614 GOOD_SIGNATURE))); 615 assertThat(dir.getPostScriptMap()).containsKey("test"); 616 assertThat(mParser.getRevision(dir.getPostScriptMap().get("test"))).isEqualTo(2); 617 } 618 619 @Test installFontFile_preinstalled_sameVersion()620 public void installFontFile_preinstalled_sameVersion() throws Exception { 621 FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(0), "test.ttf"), 622 "test.ttf,1,test"); 623 UpdatableFontDir dir = new UpdatableFontDir( 624 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil, 625 mConfigFile, mCurrentTimeSupplier, mConfigSupplier); 626 dir.loadFontFileMap(); 627 628 dir.update(Collections.singletonList(newFontUpdateRequest("test.ttf,1,test", 629 GOOD_SIGNATURE))); 630 assertThat(dir.getPostScriptMap()).containsKey("test"); 631 assertThat(mParser.getRevision(dir.getPostScriptMap().get("test"))).isEqualTo(1); 632 } 633 634 @Test installFontFile_preinstalled_downgrade()635 public void installFontFile_preinstalled_downgrade() throws Exception { 636 File file = new File(mPreinstalledFontDirs.get(0), "test.ttf"); 637 FileUtils.stringToFile(file, "test.ttf,2,test"); 638 UpdatableFontDir dir = new UpdatableFontDir( 639 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil, 640 mConfigFile, mCurrentTimeSupplier, (map) -> { 641 FontConfig.Font font = new FontConfig.Font( 642 file, null, "test", new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT), 0, null, 643 null); 644 FontConfig.FontFamily family = new FontConfig.FontFamily( 645 Collections.singletonList(font), null, FontConfig.FontFamily.VARIANT_DEFAULT); 646 return new FontConfig(Collections.emptyList(), Collections.emptyList(), 647 Collections.singletonList(new FontConfig.NamedFamilyList( 648 Collections.singletonList(family), "sans-serif")), 0, 1); 649 }); 650 dir.loadFontFileMap(); 651 652 try { 653 dir.update(Collections.singletonList(newFontUpdateRequest("test.ttf,1,test", 654 GOOD_SIGNATURE))); 655 fail("Expect SystemFontException"); 656 } catch (FontManagerService.SystemFontException e) { 657 assertThat(e.getErrorCode()).isEqualTo(FontManager.RESULT_ERROR_DOWNGRADING); 658 } 659 assertThat(dir.getPostScriptMap()).isEmpty(); 660 } 661 662 @Test installFontFile_failedToWriteConfigXml()663 public void installFontFile_failedToWriteConfigXml() throws Exception { 664 long expectedModifiedDate = 1234567890; 665 FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(0), "test.ttf"), 666 "test.ttf,1,test"); 667 668 File readonlyDir = new File(mCacheDir, "readonly"); 669 assertThat(readonlyDir.mkdir()).isTrue(); 670 File readonlyFile = new File(readonlyDir, "readonly_config.xml"); 671 672 PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config(); 673 config.lastModifiedMillis = expectedModifiedDate; 674 writeConfig(config, readonlyFile); 675 676 assertThat(readonlyDir.setWritable(false, false)).isTrue(); 677 try { 678 UpdatableFontDir dir = new UpdatableFontDir( 679 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil, 680 readonlyFile, mCurrentTimeSupplier, mConfigSupplier); 681 dir.loadFontFileMap(); 682 683 try { 684 dir.update( 685 Collections.singletonList(newFontUpdateRequest("test.ttf,2,test", 686 GOOD_SIGNATURE))); 687 } catch (FontManagerService.SystemFontException e) { 688 assertThat(e.getErrorCode()) 689 .isEqualTo(FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG); 690 } 691 assertThat(dir.getSystemFontConfig().getLastModifiedTimeMillis()) 692 .isEqualTo(expectedModifiedDate); 693 assertThat(dir.getPostScriptMap()).isEmpty(); 694 } finally { 695 assertThat(readonlyDir.setWritable(true, true)).isTrue(); 696 } 697 } 698 699 @Test installFontFile_failedToParsePostScript()700 public void installFontFile_failedToParsePostScript() throws Exception { 701 UpdatableFontDir dir = new UpdatableFontDir( 702 mUpdatableFontFilesDir, 703 new UpdatableFontDir.FontFileParser() { 704 705 @Override 706 public String getPostScriptName(File file) throws IOException { 707 return null; 708 } 709 710 @Override 711 public String buildFontFileName(File file) throws IOException { 712 return null; 713 } 714 715 @Override 716 public long getRevision(File file) throws IOException { 717 return 0; 718 } 719 720 @Override 721 public void tryToCreateTypeface(File file) throws IOException { 722 } 723 }, mFakeFsverityUtil, mConfigFile, mCurrentTimeSupplier, mConfigSupplier); 724 dir.loadFontFileMap(); 725 726 try { 727 dir.update(Collections.singletonList(newFontUpdateRequest("foo.ttf,1,foo", 728 GOOD_SIGNATURE))); 729 fail("Expect SystemFontException"); 730 } catch (FontManagerService.SystemFontException e) { 731 assertThat(e.getErrorCode()) 732 .isEqualTo(FontManager.RESULT_ERROR_INVALID_FONT_NAME); 733 } 734 assertThat(dir.getPostScriptMap()).isEmpty(); 735 } 736 737 @Test installFontFile_failedToParsePostScriptName_invalidFont()738 public void installFontFile_failedToParsePostScriptName_invalidFont() throws Exception { 739 UpdatableFontDir dir = new UpdatableFontDir( 740 mUpdatableFontFilesDir, 741 new UpdatableFontDir.FontFileParser() { 742 @Override 743 public String getPostScriptName(File file) throws IOException { 744 throw new IOException(); 745 } 746 747 @Override 748 public String buildFontFileName(File file) throws IOException { 749 throw new IOException(); 750 } 751 752 @Override 753 public long getRevision(File file) throws IOException { 754 return 0; 755 } 756 757 @Override 758 public void tryToCreateTypeface(File file) throws IOException { 759 } 760 }, mFakeFsverityUtil, mConfigFile, mCurrentTimeSupplier, mConfigSupplier); 761 dir.loadFontFileMap(); 762 763 try { 764 dir.update(Collections.singletonList(newFontUpdateRequest("foo.ttf,1,foo", 765 GOOD_SIGNATURE))); 766 fail("Expect SystemFontException"); 767 } catch (FontManagerService.SystemFontException e) { 768 assertThat(e.getErrorCode()) 769 .isEqualTo(FontManager.RESULT_ERROR_INVALID_FONT_FILE); 770 } 771 assertThat(dir.getPostScriptMap()).isEmpty(); 772 } 773 774 @Test installFontFile_failedToCreateTypeface()775 public void installFontFile_failedToCreateTypeface() throws Exception { 776 UpdatableFontDir dir = new UpdatableFontDir( 777 mUpdatableFontFilesDir, 778 new UpdatableFontDir.FontFileParser() { 779 @Override 780 public String getPostScriptName(File file) throws IOException { 781 return mParser.getPostScriptName(file); 782 } 783 784 @Override 785 public String buildFontFileName(File file) throws IOException { 786 return mParser.buildFontFileName(file); 787 } 788 789 @Override 790 public long getRevision(File file) throws IOException { 791 return mParser.getRevision(file); 792 } 793 794 @Override 795 public void tryToCreateTypeface(File file) throws IOException { 796 throw new IOException(); 797 } 798 }, mFakeFsverityUtil, mConfigFile, mCurrentTimeSupplier, mConfigSupplier); 799 dir.loadFontFileMap(); 800 801 try { 802 dir.update(Collections.singletonList(newFontUpdateRequest("foo.ttf,1,foo", 803 GOOD_SIGNATURE))); 804 fail("Expect SystemFontException"); 805 } catch (FontManagerService.SystemFontException e) { 806 assertThat(e.getErrorCode()) 807 .isEqualTo(FontManager.RESULT_ERROR_INVALID_FONT_FILE); 808 } 809 assertThat(dir.getPostScriptMap()).isEmpty(); 810 } 811 812 @Test installFontFile_renameToPsNameFailure()813 public void installFontFile_renameToPsNameFailure() throws Exception { 814 UpdatableFontDir.FsverityUtil fakeFsverityUtil = new UpdatableFontDir.FsverityUtil() { 815 816 @Override 817 public boolean isFromTrustedProvider(String path, byte[] signature) { 818 return mFakeFsverityUtil.isFromTrustedProvider(path, signature); 819 } 820 821 @Override 822 public void setUpFsverity(String path) throws IOException { 823 mFakeFsverityUtil.setUpFsverity(path); 824 } 825 826 @Override 827 public boolean rename(File src, File dest) { 828 return false; 829 } 830 }; 831 UpdatableFontDir dir = new UpdatableFontDir( 832 mUpdatableFontFilesDir, mParser, fakeFsverityUtil, 833 mConfigFile, mCurrentTimeSupplier, mConfigSupplier); 834 dir.loadFontFileMap(); 835 836 try { 837 dir.update(Collections.singletonList(newFontUpdateRequest("foo.ttf,1,foo", 838 GOOD_SIGNATURE))); 839 fail("Expect SystemFontException"); 840 } catch (FontManagerService.SystemFontException e) { 841 assertThat(e.getErrorCode()) 842 .isEqualTo(FontManager.RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE); 843 } 844 assertThat(dir.getPostScriptMap()).isEmpty(); 845 } 846 847 @Test installFontFile_batchFailure()848 public void installFontFile_batchFailure() throws Exception { 849 UpdatableFontDir dir = new UpdatableFontDir( 850 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil, 851 mConfigFile, mCurrentTimeSupplier, mConfigSupplier); 852 dir.loadFontFileMap(); 853 854 dir.update(Collections.singletonList(newFontUpdateRequest("foo.ttf,1,foo", 855 GOOD_SIGNATURE))); 856 try { 857 dir.update(Arrays.asList( 858 newFontUpdateRequest("foo.ttf,2,foo", GOOD_SIGNATURE), 859 newFontUpdateRequest("bar.ttf,2,bar", "Invalid signature"))); 860 fail("Batch update with invalid signature should fail"); 861 } catch (FontManagerService.SystemFontException e) { 862 // Expected 863 } 864 // The state should be rolled back as a whole if one of the update requests fail. 865 assertThat(dir.getPostScriptMap()).containsKey("foo"); 866 assertThat(mParser.getRevision(dir.getPostScriptMap().get("foo"))).isEqualTo(1); 867 } 868 869 @Test addFontFamily()870 public void addFontFamily() throws Exception { 871 UpdatableFontDir dir = new UpdatableFontDir( 872 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil, 873 mConfigFile, mCurrentTimeSupplier, mConfigSupplier); 874 dir.loadFontFileMap(); 875 876 dir.update(Arrays.asList( 877 newFontUpdateRequest("test.ttf,1,test", GOOD_SIGNATURE), 878 newAddFontFamilyRequest("<family name='test'>" 879 + " <font>test.ttf</font>" 880 + "</family>"))); 881 assertThat(dir.getPostScriptMap()).containsKey("test"); 882 assertThat(dir.getFontFamilyMap()).containsKey("test"); 883 assertThat(dir.getFontFamilyMap().get("test").getFamilies().size()).isEqualTo(1); 884 FontConfig.FontFamily test = dir.getFontFamilyMap().get("test").getFamilies().get(0); 885 assertThat(test.getFontList()).hasSize(1); 886 assertThat(test.getFontList().get(0).getFile()) 887 .isEqualTo(dir.getPostScriptMap().get("test")); 888 } 889 890 @Test(expected = IllegalArgumentException.class) addFontFamily_noName()891 public void addFontFamily_noName() throws Exception { 892 UpdatableFontDir dir = new UpdatableFontDir( 893 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil, 894 mConfigFile, mCurrentTimeSupplier, mConfigSupplier); 895 dir.loadFontFileMap(); 896 897 List<FontUpdateRequest> requests = Arrays.asList( 898 newFontUpdateRequest("test.ttf,1,test", GOOD_SIGNATURE), 899 newAddFontFamilyRequest("<family lang='en'>" 900 + " <font>test.ttf</font>" 901 + "</family>")); 902 dir.update(requests); 903 } 904 905 @Test addFontFamily_fontNotAvailable()906 public void addFontFamily_fontNotAvailable() throws Exception { 907 UpdatableFontDir dir = new UpdatableFontDir( 908 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil, 909 mConfigFile, mCurrentTimeSupplier, mConfigSupplier); 910 dir.loadFontFileMap(); 911 912 try { 913 dir.update(Arrays.asList(newAddFontFamilyRequest("<family name='test'>" 914 + " <font>test.ttf</font>" 915 + "</family>"))); 916 fail("Expect SystemFontException"); 917 } catch (FontManagerService.SystemFontException e) { 918 assertThat(e.getErrorCode()) 919 .isEqualTo(FontManager.RESULT_ERROR_FONT_NOT_FOUND); 920 } 921 } 922 923 @Test getSystemFontConfig()924 public void getSystemFontConfig() throws Exception { 925 UpdatableFontDir dir = new UpdatableFontDir( 926 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil, 927 mConfigFile, mCurrentTimeSupplier, mConfigSupplier); 928 dir.loadFontFileMap(); 929 // We assume we have monospace. 930 assertNamedFamilyExists(dir.getSystemFontConfig(), "monospace"); 931 932 dir.update(Arrays.asList( 933 newFontUpdateRequest("test.ttf,1,test", GOOD_SIGNATURE), 934 // Updating an existing font family. 935 newAddFontFamilyRequest("<family name='monospace'>" 936 + " <font>test.ttf</font>" 937 + "</family>"), 938 // Adding a new font family. 939 newAddFontFamilyRequest("<family name='test'>" 940 + " <font>test.ttf</font>" 941 + "</family>"))); 942 FontConfig fontConfig = dir.getSystemFontConfig(); 943 assertNamedFamilyExists(fontConfig, "monospace"); 944 FontConfig.FontFamily monospace = getLastFamily(fontConfig, "monospace"); 945 assertThat(monospace.getFontList()).hasSize(1); 946 assertThat(monospace.getFontList().get(0).getFile()) 947 .isEqualTo(dir.getPostScriptMap().get("test")); 948 assertNamedFamilyExists(fontConfig, "test"); 949 assertThat(getLastFamily(fontConfig, "test").getFontList()) 950 .isEqualTo(monospace.getFontList()); 951 } 952 953 @Test getSystemFontConfig_preserveFirstFontFamily()954 public void getSystemFontConfig_preserveFirstFontFamily() throws Exception { 955 UpdatableFontDir dir = new UpdatableFontDir( 956 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil, 957 mConfigFile, mCurrentTimeSupplier, mConfigSupplier); 958 dir.loadFontFileMap(); 959 assertThat(dir.getSystemFontConfig().getFontFamilies()).isNotEmpty(); 960 FontConfig.FontFamily firstFontFamily = dir.getSystemFontConfig().getFontFamilies().get(0); 961 962 dir.update(Arrays.asList( 963 newFontUpdateRequest("test.ttf,1,test", GOOD_SIGNATURE), 964 newAddFontFamilyRequest("<family name='sans-serif'>" 965 + " <font>test.ttf</font>" 966 + "</family>"))); 967 FontConfig fontConfig = dir.getSystemFontConfig(); 968 assertThat(dir.getSystemFontConfig().getFontFamilies()).isNotEmpty(); 969 assertThat(fontConfig.getFontFamilies().get(0)).isEqualTo(firstFontFamily); 970 FontConfig.FontFamily updated = getLastFamily(fontConfig, "sans-serif"); 971 assertThat(updated.getFontList()).hasSize(1); 972 assertThat(updated.getFontList().get(0).getFile()) 973 .isEqualTo(dir.getPostScriptMap().get("test")); 974 assertThat(updated).isNotEqualTo(firstFontFamily); 975 } 976 977 @Test deleteAllFiles()978 public void deleteAllFiles() throws Exception { 979 FakeFontFileParser parser = new FakeFontFileParser(); 980 FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil(); 981 UpdatableFontDir dirForPreparation = new UpdatableFontDir( 982 mUpdatableFontFilesDir, parser, fakeFsverityUtil, 983 mConfigFile, mCurrentTimeSupplier, mConfigSupplier); 984 dirForPreparation.loadFontFileMap(); 985 dirForPreparation.update(Collections.singletonList( 986 newFontUpdateRequest("foo.ttf,1,foo", GOOD_SIGNATURE))); 987 assertThat(mConfigFile.exists()).isTrue(); 988 assertThat(mUpdatableFontFilesDir.list()).hasLength(1); 989 990 UpdatableFontDir.deleteAllFiles(mUpdatableFontFilesDir, mConfigFile); 991 assertThat(mConfigFile.exists()).isFalse(); 992 assertThat(mUpdatableFontFilesDir.list()).hasLength(0); 993 } 994 newFontUpdateRequest(String content, String signature)995 private FontUpdateRequest newFontUpdateRequest(String content, String signature) 996 throws Exception { 997 File file = File.createTempFile("font", "ttf", mCacheDir); 998 FileUtils.stringToFile(file, content); 999 return new FontUpdateRequest( 1000 ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY), 1001 signature.getBytes()); 1002 } 1003 newAddFontFamilyRequest(String xml)1004 private static FontUpdateRequest newAddFontFamilyRequest(String xml) throws Exception { 1005 XmlPullParser mParser = Xml.newPullParser(); 1006 ByteArrayInputStream is = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)); 1007 mParser.setInput(is, "UTF-8"); 1008 mParser.nextTag(); 1009 1010 FontConfig.NamedFamilyList namedFamilyList = FontListParser.readNamedFamily( 1011 mParser, "", null, true); 1012 FontConfig.FontFamily fontFamily = namedFamilyList.getFamilies().get(0); 1013 List<FontUpdateRequest.Font> fonts = new ArrayList<>(); 1014 for (FontConfig.Font font : fontFamily.getFontList()) { 1015 String name = font.getFile().getName(); 1016 String psName = name.substring(0, name.length() - 4); // drop suffix 1017 FontUpdateRequest.Font updateFont = new FontUpdateRequest.Font( 1018 psName, font.getStyle(), font.getTtcIndex(), font.getFontVariationSettings()); 1019 fonts.add(updateFont); 1020 } 1021 FontUpdateRequest.Family family = new FontUpdateRequest.Family( 1022 namedFamilyList.getName(), fonts); 1023 return new FontUpdateRequest(family); 1024 } 1025 readConfig(File file)1026 private static PersistentSystemFontConfig.Config readConfig(File file) throws Exception { 1027 PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config(); 1028 try (InputStream is = new FileInputStream(file)) { 1029 PersistentSystemFontConfig.loadFromXml(is, config); 1030 } 1031 return config; 1032 } 1033 writeConfig(PersistentSystemFontConfig.Config config, File file)1034 private static void writeConfig(PersistentSystemFontConfig.Config config, 1035 File file) throws IOException { 1036 try (FileOutputStream fos = new FileOutputStream(file)) { 1037 PersistentSystemFontConfig.writeToXml(fos, config); 1038 } 1039 } 1040 1041 // Returns the last family with the given name, which will be used for creating Typeface. getLastFamily(FontConfig fontConfig, String familyName)1042 private static FontConfig.FontFamily getLastFamily(FontConfig fontConfig, String familyName) { 1043 List<FontConfig.NamedFamilyList> namedFamilyLists = fontConfig.getNamedFamilyLists(); 1044 for (int i = namedFamilyLists.size() - 1; i >= 0; i--) { 1045 if (familyName.equals(namedFamilyLists.get(i).getName())) { 1046 return namedFamilyLists.get(i).getFamilies().get(0); 1047 } 1048 } 1049 return null; 1050 } 1051 assertNamedFamilyExists(FontConfig fontConfig, String familyName)1052 private static void assertNamedFamilyExists(FontConfig fontConfig, String familyName) { 1053 assertThat(fontConfig.getNamedFamilyLists().stream() 1054 .map(FontConfig.NamedFamilyList::getName) 1055 .collect(Collectors.toSet())).contains(familyName); 1056 } 1057 } 1058