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