Merge "Introduce Font and its builder class"
diff --git a/api/current.txt b/api/current.txt
index d971f18..498ab95 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -15178,6 +15178,37 @@
 
 package android.graphics.fonts {
 
+  public class Font {
+    method public android.graphics.fonts.FontVariationAxis[] getAxes();
+    method public int getTtcIndex();
+    method public int getWeight();
+    method public boolean isItalic();
+    field public static final int FONT_WEIGHT_BLACK = 900; // 0x384
+    field public static final int FONT_WEIGHT_BOLD = 700; // 0x2bc
+    field public static final int FONT_WEIGHT_EXTRA_BOLD = 800; // 0x320
+    field public static final int FONT_WEIGHT_EXTRA_LIGHT = 200; // 0xc8
+    field public static final int FONT_WEIGHT_LIGHT = 300; // 0x12c
+    field public static final int FONT_WEIGHT_MEDIUM = 500; // 0x1f4
+    field public static final int FONT_WEIGHT_NORMAL = 400; // 0x190
+    field public static final int FONT_WEIGHT_SEMI_BOLD = 600; // 0x258
+    field public static final int FONT_WEIGHT_THIN = 100; // 0x64
+  }
+
+  public static class Font.Builder {
+    ctor public Font.Builder(java.nio.ByteBuffer);
+    ctor public Font.Builder(java.io.File) throws java.io.IOException;
+    ctor public Font.Builder(java.io.FileDescriptor) throws java.io.IOException;
+    ctor public Font.Builder(java.io.FileDescriptor, long, long) throws java.io.IOException;
+    ctor public Font.Builder(android.content.res.AssetManager, java.lang.String) throws java.io.IOException;
+    ctor public Font.Builder(android.content.res.Resources, int) throws java.io.IOException;
+    method public android.graphics.fonts.Font build();
+    method public android.graphics.fonts.Font.Builder setFontVariationSettings(java.lang.String);
+    method public android.graphics.fonts.Font.Builder setFontVariationSettings(android.graphics.fonts.FontVariationAxis[]);
+    method public android.graphics.fonts.Font.Builder setItalic(boolean);
+    method public android.graphics.fonts.Font.Builder setTtcIndex(int);
+    method public android.graphics.fonts.Font.Builder setWeight(int);
+  }
+
   public final class FontVariationAxis {
     ctor public FontVariationAxis(java.lang.String, float);
     method public static android.graphics.fonts.FontVariationAxis[] fromFontVariationSettings(java.lang.String);
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 3d80f3d..6856e29 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -154,6 +154,7 @@
         "android/graphics/Typeface.cpp",
         "android/graphics/Utils.cpp",
         "android/graphics/YuvToJpegEncoder.cpp",
+        "android/graphics/fonts/Font.cpp",
         "android/graphics/pdf/PdfDocument.cpp",
         "android/graphics/pdf/PdfEditor.cpp",
         "android/graphics/pdf/PdfRenderer.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 0c625a7..1820888 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -140,6 +140,7 @@
 extern int register_android_graphics_SurfaceTexture(JNIEnv* env);
 extern int register_android_graphics_drawable_AnimatedVectorDrawable(JNIEnv* env);
 extern int register_android_graphics_drawable_VectorDrawable(JNIEnv* env);
+extern int register_android_graphics_fonts_Font(JNIEnv* env);
 extern int register_android_graphics_pdf_PdfDocument(JNIEnv* env);
 extern int register_android_graphics_pdf_PdfEditor(JNIEnv* env);
 extern int register_android_graphics_pdf_PdfRenderer(JNIEnv* env);
@@ -1406,6 +1407,7 @@
     REG_JNI(register_android_graphics_YuvImage),
     REG_JNI(register_android_graphics_drawable_AnimatedVectorDrawable),
     REG_JNI(register_android_graphics_drawable_VectorDrawable),
+    REG_JNI(register_android_graphics_fonts_Font),
     REG_JNI(register_android_graphics_pdf_PdfDocument),
     REG_JNI(register_android_graphics_pdf_PdfEditor),
     REG_JNI(register_android_graphics_pdf_PdfRenderer),
diff --git a/core/jni/android/graphics/FontUtils.h b/core/jni/android/graphics/FontUtils.h
index 9eaaa49..9f6462e 100644
--- a/core/jni/android/graphics/FontUtils.h
+++ b/core/jni/android/graphics/FontUtils.h
@@ -20,6 +20,8 @@
 #include <jni.h>
 #include <memory>
 
+#include <minikin/Font.h>
+
 namespace minikin {
 class FontFamily;
 }  // namespace minikin
@@ -31,6 +33,11 @@
   std::shared_ptr<minikin::FontFamily> family;
 };
 
+struct FontWrapper {
+  FontWrapper(minikin::Font&& font) : font(std::move(font)) {}
+  minikin::Font font;
+};
+
 // Utility wrapper for java.util.List
 class ListHelper {
 public:
diff --git a/core/jni/android/graphics/fonts/Font.cpp b/core/jni/android/graphics/fonts/Font.cpp
new file mode 100644
index 0000000..2d1d7a0
--- /dev/null
+++ b/core/jni/android/graphics/fonts/Font.cpp
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#define LOG_TAG "Minikin"
+
+#include <nativehelper/JNIHelp.h>
+#include <core_jni_helpers.h>
+
+#include "SkData.h"
+#include "SkFontMgr.h"
+#include "SkRefCnt.h"
+#include "SkTypeface.h"
+#include "GraphicsJNI.h"
+#include <nativehelper/ScopedUtfChars.h>
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/android_util_AssetManager.h>
+#include <androidfw/AssetManager2.h>
+#include "Utils.h"
+#include "FontUtils.h"
+
+#include <hwui/MinikinSkia.h>
+#include <hwui/Typeface.h>
+#include <utils/FatVector.h>
+#include <minikin/FontFamily.h>
+
+#include <memory>
+
+namespace android {
+
+struct NativeFontBuilder {
+    std::vector<minikin::FontVariation> axes;
+};
+
+static inline NativeFontBuilder* toBuilder(jlong ptr) {
+    return reinterpret_cast<NativeFontBuilder*>(ptr);
+}
+
+static inline Asset* toAsset(jlong ptr) {
+    return reinterpret_cast<Asset*>(ptr);
+}
+
+static void releaseAsset(jlong asset) {
+    delete toAsset(asset);
+}
+
+static void releaseFont(jlong font) {
+    delete reinterpret_cast<FontWrapper*>(font);
+}
+
+static void release_global_ref(const void* /*data*/, void* context) {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    if (env == nullptr) {
+        JavaVMAttachArgs args;
+        args.version = JNI_VERSION_1_4;
+        args.name = "release_font_data";
+        args.group = nullptr;
+        jint result = AndroidRuntime::getJavaVM()->AttachCurrentThread(&env, &args);
+        if (result != JNI_OK) {
+            ALOGE("failed to attach to thread to release global ref.");
+            return;
+        }
+    }
+
+    jobject obj = reinterpret_cast<jobject>(context);
+    env->DeleteGlobalRef(obj);
+}
+
+// Regular JNI
+static jlong Font_Builder_getNativeAsset(
+    JNIEnv* env, jobject clazz, jobject assetMgr, jstring path, jboolean isAsset, jint cookie) {
+    NPE_CHECK_RETURN_ZERO(env, assetMgr);
+    NPE_CHECK_RETURN_ZERO(env, path);
+
+    Guarded<AssetManager2>* mgr = AssetManagerForJavaObject(env, assetMgr);
+    if (mgr == nullptr) {
+        return 0;
+    }
+
+    ScopedUtfChars str(env, path);
+    if (str.c_str() == nullptr) {
+        return 0;
+    }
+
+    std::unique_ptr<Asset> asset;
+    {
+      ScopedLock<AssetManager2> locked_mgr(*mgr);
+      if (isAsset) {
+          asset = locked_mgr->Open(str.c_str(), Asset::ACCESS_BUFFER);
+      } else if (cookie > 0) {
+          // Valid java cookies are 1-based, but AssetManager cookies are 0-based.
+          asset = locked_mgr->OpenNonAsset(str.c_str(), static_cast<ApkAssetsCookie>(cookie - 1),
+                  Asset::ACCESS_BUFFER);
+      } else {
+          asset = locked_mgr->OpenNonAsset(str.c_str(), Asset::ACCESS_BUFFER);
+      }
+    }
+
+    return reinterpret_cast<jlong>(asset.release());
+}
+
+// Regular JNI
+static jobject Font_Builder_getAssetBuffer(JNIEnv* env, jobject clazz, jlong nativeAsset) {
+    Asset* asset = toAsset(nativeAsset);
+    return env->NewDirectByteBuffer(const_cast<void*>(asset->getBuffer(false)), asset->getLength());
+}
+
+// CriticalNative
+static jlong Font_Builder_getReleaseNativeAssetFunc() {
+    return reinterpret_cast<jlong>(&releaseAsset);
+}
+
+// Regular JNI
+static jlong Font_Builder_initBuilder(JNIEnv*, jobject) {
+    return reinterpret_cast<jlong>(new NativeFontBuilder());
+}
+
+// Critical Native
+static void Font_Builder_addAxis(jlong builderPtr, jint tag, jfloat value) {
+    toBuilder(builderPtr)->axes.emplace_back(static_cast<minikin::AxisTag>(tag), value);
+}
+
+// Regular JNI
+static jlong Font_Builder_build(JNIEnv* env, jobject clazz, jlong builderPtr, jobject buffer,
+        jint weight, jboolean italic, jint ttcIndex) {
+    NPE_CHECK_RETURN_ZERO(env, buffer);
+    std::unique_ptr<NativeFontBuilder> builder(toBuilder(builderPtr));
+    const void* fontPtr = env->GetDirectBufferAddress(buffer);
+    if (fontPtr == nullptr) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", "Not a direct buffer");
+        return 0;
+    }
+    jlong fontSize = env->GetDirectBufferCapacity(buffer);
+    if (fontSize <= 0) {
+        jniThrowException(env, "java/lang/IllegalArgumentException",
+                          "buffer size must not be zero or negative");
+        return 0;
+    }
+    jobject fontRef = MakeGlobalRefOrDie(env, buffer);
+    sk_sp<SkData> data(SkData::MakeWithProc(fontPtr, fontSize,
+            release_global_ref, reinterpret_cast<void*>(fontRef)));
+
+    uirenderer::FatVector<SkFontArguments::Axis, 2> skiaAxes;
+    for (const auto& axis : builder->axes) {
+        skiaAxes.emplace_back(SkFontArguments::Axis{axis.axisTag, axis.value});
+    }
+
+    std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(std::move(data)));
+
+    SkFontArguments params;
+    params.setCollectionIndex(ttcIndex);
+    params.setAxes(skiaAxes.data(), skiaAxes.size());
+
+    sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
+    sk_sp<SkTypeface> face(fm->makeFromStream(std::move(fontData), params));
+    if (face == nullptr) {
+        jniThrowException(env, "java/lang/IllegalArgumentException",
+                          "Failed to create internal object. maybe invalid font data.");
+        return 0;
+    }
+    std::shared_ptr<minikin::MinikinFont> minikinFont =
+            std::make_shared<MinikinFontSkia>(std::move(face), fontPtr, fontSize, ttcIndex,
+                    builder->axes);
+    minikin::Font font = minikin::Font::Builder(minikinFont).setWeight(weight)
+                    .setSlant(static_cast<minikin::FontStyle::Slant>(italic)).build();
+    return reinterpret_cast<jlong>(new FontWrapper(std::move(font)));
+}
+
+// Critical Native
+static jlong Font_Builder_getReleaseNativeFont() {
+    return reinterpret_cast<jlong>(releaseFont);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static const JNINativeMethod gFontBuilderMethods[] = {
+    { "nInitBuilder", "()J", (void*) Font_Builder_initBuilder },
+    { "nAddAxis", "(JIF)V", (void*) Font_Builder_addAxis },
+    { "nBuild", "(JLjava/nio/ByteBuffer;IZI)J", (void*) Font_Builder_build },
+    { "nGetReleaseNativeFont", "()J", (void*) Font_Builder_getReleaseNativeFont },
+
+    { "nGetNativeAsset", "(Landroid/content/res/AssetManager;Ljava/lang/String;ZI)J",
+      (void*) Font_Builder_getNativeAsset },
+    { "nGetAssetBuffer", "(J)Ljava/nio/ByteBuffer;", (void*) Font_Builder_getAssetBuffer },
+    { "nGetReleaseNativeAssetFunc", "()J", (void*) Font_Builder_getReleaseNativeAssetFunc },
+};
+
+int register_android_graphics_fonts_Font(JNIEnv* env) {
+    return RegisterMethodsOrDie(env, "android/graphics/fonts/Font$Builder", gFontBuilderMethods,
+            NELEM(gFontBuilderMethods));
+}
+
+}
diff --git a/graphics/java/android/graphics/fonts/Font.java b/graphics/java/android/graphics/fonts/Font.java
new file mode 100644
index 0000000..9da61db9
--- /dev/null
+++ b/graphics/java/android/graphics/fonts/Font.java
@@ -0,0 +1,472 @@
+/*
+ * Copyright (C) 2018 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.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.util.TypedValue;
+
+import com.android.internal.util.Preconditions;
+
+import dalvik.annotation.optimization.CriticalNative;
+
+import libcore.util.NativeAllocationRegistry;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+
+/**
+ * A font class can be used for creating FontFamily.
+ */
+public class Font {
+    private static final String TAG = "Font";
+
+    private static final int NOT_SPECIFIED = -1;
+    private static final int STYLE_ITALIC = 1;
+    private static final int STYLE_NORMAL = 0;
+
+    /**
+     * A font weight value for the thin weight
+     */
+    public static final int FONT_WEIGHT_THIN = 100;
+
+    /**
+     * A font weight value for the extra-light weight
+     */
+    public static final int FONT_WEIGHT_EXTRA_LIGHT = 200;
+
+    /**
+     * A font weight value for the light weight
+     */
+    public static final int FONT_WEIGHT_LIGHT = 300;
+
+    /**
+     * A font weight value for the normal weight
+     */
+    public static final int FONT_WEIGHT_NORMAL = 400;
+
+    /**
+     * A font weight value for the medium weight
+     */
+    public static final int FONT_WEIGHT_MEDIUM = 500;
+
+    /**
+     * A font weight value for the semi-bold weight
+     */
+    public static final int FONT_WEIGHT_SEMI_BOLD = 600;
+
+    /**
+     * A font weight value for the bold weight.
+     */
+    public static final int FONT_WEIGHT_BOLD = 700;
+
+    /**
+     * A font weight value for the extra-bold weight
+     */
+    public static final int FONT_WEIGHT_EXTRA_BOLD = 800;
+
+    /**
+     * A font weight value for the black weight
+     */
+    public static final int FONT_WEIGHT_BLACK = 900;
+
+    /**
+     * A builder class for creating new Font.
+     */
+    public static class Builder {
+        private static final NativeAllocationRegistry sAssetByteBufferRegistroy =
+                new NativeAllocationRegistry(ByteBuffer.class.getClassLoader(),
+                    nGetReleaseNativeAssetFunc(), 64);
+
+        private static final NativeAllocationRegistry sFontRegistory =
+                new NativeAllocationRegistry(Font.class.getClassLoader(),
+                    nGetReleaseNativeFont(), 64);
+
+        private @Nullable ByteBuffer mBuffer;
+        private @IntRange(from = -1, to = 1000) int mWeight = NOT_SPECIFIED;
+        private @IntRange(from = -1, to = 1) int mItalic = NOT_SPECIFIED;
+        private @IntRange(from = 0) int mTtcIndex = 0;
+        private @Nullable FontVariationAxis[] mAxes = null;
+
+        /**
+         * Constructs a builder with a byte buffer.
+         *
+         * Note that only direct buffer can be used as the source of font data.
+         *
+         * @see ByteBuffer#allocateDirect(int)
+         * @param buffer a byte buffer of a font data
+         */
+        public Builder(@NonNull ByteBuffer buffer) {
+            Preconditions.checkNotNull(buffer, "buffer can not be null");
+            if (!buffer.isDirect()) {
+                throw new IllegalArgumentException(
+                        "Only direct buffer can be used as the source of font data.");
+            }
+            mBuffer = buffer;
+        }
+
+        /**
+         * Constructs a builder with a file path.
+         *
+         * @param path a file path to the font file
+         */
+        public Builder(@NonNull File path) throws IOException {
+            Preconditions.checkNotNull(path, "path can not be null");
+            try (FileInputStream fis = new FileInputStream(path)) {
+                final FileChannel fc = fis.getChannel();
+                mBuffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());
+            }
+        }
+
+        /**
+         * Constructs a builder with a file descriptor.
+         *
+         * @param fd a file descriptor
+         */
+        public Builder(@NonNull FileDescriptor fd) throws IOException {
+            this(fd, 0, -1);
+        }
+
+        /**
+         * Constructs a builder with a file descriptor.
+         *
+         * @param fd a file descriptor
+         * @param offset an offset to of the font data in the file
+         * @param size a size of the font data. If -1 is passed, use until end of the file.
+         */
+        public Builder(@NonNull FileDescriptor fd, @IntRange(from = 0) long offset,
+                @IntRange(from = -1) long size) throws IOException {
+            try (FileInputStream fis = new FileInputStream(fd)) {
+                final FileChannel fc = fis.getChannel();
+                size = (size == -1) ? fc.size() - offset : size;
+                mBuffer = fc.map(FileChannel.MapMode.READ_ONLY, offset, size);
+            }
+        }
+
+        /**
+         * Constructs a builder from an asset manager and a file path in an asset directory.
+         *
+         * @param am the application's asset manager
+         * @param path the file name of the font data in the asset directory
+         */
+        public Builder(@NonNull AssetManager am, @NonNull String path) throws IOException {
+            final long nativeAsset = nGetNativeAsset(am, path, true /* is asset */, 0 /* cookie */);
+            if (nativeAsset == 0) {
+                throw new FileNotFoundException("Unable to open " + path);
+            }
+            final ByteBuffer b = nGetAssetBuffer(nativeAsset);
+            sAssetByteBufferRegistroy.registerNativeAllocation(b, nativeAsset);
+            if (b == null) {
+                throw new FileNotFoundException(path + " not found");
+            }
+            mBuffer = b;
+        }
+
+        /**
+         * Constructs a builder from resources.
+         *
+         * Resource ID must points the font file. XML font can not be used here.
+         *
+         * @param res the resource of this application.
+         * @param resId the resource ID of font file.
+         */
+        public Builder(@NonNull Resources res, int resId) throws IOException {
+            final TypedValue value = new TypedValue();
+            res.getValue(resId, value, true);
+            if (value.string == null) {
+                throw new FileNotFoundException(resId + " not found");
+            }
+            final String str = value.string.toString();
+            if (str.toLowerCase().endsWith(".xml")) {
+                throw new FileNotFoundException(resId + " must be font file.");
+            }
+            final long nativeAsset = nGetNativeAsset(res.getAssets(), str, false /* is asset */,
+                    value.assetCookie);
+            if (nativeAsset == 0) {
+                throw new FileNotFoundException("Unable to open " + str);
+            }
+            final ByteBuffer b = nGetAssetBuffer(nativeAsset);
+            sAssetByteBufferRegistroy.registerNativeAllocation(b, nativeAsset);
+            if (b == null) {
+                throw new FileNotFoundException(str + " not found");
+            }
+            mBuffer = b;
+        }
+
+        /**
+         * Sets weight of the font.
+         *
+         * Tells the system the weight of the given font. If this function is not called, the system
+         * will resolve the weight value by reading font tables.
+         *
+         * Here are pairs of the common names and their values.
+         * <p>
+         *  <table>
+         *  <thead>
+         *  <tr>
+         *  <th align="center">Value</th>
+         *  <th align="center">Name</th>
+         *  <th align="center">Android Definition</th>
+         *  </tr>
+         *  </thead>
+         *  <tbody>
+         *  <tr>
+         *  <td align="center">100</td>
+         *  <td align="center">Thin</td>
+         *  <td align="center">{@link Font#FONT_WEIGHT_THIN}</td>
+         *  </tr>
+         *  <tr>
+         *  <td align="center">200</td>
+         *  <td align="center">Extra Light (Ultra Light)</td>
+         *  <td align="center">{@link Font#FONT_WEIGHT_EXTRA_LIGHT}</td>
+         *  </tr>
+         *  <tr>
+         *  <td align="center">300</td>
+         *  <td align="center">Light</td>
+         *  <td align="center">{@link Font#FONT_WEIGHT_LIGHT}</td>
+         *  </tr>
+         *  <tr>
+         *  <td align="center">400</td>
+         *  <td align="center">Normal (Regular)</td>
+         *  <td align="center">{@link Font#FONT_WEIGHT_NORMAL}</td>
+         *  </tr>
+         *  <tr>
+         *  <td align="center">500</td>
+         *  <td align="center">Medium</td>
+         *  <td align="center">{@link Font#FONT_WEIGHT_MEDIUM}</td>
+         *  </tr>
+         *  <tr>
+         *  <td align="center">600</td>
+         *  <td align="center">Semi Bold (Demi Bold)</td>
+         *  <td align="center">{@link Font#FONT_WEIGHT_SEMI_BOLD}</td>
+         *  </tr>
+         *  <tr>
+         *  <td align="center">700</td>
+         *  <td align="center">Bold</td>
+         *  <td align="center">{@link Font#FONT_WEIGHT_BOLD}</td>
+         *  </tr>
+         *  <tr>
+         *  <td align="center">800</td>
+         *  <td align="center">Extra Bold (Ultra Bold)</td>
+         *  <td align="center">{@link Font#FONT_WEIGHT_EXTRA_BOLD}</td>
+         *  </tr>
+         *  <tr>
+         *  <td align="center">900</td>
+         *  <td align="center">Black (Heavy)</td>
+         *  <td align="center">{@link Font#FONT_WEIGHT_BLACK}</td>
+         *  </tr>
+         *  </tbody>
+         * </p>
+         *
+         * @see Font#FONT_WEIGHT_THIN
+         * @see Font#FONT_WEIGHT_EXTRA_LIGHT
+         * @see Font#FONT_WEIGHT_LIGHT
+         * @see Font#FONT_WEIGHT_NORMAL
+         * @see Font#FONT_WEIGHT_MEDIUM
+         * @see Font#FONT_WEIGHT_SEMI_BOLD
+         * @see Font#FONT_WEIGHT_BOLD
+         * @see Font#FONT_WEIGHT_EXTRA_BOLD
+         * @see Font#FONT_WEIGHT_BLACK
+         * @param weight a weight value
+         * @return this builder
+         */
+        public @NonNull Builder setWeight(@IntRange(from = 1, to = 1000) int weight) {
+            Preconditions.checkArgument(1 <= weight && weight <= 1000);
+            mWeight = weight;
+            return this;
+        }
+
+        /**
+         * Sets italic information of the font.
+         *
+         * Tells the system the style of the given font. If this function is not called, the system
+         * will resolve the style by reading font tables.
+         *
+         * For example, if you want to use italic font as upright font, call {@code
+         * setItalic(false)} explicitly.
+         *
+         * @param italic {@code true} if the font is italic. Otherwise {@code false}.
+         * @return this builder
+         */
+        public @NonNull Builder setItalic(boolean italic) {
+            mItalic = italic ? STYLE_ITALIC : STYLE_NORMAL;
+            return this;
+        }
+
+        /**
+         * Sets an index of the font collection. See {@link android.R.attr#ttcIndex}.
+         *
+         * @param ttcIndex An index of the font collection. If the font source is not font
+         *                 collection, do not call this method or specify 0.
+         * @return this builder
+         */
+        public @NonNull Builder setTtcIndex(@IntRange(from = 0) int ttcIndex) {
+            mTtcIndex = ttcIndex;
+            return this;
+        }
+
+        /**
+         * Sets the font variation settings.
+         *
+         * @param variationSettings see {@link FontVariationAxis#fromFontVariationSettings(String)}
+         * @return this builder
+         * @throws IllegalArgumentException If given string is not a valid font variation settings
+         *                                  format.
+         */
+        public @NonNull Builder setFontVariationSettings(@Nullable String variationSettings) {
+            mAxes = FontVariationAxis.fromFontVariationSettings(variationSettings);
+            return this;
+        }
+
+        /**
+         * Sets the font variation settings.
+         *
+         * @param axes an array of font variation axis tag-value pairs
+         * @return this builder
+         */
+        public @NonNull Builder setFontVariationSettings(@Nullable FontVariationAxis[] axes) {
+            mAxes = axes;
+            return this;
+        }
+
+        /**
+         * Creates the font based on the configured values.
+         * @return the Font object
+         */
+        public @Nullable Font build() {
+            if (mWeight == NOT_SPECIFIED || mItalic == NOT_SPECIFIED) {
+                final int packed = FontFileUtil.analyzeStyle(mBuffer, mTtcIndex, mAxes);
+                if (FontFileUtil.isSuccess(packed)) {
+                    if (mWeight == NOT_SPECIFIED) {
+                        mWeight = FontFileUtil.unpackWeight(packed);
+                    }
+                    if (mItalic == NOT_SPECIFIED) {
+                        mItalic = FontFileUtil.unpackItalic(packed) ? STYLE_ITALIC : STYLE_NORMAL;
+                    }
+                } else {
+                    mWeight = 400;
+                    mItalic = STYLE_NORMAL;
+                }
+            }
+            final boolean italic = (mItalic == STYLE_ITALIC);
+            final long builderPtr = nInitBuilder();
+            if (mAxes != null) {
+                for (FontVariationAxis axis : mAxes) {
+                    nAddAxis(builderPtr, axis.getOpenTypeTagValue(), axis.getStyleValue());
+                }
+            }
+            final long ptr = nBuild(builderPtr, mBuffer, mWeight, italic, mTtcIndex);
+            final Font font = new Font(ptr, mWeight, italic, mTtcIndex, mAxes);
+            sFontRegistory.registerNativeAllocation(font, ptr);
+            return font;
+        }
+
+        /**
+         * Native methods for accessing underlying buffer in Asset
+         */
+        private static native long nGetNativeAsset(
+                @NonNull AssetManager am, @NonNull String path, boolean isAsset, int cookie);
+        private static native ByteBuffer nGetAssetBuffer(long nativeAsset);
+        @CriticalNative
+        private static native long nGetReleaseNativeAssetFunc();
+
+        /**
+         * Native methods for creating Font
+         */
+        private static native long nInitBuilder();
+        @CriticalNative
+        private static native void nAddAxis(long builderPtr, int tag, float value);
+        private static native long nBuild(
+                long builderPtr, ByteBuffer buffer, int weight, boolean italic, int ttcIndex);
+        @CriticalNative
+        private static native long nGetReleaseNativeFont();
+    }
+
+    private final long mNativePtr;  // address of the shared ptr of minikin::Font
+    private final @IntRange(from = 0, to = 1000) int mWeight;
+    private final boolean mItalic;
+    private final @IntRange(from = 0) int mTtcIndex;
+    private final @Nullable FontVariationAxis[] mAxes;
+
+    /**
+     * Use Builder instead
+     */
+    private Font(long nativePtr, @IntRange(from = 0, to = 1000) int weight, boolean italic,
+            @IntRange(from = 0) int ttcIndex, @Nullable FontVariationAxis[] axes) {
+        mWeight = weight;
+        mItalic = italic;
+        mNativePtr = nativePtr;
+        mTtcIndex = ttcIndex;
+        mAxes = axes;
+    }
+
+    /**
+     * Get a weight value associated with this font.
+     *
+     * @see Builder#setWeight(int)
+     * @return a weight value
+     */
+    public @IntRange(from = 0, to = 1000)int getWeight() {
+        return mWeight;
+    }
+
+    /**
+     * Returns true if this font is marked as italic, otherwise returns false.
+     *
+     * @see Builder#setItalic(boolean)
+     * @return true if italic, otherwise false
+     */
+    public boolean isItalic() {
+        return mItalic;
+    }
+
+    /**
+     * Get a TTC index value associated with this font.
+     *
+     * If TTF/OTF file is provided, this value is always 0.
+     *
+     * @see Builder#setTtcIndex(int)
+     * @return a TTC index value
+     */
+    public @IntRange(from = 0) int getTtcIndex() {
+        return mTtcIndex;
+    }
+
+    /**
+     * Get a font variation settings associated with this font
+     *
+     * @see Builder#setFontVariationSettings(String)
+     * @see Builder#setFontVariationSettings(FontVariationAxis[])
+     * @return font variation settings
+     */
+    public @Nullable FontVariationAxis[] getAxes() {
+        return mAxes;
+    }
+
+    /** @hide */
+    public long getNativePtr() {
+        return mNativePtr;
+    }
+}
diff --git a/graphics/java/android/graphics/fonts/FontFileUtil.java b/graphics/java/android/graphics/fonts/FontFileUtil.java
index d15f581..f8b456b 100644
--- a/graphics/java/android/graphics/fonts/FontFileUtil.java
+++ b/graphics/java/android/graphics/fonts/FontFileUtil.java
@@ -20,7 +20,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 
-import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 
@@ -46,6 +45,13 @@
         return (packed & 0x10000) != 0;
     }
 
