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