• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.graphics.fonts;
18 
19 import static com.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.platform.test.flag.junit.CheckFlagsRule;
34 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
35 import android.system.Os;
36 import android.text.FontConfig;
37 import android.util.Xml;
38 
39 import androidx.test.InstrumentationRegistry;
40 import androidx.test.filters.SmallTest;
41 import androidx.test.runner.AndroidJUnit4;
42 
43 import org.junit.After;
44 import org.junit.Before;
45 import org.junit.Rule;
46 import org.junit.Test;
47 import org.junit.runner.RunWith;
48 import org.xmlpull.v1.XmlPullParser;
49 
50 import java.io.ByteArrayInputStream;
51 import java.io.File;
52 import java.io.FileInputStream;
53 import java.io.FileOutputStream;
54 import java.io.IOException;
55 import java.io.InputStream;
56 import java.nio.charset.StandardCharsets;
57 import java.util.ArrayList;
58 import java.util.Arrays;
59 import java.util.Collections;
60 import java.util.HashSet;
61 import java.util.List;
62 import java.util.Map;
63 import java.util.Set;
64 import java.util.function.Function;
65 import java.util.function.Supplier;
66 import java.util.stream.Collectors;
67 
68 @Presubmit
69 @SmallTest
70 @RunWith(AndroidJUnit4.class)
71 public final class UpdatableFontDirTest {
72 
73     private static final String LEGACY_FONTS_XML = "/system/etc/fonts.xml";
74 
75     @Rule
76     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
77 
78     /**
79      * A {@link UpdatableFontDir.FontFileParser} for testing. Instead of using real font files,
80      * this test uses fake font files. A fake font file has its PostScript naem and revision as the
81      * file content.
82      */
83     private static class FakeFontFileParser implements UpdatableFontDir.FontFileParser {
84         @Override
getPostScriptName(File file)85         public String getPostScriptName(File file) throws IOException {
86             String content = FileUtils.readTextFile(file, 100, "");
87             return content.split(",")[2];
88         }
89 
90         @Override
buildFontFileName(File file)91         public String buildFontFileName(File file) throws IOException {
92             String content = FileUtils.readTextFile(file, 100, "");
93             return content.split(",")[0];
94         }
95 
96         @Override
getRevision(File file)97         public long getRevision(File file) throws IOException {
98             String content = FileUtils.readTextFile(file, 100, "");
99             return Long.parseLong(content.split(",")[1]);
100         }
101 
102         @Override
tryToCreateTypeface(File file)103         public void tryToCreateTypeface(File file) throws Throwable {
104         }
105     }
106 
107     // FakeFsverityUtil will successfully set up fake fs-verity if the signature is GOOD_SIGNATURE.
108     private static final String GOOD_SIGNATURE = "Good signature";
109 
110     /** A fake FsverityUtil to keep fake verity bit in memory. */
111     private static class FakeFsverityUtil implements UpdatableFontDir.FsverityUtil {
112         private final Set<String> mHasFsverityPaths = new HashSet<>();
113 
remove(String name)114         public void remove(String name) {
115             mHasFsverityPaths.remove(name);
116         }
117 
118         @Override
isFromTrustedProvider(String path, byte[] signature)119         public boolean isFromTrustedProvider(String path, byte[] signature) {
120             if (!mHasFsverityPaths.contains(path)) {
121                 return false;
122             }
123             String fakeSignature = new String(signature, StandardCharsets.UTF_8);
124             return GOOD_SIGNATURE.equals(fakeSignature);
125         }
126 
127         @Override
setUpFsverity(String path)128         public void setUpFsverity(String path) throws IOException {
129             mHasFsverityPaths.add(path);
130         }
131 
132         @Override
rename(File src, File dest)133         public boolean rename(File src, File dest) {
134             if (src.renameTo(dest)) {
135                 mHasFsverityPaths.remove(src.getAbsolutePath());
136                 mHasFsverityPaths.add(dest.getAbsolutePath());
137                 return true;
138             }
139             return false;
140         }
141     }
142 
143     private static final long CURRENT_TIME = 1234567890L;
144 
145     private File mCacheDir;
146     private File mUpdatableFontFilesDir;
147     private File mConfigFile;
148     private List<File> mPreinstalledFontDirs;
149     private final Supplier<Long> mCurrentTimeSupplier = () -> CURRENT_TIME;
150     private final Function<Map<String, File>, FontConfig> mConfigSupplier =
151             (map) -> SystemFonts.getSystemFontConfigForTesting(LEGACY_FONTS_XML, map, 0, 0);
152     private FakeFontFileParser mParser;
153     private FakeFsverityUtil mFakeFsverityUtil;
154 
155     @SuppressWarnings("ResultOfMethodCallIgnored")
156     @Before
setUp()157     public void setUp() {
158         Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
159         mCacheDir = new File(context.getCacheDir(), "UpdatableFontDirTest");
160         FileUtils.deleteContentsAndDir(mCacheDir);
161         mCacheDir.mkdirs();
162         mUpdatableFontFilesDir = new File(mCacheDir, "updatable_fonts");
163         mUpdatableFontFilesDir.mkdir();
164         mPreinstalledFontDirs = new ArrayList<>();
165         mPreinstalledFontDirs.add(new File(mCacheDir, "system_fonts"));
166         mPreinstalledFontDirs.add(new File(mCacheDir, "product_fonts"));
167         for (File dir : mPreinstalledFontDirs) {
168             dir.mkdir();
169         }
170         mConfigFile = new File(mCacheDir, "config.xml");
171         mParser = new FakeFontFileParser();
172         mFakeFsverityUtil = new FakeFsverityUtil();
173     }
174 
175     @After
tearDown()176     public void tearDown() {
177         FileUtils.deleteContentsAndDir(mCacheDir);
178     }
179 
180     @Test
construct()181     public void construct() throws Exception {
182         long expectedModifiedDate = CURRENT_TIME / 2;
183         PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config();
184         config.lastModifiedMillis = expectedModifiedDate;
185         writeConfig(config, mConfigFile);
186         UpdatableFontDir dirForPreparation = new UpdatableFontDir(
187                 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
188                 mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
189         dirForPreparation.loadFontFileMap();
190         assertThat(dirForPreparation.getSystemFontConfig().getLastModifiedTimeMillis())
191                 .isEqualTo(expectedModifiedDate);
192         dirForPreparation.update(Arrays.asList(
193                 newFontUpdateRequest("foo.ttf,1,foo", GOOD_SIGNATURE),
194                 newFontUpdateRequest("bar.ttf,2,bar", GOOD_SIGNATURE),
195                 newFontUpdateRequest("foo.ttf,3,foo", GOOD_SIGNATURE),
196                 newFontUpdateRequest("bar.ttf,4,bar", GOOD_SIGNATURE),
197                 newAddFontFamilyRequest("<family name='foobar'>"
198                         + "  <font>foo.ttf</font>"
199                         + "  <font>bar.ttf</font>"
200                         + "</family>")));
201         // Verifies that getLastModifiedTimeMillis() returns the value of currentTimeMillis.
202         assertThat(dirForPreparation.getSystemFontConfig().getLastModifiedTimeMillis())
203                 .isEqualTo(CURRENT_TIME);
204         // Four font dirs are created.
205         assertThat(mUpdatableFontFilesDir.list()).hasLength(4);
206         assertThat(dirForPreparation.getSystemFontConfig().getLastModifiedTimeMillis())
207                 .isNotEqualTo(expectedModifiedDate);
208 
209         UpdatableFontDir dir = new UpdatableFontDir(
210                 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
211                 mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
212         dir.loadFontFileMap();
213         assertThat(dir.getPostScriptMap()).containsKey("foo");
214         assertThat(mParser.getRevision(dir.getPostScriptMap().get("foo"))).isEqualTo(3);
215         assertThat(dir.getPostScriptMap()).containsKey("bar");
216         assertThat(mParser.getRevision(dir.getPostScriptMap().get("bar"))).isEqualTo(4);
217         // Outdated font dir should be deleted.
218         assertThat(mUpdatableFontFilesDir.list()).hasLength(2);
219         assertNamedFamilyExists(dir.getSystemFontConfig(), "foobar");
220         assertThat(dir.getFontFamilyMap()).containsKey("foobar");
221         assertThat(dir.getFontFamilyMap().get("foobar").getFamilies().size()).isEqualTo(1);
222         FontConfig.FontFamily foobar = dir.getFontFamilyMap().get("foobar").getFamilies().get(0);
223         assertThat(foobar.getFontList()).hasSize(2);
224         assertThat(foobar.getFontList().get(0).getFile())
225                 .isEqualTo(dir.getPostScriptMap().get("foo"));
226         assertThat(foobar.getFontList().get(1).getFile())
227                 .isEqualTo(dir.getPostScriptMap().get("bar"));
228     }
229 
230     @Test
construct_empty()231     public void construct_empty() {
232         UpdatableFontDir dir = new UpdatableFontDir(
233                 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
234                 mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
235         dir.loadFontFileMap();
236         assertThat(dir.getPostScriptMap()).isEmpty();
237         assertThat(dir.getFontFamilyMap()).isEmpty();
238     }
239 
240     @Test
construct_missingFsverity()241     public void construct_missingFsverity() throws Exception {
242         UpdatableFontDir dirForPreparation = new UpdatableFontDir(
243                 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
244                 mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
245         dirForPreparation.loadFontFileMap();
246         dirForPreparation.update(Arrays.asList(
247                 newFontUpdateRequest("foo.ttf,1,foo", GOOD_SIGNATURE),
248                 newFontUpdateRequest("bar.ttf,2,bar", GOOD_SIGNATURE),
249                 newFontUpdateRequest("foo.ttf,3,foo", GOOD_SIGNATURE),
250                 newFontUpdateRequest("bar.ttf,4,bar", GOOD_SIGNATURE),
251                 newAddFontFamilyRequest("<family name='foobar'>"
252                         + "  <font>foo.ttf</font>"
253                         + "  <font>bar.ttf</font>"
254                         + "</family>")));
255         // Four font dirs are created.
256         assertThat(mUpdatableFontFilesDir.list()).hasLength(4);
257 
258         mFakeFsverityUtil.remove(
259                 dirForPreparation.getPostScriptMap().get("foo").getAbsolutePath());
260         UpdatableFontDir dir = new UpdatableFontDir(
261                 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
262                 mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
263         dir.loadFontFileMap();
264         assertThat(dir.getPostScriptMap()).isEmpty();
265         // All font dirs (including dir for "bar.ttf") should be deleted.
266         assertThat(mUpdatableFontFilesDir.list()).hasLength(0);
267         assertThat(dir.getFontFamilyMap()).isEmpty();
268     }
269 
270     @Test
construct_fontNameMismatch()271     public void construct_fontNameMismatch() throws Exception {
272         UpdatableFontDir dirForPreparation = new UpdatableFontDir(
273                 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
274                 mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
275         dirForPreparation.loadFontFileMap();
276         dirForPreparation.update(Arrays.asList(
277                 newFontUpdateRequest("foo.ttf,1,foo", GOOD_SIGNATURE),
278                 newFontUpdateRequest("bar.ttf,2,bar", GOOD_SIGNATURE),
279                 newFontUpdateRequest("foo.ttf,3,foo", GOOD_SIGNATURE),
280                 newFontUpdateRequest("bar.ttf,4,bar", GOOD_SIGNATURE),
281                 newAddFontFamilyRequest("<family name='foobar'>"
282                         + "  <font>foo.ttf</font>"
283                         + "  <font>bar.ttf</font>"
284                         + "</family>")));
285         // Four font dirs are created.
286         assertThat(mUpdatableFontFilesDir.list()).hasLength(4);
287 
288         // Overwrite "foo.ttf" with wrong contents.
289         FileUtils.stringToFile(dirForPreparation.getPostScriptMap().get("foo"), "bar,4");
290 
291         UpdatableFontDir dir = new UpdatableFontDir(
292                 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
293                 mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
294         dir.loadFontFileMap();
295         assertThat(dir.getPostScriptMap()).isEmpty();
296         // All font dirs (including dir for "bar.ttf") should be deleted.
297         assertThat(mUpdatableFontFilesDir.list()).hasLength(0);
298         assertThat(dir.getFontFamilyMap()).isEmpty();
299     }
300 
301     @Test
construct_missingSignatureFile()302     public void construct_missingSignatureFile() throws Exception {
303         UpdatableFontDir dirForPreparation = new UpdatableFontDir(
304                 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
305                 mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
306         dirForPreparation.loadFontFileMap();
307         dirForPreparation.update(Arrays.asList(
308                 newFontUpdateRequest("foo.ttf,1,foo", GOOD_SIGNATURE)));
309         assertThat(mUpdatableFontFilesDir.list()).hasLength(1);
310 
311         // Remove signature file next to the font file.
312         File fontDir = dirForPreparation.getPostScriptMap().get("foo");
313         File sigFile = new File(fontDir.getParentFile(), "font.fsv_sig");
314         assertThat(sigFile.exists()).isTrue();
315         sigFile.delete();
316 
317         UpdatableFontDir dir = new UpdatableFontDir(
318                 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
319                 mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
320         dir.loadFontFileMap();
321         // The font file should be removed and should not be loaded.
322         assertThat(dir.getPostScriptMap()).isEmpty();
323         assertThat(mUpdatableFontFilesDir.list()).hasLength(0);
324         assertThat(dir.getFontFamilyMap()).isEmpty();
325     }
326 
327     @Test
construct_olderThanPreinstalledFont()328     public void construct_olderThanPreinstalledFont() throws Exception {
329         Function<Map<String, File>, FontConfig> configSupplier = (map) -> {
330             FontConfig.Font fooFont = new FontConfig.Font(
331                     new File(mPreinstalledFontDirs.get(0), "foo.ttf"), null, "foo",
332                     new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT), 0, null, null,
333                     FontConfig.Font.VAR_TYPE_AXES_NONE);
334             FontConfig.Font barFont = new FontConfig.Font(
335                     new File(mPreinstalledFontDirs.get(1), "bar.ttf"), null, "bar",
336                     new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT), 0, null, null,
337                     FontConfig.Font.VAR_TYPE_AXES_NONE);
338 
339             FontConfig.FontFamily family = new FontConfig.FontFamily(
340                     Arrays.asList(fooFont, barFont), null,
341                     FontConfig.FontFamily.VARIANT_DEFAULT);
342             return new FontConfig(Collections.emptyList(),
343                     Collections.emptyList(),
344                     Collections.singletonList(new FontConfig.NamedFamilyList(
345                             Collections.singletonList(family), "sans-serif")),
346                     Collections.emptyList(), 0, 1);
347         };
348 
349         UpdatableFontDir dirForPreparation = new UpdatableFontDir(
350                 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
351                 mConfigFile, mCurrentTimeSupplier, configSupplier);
352         dirForPreparation.loadFontFileMap();
353         dirForPreparation.update(Arrays.asList(
354                 newFontUpdateRequest("foo.ttf,1,foo", GOOD_SIGNATURE),
355                 newFontUpdateRequest("bar.ttf,2,bar", GOOD_SIGNATURE),
356                 newFontUpdateRequest("foo.ttf,3,foo", GOOD_SIGNATURE),
357                 newFontUpdateRequest("bar.ttf,4,bar", GOOD_SIGNATURE),
358                 newAddFontFamilyRequest("<family name='foobar'>"
359                         + "  <font>foo.ttf</font>"
360                         + "  <font>bar.ttf</font>"
361                         + "</family>")));
362         // Four font dirs are created.
363         assertThat(mUpdatableFontFilesDir.list()).hasLength(4);
364 
365         // Add preinstalled fonts.
366         FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(0), "foo.ttf"), "foo,5,foo");
367         FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(1), "bar.ttf"), "bar,1,bar");
368         FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(1), "bar.ttf"), "bar,2,bar");
369         UpdatableFontDir dir = new UpdatableFontDir(
370                 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
371                 mConfigFile, mCurrentTimeSupplier, configSupplier);
372         dir.loadFontFileMap();
373         // For foo.ttf, preinstalled font (revision 5) should be used.
374         assertThat(dir.getPostScriptMap()).doesNotContainKey("foo");
375         // For bar.ttf, updated font (revision 4) should be used.
376         assertThat(dir.getPostScriptMap()).containsKey("bar");
377         assertThat(mParser.getRevision(dir.getPostScriptMap().get("bar"))).isEqualTo(4);
378         // Outdated font dir should be deleted.
379         // We don't delete bar.ttf in this case, because it's normal that OTA updates preinstalled
380         // fonts.
381         assertThat(mUpdatableFontFilesDir.list()).hasLength(1);
382         // Font family depending on obsoleted font should be removed.
383         assertThat(dir.getFontFamilyMap()).isEmpty();
384     }
385 
386     @Test
construct_failedToLoadConfig()387     public void construct_failedToLoadConfig() throws Exception {
388         UpdatableFontDir dir = new UpdatableFontDir(
389                 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
390                 new File("/dev/null"), mCurrentTimeSupplier, mConfigSupplier);
391         dir.loadFontFileMap();
392         assertThat(dir.getPostScriptMap()).isEmpty();
393         assertThat(dir.getFontFamilyMap()).isEmpty();
394     }
395 
396     @Test
construct_afterBatchFailure()397     public void construct_afterBatchFailure() throws Exception {
398         UpdatableFontDir dirForPreparation = new UpdatableFontDir(
399                 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
400                 mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
401         dirForPreparation.loadFontFileMap();
402         dirForPreparation.update(Arrays.asList(
403                 newFontUpdateRequest("foo.ttf,1,foo", GOOD_SIGNATURE),
404                 newAddFontFamilyRequest("<family name='foobar'>"
405                         + "  <font>foo.ttf</font>"
406                         + "</family>")));
407         try {
408             dirForPreparation.update(Arrays.asList(
409                     newFontUpdateRequest("foo.ttf,2,foo", GOOD_SIGNATURE),
410                     newFontUpdateRequest("bar.ttf,2,bar", "Invalid signature"),
411                     newAddFontFamilyRequest("<family name='foobar'>"
412                             + "  <font>foo.ttf</font>"
413                             + "  <font>bar.ttf</font>"
414                             + "</family>")));
415             fail("Batch update with invalid signature should fail");
416         } catch (FontManagerService.SystemFontException e) {
417             // Expected
418         }
419 
420         UpdatableFontDir dir = new UpdatableFontDir(
421                 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
422                 mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
423         dir.loadFontFileMap();
424         // The state should be rolled back as a whole if one of the update requests fail.
425         assertThat(dir.getPostScriptMap()).containsKey("foo");
426         assertThat(mParser.getRevision(dir.getPostScriptMap().get("foo"))).isEqualTo(1);
427         assertThat(dir.getFontFamilyMap()).containsKey("foobar");
428         assertThat(dir.getFontFamilyMap().get("foobar").getFamilies().size()).isEqualTo(1);
429         FontConfig.FontFamily foobar = dir.getFontFamilyMap().get("foobar").getFamilies().get(0);
430         assertThat(foobar.getFontList()).hasSize(1);
431         assertThat(foobar.getFontList().get(0).getFile())
432                 .isEqualTo(dir.getPostScriptMap().get("foo"));
433     }
434 
435     @Test
loadFontFileMap_twice()436     public void loadFontFileMap_twice() throws Exception {
437         UpdatableFontDir dir = new UpdatableFontDir(
438                 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
439                 mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
440         dir.loadFontFileMap();
441         dir.update(Collections.singletonList(newFontUpdateRequest("test.ttf,1,test",
442                 GOOD_SIGNATURE)));
443         assertThat(dir.getPostScriptMap()).containsKey("test");
444         File fontFile = dir.getPostScriptMap().get("test");
445         dir.loadFontFileMap();
446         assertThat(dir.getPostScriptMap().get("test")).isEqualTo(fontFile);
447     }
448 
449     @Test
installFontFile()450     public void installFontFile() throws Exception {
451         UpdatableFontDir dir = new UpdatableFontDir(
452                 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
453                 mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
454         dir.loadFontFileMap();
455 
456         dir.update(Collections.singletonList(newFontUpdateRequest("test.ttf,1,test",
457                 GOOD_SIGNATURE)));
458         assertThat(dir.getPostScriptMap()).containsKey("test");
459         assertThat(mParser.getRevision(dir.getPostScriptMap().get("test"))).isEqualTo(1);
460         File fontFile = dir.getPostScriptMap().get("test");
461         assertThat(Os.stat(fontFile.getAbsolutePath()).st_mode & 0777).isEqualTo(0644);
462         File fontDir = fontFile.getParentFile();
463         assertThat(Os.stat(fontDir.getAbsolutePath()).st_mode & 0777).isEqualTo(0711);
464     }
465 
466     @Test
installFontFile_upgrade()467     public void installFontFile_upgrade() throws Exception {
468         UpdatableFontDir dir = new UpdatableFontDir(
469                 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
470                 mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
471         dir.loadFontFileMap();
472 
473         dir.update(Collections.singletonList(newFontUpdateRequest("test.ttf,1,test",
474                 GOOD_SIGNATURE)));
475         Map<String, File> mapBeforeUpgrade = dir.getPostScriptMap();
476         dir.update(Collections.singletonList(newFontUpdateRequest("test.ttf,2,test",
477                 GOOD_SIGNATURE)));
478         assertThat(dir.getPostScriptMap()).containsKey("test");
479         assertThat(mParser.getRevision(dir.getPostScriptMap().get("test"))).isEqualTo(2);
480         assertThat(mapBeforeUpgrade).containsKey("test");
481         assertWithMessage("Older fonts should not be deleted until next loadFontFileMap")
482                 .that(mParser.getRevision(mapBeforeUpgrade.get("test"))).isEqualTo(1);
483         // Check that updatedFontDirs is pruned.
484         assertWithMessage("config.updatedFontDirs should only list latest active dirs")
485                 .that(readConfig(mConfigFile).updatedFontDirs)
486                 .containsExactly(dir.getPostScriptMap().get("test").getParentFile().getName());
487     }
488 
489     @Test
installFontFile_systemFontHasPSNameDifferentFromFileName()490     public void installFontFile_systemFontHasPSNameDifferentFromFileName() throws Exception {
491 
492         // Setup the environment that the system installed font file named "foo.ttf" has PostScript
493         // name "bar".
494         File file = new File(mPreinstalledFontDirs.get(0), "foo.ttf");
495         FileUtils.stringToFile(file, "foo.ttf,1,bar");
496         UpdatableFontDir dir = new UpdatableFontDir(
497                 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
498                 mConfigFile, mCurrentTimeSupplier, (map) -> {
499             FontConfig.Font font = new FontConfig.Font(
500                     file, null, "bar", new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT),
501                     0, null, null, FontConfig.Font.VAR_TYPE_AXES_NONE);
502             FontConfig.FontFamily family = new FontConfig.FontFamily(
503                     Collections.singletonList(font), null, FontConfig.FontFamily.VARIANT_DEFAULT);
504             return new FontConfig(
505                     Collections.emptyList(),
506                     Collections.emptyList(),
507                     Collections.singletonList(new FontConfig.NamedFamilyList(
508                             Collections.singletonList(family), "sans-serif")),
509                     Collections.emptyList(), 0, 1);
510         });
511         dir.loadFontFileMap();
512 
513         dir.update(Collections.singletonList(newFontUpdateRequest("bar.ttf,2,bar",
514                 GOOD_SIGNATURE)));
515         assertThat(dir.getPostScriptMap()).containsKey("bar");
516         assertThat(dir.getPostScriptMap().size()).isEqualTo(1);
517         assertThat(mParser.getRevision(dir.getPostScriptMap().get("bar"))).isEqualTo(2);
518         File fontFile = dir.getPostScriptMap().get("bar");
519         assertThat(Os.stat(fontFile.getAbsolutePath()).st_mode & 0777).isEqualTo(0644);
520         File fontDir = fontFile.getParentFile();
521         assertThat(Os.stat(fontDir.getAbsolutePath()).st_mode & 0777).isEqualTo(0711);
522     }
523 
524     @Test
installFontFile_sameVersion()525     public void installFontFile_sameVersion() throws Exception {
526         UpdatableFontDir dir = new UpdatableFontDir(
527                 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
528                 mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
529         dir.loadFontFileMap();
530 
531         dir.update(Collections.singletonList(newFontUpdateRequest("test.ttf,1,test",
532                 GOOD_SIGNATURE)));
533         dir.update(Collections.singletonList(newFontUpdateRequest("test.ttf,1,test",
534                 GOOD_SIGNATURE)));
535         assertThat(dir.getPostScriptMap()).containsKey("test");
536         assertThat(mParser.getRevision(dir.getPostScriptMap().get("test"))).isEqualTo(1);
537     }
538 
539     @Test
installFontFile_downgrade()540     public void installFontFile_downgrade() throws Exception {
541         UpdatableFontDir dir = new UpdatableFontDir(
542                 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
543                 mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
544         dir.loadFontFileMap();
545 
546         dir.update(Collections.singletonList(newFontUpdateRequest("test.ttf,2,test",
547                 GOOD_SIGNATURE)));
548         try {
549             dir.update(Collections.singletonList(newFontUpdateRequest("test.ttf,1,test",
550                     GOOD_SIGNATURE)));
551             fail("Expect SystemFontException");
552         } catch (FontManagerService.SystemFontException e) {
553             assertThat(e.getErrorCode()).isEqualTo(FontManager.RESULT_ERROR_DOWNGRADING);
554         }
555         assertThat(dir.getPostScriptMap()).containsKey("test");
556         assertWithMessage("Font should not be downgraded to an older revision")
557                 .that(mParser.getRevision(dir.getPostScriptMap().get("test"))).isEqualTo(2);
558         // Check that updatedFontDirs is not updated.
559         assertWithMessage("config.updatedFontDirs should only list latest active dirs")
560                 .that(readConfig(mConfigFile).updatedFontDirs)
561                 .containsExactly(dir.getPostScriptMap().get("test").getParentFile().getName());
562     }
563 
564     @Test
installFontFile_multiple()565     public void installFontFile_multiple() throws Exception {
566         UpdatableFontDir dir = new UpdatableFontDir(
567                 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
568                 mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
569         dir.loadFontFileMap();
570 
571         dir.update(Collections.singletonList(newFontUpdateRequest("foo.ttf,1,foo",
572                 GOOD_SIGNATURE)));
573         dir.update(Collections.singletonList(newFontUpdateRequest("bar.ttf,2,bar",
574                 GOOD_SIGNATURE)));
575         assertThat(dir.getPostScriptMap()).containsKey("foo");
576         assertThat(mParser.getRevision(dir.getPostScriptMap().get("foo"))).isEqualTo(1);
577         assertThat(dir.getPostScriptMap()).containsKey("bar");
578         assertThat(mParser.getRevision(dir.getPostScriptMap().get("bar"))).isEqualTo(2);
579     }
580 
581     @Test
installFontFile_batch()582     public void installFontFile_batch() throws Exception {
583         UpdatableFontDir dir = new UpdatableFontDir(
584                 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
585                 mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
586         dir.loadFontFileMap();
587 
588         dir.update(Arrays.asList(
589                 newFontUpdateRequest("foo.ttf,1,foo", GOOD_SIGNATURE),
590                 newFontUpdateRequest("bar.ttf,2,bar", GOOD_SIGNATURE)));
591         assertThat(dir.getPostScriptMap()).containsKey("foo");
592         assertThat(mParser.getRevision(dir.getPostScriptMap().get("foo"))).isEqualTo(1);
593         assertThat(dir.getPostScriptMap()).containsKey("bar");
594         assertThat(mParser.getRevision(dir.getPostScriptMap().get("bar"))).isEqualTo(2);
595     }
596 
597     @Test
installFontFile_invalidSignature()598     public void installFontFile_invalidSignature() throws Exception {
599         UpdatableFontDir dir = new UpdatableFontDir(
600                 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
601                 mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
602         dir.loadFontFileMap();
603 
604         try {
605             dir.update(
606                     Collections.singletonList(newFontUpdateRequest("test.ttf,1,test",
607                             "Invalid signature")));
608             fail("Expect SystemFontException");
609         } catch (FontManagerService.SystemFontException e) {
610             assertThat(e.getErrorCode())
611                     .isEqualTo(FontManager.RESULT_ERROR_VERIFICATION_FAILURE);
612         }
613         assertThat(dir.getPostScriptMap()).isEmpty();
614     }
615 
616     @Test
installFontFile_preinstalled_upgrade()617     public void installFontFile_preinstalled_upgrade() throws Exception {
618         FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(0), "test.ttf"),
619                 "test.ttf,1,test");
620         UpdatableFontDir dir = new UpdatableFontDir(
621                 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
622                 mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
623         dir.loadFontFileMap();
624 
625         dir.update(Collections.singletonList(newFontUpdateRequest("test.ttf,2,test",
626                 GOOD_SIGNATURE)));
627         assertThat(dir.getPostScriptMap()).containsKey("test");
628         assertThat(mParser.getRevision(dir.getPostScriptMap().get("test"))).isEqualTo(2);
629     }
630 
631     @Test
installFontFile_preinstalled_sameVersion()632     public void installFontFile_preinstalled_sameVersion() throws Exception {
633         FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(0), "test.ttf"),
634                 "test.ttf,1,test");
635         UpdatableFontDir dir = new UpdatableFontDir(
636                 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
637                 mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
638         dir.loadFontFileMap();
639 
640         dir.update(Collections.singletonList(newFontUpdateRequest("test.ttf,1,test",
641                 GOOD_SIGNATURE)));
642         assertThat(dir.getPostScriptMap()).containsKey("test");
643         assertThat(mParser.getRevision(dir.getPostScriptMap().get("test"))).isEqualTo(1);
644     }
645 
646     @Test
installFontFile_preinstalled_downgrade()647     public void installFontFile_preinstalled_downgrade() throws Exception {
648         File file = new File(mPreinstalledFontDirs.get(0), "test.ttf");
649         FileUtils.stringToFile(file, "test.ttf,2,test");
650         UpdatableFontDir dir = new UpdatableFontDir(
651                 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
652                 mConfigFile, mCurrentTimeSupplier, (map) -> {
653             FontConfig.Font font = new FontConfig.Font(
654                     file, null, "test", new FontStyle(400, FontStyle.FONT_SLANT_UPRIGHT), 0, null,
655                     null, FontConfig.Font.VAR_TYPE_AXES_NONE);
656             FontConfig.FontFamily family = new FontConfig.FontFamily(
657                     Collections.singletonList(font), null, FontConfig.FontFamily.VARIANT_DEFAULT);
658             return new FontConfig(Collections.emptyList(), Collections.emptyList(),
659                     Collections.singletonList(new FontConfig.NamedFamilyList(
660                             Collections.singletonList(family), "sans-serif")),
661                     Collections.emptyList(), 0, 1);
662         });
663         dir.loadFontFileMap();
664 
665         try {
666             dir.update(Collections.singletonList(newFontUpdateRequest("test.ttf,1,test",
667                     GOOD_SIGNATURE)));
668             fail("Expect SystemFontException");
669         } catch (FontManagerService.SystemFontException e) {
670             assertThat(e.getErrorCode()).isEqualTo(FontManager.RESULT_ERROR_DOWNGRADING);
671         }
672         assertThat(dir.getPostScriptMap()).isEmpty();
673     }
674 
675     @Test
installFontFile_failedToWriteConfigXml()676     public void installFontFile_failedToWriteConfigXml() throws Exception {
677         long expectedModifiedDate = 1234567890;
678         FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(0), "test.ttf"),
679                 "test.ttf,1,test");
680 
681         File readonlyDir = new File(mCacheDir, "readonly");
682         assertThat(readonlyDir.mkdir()).isTrue();
683         File readonlyFile = new File(readonlyDir, "readonly_config.xml");
684 
685         PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config();
686         config.lastModifiedMillis = expectedModifiedDate;
687         writeConfig(config, readonlyFile);
688 
689         assertThat(readonlyDir.setWritable(false, false)).isTrue();
690         try {
691             UpdatableFontDir dir = new UpdatableFontDir(
692                     mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
693                     readonlyFile, mCurrentTimeSupplier, mConfigSupplier);
694             dir.loadFontFileMap();
695 
696             try {
697                 dir.update(
698                         Collections.singletonList(newFontUpdateRequest("test.ttf,2,test",
699                                 GOOD_SIGNATURE)));
700             } catch (FontManagerService.SystemFontException e) {
701                 assertThat(e.getErrorCode())
702                         .isEqualTo(FontManager.RESULT_ERROR_FAILED_UPDATE_CONFIG);
703             }
704             assertThat(dir.getSystemFontConfig().getLastModifiedTimeMillis())
705                     .isEqualTo(expectedModifiedDate);
706             assertThat(dir.getPostScriptMap()).isEmpty();
707         } finally {
708             assertThat(readonlyDir.setWritable(true, true)).isTrue();
709         }
710     }
711 
712     @Test
installFontFile_failedToParsePostScript()713     public void installFontFile_failedToParsePostScript() throws Exception {
714         UpdatableFontDir dir = new UpdatableFontDir(
715                 mUpdatableFontFilesDir,
716                 new UpdatableFontDir.FontFileParser() {
717 
718                     @Override
719                     public String getPostScriptName(File file) throws IOException {
720                         return null;
721                     }
722 
723                     @Override
724                     public String buildFontFileName(File file) throws IOException {
725                         return null;
726                     }
727 
728                     @Override
729                     public long getRevision(File file) throws IOException {
730                         return 0;
731                     }
732 
733                     @Override
734                     public void tryToCreateTypeface(File file) throws IOException {
735                     }
736                 }, mFakeFsverityUtil, mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
737         dir.loadFontFileMap();
738 
739         try {
740             dir.update(Collections.singletonList(newFontUpdateRequest("foo.ttf,1,foo",
741                     GOOD_SIGNATURE)));
742             fail("Expect SystemFontException");
743         } catch (FontManagerService.SystemFontException e) {
744             assertThat(e.getErrorCode())
745                     .isEqualTo(FontManager.RESULT_ERROR_INVALID_FONT_NAME);
746         }
747         assertThat(dir.getPostScriptMap()).isEmpty();
748     }
749 
750     @Test
installFontFile_failedToParsePostScriptName_invalidFont()751     public void installFontFile_failedToParsePostScriptName_invalidFont() throws Exception {
752         UpdatableFontDir dir = new UpdatableFontDir(
753                 mUpdatableFontFilesDir,
754                 new UpdatableFontDir.FontFileParser() {
755                     @Override
756                     public String getPostScriptName(File file) throws IOException {
757                         throw new IOException();
758                     }
759 
760                     @Override
761                     public String buildFontFileName(File file) throws IOException {
762                         throw new IOException();
763                     }
764 
765                     @Override
766                     public long getRevision(File file) throws IOException {
767                         return 0;
768                     }
769 
770                     @Override
771                     public void tryToCreateTypeface(File file) throws IOException {
772                     }
773                 }, mFakeFsverityUtil, mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
774         dir.loadFontFileMap();
775 
776         try {
777             dir.update(Collections.singletonList(newFontUpdateRequest("foo.ttf,1,foo",
778                     GOOD_SIGNATURE)));
779             fail("Expect SystemFontException");
780         } catch (FontManagerService.SystemFontException e) {
781             assertThat(e.getErrorCode())
782                     .isEqualTo(FontManager.RESULT_ERROR_INVALID_FONT_FILE);
783         }
784         assertThat(dir.getPostScriptMap()).isEmpty();
785     }
786 
787     @Test
installFontFile_failedToCreateTypeface()788     public void installFontFile_failedToCreateTypeface() throws Exception {
789         UpdatableFontDir dir = new UpdatableFontDir(
790                 mUpdatableFontFilesDir,
791                 new UpdatableFontDir.FontFileParser() {
792                     @Override
793                     public String getPostScriptName(File file) throws IOException {
794                         return mParser.getPostScriptName(file);
795                     }
796 
797                     @Override
798                     public String buildFontFileName(File file) throws IOException {
799                         return mParser.buildFontFileName(file);
800                     }
801 
802                     @Override
803                     public long getRevision(File file) throws IOException {
804                         return mParser.getRevision(file);
805                     }
806 
807                     @Override
808                     public void tryToCreateTypeface(File file) throws IOException {
809                         throw new IOException();
810                     }
811                 }, mFakeFsverityUtil, mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
812         dir.loadFontFileMap();
813 
814         try {
815             dir.update(Collections.singletonList(newFontUpdateRequest("foo.ttf,1,foo",
816                     GOOD_SIGNATURE)));
817             fail("Expect SystemFontException");
818         } catch (FontManagerService.SystemFontException e) {
819             assertThat(e.getErrorCode())
820                     .isEqualTo(FontManager.RESULT_ERROR_INVALID_FONT_FILE);
821         }
822         assertThat(dir.getPostScriptMap()).isEmpty();
823     }
824 
825     @Test
installFontFile_renameToPsNameFailure()826     public void installFontFile_renameToPsNameFailure() throws Exception {
827         UpdatableFontDir.FsverityUtil fakeFsverityUtil = new UpdatableFontDir.FsverityUtil() {
828 
829             @Override
830             public boolean isFromTrustedProvider(String path, byte[] signature) {
831                 return mFakeFsverityUtil.isFromTrustedProvider(path, signature);
832             }
833 
834             @Override
835             public void setUpFsverity(String path) throws IOException {
836                 mFakeFsverityUtil.setUpFsverity(path);
837             }
838 
839             @Override
840             public boolean rename(File src, File dest) {
841                 return false;
842             }
843         };
844         UpdatableFontDir dir = new UpdatableFontDir(
845                 mUpdatableFontFilesDir, mParser, fakeFsverityUtil,
846                 mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
847         dir.loadFontFileMap();
848 
849         try {
850             dir.update(Collections.singletonList(newFontUpdateRequest("foo.ttf,1,foo",
851                     GOOD_SIGNATURE)));
852             fail("Expect SystemFontException");
853         } catch (FontManagerService.SystemFontException e) {
854             assertThat(e.getErrorCode())
855                     .isEqualTo(FontManager.RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE);
856         }
857         assertThat(dir.getPostScriptMap()).isEmpty();
858     }
859 
860     @Test
installFontFile_batchFailure()861     public void installFontFile_batchFailure() throws Exception {
862         UpdatableFontDir dir = new UpdatableFontDir(
863                 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
864                 mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
865         dir.loadFontFileMap();
866 
867         dir.update(Collections.singletonList(newFontUpdateRequest("foo.ttf,1,foo",
868                 GOOD_SIGNATURE)));
869         try {
870             dir.update(Arrays.asList(
871                     newFontUpdateRequest("foo.ttf,2,foo", GOOD_SIGNATURE),
872                     newFontUpdateRequest("bar.ttf,2,bar", "Invalid signature")));
873             fail("Batch update with invalid signature should fail");
874         } catch (FontManagerService.SystemFontException e) {
875             // Expected
876         }
877         // The state should be rolled back as a whole if one of the update requests fail.
878         assertThat(dir.getPostScriptMap()).containsKey("foo");
879         assertThat(mParser.getRevision(dir.getPostScriptMap().get("foo"))).isEqualTo(1);
880     }
881 
882     @Test
addFontFamily()883     public void addFontFamily() throws Exception {
884         UpdatableFontDir dir = new UpdatableFontDir(
885                 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
886                 mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
887         dir.loadFontFileMap();
888 
889         dir.update(Arrays.asList(
890                 newFontUpdateRequest("test.ttf,1,test", GOOD_SIGNATURE),
891                 newAddFontFamilyRequest("<family name='test'>"
892                         + "  <font>test.ttf</font>"
893                         + "</family>")));
894         assertThat(dir.getPostScriptMap()).containsKey("test");
895         assertThat(dir.getFontFamilyMap()).containsKey("test");
896         assertThat(dir.getFontFamilyMap().get("test").getFamilies().size()).isEqualTo(1);
897         FontConfig.FontFamily test = dir.getFontFamilyMap().get("test").getFamilies().get(0);
898         assertThat(test.getFontList()).hasSize(1);
899         assertThat(test.getFontList().get(0).getFile())
900                 .isEqualTo(dir.getPostScriptMap().get("test"));
901     }
902 
903     @Test(expected = IllegalArgumentException.class)
addFontFamily_noName()904     public void addFontFamily_noName() throws Exception {
905         UpdatableFontDir dir = new UpdatableFontDir(
906                 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
907                 mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
908         dir.loadFontFileMap();
909 
910         List<FontUpdateRequest> requests = Arrays.asList(
911                 newFontUpdateRequest("test.ttf,1,test", GOOD_SIGNATURE),
912                 newAddFontFamilyRequest("<family lang='en'>"
913                         + "  <font>test.ttf</font>"
914                         + "</family>"));
915         dir.update(requests);
916     }
917 
918     @Test
addFontFamily_fontNotAvailable()919     public void addFontFamily_fontNotAvailable() throws Exception {
920         UpdatableFontDir dir = new UpdatableFontDir(
921                 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
922                 mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
923         dir.loadFontFileMap();
924 
925         try {
926             dir.update(Arrays.asList(newAddFontFamilyRequest("<family name='test'>"
927                     + "  <font>test.ttf</font>"
928                     + "</family>")));
929             fail("Expect SystemFontException");
930         } catch (FontManagerService.SystemFontException e) {
931             assertThat(e.getErrorCode())
932                     .isEqualTo(FontManager.RESULT_ERROR_FONT_NOT_FOUND);
933         }
934     }
935 
936     @Test
getSystemFontConfig()937     public void getSystemFontConfig() throws Exception {
938         UpdatableFontDir dir = new UpdatableFontDir(
939                 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
940                 mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
941         dir.loadFontFileMap();
942         // We assume we have monospace.
943         assertNamedFamilyExists(dir.getSystemFontConfig(), "monospace");
944 
945         dir.update(Arrays.asList(
946                 newFontUpdateRequest("test.ttf,1,test", GOOD_SIGNATURE),
947                 // Updating an existing font family.
948                 newAddFontFamilyRequest("<family name='monospace'>"
949                         + "  <font>test.ttf</font>"
950                         + "</family>"),
951                 // Adding a new font family.
952                 newAddFontFamilyRequest("<family name='test'>"
953                         + "  <font>test.ttf</font>"
954                         + "</family>")));
955         FontConfig fontConfig = dir.getSystemFontConfig();
956         assertNamedFamilyExists(fontConfig, "monospace");
957         FontConfig.FontFamily monospace = getLastFamily(fontConfig, "monospace");
958         assertThat(monospace.getFontList()).hasSize(1);
959         assertThat(monospace.getFontList().get(0).getFile())
960                 .isEqualTo(dir.getPostScriptMap().get("test"));
961         assertNamedFamilyExists(fontConfig, "test");
962         assertThat(getLastFamily(fontConfig, "test").getFontList())
963                 .isEqualTo(monospace.getFontList());
964     }
965 
966     @Test
getSystemFontConfig_preserveFirstFontFamily()967     public void getSystemFontConfig_preserveFirstFontFamily() throws Exception {
968         UpdatableFontDir dir = new UpdatableFontDir(
969                 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
970                 mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
971         dir.loadFontFileMap();
972         assertThat(dir.getSystemFontConfig().getFontFamilies()).isNotEmpty();
973         FontConfig.FontFamily firstFontFamily = dir.getSystemFontConfig().getFontFamilies().get(0);
974 
975         dir.update(Arrays.asList(
976                 newFontUpdateRequest("test.ttf,1,test", GOOD_SIGNATURE),
977                 newAddFontFamilyRequest("<family name='sans-serif'>"
978                         + "  <font>test.ttf</font>"
979                         + "</family>")));
980         FontConfig fontConfig = dir.getSystemFontConfig();
981         assertThat(dir.getSystemFontConfig().getFontFamilies()).isNotEmpty();
982         assertThat(fontConfig.getFontFamilies().get(0)).isEqualTo(firstFontFamily);
983         FontConfig.FontFamily updated = getLastFamily(fontConfig, "sans-serif");
984         assertThat(updated.getFontList()).hasSize(1);
985         assertThat(updated.getFontList().get(0).getFile())
986                 .isEqualTo(dir.getPostScriptMap().get("test"));
987         assertThat(updated).isNotEqualTo(firstFontFamily);
988     }
989 
990     @Test
deleteAllFiles()991     public void deleteAllFiles() throws Exception {
992         FakeFontFileParser parser = new FakeFontFileParser();
993         FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
994         UpdatableFontDir dirForPreparation = new UpdatableFontDir(
995                 mUpdatableFontFilesDir, parser, fakeFsverityUtil,
996                 mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
997         dirForPreparation.loadFontFileMap();
998         dirForPreparation.update(Collections.singletonList(
999                 newFontUpdateRequest("foo.ttf,1,foo", GOOD_SIGNATURE)));
1000         assertThat(mConfigFile.exists()).isTrue();
1001         assertThat(mUpdatableFontFilesDir.list()).hasLength(1);
1002 
1003         UpdatableFontDir.deleteAllFiles(mUpdatableFontFilesDir, mConfigFile);
1004         assertThat(mConfigFile.exists()).isFalse();
1005         assertThat(mUpdatableFontFilesDir.list()).hasLength(0);
1006     }
1007 
createNewUpdateDir()1008     private UpdatableFontDir createNewUpdateDir() {
1009         UpdatableFontDir dir = new UpdatableFontDir(
1010                 mUpdatableFontFilesDir, mParser, mFakeFsverityUtil,
1011                 mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
1012         dir.loadFontFileMap();
1013         return dir;
1014     }
1015 
installTestFontFamilies(int version)1016     private UpdatableFontDir installTestFontFamilies(int version) {
1017         UpdatableFontDir dir = createNewUpdateDir();
1018         try {
1019             dir.update(Arrays.asList(
1020                     newFontUpdateRequest("foo.ttf," + version + ",foo", GOOD_SIGNATURE),
1021                     newFontUpdateRequest("bar.ttf," + version + ",bar", GOOD_SIGNATURE),
1022                     newAddFontFamilyRequest("<family name='foobar'>"
1023                             + "  <font>foo.ttf</font>"
1024                             + "  <font>bar.ttf</font>"
1025                             + "</family>")));
1026         } catch (Exception e) {
1027             throw new RuntimeException(e);
1028         }
1029         return dir;
1030     }
1031 
installTestFontFile(int numFonts, int version)1032     private UpdatableFontDir installTestFontFile(int numFonts, int version) {
1033         UpdatableFontDir dir = createNewUpdateDir();
1034         List<FontUpdateRequest> requests = new ArrayList<>();
1035         if (numFonts <= 0 || numFonts > 3) {
1036             throw new IllegalArgumentException("numFont must be 1, 2 or 3");
1037         }
1038         try {
1039             requests.add(newFontUpdateRequest("foo.ttf," + version + ",foo", GOOD_SIGNATURE));
1040             if (numFonts >= 2) {
1041                 requests.add(newFontUpdateRequest("bar.ttf," + version + ",bar", GOOD_SIGNATURE));
1042             }
1043             if (numFonts == 3) {
1044                 requests.add(newFontUpdateRequest("baz.ttf," + version + ",baz", GOOD_SIGNATURE));
1045             }
1046             dir.update(requests);
1047         } catch (Exception e) {
1048             throw new RuntimeException(e);
1049         }
1050         return dir;
1051     }
1052 
collectSignatureFiles()1053     private List<File> collectSignatureFiles() {
1054         return Arrays.stream(mUpdatableFontFilesDir.listFiles())
1055                 .map((file) -> file.listFiles((unused, s) -> s.endsWith(".fsv_sig")))
1056                 .flatMap(Arrays::stream)
1057                 .toList();
1058     }
1059 
collectFontFiles()1060     private List<File> collectFontFiles() {
1061         return Arrays.stream(mUpdatableFontFilesDir.listFiles())
1062                 .map((file) -> file.listFiles((unused, s) -> s.endsWith(".ttf")))
1063                 .flatMap(Arrays::stream)
1064                 .toList();
1065     }
1066 
removeAll(List<File> files)1067     private void removeAll(List<File> files) {
1068         files.forEach((File file) -> {
1069             if (file.isDirectory()) {
1070                 removeAll(List.of(file.listFiles()));
1071             } else {
1072                 assertThat(file.delete()).isTrue();
1073             }
1074         });
1075     }
1076 
assertTestFontFamilyInstalled(UpdatableFontDir dir, int version)1077     private void assertTestFontFamilyInstalled(UpdatableFontDir dir, int version) {
1078         try {
1079             assertNamedFamilyExists(dir.getSystemFontConfig(), "foobar");
1080             assertThat(dir.getFontFamilyMap()).containsKey("foobar");
1081             assertThat(dir.getFontFamilyMap().get("foobar").getFamilies().size()).isEqualTo(1);
1082             FontConfig.FontFamily foobar = dir.getFontFamilyMap().get("foobar").getFamilies()
1083                     .get(0);
1084             assertThat(foobar.getFontList()).hasSize(2);
1085             assertThat(foobar.getFontList().get(0).getFile())
1086                     .isEqualTo(dir.getPostScriptMap().get("foo"));
1087             assertThat(mParser.getRevision(dir.getPostScriptMap().get("foo"))).isEqualTo(version);
1088             assertThat(foobar.getFontList().get(1).getFile())
1089                     .isEqualTo(dir.getPostScriptMap().get("bar"));
1090             assertThat(mParser.getRevision(dir.getPostScriptMap().get("bar"))).isEqualTo(version);
1091         } catch (Exception e) {
1092             throw new RuntimeException(e);
1093         }
1094     }
1095 
assertTestFontInstalled(UpdatableFontDir dir, int version)1096     private void assertTestFontInstalled(UpdatableFontDir dir, int version) {
1097         try {
1098             assertThat(dir.getPostScriptMap().containsKey("foo")).isTrue();
1099             assertThat(mParser.getRevision(dir.getPostScriptMap().get("foo"))).isEqualTo(version);
1100         } catch (Exception e) {
1101             throw new RuntimeException(e);
1102         }
1103     }
1104 
1105     @Test
signatureMissingCase_fontFamilyInstalled_fontFamilyInstallLater()1106     public void signatureMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
1107         // Install font families, foo.ttf, bar.ttf.
1108         installTestFontFamilies(1 /* version */);
1109 
1110         // Delete one signature file
1111         assertThat(collectSignatureFiles().get(0).delete()).isTrue();
1112 
1113         // New instance of UpdatableFontDir, this emulate a device reboot.
1114         UpdatableFontDir dir = installTestFontFamilies(2 /* version */);
1115 
1116         // Make sure the font installation succeeds.
1117         assertTestFontFamilyInstalled(dir, 2 /* version */);
1118 
1119         // Make sure after the reboot, the configuration remains.
1120         UpdatableFontDir nextDir = createNewUpdateDir();
1121         assertTestFontFamilyInstalled(nextDir, 2 /* version */);
1122     }
1123 
1124     @Test
signatureMissingCase_fontFamilyInstalled_fontInstallLater()1125     public void signatureMissingCase_fontFamilyInstalled_fontInstallLater() {
1126         // Install font families, foo.ttf, bar.ttf.
1127         installTestFontFamilies(1);
1128 
1129         // Delete one signature file
1130         assertThat(collectSignatureFiles().get(0).delete()).isTrue();
1131 
1132         // New instance of UpdatableFontDir, this emulate a device reboot.
1133         UpdatableFontDir dir = installTestFontFile(1 /* numFonts */, 2 /* version */);
1134 
1135         // Make sure the font installation succeeds.
1136         assertTestFontInstalled(dir, 2 /* version */);
1137 
1138         // Make sure after the reboot, the configuration remains.
1139         UpdatableFontDir nextDir = createNewUpdateDir();
1140         assertTestFontInstalled(nextDir, 2 /* version */);
1141     }
1142 
1143     @Test
signatureMissingCase_fontFileInstalled_fontFamilyInstallLater()1144     public void signatureMissingCase_fontFileInstalled_fontFamilyInstallLater() {
1145         // Install font file, foo.ttf and bar.ttf
1146         installTestFontFile(2 /* numFonts */, 1 /* version */);
1147 
1148         // Delete one signature file
1149         assertThat(collectSignatureFiles().get(0).delete()).isTrue();
1150 
1151         // New instance of UpdatableFontDir, this emulate a device reboot.
1152         UpdatableFontDir dir = installTestFontFamilies(2 /* version */);
1153 
1154         // Make sure the font installation succeeds.
1155         assertTestFontFamilyInstalled(dir, 2 /* version */);
1156 
1157         // Make sure after the reboot, the configuration remains.
1158         UpdatableFontDir nextDir = createNewUpdateDir();
1159         assertTestFontFamilyInstalled(nextDir, 2 /* version */);
1160     }
1161 
1162     @Test
signatureMissingCase_fontFileInstalled_fontFileInstallLater()1163     public void signatureMissingCase_fontFileInstalled_fontFileInstallLater() {
1164         // Install font file, foo.ttf and bar.ttf
1165         installTestFontFile(2 /* numFonts */, 1 /* version */);
1166 
1167         // Delete one signature file
1168         assertThat(collectSignatureFiles().get(0).delete()).isTrue();
1169 
1170         // New instance of UpdatableFontDir, this emulate a device reboot.
1171         UpdatableFontDir dir = installTestFontFile(2 /* numFonts */, 2 /* version */);
1172 
1173         // Make sure the font installation succeeds.
1174         assertTestFontInstalled(dir, 2 /* version */);
1175 
1176         // Make sure after the reboot, the configuration remains.
1177         UpdatableFontDir nextDir = createNewUpdateDir();
1178         assertTestFontInstalled(nextDir, 2 /* version */);
1179     }
1180 
1181     @Test
signatureAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater()1182     public void signatureAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
1183         // Install font families, foo.ttf, bar.ttf.
1184         installTestFontFamilies(1 /* version */);
1185 
1186         // Delete all signature files
1187         removeAll(collectSignatureFiles());
1188 
1189         // New instance of UpdatableFontDir, this emulate a device reboot.
1190         UpdatableFontDir dir = installTestFontFamilies(2 /* version */);
1191 
1192         // Make sure the font installation succeeds.
1193         assertTestFontFamilyInstalled(dir, 2 /* version */);
1194 
1195         // Make sure after the reboot, the configuration remains.
1196         UpdatableFontDir nextDir = createNewUpdateDir();
1197         assertTestFontFamilyInstalled(nextDir, 2 /* version */);
1198     }
1199 
1200     @Test
signatureAllMissingCase_fontFamilyInstalled_fontInstallLater()1201     public void signatureAllMissingCase_fontFamilyInstalled_fontInstallLater() {
1202         // Install font families, foo.ttf, bar.ttf.
1203         installTestFontFamilies(1 /* version */);
1204 
1205         // Delete all signature files
1206         removeAll(collectSignatureFiles());
1207 
1208         // New instance of UpdatableFontDir, this emulate a device reboot.
1209         UpdatableFontDir dir = installTestFontFile(1 /* numFonts */, 2 /* version */);
1210 
1211         // Make sure the font installation succeeds.
1212         assertTestFontInstalled(dir, 2 /* version */);
1213 
1214         // Make sure after the reboot, the configuration remains.
1215         UpdatableFontDir nextDir = createNewUpdateDir();
1216         assertTestFontInstalled(nextDir, 2 /* version */);
1217     }
1218 
1219     @Test
signatureAllMissingCase_fontFileInstalled_fontFamilyInstallLater()1220     public void signatureAllMissingCase_fontFileInstalled_fontFamilyInstallLater() {
1221         // Install font file, foo.ttf
1222         installTestFontFile(1 /* numFonts */, 1 /* version */);
1223 
1224         // Delete all signature files
1225         removeAll(collectSignatureFiles());
1226 
1227         // New instance of UpdatableFontDir, this emulate a device reboot.
1228         UpdatableFontDir dir = installTestFontFamilies(2 /* version */);
1229 
1230         // Make sure the font installation succeeds.
1231         assertTestFontFamilyInstalled(dir, 2 /* version */);
1232 
1233         // Make sure after the reboot, the configuration remains.
1234         UpdatableFontDir nextDir = createNewUpdateDir();
1235         assertTestFontFamilyInstalled(nextDir, 2 /* version */);
1236     }
1237 
1238     @Test
signatureAllMissingCase_fontFileInstalled_fontFileInstallLater()1239     public void signatureAllMissingCase_fontFileInstalled_fontFileInstallLater() {
1240         // Install font file, foo.ttf
1241         installTestFontFile(1 /* numFonts */, 1 /* version */);
1242 
1243         // Delete all signature files
1244         removeAll(collectSignatureFiles());
1245 
1246         // New instance of UpdatableFontDir, this emulate a device reboot.
1247         UpdatableFontDir dir = installTestFontFile(1 /* numFonts */, 2 /* version */);
1248 
1249         // Make sure the font installation succeeds.
1250         assertTestFontInstalled(dir, 2 /* version */);
1251 
1252         // Make sure after the reboot, the configuration remains.
1253         UpdatableFontDir nextDir = createNewUpdateDir();
1254         assertTestFontInstalled(nextDir, 2 /* version */);
1255     }
1256 
1257     @Test
fontMissingCase_fontFamilyInstalled_fontFamilyInstallLater()1258     public void fontMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
1259         // Install font families, foo.ttf, bar.ttf.
1260         installTestFontFamilies(1 /* version */);
1261 
1262         // Delete one font file
1263         assertThat(collectFontFiles().get(0).delete()).isTrue();
1264 
1265         // New instance of UpdatableFontDir, this emulate a device reboot.
1266         UpdatableFontDir dir = installTestFontFamilies(2 /* version */);
1267 
1268         // Make sure the font installation succeeds.
1269         assertTestFontFamilyInstalled(dir, 2 /* version */);
1270 
1271         // Make sure after the reboot, the configuration remains.
1272         UpdatableFontDir nextDir = createNewUpdateDir();
1273         assertTestFontFamilyInstalled(nextDir, 2 /* version */);
1274     }
1275 
1276     @Test
fontMissingCase_fontFamilyInstalled_fontInstallLater()1277     public void fontMissingCase_fontFamilyInstalled_fontInstallLater() {
1278         // Install font families, foo.ttf, bar.ttf.
1279         installTestFontFamilies(1);
1280 
1281         // Delete one font file
1282         assertThat(collectFontFiles().get(0).delete()).isTrue();
1283 
1284         // New instance of UpdatableFontDir, this emulate a device reboot.
1285         UpdatableFontDir dir = installTestFontFile(1 /* numFonts */, 2 /* version */);
1286 
1287         // Make sure the font installation succeeds.
1288         assertTestFontInstalled(dir, 2 /* version */);
1289 
1290         // Make sure after the reboot, the configuration remains.
1291         UpdatableFontDir nextDir = createNewUpdateDir();
1292         assertTestFontInstalled(nextDir, 2 /* version */);
1293     }
1294 
1295     @Test
fontMissingCase_fontFileInstalled_fontFamilyInstallLater()1296     public void fontMissingCase_fontFileInstalled_fontFamilyInstallLater() {
1297         // Install font file, foo.ttf and bar.ttf
1298         installTestFontFile(2 /* numFonts */, 1 /* version */);
1299 
1300         // Delete one font file
1301         assertThat(collectFontFiles().get(0).delete()).isTrue();
1302 
1303         // New instance of UpdatableFontDir, this emulate a device reboot.
1304         UpdatableFontDir dir = installTestFontFamilies(2 /* version */);
1305 
1306         // Make sure the font installation succeeds.
1307         assertTestFontFamilyInstalled(dir, 2 /* version */);
1308 
1309         // Make sure after the reboot, the configuration remains.
1310         UpdatableFontDir nextDir = createNewUpdateDir();
1311         assertTestFontFamilyInstalled(nextDir, 2 /* version */);
1312     }
1313 
1314     @Test
fontMissingCase_fontFileInstalled_fontFileInstallLater()1315     public void fontMissingCase_fontFileInstalled_fontFileInstallLater() {
1316         // Install font file, foo.ttf and bar.ttf
1317         installTestFontFile(2 /* numFonts */, 1 /* version */);
1318 
1319         // Delete one font file
1320         assertThat(collectFontFiles().get(0).delete()).isTrue();
1321 
1322         // New instance of UpdatableFontDir, this emulate a device reboot.
1323         UpdatableFontDir dir = installTestFontFile(2 /* numFonts */, 2 /* version */);
1324 
1325         // Make sure the font installation succeeds.
1326         assertTestFontInstalled(dir, 2 /* version */);
1327 
1328         // Make sure after the reboot, the configuration remains.
1329         UpdatableFontDir nextDir = createNewUpdateDir();
1330         assertTestFontInstalled(nextDir, 2 /* version */);
1331     }
1332 
1333     @Test
fontAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater()1334     public void fontAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
1335         // Install font families, foo.ttf, bar.ttf.
1336         installTestFontFamilies(1 /* version */);
1337 
1338         // Delete all font files
1339         removeAll(collectFontFiles());
1340 
1341         // New instance of UpdatableFontDir, this emulate a device reboot.
1342         UpdatableFontDir dir = installTestFontFamilies(2 /* version */);
1343 
1344         // Make sure the font installation succeeds.
1345         assertTestFontFamilyInstalled(dir, 2 /* version */);
1346 
1347         // Make sure after the reboot, the configuration remains.
1348         UpdatableFontDir nextDir = createNewUpdateDir();
1349         assertTestFontFamilyInstalled(nextDir, 2 /* version */);
1350     }
1351 
1352     @Test
fontAllMissingCase_fontFamilyInstalled_fontInstallLater()1353     public void fontAllMissingCase_fontFamilyInstalled_fontInstallLater() {
1354         // Install font families, foo.ttf, bar.ttf.
1355         installTestFontFamilies(1 /* version */);
1356 
1357         // Delete all font files
1358         removeAll(collectFontFiles());
1359 
1360         // New instance of UpdatableFontDir, this emulate a device reboot.
1361         UpdatableFontDir dir = installTestFontFile(1 /* numFonts */, 2 /* version */);
1362 
1363         // Make sure the font installation succeeds.
1364         assertTestFontInstalled(dir, 2 /* version */);
1365 
1366         // Make sure after the reboot, the configuration remains.
1367         UpdatableFontDir nextDir = createNewUpdateDir();
1368         assertTestFontInstalled(nextDir, 2 /* version */);
1369     }
1370 
1371     @Test
fontAllMissingCase_fontFileInstalled_fontFamilyInstallLater()1372     public void fontAllMissingCase_fontFileInstalled_fontFamilyInstallLater() {
1373         // Install font file, foo.ttf
1374         installTestFontFile(1 /* numFonts */, 1 /* version */);
1375 
1376         // Delete all font files
1377         removeAll(collectFontFiles());
1378 
1379         // New instance of UpdatableFontDir, this emulate a device reboot.
1380         UpdatableFontDir dir = installTestFontFamilies(2 /* version */);
1381 
1382         // Make sure the font installation succeeds.
1383         assertTestFontFamilyInstalled(dir, 2 /* version */);
1384 
1385         // Make sure after the reboot, the configuration remains.
1386         UpdatableFontDir nextDir = createNewUpdateDir();
1387         assertTestFontFamilyInstalled(nextDir, 2 /* version */);
1388     }
1389 
1390     @Test
fontAllMissingCase_fontFileInstalled_fontFileInstallLater()1391     public void fontAllMissingCase_fontFileInstalled_fontFileInstallLater() {
1392         // Install font file, foo.ttf
1393         installTestFontFile(1 /* numFonts */, 1 /* version */);
1394 
1395         // Delete all font files
1396         removeAll(collectFontFiles());
1397 
1398         // New instance of UpdatableFontDir, this emulate a device reboot.
1399         UpdatableFontDir dir = installTestFontFile(1 /* numFonts */, 2 /* version */);
1400 
1401         // Make sure the font installation succeeds.
1402         assertTestFontInstalled(dir, 2 /* version */);
1403 
1404         // Make sure after the reboot, the configuration remains.
1405         UpdatableFontDir nextDir = createNewUpdateDir();
1406         assertTestFontInstalled(nextDir, 2 /* version */);
1407     }
1408 
1409     @Test
fontDirAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater()1410     public void fontDirAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
1411         // Install font families, foo.ttf, bar.ttf.
1412         installTestFontFamilies(1 /* version */);
1413 
1414         // Delete all font files
1415         removeAll(List.of(mUpdatableFontFilesDir.listFiles()));
1416 
1417         // New instance of UpdatableFontDir, this emulate a device reboot.
1418         UpdatableFontDir dir = installTestFontFamilies(2 /* version */);
1419 
1420         // Make sure the font installation succeeds.
1421         assertTestFontFamilyInstalled(dir, 2 /* version */);
1422 
1423         // Make sure after the reboot, the configuration remains.
1424         UpdatableFontDir nextDir = createNewUpdateDir();
1425         assertTestFontFamilyInstalled(nextDir, 2 /* version */);
1426     }
1427 
1428     @Test
fontDirAllMissingCase_fontFamilyInstalled_fontInstallLater()1429     public void fontDirAllMissingCase_fontFamilyInstalled_fontInstallLater() {
1430         // Install font families, foo.ttf, bar.ttf.
1431         installTestFontFamilies(1 /* version */);
1432 
1433         // Delete all font files
1434         removeAll(List.of(mUpdatableFontFilesDir.listFiles()));
1435 
1436         // New instance of UpdatableFontDir, this emulate a device reboot.
1437         UpdatableFontDir dir = installTestFontFile(1 /* numFonts */, 2 /* version */);
1438 
1439         // Make sure the font installation succeeds.
1440         assertTestFontInstalled(dir, 2 /* version */);
1441 
1442         // Make sure after the reboot, the configuration remains.
1443         UpdatableFontDir nextDir = createNewUpdateDir();
1444         assertTestFontInstalled(nextDir, 2 /* version */);
1445     }
1446 
1447     @Test
fontDirAllMissingCase_fontFileInstalled_fontFamilyInstallLater()1448     public void fontDirAllMissingCase_fontFileInstalled_fontFamilyInstallLater() {
1449         // Install font file, foo.ttf
1450         installTestFontFile(1 /* numFonts */, 1 /* version */);
1451 
1452         // Delete all font files
1453         removeAll(List.of(mUpdatableFontFilesDir.listFiles()));
1454 
1455         // New instance of UpdatableFontDir, this emulate a device reboot.
1456         UpdatableFontDir dir = installTestFontFamilies(2 /* version */);
1457 
1458         // Make sure the font installation succeeds.
1459         assertTestFontFamilyInstalled(dir, 2 /* version */);
1460 
1461         // Make sure after the reboot, the configuration remains.
1462         UpdatableFontDir nextDir = createNewUpdateDir();
1463         assertTestFontFamilyInstalled(nextDir, 2 /* version */);
1464     }
1465 
1466     @Test
fontDirAllMissingCase_fontFileInstalled_fontFileInstallLater()1467     public void fontDirAllMissingCase_fontFileInstalled_fontFileInstallLater() {
1468         // Install font file, foo.ttf
1469         installTestFontFile(1 /* numFonts */, 1 /* version */);
1470 
1471         // Delete all font files
1472         removeAll(List.of(mUpdatableFontFilesDir.listFiles()));
1473 
1474         // New instance of UpdatableFontDir, this emulate a device reboot.
1475         UpdatableFontDir dir = installTestFontFile(1 /* numFonts */, 2 /* version */);
1476 
1477         // Make sure the font installation succeeds.
1478         assertTestFontInstalled(dir, 2 /* version */);
1479 
1480         // Make sure after the reboot, the configuration remains.
1481         UpdatableFontDir nextDir = createNewUpdateDir();
1482         assertTestFontInstalled(nextDir, 2 /* version */);
1483     }
1484 
1485     @Test
dirContentAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater()1486     public void dirContentAllMissingCase_fontFamilyInstalled_fontFamilyInstallLater() {
1487         // Install font families, foo.ttf, bar.ttf.
1488         installTestFontFamilies(1 /* version */);
1489 
1490         // Delete all font files
1491         removeAll(collectFontFiles());
1492         removeAll(collectSignatureFiles());
1493 
1494         // New instance of UpdatableFontDir, this emulate a device reboot.
1495         UpdatableFontDir dir = installTestFontFamilies(2 /* version */);
1496 
1497         // Make sure the font installation succeeds.
1498         assertTestFontFamilyInstalled(dir, 2 /* version */);
1499 
1500         // Make sure after the reboot, the configuration remains.
1501         UpdatableFontDir nextDir = createNewUpdateDir();
1502         assertTestFontFamilyInstalled(nextDir, 2 /* version */);
1503     }
1504 
1505     @Test
dirContentAllMissingCase_fontFamilyInstalled_fontInstallLater()1506     public void dirContentAllMissingCase_fontFamilyInstalled_fontInstallLater() {
1507         // Install font families, foo.ttf, bar.ttf.
1508         installTestFontFamilies(1 /* version */);
1509 
1510         // Delete all font files
1511         removeAll(collectFontFiles());
1512         removeAll(collectSignatureFiles());
1513 
1514         // New instance of UpdatableFontDir, this emulate a device reboot.
1515         UpdatableFontDir dir = installTestFontFile(1 /* numFonts */, 2 /* version */);
1516 
1517         // Make sure the font installation succeeds.
1518         assertTestFontInstalled(dir, 2 /* version */);
1519 
1520         // Make sure after the reboot, the configuration remains.
1521         UpdatableFontDir nextDir = createNewUpdateDir();
1522         assertTestFontInstalled(nextDir, 2 /* version */);
1523     }
1524 
1525     @Test
dirContentAllMissingCase_fontFileInstalled_fontFamilyInstallLater()1526     public void dirContentAllMissingCase_fontFileInstalled_fontFamilyInstallLater() {
1527         // Install font file, foo.ttf
1528         installTestFontFile(1 /* numFonts */, 1 /* version */);
1529 
1530         // Delete all font files
1531         removeAll(collectFontFiles());
1532         removeAll(collectSignatureFiles());
1533 
1534         // New instance of UpdatableFontDir, this emulate a device reboot.
1535         UpdatableFontDir dir = installTestFontFamilies(2 /* version */);
1536 
1537         // Make sure the font installation succeeds.
1538         assertTestFontFamilyInstalled(dir, 2 /* version */);
1539 
1540         // Make sure after the reboot, the configuration remains.
1541         UpdatableFontDir nextDir = createNewUpdateDir();
1542         assertTestFontFamilyInstalled(nextDir, 2 /* version */);
1543     }
1544 
1545     @Test
dirContentAllMissingCase_fontFileInstalled_fontFileInstallLater()1546     public void dirContentAllMissingCase_fontFileInstalled_fontFileInstallLater() {
1547         // Install font file, foo.ttf
1548         installTestFontFile(1 /* numFonts */, 1 /* version */);
1549 
1550         // Delete all font files
1551         removeAll(collectFontFiles());
1552         removeAll(collectSignatureFiles());
1553 
1554         // New instance of UpdatableFontDir, this emulate a device reboot.
1555         UpdatableFontDir dir = installTestFontFile(1 /* numFonts */, 2 /* version */);
1556 
1557         // Make sure the font installation succeeds.
1558         assertTestFontInstalled(dir, 2 /* version */);
1559 
1560         // Make sure after the reboot, the configuration remains.
1561         UpdatableFontDir nextDir = createNewUpdateDir();
1562         assertTestFontInstalled(nextDir, 2 /* version */);
1563     }
1564 
newFontUpdateRequest(String content, String signature)1565     private FontUpdateRequest newFontUpdateRequest(String content, String signature)
1566             throws Exception {
1567         File file = File.createTempFile("font", "ttf", mCacheDir);
1568         FileUtils.stringToFile(file, content);
1569         return new FontUpdateRequest(
1570                 ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY),
1571                 signature.getBytes());
1572     }
1573 
newAddFontFamilyRequest(String xml)1574     private static FontUpdateRequest newAddFontFamilyRequest(String xml) throws Exception {
1575         XmlPullParser mParser = Xml.newPullParser();
1576         ByteArrayInputStream is = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8));
1577         mParser.setInput(is, "UTF-8");
1578         mParser.nextTag();
1579 
1580         FontConfig.NamedFamilyList namedFamilyList = FontListParser.readNamedFamily(
1581                 mParser, "", null, true);
1582         FontConfig.FontFamily fontFamily = namedFamilyList.getFamilies().get(0);
1583         List<FontUpdateRequest.Font> fonts = new ArrayList<>();
1584         for (FontConfig.Font font : fontFamily.getFontList()) {
1585             String name = font.getFile().getName();
1586             String psName = name.substring(0, name.length() - 4);  // drop suffix
1587             FontUpdateRequest.Font updateFont = new FontUpdateRequest.Font(
1588                     psName, font.getStyle(), font.getTtcIndex(), font.getFontVariationSettings());
1589             fonts.add(updateFont);
1590         }
1591         FontUpdateRequest.Family family = new FontUpdateRequest.Family(
1592                 namedFamilyList.getName(), fonts);
1593         return new FontUpdateRequest(family);
1594     }
1595 
readConfig(File file)1596     private static PersistentSystemFontConfig.Config readConfig(File file) throws Exception {
1597         PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config();
1598         try (InputStream is = new FileInputStream(file)) {
1599             PersistentSystemFontConfig.loadFromXml(is, config);
1600         }
1601         return config;
1602     }
1603 
writeConfig(PersistentSystemFontConfig.Config config, File file)1604     private static void writeConfig(PersistentSystemFontConfig.Config config,
1605             File file) throws IOException {
1606         try (FileOutputStream fos = new FileOutputStream(file)) {
1607             PersistentSystemFontConfig.writeToXml(fos, config);
1608         }
1609     }
1610 
1611     // Returns the last family with the given name, which will be used for creating Typeface.
getLastFamily(FontConfig fontConfig, String familyName)1612     private static FontConfig.FontFamily getLastFamily(FontConfig fontConfig, String familyName) {
1613         List<FontConfig.NamedFamilyList> namedFamilyLists = fontConfig.getNamedFamilyLists();
1614         for (int i = namedFamilyLists.size() - 1; i >= 0; i--) {
1615             if (familyName.equals(namedFamilyLists.get(i).getName())) {
1616                 return namedFamilyLists.get(i).getFamilies().get(0);
1617             }
1618         }
1619         return null;
1620     }
1621 
assertNamedFamilyExists(FontConfig fontConfig, String familyName)1622     private static void assertNamedFamilyExists(FontConfig fontConfig, String familyName) {
1623         assertThat(fontConfig.getNamedFamilyLists().stream()
1624                 .map(FontConfig.NamedFamilyList::getName)
1625                 .collect(Collectors.toSet())).contains(familyName);
1626     }
1627 }
1628