• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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 android.Manifest;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.RequiresPermission;
23 import android.content.Context;
24 import android.graphics.Typeface;
25 import android.graphics.fonts.FontFamily;
26 import android.graphics.fonts.FontManager;
27 import android.graphics.fonts.FontUpdateRequest;
28 import android.graphics.fonts.SystemFonts;
29 import android.os.ParcelFileDescriptor;
30 import android.os.ResultReceiver;
31 import android.os.SharedMemory;
32 import android.os.ShellCallback;
33 import android.system.ErrnoException;
34 import android.text.FontConfig;
35 import android.util.AndroidException;
36 import android.util.ArrayMap;
37 import android.util.IndentingPrintWriter;
38 import android.util.Slog;
39 
40 import com.android.internal.annotations.GuardedBy;
41 import com.android.internal.graphics.fonts.IFontManager;
42 import com.android.internal.security.VerityUtils;
43 import com.android.internal.util.DumpUtils;
44 import com.android.internal.util.Preconditions;
45 import com.android.server.LocalServices;
46 import com.android.server.SystemService;
47 
48 import java.io.File;
49 import java.io.FileDescriptor;
50 import java.io.IOException;
51 import java.io.PrintWriter;
52 import java.nio.ByteBuffer;
53 import java.nio.DirectByteBuffer;
54 import java.nio.NioUtils;
55 import java.util.Collections;
56 import java.util.List;
57 import java.util.Map;
58 import java.util.Objects;
59 
60 /** A service for managing system fonts. */
61 public final class FontManagerService extends IFontManager.Stub {
62     private static final String TAG = "FontManagerService";
63 
64     private static final String FONT_FILES_DIR = "/data/fonts/files";
65     private static final String CONFIG_XML_FILE = "/data/fonts/config/config.xml";
66 
67     @RequiresPermission(Manifest.permission.UPDATE_FONTS)
68     @Override
getFontConfig()69     public FontConfig getFontConfig() {
70         getContext().enforceCallingPermission(Manifest.permission.UPDATE_FONTS,
71                 "UPDATE_FONTS permission required.");
72         return getSystemFontConfig();
73     }
74 
75     @RequiresPermission(Manifest.permission.UPDATE_FONTS)
76     @Override
updateFontFamily(@onNull List<FontUpdateRequest> requests, int baseVersion)77     public int updateFontFamily(@NonNull List<FontUpdateRequest> requests, int baseVersion) {
78         try {
79             Preconditions.checkArgumentNonnegative(baseVersion);
80             Objects.requireNonNull(requests);
81             getContext().enforceCallingPermission(Manifest.permission.UPDATE_FONTS,
82                     "UPDATE_FONTS permission required.");
83             try {
84                 update(baseVersion, requests);
85                 return FontManager.RESULT_SUCCESS;
86             } catch (SystemFontException e) {
87                 Slog.e(TAG, "Failed to update font family", e);
88                 return e.getErrorCode();
89             }
90         } finally {
91             closeFileDescriptors(requests);
92         }
93     }
94 
closeFileDescriptors(@ullable List<FontUpdateRequest> requests)95     private static void closeFileDescriptors(@Nullable List<FontUpdateRequest> requests) {
96         // Make sure we close every passed FD, even if 'requests' is constructed incorrectly and
97         // some fields are null.
98         if (requests == null) return;
99         for (FontUpdateRequest request : requests) {
100             if (request == null) continue;
101             ParcelFileDescriptor fd = request.getFd();
102             if (fd == null) continue;
103             try {
104                 fd.close();
105             } catch (IOException e) {
106                 Slog.w(TAG, "Failed to close fd", e);
107             }
108         }
109     }
110 
111     /* package */ static class SystemFontException extends AndroidException {
112         private final int mErrorCode;
113 
SystemFontException(@ontManager.ResultCode int errorCode, String msg, Throwable cause)114         SystemFontException(@FontManager.ResultCode int errorCode, String msg, Throwable cause) {
115             super(msg, cause);
116             mErrorCode = errorCode;
117         }
118 
SystemFontException(int errorCode, String msg)119         SystemFontException(int errorCode, String msg) {
120             super(msg);
121             mErrorCode = errorCode;
122         }
123 
124         @FontManager.ResultCode
getErrorCode()125         int getErrorCode() {
126             return mErrorCode;
127         }
128     }
129 
130     /** Class to manage FontManagerService's lifecycle. */
131     public static final class Lifecycle extends SystemService {
132         private final FontManagerService mService;
133 
Lifecycle(@onNull Context context, boolean safeMode)134         public Lifecycle(@NonNull Context context, boolean safeMode) {
135             super(context);
136             mService = new FontManagerService(context, safeMode);
137         }
138 
139         @Override
onStart()140         public void onStart() {
141             LocalServices.addService(FontManagerInternal.class,
142                     new FontManagerInternal() {
143                         @Override
144                         @Nullable
145                         public SharedMemory getSerializedSystemFontMap() {
146                             if (!Typeface.ENABLE_LAZY_TYPEFACE_INITIALIZATION) {
147                                 return null;
148                             }
149                             return mService.getCurrentFontMap();
150                         }
151                     });
152             publishBinderService(Context.FONT_SERVICE, mService);
153         }
154     }
155 
156     private static class FsverityUtilImpl implements UpdatableFontDir.FsverityUtil {
157         @Override
hasFsverity(String filePath)158         public boolean hasFsverity(String filePath) {
159             return VerityUtils.hasFsverity(filePath);
160         }
161 
162         @Override
setUpFsverity(String filePath, byte[] pkcs7Signature)163         public void setUpFsverity(String filePath, byte[] pkcs7Signature) throws IOException {
164             VerityUtils.setUpFsverity(filePath, pkcs7Signature);
165         }
166 
167         @Override
rename(File src, File dest)168         public boolean rename(File src, File dest) {
169             // rename system call preserves fs-verity bit.
170             return src.renameTo(dest);
171         }
172     }
173 
174     @NonNull
175     private final Context mContext;
176 
177     private final Object mUpdatableFontDirLock = new Object();
178 
179     @GuardedBy("mUpdatableFontDirLock")
180     @Nullable
181     private final UpdatableFontDir mUpdatableFontDir;
182 
183     // mSerializedFontMapLock can be acquired while holding mUpdatableFontDirLock.
184     // mUpdatableFontDirLock should not be newly acquired while holding mSerializedFontMapLock.
185     private final Object mSerializedFontMapLock = new Object();
186 
187     @GuardedBy("mSerializedFontMapLock")
188     @Nullable
189     private SharedMemory mSerializedFontMap = null;
190 
FontManagerService(Context context, boolean safeMode)191     private FontManagerService(Context context, boolean safeMode) {
192         if (safeMode) {
193             Slog.i(TAG, "Entering safe mode. Deleting all font updates.");
194             UpdatableFontDir.deleteAllFiles(new File(FONT_FILES_DIR), new File(CONFIG_XML_FILE));
195         }
196         mContext = context;
197         mUpdatableFontDir = createUpdatableFontDir(safeMode);
198         initialize();
199     }
200 
201     @Nullable
createUpdatableFontDir(boolean safeMode)202     private static UpdatableFontDir createUpdatableFontDir(boolean safeMode) {
203         // Never read updatable font files in safe mode.
204         if (safeMode) return null;
205         // If apk verity is supported, fs-verity should be available.
206         if (!VerityUtils.isFsVeritySupported()) return null;
207         return new UpdatableFontDir(new File(FONT_FILES_DIR), new OtfFontFileParser(),
208                 new FsverityUtilImpl(), new File(CONFIG_XML_FILE));
209     }
210 
initialize()211     private void initialize() {
212         synchronized (mUpdatableFontDirLock) {
213             if (mUpdatableFontDir == null) {
214                 setSerializedFontMap(serializeSystemServerFontMap());
215                 return;
216             }
217             mUpdatableFontDir.loadFontFileMap();
218             updateSerializedFontMap();
219         }
220     }
221 
222     @NonNull
getContext()223     public Context getContext() {
224         return mContext;
225     }
226 
getCurrentFontMap()227     @Nullable /* package */ SharedMemory getCurrentFontMap() {
228         synchronized (mSerializedFontMapLock) {
229             return mSerializedFontMap;
230         }
231     }
232 
update(int baseVersion, List<FontUpdateRequest> requests)233     /* package */ void update(int baseVersion, List<FontUpdateRequest> requests)
234             throws SystemFontException {
235         if (mUpdatableFontDir == null) {
236             throw new SystemFontException(
237                     FontManager.RESULT_ERROR_FONT_UPDATER_DISABLED,
238                     "The font updater is disabled.");
239         }
240         synchronized (mUpdatableFontDirLock) {
241             // baseVersion == -1 only happens from shell command. This is filtered and treated as
242             // error from SystemApi call.
243             if (baseVersion != -1 && mUpdatableFontDir.getConfigVersion() != baseVersion) {
244                 throw new SystemFontException(
245                         FontManager.RESULT_ERROR_VERSION_MISMATCH,
246                         "The base config version is older than current.");
247             }
248             mUpdatableFontDir.update(requests);
249             updateSerializedFontMap();
250         }
251     }
252 
253     /**
254      * Clears all updates and restarts FontManagerService.
255      *
256      * <p>CAUTION: this method is not safe. Existing processes may crash due to missing font files.
257      * This method is only for {@link FontManagerShellCommand}.
258      */
clearUpdates()259     /* package */ void clearUpdates() {
260         UpdatableFontDir.deleteAllFiles(new File(FONT_FILES_DIR), new File(CONFIG_XML_FILE));
261         initialize();
262     }
263 
264     /**
265      * Restarts FontManagerService, removing not-the-latest font files.
266      *
267      * <p>CAUTION: this method is not safe. Existing processes may crash due to missing font files.
268      * This method is only for {@link FontManagerShellCommand}.
269      */
restart()270     /* package */ void restart() {
271         initialize();
272     }
273 
getFontFileMap()274     /* package */ Map<String, File> getFontFileMap() {
275         if (mUpdatableFontDir == null) {
276             return Collections.emptyMap();
277         }
278         synchronized (mUpdatableFontDirLock) {
279             return mUpdatableFontDir.getPostScriptMap();
280         }
281     }
282 
283     @Override
dump(@onNull FileDescriptor fd, @NonNull PrintWriter writer, @Nullable String[] args)284     public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer,
285             @Nullable String[] args) {
286         if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) return;
287         new FontManagerShellCommand(this).dumpAll(new IndentingPrintWriter(writer, "  "));
288     }
289 
290     @Override
onShellCommand(@ullable FileDescriptor in, @Nullable FileDescriptor out, @Nullable FileDescriptor err, @NonNull String[] args, @Nullable ShellCallback callback, @NonNull ResultReceiver result)291     public void onShellCommand(@Nullable FileDescriptor in,
292             @Nullable FileDescriptor out,
293             @Nullable FileDescriptor err,
294             @NonNull String[] args,
295             @Nullable ShellCallback callback,
296             @NonNull ResultReceiver result) {
297         new FontManagerShellCommand(this).exec(this, in, out, err, args, callback, result);
298     }
299 
300     /**
301      * Returns an active system font configuration.
302      */
getSystemFontConfig()303     public @NonNull FontConfig getSystemFontConfig() {
304         if (mUpdatableFontDir == null) {
305             return SystemFonts.getSystemPreinstalledFontConfig();
306         }
307         synchronized (mUpdatableFontDirLock) {
308             return mUpdatableFontDir.getSystemFontConfig();
309         }
310     }
311 
312     /**
313      * Makes new serialized font map data and updates mSerializedFontMap.
314      */
updateSerializedFontMap()315     private void updateSerializedFontMap() {
316         SharedMemory serializedFontMap = serializeFontMap(getSystemFontConfig());
317         if (serializedFontMap == null) {
318             // Fallback to the preloaded config.
319             serializedFontMap = serializeSystemServerFontMap();
320         }
321         setSerializedFontMap(serializedFontMap);
322     }
323 
324     @Nullable
serializeFontMap(FontConfig fontConfig)325     private static SharedMemory serializeFontMap(FontConfig fontConfig) {
326         final ArrayMap<String, ByteBuffer> bufferCache = new ArrayMap<>();
327         try {
328             final Map<String, FontFamily[]> fallback =
329                     SystemFonts.buildSystemFallback(fontConfig, bufferCache);
330             final Map<String, Typeface> typefaceMap =
331                     SystemFonts.buildSystemTypefaces(fontConfig, fallback);
332             return Typeface.serializeFontMap(typefaceMap);
333         } catch (IOException | ErrnoException e) {
334             Slog.w(TAG, "Failed to serialize updatable font map. "
335                     + "Retrying with system image fonts.", e);
336             return null;
337         } finally {
338             // Unmap buffers promptly, as we map a lot of files and may hit mmap limit before
339             // GC collects ByteBuffers and unmaps them.
340             for (ByteBuffer buffer : bufferCache.values()) {
341                 if (buffer instanceof DirectByteBuffer) {
342                     NioUtils.freeDirectBuffer(buffer);
343                 }
344             }
345         }
346     }
347 
348     @Nullable
serializeSystemServerFontMap()349     private static SharedMemory serializeSystemServerFontMap() {
350         try {
351             return Typeface.serializeFontMap(Typeface.getSystemFontMap());
352         } catch (IOException | ErrnoException e) {
353             Slog.e(TAG, "Failed to serialize SystemServer system font map", e);
354             return null;
355         }
356     }
357 
setSerializedFontMap(SharedMemory serializedFontMap)358     private void setSerializedFontMap(SharedMemory serializedFontMap) {
359         SharedMemory oldFontMap = null;
360         synchronized (mSerializedFontMapLock) {
361             oldFontMap = mSerializedFontMap;
362             mSerializedFontMap = serializedFontMap;
363         }
364         if (oldFontMap != null) {
365             oldFontMap.close();
366         }
367     }
368 }
369