Add FontManager#updateFontFamily() API.

Bug: 179103383
Test: atest CtsGraphicsTestCases:FontFamilyUpdateRequestTest
Test: atest CtsGraphicsTestCases:FontFileUpdateRequestTest
Test: atest CtsGraphicsTestCases:FontManagerTest
Test: atest FrameworksServicesTests:UpdatableFontDirTest
Test: atest UpdatableSystemFontTest
Change-Id: I0103784c72d60567675daf73e95e4d716f091e4a
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index bfdce37..178efba 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2810,16 +2810,48 @@
 
 package android.graphics.fonts {
 
+  public final class FontFamilyUpdateRequest {
+    method @NonNull public java.util.List<android.graphics.fonts.FontFamilyUpdateRequest.FontFamily> getFontFamilies();
+    method @NonNull public java.util.List<android.graphics.fonts.FontFileUpdateRequest> getFontFileUpdateRequests();
+  }
+
+  public static final class FontFamilyUpdateRequest.Builder {
+    ctor public FontFamilyUpdateRequest.Builder();
+    method @NonNull public android.graphics.fonts.FontFamilyUpdateRequest.Builder addFontFamily(@NonNull android.graphics.fonts.FontFamilyUpdateRequest.FontFamily);
+    method @NonNull public android.graphics.fonts.FontFamilyUpdateRequest.Builder addFontFileUpdateRequest(@NonNull android.graphics.fonts.FontFileUpdateRequest);
+    method @NonNull public android.graphics.fonts.FontFamilyUpdateRequest build();
+  }
+
+  public static final class FontFamilyUpdateRequest.Font {
+    ctor public FontFamilyUpdateRequest.Font(@NonNull String, @NonNull android.graphics.fonts.FontStyle, @NonNull java.util.List<android.graphics.fonts.FontVariationAxis>);
+    method @NonNull public java.util.List<android.graphics.fonts.FontVariationAxis> getAxes();
+    method @NonNull public String getPostScriptName();
+    method @NonNull public android.graphics.fonts.FontStyle getStyle();
+  }
+
+  public static final class FontFamilyUpdateRequest.FontFamily {
+    ctor public FontFamilyUpdateRequest.FontFamily(@NonNull String, @NonNull java.util.List<android.graphics.fonts.FontFamilyUpdateRequest.Font>);
+    method @NonNull public java.util.List<android.graphics.fonts.FontFamilyUpdateRequest.Font> getFonts();
+    method @NonNull public String getName();
+  }
+
+  public final class FontFileUpdateRequest {
+    ctor public FontFileUpdateRequest(@NonNull android.os.ParcelFileDescriptor, @NonNull byte[]);
+    method @NonNull public android.os.ParcelFileDescriptor getParcelFileDescriptor();
+    method @NonNull public byte[] getSignature();
+  }
+
   public class FontManager {
     method @Nullable public android.text.FontConfig getFontConfig();
+    method @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFamily(@NonNull android.graphics.fonts.FontFamilyUpdateRequest, @IntRange(from=0) int);
     method @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFile(@NonNull android.os.ParcelFileDescriptor, @NonNull byte[], @IntRange(from=0) int);
     field public static final int RESULT_ERROR_DOWNGRADING = -5; // 0xfffffffb
     field public static final int RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE = -1; // 0xffffffff
     field public static final int RESULT_ERROR_FAILED_UPDATE_CONFIG = -6; // 0xfffffffa
+    field public static final int RESULT_ERROR_FONT_NOT_FOUND = -9; // 0xfffffff7
     field public static final int RESULT_ERROR_FONT_UPDATER_DISABLED = -7; // 0xfffffff9
     field public static final int RESULT_ERROR_INVALID_FONT_FILE = -3; // 0xfffffffd
     field public static final int RESULT_ERROR_INVALID_FONT_NAME = -4; // 0xfffffffc
-    field public static final int RESULT_ERROR_REMOTE_EXCEPTION = -9; // 0xfffffff7
     field public static final int RESULT_ERROR_VERIFICATION_FAILURE = -2; // 0xfffffffe
     field public static final int RESULT_ERROR_VERSION_MISMATCH = -8; // 0xfffffff8
     field public static final int RESULT_SUCCESS = 0; // 0x0
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 0611031..4d2db4d 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -836,14 +836,15 @@
 
   public class FontManager {
     method @Nullable public android.text.FontConfig getFontConfig();
+    method @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFamily(@NonNull android.graphics.fonts.FontFamilyUpdateRequest, @IntRange(from=0) int);
     method @RequiresPermission(android.Manifest.permission.UPDATE_FONTS) public int updateFontFile(@NonNull android.os.ParcelFileDescriptor, @NonNull byte[], @IntRange(from=0) int);
     field public static final int RESULT_ERROR_DOWNGRADING = -5; // 0xfffffffb
     field public static final int RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE = -1; // 0xffffffff
     field public static final int RESULT_ERROR_FAILED_UPDATE_CONFIG = -6; // 0xfffffffa
+    field public static final int RESULT_ERROR_FONT_NOT_FOUND = -9; // 0xfffffff7
     field public static final int RESULT_ERROR_FONT_UPDATER_DISABLED = -7; // 0xfffffff9
     field public static final int RESULT_ERROR_INVALID_FONT_FILE = -3; // 0xfffffffd
     field public static final int RESULT_ERROR_INVALID_FONT_NAME = -4; // 0xfffffffc
-    field public static final int RESULT_ERROR_REMOTE_EXCEPTION = -9; // 0xfffffff7
     field public static final int RESULT_ERROR_VERIFICATION_FAILURE = -2; // 0xfffffffe
     field public static final int RESULT_ERROR_VERSION_MISMATCH = -8; // 0xfffffff8
     field public static final int RESULT_SUCCESS = 0; // 0x0
diff --git a/core/java/android/graphics/fonts/FontFamilyUpdateRequest.java b/core/java/android/graphics/fonts/FontFamilyUpdateRequest.java
new file mode 100644
index 0000000..25758e9
--- /dev/null
+++ b/core/java/android/graphics/fonts/FontFamilyUpdateRequest.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.fonts;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Request for updating or adding a font family on the system.
+ *
+ * <p>You can update or add a font family with custom style parameters. The following example
+ * defines a font family called "roboto" using "Roboto-Regular" font file that is already available
+ * on the system by preloading or {@link FontManager#updateFontFile}.
+ * <pre>
+ * FontManager fm = getContext().getSystemService(FontManager.class);
+ * fm.updateFontFamily(new FontFamilyUpdateRequest.Builder()
+ *     .addFontFamily(new FontFamilyUpdateRequest.FontFamily("roboto", Arrays.asList(
+ *         new FontFamilyUpdateRequest.Font(
+ *             "Roboto-Regular",
+ *             new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
+ *             Collections.emptyList()),
+ *         new FontFamilyUpdateRequest.Font(
+ *             "Roboto-Regular",
+ *             new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_ITALIC),
+ *             Collections.emptyList()))))
+ *     .build(), fm.getFontConfig().getConfigVersion());
+ * </pre>
+ *
+ * <p>You can update or add font files in the same request by calling
+ * {@link FontFamilyUpdateRequest.Builder#addFontFileUpdateRequest(FontFileUpdateRequest)}.
+ * The following example adds "YourFont" font file and defines "your-font" font family in the same
+ * request. In this case, the font file represented by {@code yourFontFd} should be an OpenType
+ * compliant font file and have "YourFont" as PostScript name (ID=6) in 'name' table.
+ * <pre>
+ * FontManager fm = getContext().getSystemService(FontManager.class);
+ * fm.updateFontFamily(new FontFamilyUpdateRequest.Builder()
+ *     .addFontFileUpdateRequest(new FontFileUpdateRequest(yourFontFd, signature))
+ *     .addFontFamily(new FontFamilyUpdateRequest.FontFamily("your-font", Arrays.asList(
+ *         new FontFamilyUpdateRequest.Font(
+ *             "YourFont",
+ *             new FontStyle(FONT_WEIGHT_NORMAL, FONT_SLANT_UPRIGHT),
+ *             Collections.emptyList()))))
+ *     .build(), fm.getFontConfig().getConfigVersion());
+ * </pre>
+ *
+ * @hide
+ */
+@SystemApi
+public final class FontFamilyUpdateRequest {
+
+    /**
+     * A font family definition.
+     */
+    public static final class FontFamily {
+        @NonNull
+        private final String mName;
+        @NonNull
+        private final List<Font> mFonts;
+
+        /**
+         * Constructs a FontFamily.
+         *
+         * <p>A font family has a name to identify the font family. Apps can use
+         * {@link android.graphics.Typeface#create(String, int)} or XML resources to use a specific
+         * font family.
+         *
+         * <p>A font family consists of multiple fonts with different styles. The style information
+         * can be specified by {@link Font}.
+         *
+         * @see android.graphics.Typeface#create(String, int)
+         * @see Font
+         */
+        public FontFamily(@NonNull String name, @NonNull List<Font> fonts) {
+            Objects.requireNonNull(name);
+            Preconditions.checkStringNotEmpty(name);
+            Objects.requireNonNull(fonts);
+            Preconditions.checkCollectionElementsNotNull(fonts, "fonts");
+            Preconditions.checkCollectionNotEmpty(fonts, "fonts");
+            mName = name;
+            mFonts = fonts;
+        }
+
+        /**
+         * Returns the name of this family.
+         */
+        @NonNull
+        public String getName() {
+            return mName;
+        }
+
+        /**
+         * Returns the fonts in this family.
+         */
+        @NonNull
+        public List<Font> getFonts() {
+            return mFonts;
+        }
+    }
+
+    /**
+     * A single entry in a font family representing a font.
+     */
+    public static final class Font {
+
+        @NonNull
+        private final String mPostScriptName;
+        @NonNull
+        private final FontStyle mStyle;
+        @NonNull
+        private final List<FontVariationAxis> mAxes;
+
+        /**
+         * Constructs a FontStyleVariation.
+         *
+         * <p>A font has a PostScript name to identify the font file to use, a {@link FontStyle}
+         * to specify the style, and a list of {@link FontVariationAxis} to specify axis tags and
+         * values for variable fonts. If the font file identified by {@code postScriptName} is not a
+         * variable font, {@code axes} must be empty.
+         *
+         * @param postScriptName The PostScript name of the font file to use. PostScript name is in
+         *                       Name ID 6 field in 'name' table, as specified by OpenType
+         *                       specification.
+         * @param style          The style for this font.
+         * @param axes           A list of {@link FontVariationAxis} to specify axis tags and values
+         *                       for variable fonts.
+         */
+        public Font(@NonNull String postScriptName, @NonNull FontStyle style,
+                @NonNull List<FontVariationAxis> axes) {
+            Objects.requireNonNull(postScriptName);
+            Preconditions.checkStringNotEmpty(postScriptName);
+            Objects.requireNonNull(style);
+            Objects.requireNonNull(axes);
+            Preconditions.checkCollectionElementsNotNull(axes, "axes");
+            mPostScriptName = postScriptName;
+            mStyle = style;
+            mAxes = axes;
+        }
+
+        /**
+         * Returns PostScript name of the font file to use.
+         */
+        @NonNull
+        public String getPostScriptName() {
+            return mPostScriptName;
+        }
+
+        /**
+         * Returns the style.
+         */
+        @NonNull
+        public FontStyle getStyle() {
+            return mStyle;
+        }
+
+        /**
+         * Returns the list of {@link FontVariationAxis}.
+         */
+        @NonNull
+        public List<FontVariationAxis> getAxes() {
+            return mAxes;
+        }
+    }
+
+    /**
+     * Builds a {@link FontFamilyUpdateRequest}.
+     */
+    public static final class Builder {
+        @NonNull
+        private final List<FontFileUpdateRequest> mFontFileUpdateRequests = new ArrayList<>();
+        @NonNull
+        private final List<FontFamily> mFontFamilies = new ArrayList<>();
+
+        /**
+         * Constructs a FontFamilyUpdateRequest.Builder.
+         */
+        public Builder() {
+        }
+
+        /**
+         * Adds a {@link FontFileUpdateRequest} to execute as a part of the constructed
+         * {@link FontFamilyUpdateRequest}.
+         *
+         * @param request A font file update request.
+         * @return This builder object.
+         */
+        @NonNull
+        public Builder addFontFileUpdateRequest(@NonNull FontFileUpdateRequest request) {
+            Objects.requireNonNull(request);
+            mFontFileUpdateRequests.add(request);
+            return this;
+        }
+
+        /**
+         * Adds a font family to update an existing font family in the system font config or
+         * add as a new font family to the system font config.
+         *
+         * @param fontFamily An font family definition to add or update.
+         * @return This builder object.
+         */
+        @NonNull
+        public Builder addFontFamily(@NonNull FontFamily fontFamily) {
+            Objects.requireNonNull(fontFamily);
+            mFontFamilies.add(fontFamily);
+            return this;
+        }
+
+        /**
+         * Builds a {@link FontFamilyUpdateRequest}.
+         */
+        @NonNull
+        public FontFamilyUpdateRequest build() {
+            return new FontFamilyUpdateRequest(mFontFileUpdateRequests, mFontFamilies);
+        }
+    }
+
+    @NonNull
+    private final List<FontFileUpdateRequest> mFontFiles;
+
+    @NonNull
+    private final List<FontFamily> mFontFamilies;
+
+    private FontFamilyUpdateRequest(@NonNull List<FontFileUpdateRequest> fontFiles,
+            @NonNull List<FontFamily> fontFamilies) {
+        mFontFiles = fontFiles;
+        mFontFamilies = fontFamilies;
+    }
+
+    /**
+     * Returns the list of {@link FontFileUpdateRequest} that will be executed as a part of this
+     * request.
+     */
+    @NonNull
+    public List<FontFileUpdateRequest> getFontFileUpdateRequests() {
+        return mFontFiles;
+    }
+
+    /**
+     * Returns the list of {@link FontFamily} that will be updated in this request.
+     */
+    @NonNull
+    public List<FontFamily> getFontFamilies() {
+        return mFontFamilies;
+    }
+}
diff --git a/core/java/android/graphics/fonts/FontFileUpdateRequest.java b/core/java/android/graphics/fonts/FontFileUpdateRequest.java
new file mode 100644
index 0000000..cf1dca9
--- /dev/null
+++ b/core/java/android/graphics/fonts/FontFileUpdateRequest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.fonts;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.ParcelFileDescriptor;
+
+import java.util.Objects;
+
+/**
+ * Request for updating a font file on the system.
+ *
+ * @hide
+ */
+@SystemApi
+public final class FontFileUpdateRequest {
+
+    private final ParcelFileDescriptor mParcelFileDescriptor;
+    private final byte[] mSignature;
+
+    /**
+     * Creates a FontFileUpdateRequest with the given file and signature.
+     *
+     * @param parcelFileDescriptor A file descriptor of the font file.
+     * @param signature            A PKCS#7 detached signature for verifying the font file.
+     */
+    public FontFileUpdateRequest(@NonNull ParcelFileDescriptor parcelFileDescriptor,
+            @NonNull byte[] signature) {
+        Objects.requireNonNull(parcelFileDescriptor);
+        Objects.requireNonNull(signature);
+        mParcelFileDescriptor = parcelFileDescriptor;
+        mSignature = signature;
+    }
+
+    /**
+     * Returns the file descriptor of the font file.
+     */
+    @NonNull
+    public ParcelFileDescriptor getParcelFileDescriptor() {
+        return mParcelFileDescriptor;
+    }
+
+    /**
+     * Returns the PKCS#7 detached signature for verifying the font file.
+     */
+    @NonNull
+    public byte[] getSignature() {
+        return mSignature;
+    }
+}
diff --git a/core/java/android/graphics/fonts/FontManager.java b/core/java/android/graphics/fonts/FontManager.java
index abb4f9f..f087dd0 100644
--- a/core/java/android/graphics/fonts/FontManager.java
+++ b/core/java/android/graphics/fonts/FontManager.java
@@ -35,6 +35,8 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -58,7 +60,7 @@
                     RESULT_ERROR_VERIFICATION_FAILURE, RESULT_ERROR_VERSION_MISMATCH,
                     RESULT_ERROR_INVALID_FONT_FILE, RESULT_ERROR_INVALID_FONT_NAME,
                     RESULT_ERROR_DOWNGRADING, RESULT_ERROR_FAILED_UPDATE_CONFIG,
-                    RESULT_ERROR_FONT_UPDATER_DISABLED, RESULT_ERROR_REMOTE_EXCEPTION })
+                    RESULT_ERROR_FONT_UPDATER_DISABLED, RESULT_ERROR_FONT_NOT_FOUND })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ResultCode {}
 