+    /**
+     * Returns true if the analyzeStyle succeeded
+     */
+    public static boolean isSuccess(int packed) {
+        return packed != ANALYZE_ERROR;
+    }
+
     private static int pack(@IntRange(from = 0, to = 1000) int weight, boolean italic) {
         return weight | (italic ? 0x10000 : 0);
     }
@@ -55,12 +61,13 @@
     private static final int TTC_TAG = 0x74746366;
     private static final int OS2_TABLE_TAG = 0x4F532F32;
 
+    private static final int ANALYZE_ERROR = 0xFFFFFFFF;
+
     /**
      * Analyze the font file returns packed style info
      */
     public static final int analyzeStyle(@NonNull ByteBuffer buffer,
-            @IntRange(from = 0) int ttcIndex, @Nullable FontVariationAxis[] varSettings)
-            throws IOException {
+            @IntRange(from = 0) int ttcIndex, @Nullable FontVariationAxis[] varSettings) {
         int weight = -1;
         int italic = -1;
         if (varSettings != null) {
@@ -88,7 +95,7 @@
             if (magicNumber == TTC_TAG) {
                 // TTC file.
                 if (ttcIndex >= buffer.getInt(8 /* offset to number of fonts in TTC */)) {
-                    throw new IOException("Font index out of bounds");
+                    return ANALYZE_ERROR;
                 }
                 fontFileOffset = buffer.getInt(
                     12 /* offset to array of offsets of font files */ + 4 * ttcIndex);
@@ -96,7 +103,7 @@
             int sfntVersion = buffer.getInt(fontFileOffset);
 
             if (sfntVersion != SFNT_VERSION_1 && sfntVersion != SFNT_VERSION_OTTO) {
-                throw new IOException("Unknown font file format");
+                return ANALYZE_ERROR;
             }
 
             int numTables = buffer.getShort(fontFileOffset + 4 /* offset to number of tables */);