Clear updated font files in safe mode.

Bug: 176939176
Bug: 181536798
Bug: 187190639
Test: atest FrameworksServicesTests:UpdatableFontDirTest
Test: atest UpdatableSystemFontTest
Test: manually triggered safe mode and confirmed updated fonts are
      removed.
Change-Id: I946a7df383a27bbfda2931a916ca28d8ee14feb6
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
index 7c013e0..1bfb6a9 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
@@ -63,6 +63,7 @@
     private static final String TAG = "FontManagerService";
 
     private static final String FONT_FILES_DIR = "/data/fonts/files";
+    private static final String CONFIG_XML_FILE = "/data/fonts/config/config.xml";
 
     @Override
     public FontConfig getFontConfig() {
@@ -126,9 +127,9 @@
     public static final class Lifecycle extends SystemService {
         private final FontManagerService mService;
 
-        public Lifecycle(@NonNull Context context) {
+        public Lifecycle(@NonNull Context context, boolean safeMode) {
             super(context);
-            mService = new FontManagerService(context);
+            mService = new FontManagerService(context, safeMode);
         }
 
         @Override
@@ -237,18 +238,24 @@
     @Nullable
     private SharedMemory mSerializedFontMap = null;
 
-    private FontManagerService(Context context) {
+    private FontManagerService(Context context, boolean safeMode) {
+        if (safeMode) {
+            Slog.i(TAG, "Entering safe mode. Deleting all font updates.");
+            UpdatableFontDir.deleteAllFiles(new File(FONT_FILES_DIR), new File(CONFIG_XML_FILE));
+        }
         mContext = context;
-        mUpdatableFontDir = createUpdatableFontDir();
+        mUpdatableFontDir = createUpdatableFontDir(safeMode);
         initialize();
     }
 
     @Nullable
-    private static UpdatableFontDir createUpdatableFontDir() {
+    private static UpdatableFontDir createUpdatableFontDir(boolean safeMode) {
+        // Never read updatable font files in safe mode.
+        if (safeMode) return null;
         // If apk verity is supported, fs-verity should be available.
         if (!VerityUtils.isFsVeritySupported()) return null;
-        return new UpdatableFontDir(new File(FONT_FILES_DIR),
-                new OtfFontFileParser(), new FsverityUtilImpl());
+        return new UpdatableFontDir(new File(FONT_FILES_DIR), new OtfFontFileParser(),
+                new FsverityUtilImpl(), new File(CONFIG_XML_FILE));
     }
 
     private void initialize() {
@@ -299,18 +306,23 @@
         }
     }
 
-    /* package */ void clearUpdates() throws SystemFontException {
-        if (mUpdatableFontDir == null) {
-            throw new SystemFontException(
-                    FontManager.RESULT_ERROR_FONT_UPDATER_DISABLED,
-                    "The font updater is disabled.");
-        }
-        synchronized (mUpdatableFontDirLock) {
-            mUpdatableFontDir.clearUpdates();
-            updateSerializedFontMap();
-        }
+    /**
+     * Clears all updates and restarts FontManagerService.
+     *
+     * <p>CAUTION: this method is not safe. Existing processes may crash due to missing font files.
+     * This method is only for {@link FontManagerShellCommand}.
+     */
+    /* package */ void clearUpdates() {
+        UpdatableFontDir.deleteAllFiles(new File(FONT_FILES_DIR), new File(CONFIG_XML_FILE));
+        initialize();
     }
 
+    /**
+     * Restarts FontManagerService, removing not-the-latest font files.
+     *
+     * <p>CAUTION: this method is not safe. Existing processes may crash due to missing font files.
+     * This method is only for {@link FontManagerShellCommand}.
+     */
     /* package */ void restart() {
         initialize();
     }
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
index e4928ce..3fecef7 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
@@ -448,7 +448,7 @@
         }
     }
 
-    private int clear(ShellCommand shell) throws SystemFontException {
+    private int clear(ShellCommand shell) {
         mService.clearUpdates();
         shell.getOutPrintWriter().println("Success");
         return 0;
diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
index d532605..4fa08eb 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -59,8 +59,6 @@
     private static final String TAG = "UpdatableFontDir";
     private static final String RANDOM_DIR_PREFIX = "~~";
 
-    private static final String CONFIG_XML_FILE = "/data/fonts/config/config.xml";
-
     /** Interface to mock font file access in tests. */
     interface FontFileParser {
         String getPostScriptName(File file) throws IOException;
@@ -137,8 +135,9 @@
      */
     private final ArrayMap<String, FontFileInfo> mFontFileInfoMap = new ArrayMap<>();
 
-    UpdatableFontDir(File filesDir, FontFileParser parser, FsverityUtil fsverityUtil) {
-        this(filesDir, parser, fsverityUtil, new File(CONFIG_XML_FILE),
+    UpdatableFontDir(File filesDir, FontFileParser parser, FsverityUtil fsverityUtil,
+            File configFile) {
+        this(filesDir, parser, fsverityUtil, configFile,
                 System::currentTimeMillis,
                 (map) -> SystemFonts.getSystemFontConfig(map, 0, 0)
         );
@@ -213,17 +212,6 @@
         }
     }
 
-    /* package */ void clearUpdates() throws SystemFontException {
-        mFontFileInfoMap.clear();
-        FileUtils.deleteContents(mFilesDir);
-
-        mLastModifiedMillis = mCurrentTimeSupplier.get();
-        PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config();
-        config.lastModifiedMillis = mLastModifiedMillis;
-        writePersistentConfig(config);
-        mConfigVersion++;
-    }
-
     /**
      * Applies multiple {@link FontUpdateRequest}s in transaction.
      * If one of the request fails, the fonts and config are rolled back to the previous state
@@ -604,4 +592,19 @@
         }
         return familyMap;
     }
+
+    /* package */ static void deleteAllFiles(File filesDir, File configFile) {
+        // As this method is called in safe mode, try to delete all files even though an exception
+        // is thrown.
+        try {
+            new AtomicFile(configFile).delete();
+        } catch (Throwable t) {
+            Slog.w(TAG, "Failed to delete " + configFile);
+        }
+        try {
+            FileUtils.deleteContents(filesDir);
+        } catch (Throwable t) {
+            Slog.w(TAG, "Failed to delete " + filesDir);
+        }
+    }
 }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 8dc5011..fd76e4aa 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1804,7 +1804,7 @@
             t.traceEnd();
 
             t.traceBegin("StartFontManagerService");
-            mSystemServiceManager.startService(FontManagerService.Lifecycle.class);
+            mSystemServiceManager.startService(new FontManagerService.Lifecycle(context, safeMode));
             t.traceEnd();
 
             t.traceBegin("StartTextServicesManager");
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
index 7b48037..6b7e1dd 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
@@ -949,6 +949,24 @@
         assertThat(updated).isNotEqualTo(firstFontFamily);
     }
 
+    @Test
+    public void deleteAllFiles() throws Exception {
+        FakeFontFileParser parser = new FakeFontFileParser();
+        FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
+        UpdatableFontDir dirForPreparation = new UpdatableFontDir(
+                mUpdatableFontFilesDir, parser, fakeFsverityUtil,
+                mConfigFile, mCurrentTimeSupplier, mConfigSupplier);
+        dirForPreparation.loadFontFileMap();
+        dirForPreparation.update(Collections.singletonList(
+                newFontUpdateRequest("foo.ttf,1,foo", GOOD_SIGNATURE)));
+        assertThat(mConfigFile.exists()).isTrue();
+        assertThat(mUpdatableFontFilesDir.list()).hasLength(1);
+
+        UpdatableFontDir.deleteAllFiles(mUpdatableFontFilesDir, mConfigFile);
+        assertThat(mConfigFile.exists()).isFalse();
+        assertThat(mUpdatableFontFilesDir.list()).hasLength(0);
+    }
+
     private FontUpdateRequest newFontUpdateRequest(String content, String signature)
             throws Exception {
         File file = File.createTempFile("font", "ttf", mCacheDir);