@@ -131,9 +133,10 @@
     public static final int RESULT_ERROR_VERSION_MISMATCH = -8;
 
     /**
-     * Indicates a failure due to IPC communication.
+     * Indicates a failure occurred because a font with the specified PostScript name could not be
+     * found.
      */
-    public static final int RESULT_ERROR_REMOTE_EXCEPTION = -9;
+    public static final int RESULT_ERROR_FONT_NOT_FOUND = -9;
 
     /**
      * Indicates a failure of opening font file.
@@ -253,7 +256,6 @@
      * @see #RESULT_ERROR_DOWNGRADING
      * @see #RESULT_ERROR_FAILED_UPDATE_CONFIG
      * @see #RESULT_ERROR_FONT_UPDATER_DISABLED
-     * @see #RESULT_ERROR_REMOTE_EXCEPTION
      */
     @RequiresPermission(Manifest.permission.UPDATE_FONTS) public @ResultCode int updateFontFile(
             @NonNull ParcelFileDescriptor pfd,
@@ -261,10 +263,69 @@
             @IntRange(from = 0) int baseVersion
     ) {
         try {
-            return mIFontManager.updateFont(baseVersion, new FontUpdateRequest(pfd, signature));
+            return mIFontManager.updateFontFile(new FontUpdateRequest(pfd, signature), baseVersion);
         } catch (RemoteException e) {
-            Log.e(TAG, "Failed to call updateFont API", e);
-            return RESULT_ERROR_REMOTE_EXCEPTION;
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Update or add system wide font families.
+     *
+     * <p>This method will update existing font families or add new font families. The updated
+     * font family definitions will be used when creating {@link android.graphics.Typeface} objects
+     * with using {@link android.graphics.Typeface#create(String, int)} specifying the family name,
+     * or through XML resources. Note that system fallback fonts cannot be modified by this method.
+     * Apps must use {@link android.graphics.Typeface.CustomFallbackBuilder} to use custom fallback
+     * fonts.
+     *
+     * <p>Font files can be updated by including {@link FontFileUpdateRequest} to {@code request}
+     * via {@link FontFamilyUpdateRequest.Builder#addFontFileUpdateRequest(FontFileUpdateRequest)}.
+     * The same constraints as {@link #updateFontFile} will apply when updating font files.
+     *
+     * <p>The caller must specify the base config version for keeping the font configuration
+     * consistent. If the font configuration is updated for some reason between the time you get
+     * a configuration with {@link #getFontConfig()} and the time when you call this method,
+     * {@link #RESULT_ERROR_VERSION_MISMATCH} will be returned. Get the latest font configuration by
+     * calling {@link #getFontConfig()} and call this method again with the latest config version.
+     *
+     * @param request A {@link FontFamilyUpdateRequest} to execute.
+     * @param baseVersion A base config version to be updated. You can get the latest config version
+     *                    by {@link FontConfig#getConfigVersion()} via {@link #getFontConfig()}. If
+     *                    the system has a newer config version, the update will fail with
+     *                    {@link #RESULT_ERROR_VERSION_MISMATCH}.
+     * @return A result code.
+     * @see FontConfig#getConfigVersion()
+     * @see #getFontConfig()
+     * @see #RESULT_SUCCESS
+     * @see #RESULT_ERROR_FAILED_TO_WRITE_FONT_FILE
+     * @see #RESULT_ERROR_VERIFICATION_FAILURE
+     * @see #RESULT_ERROR_VERSION_MISMATCH
+     * @see #RESULT_ERROR_INVALID_FONT_FILE
+     * @see #RESULT_ERROR_INVALID_FONT_NAME
+     * @see #RESULT_ERROR_DOWNGRADING
+     * @see #RESULT_ERROR_FAILED_UPDATE_CONFIG
+     * @see #RESULT_ERROR_FONT_UPDATER_DISABLED
+     * @see #RESULT_ERROR_FONT_NOT_FOUND
+     */
+    @RequiresPermission(Manifest.permission.UPDATE_FONTS) public @ResultCode int updateFontFamily(
+            @NonNull FontFamilyUpdateRequest request, @IntRange(from = 0) int baseVersion) {
+        List<FontUpdateRequest> requests = new ArrayList<>();
+        List<FontFileUpdateRequest> fontFileUpdateRequests = request.getFontFileUpdateRequests();
+        for (int i = 0; i < fontFileUpdateRequests.size(); i++) {
+            FontFileUpdateRequest fontFile = fontFileUpdateRequests.get(i);
+            requests.add(new FontUpdateRequest(fontFile.getParcelFileDescriptor(),
+                    fontFile.getSignature()));
+        }
+        List<FontFamilyUpdateRequest.FontFamily> fontFamilies = request.getFontFamilies();
+        for (int i = 0; i < fontFamilies.size(); i++) {
+            FontFamilyUpdateRequest.FontFamily fontFamily = fontFamilies.get(i);
+            requests.add(new FontUpdateRequest(fontFamily.getName(), fontFamily.getFonts()));
+        }
+        try {
+            return mIFontManager.updateFontFamily(requests, baseVersion);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
         }
     }
 
diff --git a/core/java/android/graphics/fonts/FontUpdateRequest.java b/core/java/android/graphics/fonts/FontUpdateRequest.java
index f551d6a..b79c8f62 100644
--- a/core/java/android/graphics/fonts/FontUpdateRequest.java
+++ b/core/java/android/graphics/fonts/FontUpdateRequest.java
@@ -19,13 +19,17 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.os.LocaleList;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
 import android.text.FontConfig;
 
+import java.io.File;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Represents a font update request. Currently only font install request is supported.
@@ -80,6 +84,26 @@
         mFontFamily = fontFamily;
     }
 
+    public FontUpdateRequest(@NonNull String postScriptName,
+            @NonNull List<FontFamilyUpdateRequest.Font> variations) {
+        // TODO: Serialize the request directly instead of reusing FontConfig.FontFamily.
+        this(createFontFamily(postScriptName, variations));
+    }
+
+    private static FontConfig.FontFamily createFontFamily(@NonNull String postScriptName,
+            @NonNull List<FontFamilyUpdateRequest.Font> fonts) {
+        List<FontConfig.Font> configFonts = new ArrayList<>(fonts.size());
+        for (FontFamilyUpdateRequest.Font font : fonts) {
+            // TODO: Support .otf.
+            configFonts.add(new FontConfig.Font(new File(font.getPostScriptName() + ".ttf"), null,
+                    font.getStyle(), 0 /* index */,
+                    FontVariationAxis.toFontVariationSettings(font.getAxes()),
+                    null /* fontFamilyName */));
+        }
+        return new FontConfig.FontFamily(configFonts, postScriptName,
+                LocaleList.getEmptyLocaleList(), FontConfig.FontFamily.VARIANT_DEFAULT);
+    }
+
     protected FontUpdateRequest(Parcel in) {
         mType = in.readInt();
         mFd = in.readParcelable(ParcelFileDescriptor.class.getClassLoader());
diff --git a/core/java/com/android/internal/graphics/fonts/IFontManager.aidl b/core/java/com/android/internal/graphics/fonts/IFontManager.aidl
index dfcc914..1c7eca8 100644
--- a/core/java/com/android/internal/graphics/fonts/IFontManager.aidl
+++ b/core/java/com/android/internal/graphics/fonts/IFontManager.aidl
@@ -20,6 +20,8 @@
 import android.graphics.fonts.FontUpdateRequest;
 import android.text.FontConfig;
 
+import java.util.List;
+
 /**
  * System private interface for talking with
  * {@link com.android.server.graphics.fonts.FontManagerService}.
@@ -28,5 +30,7 @@
 interface IFontManager {
     FontConfig getFontConfig();
 
-    int updateFont(int baseVersion, in FontUpdateRequest request);
+    int updateFontFile(in FontUpdateRequest request, int baseVersion);
+
+    int updateFontFamily(in List<FontUpdateRequest> request, int baseVersion);
 }
diff --git a/graphics/java/android/graphics/fonts/FontVariationAxis.java b/graphics/java/android/graphics/fonts/FontVariationAxis.java
index 7bd5817..d1fe2cd 100644
--- a/graphics/java/android/graphics/fonts/FontVariationAxis.java
+++ b/graphics/java/android/graphics/fonts/FontVariationAxis.java
@@ -23,6 +23,7 @@
 import android.text.TextUtils;
 
 import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 import java.util.regex.Pattern;
 
@@ -189,6 +190,17 @@
         return TextUtils.join(",", axes);
     }
 
+    /**
+     * Stringify the array of FontVariationAxis.
+     * @hide
+     */
+    public static @NonNull String toFontVariationSettings(@Nullable List<FontVariationAxis> axes) {
+        if (axes == null) {
+            return "";
+        }
+        return TextUtils.join(",", axes);
+    }
+
     @Override
     public boolean equals(@Nullable Object o) {
         if (o == this) {
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 1b27572..09c0937 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
@@ -72,11 +72,11 @@
     }
 
     @Override
-    public int updateFont(int baseVersion, @NonNull FontUpdateRequest request) {
+    public int updateFontFile(@NonNull FontUpdateRequest request, int baseVersion) {
+        Preconditions.checkArgumentNonnegative(baseVersion);
         Objects.requireNonNull(request);
         Objects.requireNonNull(request.getFd());
         Objects.requireNonNull(request.getSignature());
-        Preconditions.checkArgumentNonnegative(baseVersion);
         getContext().enforceCallingPermission(Manifest.permission.UPDATE_FONTS,
                 "UPDATE_FONTS permission required.");
         try {
@@ -88,6 +88,21 @@
         }
     }
 
+    @Override
+    public int updateFontFamily(@NonNull List<FontUpdateRequest> requests, int baseVersion) {
+        Preconditions.checkArgumentNonnegative(baseVersion);
+        Objects.requireNonNull(requests);
+        getContext().enforceCallingPermission(Manifest.permission.UPDATE_FONTS,
+                "UPDATE_FONTS permission required.");
+        try {
+            update(baseVersion, requests);
+            return FontManager.RESULT_SUCCESS;
+        } catch (SystemFontException e) {
+            Slog.e(TAG, "Failed to update font family", e);
+            return e.getErrorCode();
+        }
+    }
+
     /* package */ static class SystemFontException extends AndroidException {
         private final int mErrorCode;
 
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 45f2a38..08ddc6d 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -45,6 +45,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * Manages set of updatable font files.
@@ -182,8 +183,12 @@
             List<FontConfig.FontFamily> fontFamilies = config.fontFamilies;
             for (int i = 0; i < fontFamilies.size(); i++) {
                 FontConfig.FontFamily fontFamily = fontFamilies.get(i);
-                // Ignore failures as updated fonts may be obsoleted by system OTA update.
-                addFontFamily(fontFamily);
+                try {
+                    addFontFamily(fontFamily);
+                } catch (SystemFontException e) {
+                    // Ignore failures as updated fonts may be obsoleted by system OTA update.
+                    Slog.i(TAG, "Obsolete font family: " + fontFamily.getName());
+                }
             }
             success = true;
         } catch (Throwable t) {
@@ -236,10 +241,7 @@
                                 request.getFd().getFileDescriptor(), request.getSignature());
                         break;
                     case FontUpdateRequest.TYPE_UPDATE_FONT_FAMILY:
-                        // TODO: define error code.
-                        if (!addFontFamily(request.getFontFamily())) {
-                            throw new IllegalArgumentException("Invalid font family");
-                        }
+                        addFontFamily(request.getFontFamily());
                         break;
                 }
             }
@@ -495,18 +497,15 @@
      * Unnamed font families are used as other named font family's fallback fonts to guarantee a
      * complete glyph coverage.
      */
-    private boolean addFontFamily(FontConfig.FontFamily fontFamily) {
-        if (fontFamily.getName() == null) {
-            Slog.e(TAG, "Name is null.");
-            return false;
-        }
+    private void addFontFamily(FontConfig.FontFamily fontFamily) throws SystemFontException {
+        Objects.requireNonNull(fontFamily.getName());
         FontConfig.FontFamily resolvedFontFamily = resolveFontFiles(fontFamily);
         if (resolvedFontFamily == null) {
-            Slog.e(TAG, "Required fonts are not available");
-            return false;
+            throw new SystemFontException(
+                    FontManager.RESULT_ERROR_FONT_NOT_FOUND,
+                    "Required fonts are not available");
         }
         mFontFamilyMap.put(resolvedFontFamily.getName(), resolvedFontFamily);
-        return true;
     }
 
     @Nullable
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 4bbf96f..7771afc 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
@@ -684,8 +684,8 @@
                     newAddFontFamilyRequest("<family lang='en'>"
                             + "  <font>test.ttf</font>"
                             + "</family>")));
-            fail("Expect IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
+            fail("Expect NullPointerException");
+        } catch (NullPointerException e) {
             // Expect
         }
     }
@@ -703,9 +703,10 @@
             dir.update(Arrays.asList(newAddFontFamilyRequest("<family name='test'>"
                     + "  <font>test.ttf</font>"
                     + "</family>")));
-            fail("Expect IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            // Expect
+            fail("Expect SystemFontException");
+        } catch (FontManagerService.SystemFontException e) {
+            assertThat(e.getErrorCode())
+                    .isEqualTo(FontManager.RESULT_ERROR_FONT_NOT_FOUND);
         }
     }