Merge "Introduce FontsContract.fetchFonts and expose URI for watching." into oc-dev
diff --git a/api/current.txt b/api/current.txt
index 9bd0813f..0873990 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -34501,6 +34501,9 @@
   }
 
   public class FontsContract {
+    method public static android.graphics.Typeface buildTypeface(android.content.Context, android.os.CancellationSignal, android.provider.FontsContract.FontInfo[], int, boolean, java.lang.String);
+    method public static android.graphics.Typeface buildTypeface(android.content.Context, android.os.CancellationSignal, android.provider.FontsContract.FontInfo[]);
+    method public static android.provider.FontsContract.FontFamilyResult fetchFonts(android.content.Context, android.os.CancellationSignal, android.graphics.fonts.FontRequest) throws android.content.pm.PackageManager.NameNotFoundException;
   }
 
   public static final class FontsContract.Columns implements android.provider.BaseColumns {
@@ -34517,6 +34520,23 @@
     field public static final java.lang.String WEIGHT = "font_weight";
   }
 
+  public static class FontsContract.FontFamilyResult {
+    method public android.provider.FontsContract.FontInfo[] getFonts();
+    method public int getStatusCode();
+    field public static final int STATUS_OK = 0; // 0x0
+    field public static final int STATUS_UNEXPECTED_DATA_PROVIDED = 2; // 0x2
+    field public static final int STATUS_WRONG_CERTIFICATES = 1; // 0x1
+  }
+
+  public static class FontsContract.FontInfo {
+    method public android.graphics.fonts.FontVariationAxis[] getAxes();
+    method public int getResultCode();
+    method public int getTtcIndex();
+    method public android.net.Uri getUri();
+    method public int getWeight();
+    method public boolean isItalic();
+  }
+
   public final deprecated class LiveFolders implements android.provider.BaseColumns {
     field public static final java.lang.String ACTION_CREATE_LIVE_FOLDER = "android.intent.action.CREATE_LIVE_FOLDER";
     field public static final java.lang.String DESCRIPTION = "description";
diff --git a/api/system-current.txt b/api/system-current.txt
index 96a5d29..5cd3b0a 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -37478,6 +37478,9 @@
   }
 
   public class FontsContract {
+    method public static android.graphics.Typeface buildTypeface(android.content.Context, android.os.CancellationSignal, android.provider.FontsContract.FontInfo[], int, boolean, java.lang.String);
+    method public static android.graphics.Typeface buildTypeface(android.content.Context, android.os.CancellationSignal, android.provider.FontsContract.FontInfo[]);
+    method public static android.provider.FontsContract.FontFamilyResult fetchFonts(android.content.Context, android.os.CancellationSignal, android.graphics.fonts.FontRequest) throws android.content.pm.PackageManager.NameNotFoundException;
   }
 
   public static final class FontsContract.Columns implements android.provider.BaseColumns {
@@ -37494,6 +37497,23 @@
     field public static final java.lang.String WEIGHT = "font_weight";
   }
 
+  public static class FontsContract.FontFamilyResult {
+    method public android.provider.FontsContract.FontInfo[] getFonts();
+    method public int getStatusCode();
+    field public static final int STATUS_OK = 0; // 0x0
+    field public static final int STATUS_UNEXPECTED_DATA_PROVIDED = 2; // 0x2
+    field public static final int STATUS_WRONG_CERTIFICATES = 1; // 0x1
+  }
+
+  public static class FontsContract.FontInfo {
+    method public android.graphics.fonts.FontVariationAxis[] getAxes();
+    method public int getResultCode();
+    method public int getTtcIndex();
+    method public android.net.Uri getUri();
+    method public int getWeight();
+    method public boolean isItalic();
+  }
+
   public final deprecated class LiveFolders implements android.provider.BaseColumns {
     field public static final java.lang.String ACTION_CREATE_LIVE_FOLDER = "android.intent.action.CREATE_LIVE_FOLDER";
     field public static final java.lang.String DESCRIPTION = "description";
diff --git a/api/test-current.txt b/api/test-current.txt
index f9646bd..c65d8da 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -34644,6 +34644,9 @@
   }
 
   public class FontsContract {
+    method public static android.graphics.Typeface buildTypeface(android.content.Context, android.os.CancellationSignal, android.provider.FontsContract.FontInfo[], int, boolean, java.lang.String);
+    method public static android.graphics.Typeface buildTypeface(android.content.Context, android.os.CancellationSignal, android.provider.FontsContract.FontInfo[]);
+    method public static android.provider.FontsContract.FontFamilyResult fetchFonts(android.content.Context, android.os.CancellationSignal, android.graphics.fonts.FontRequest) throws android.content.pm.PackageManager.NameNotFoundException;
   }
 
   public static final class FontsContract.Columns implements android.provider.BaseColumns {
@@ -34660,6 +34663,23 @@
     field public static final java.lang.String WEIGHT = "font_weight";
   }
 
+  public static class FontsContract.FontFamilyResult {
+    method public android.provider.FontsContract.FontInfo[] getFonts();
+    method public int getStatusCode();
+    field public static final int STATUS_OK = 0; // 0x0
+    field public static final int STATUS_UNEXPECTED_DATA_PROVIDED = 2; // 0x2
+    field public static final int STATUS_WRONG_CERTIFICATES = 1; // 0x1
+  }
+
+  public static class FontsContract.FontInfo {
+    method public android.graphics.fonts.FontVariationAxis[] getAxes();
+    method public int getResultCode();
+    method public int getTtcIndex();
+    method public android.net.Uri getUri();
+    method public int getWeight();
+    method public boolean isItalic();
+  }
+
   public final deprecated class LiveFolders implements android.provider.BaseColumns {
     field public static final java.lang.String ACTION_CREATE_LIVE_FOLDER = "android.intent.action.CREATE_LIVE_FOLDER";
     field public static final java.lang.String DESCRIPTION = "description";
diff --git a/core/java/android/provider/FontsContract.java b/core/java/android/provider/FontsContract.java
index f2aed5d..f9508902 100644
--- a/core/java/android/provider/FontsContract.java
+++ b/core/java/android/provider/FontsContract.java
@@ -15,10 +15,18 @@
  */
 package android.provider;
 
+import static android.graphics.fonts.FontVariationAxis.InvalidFormatException;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.Context;
 import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManager;
 import android.content.pm.ProviderInfo;
 import android.content.pm.Signature;
@@ -26,8 +34,10 @@
 import android.graphics.Typeface;
 import android.graphics.fonts.FontRequest;
 import android.graphics.fonts.FontResult;
+import android.graphics.fonts.FontVariationAxis;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.ParcelFileDescriptor;
@@ -37,14 +47,22 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
 
+import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Utility class to deal with Font ContentProviders.
@@ -140,7 +158,6 @@
      * @hide
      */
     public static final String PARCEL_FONT_RESULTS = "font_results";
-
     // Error codes internal to the system, which can not come from a provider. To keep the number
     // space open for new provider codes, these should all be negative numbers.
     /** @hide */
@@ -165,11 +182,128 @@
         mPackageManager = mContext.getPackageManager();
     }
 
-    /** @hide */
-    @VisibleForTesting
-    public FontsContract(Context context, PackageManager packageManager) {
-        mContext = context;
-        mPackageManager = packageManager;
+    /**
+     * Object represent a font entry in the family returned from {@link #fetchFonts}.
+     */
+    public static class FontInfo {
+        private final Uri mUri;
+        private final int mTtcIndex;
+        private final FontVariationAxis[] mAxes;
+        private final int mWeight;
+        private final boolean mItalic;
+        private final int mResultCode;
+
+        /**
+         * Creates a Font with all the information needed about a provided font.
+         * @param uri A URI associated to the font file.
+         * @param ttcIndex If providing a TTC_INDEX file, the index to point to. Otherwise, 0.
+         * @param axes If providing a variation font, the settings for it. May be null.
+         * @param weight An integer that indicates the font weight.
+         * @param italic A boolean that indicates the font is italic style or not.
+         * @param resultCode A boolean that indicates the font contents is ready.
+         */
+        /** @hide */
+        public FontInfo(@NonNull Uri uri, @IntRange(from = 0) int ttcIndex,
+                @Nullable FontVariationAxis[] axes, @IntRange(from = 1, to = 1000) int weight,
+                boolean italic, int resultCode) {
+            mUri = Preconditions.checkNotNull(uri);
+            mTtcIndex = ttcIndex;
+            mAxes = axes;
+            mWeight = weight;
+            mItalic = italic;
+            mResultCode = resultCode;
+        }
+
+        /**
+         * Returns a URI associated to this record.
+         */
+        public @NonNull Uri getUri() {
+            return mUri;
+        }
+
+        /**
+         * Returns the index to be used to access this font when accessing a TTC file.
+         */
+        public @IntRange(from = 0) int getTtcIndex() {
+            return mTtcIndex;
+        }
+
+        /**
+         * Returns the list of axes associated to this font.
+         */
+        public @Nullable FontVariationAxis[] getAxes() {
+            return mAxes;
+        }
+
+        /**
+         * Returns the weight value for this font.
+         */
+        public @IntRange(from = 1, to = 1000) int getWeight() {
+            return mWeight;
+        }
+
+        /**
+         * Returns whether this font is italic.
+         */
+        public boolean isItalic() {
+            return mItalic;
+        }
+
+        /**
+         * Returns result code.
+         *
+         * {@link FontsContract.Columns#RESULT_CODE}
+         */
+        public int getResultCode() {
+            return mResultCode;
+        }
+    }
+
+    /**
+     * Object returned from {@link #fetchFonts}.
+     */
+    public static class FontFamilyResult {
+        /**
+         * Constant represents that the font was successfully retrieved. Note that when this value
+         * is set and {@link #getFonts} returns an empty array, it means there were no fonts
+         * matching the given query.
+         */
+        public static final int STATUS_OK = 0;
+
+        /**
+         * Constant represents that the given certificate was not matched with the provider's
+         * signature. {@link #getFonts} returns null if this status was set.
+         */
+        public static final int STATUS_WRONG_CERTIFICATES = 1;
+
+        /**
+         * Constant represents that the provider returns unexpected data. {@link #getFonts} returns
+         * null if this status was set. For example, this value is set when the font provider
+         * gives invalid format of variation settings.
+         */
+        public static final int STATUS_UNEXPECTED_DATA_PROVIDED = 2;
+
+        /** @hide */
+        @IntDef({STATUS_OK, STATUS_WRONG_CERTIFICATES, STATUS_UNEXPECTED_DATA_PROVIDED})
+        @Retention(RetentionPolicy.SOURCE)
+        @interface FontResultStatus {}
+
+        private final @FontResultStatus int mStatusCode;
+        private final FontInfo[] mFonts;
+
+        /** @hide */
+        public FontFamilyResult(@FontResultStatus int statusCode, @Nullable FontInfo[] fonts) {
+            mStatusCode = statusCode;
+            mFonts = fonts;
+        }
+
+        public @FontResultStatus int getStatusCode() {
+            return mStatusCode;
+        }
+
+        public @NonNull FontInfo[] getFonts() {
+            return mFonts;
+        }
     }
 
     // We use a background thread to post the content resolving work for all requests on. This
@@ -196,33 +330,210 @@
                 mHandler = new Handler(mThread.getLooper());
             }
             mHandler.post(() -> {
-                ProviderInfo providerInfo = getProvider(request, receiver);
-                if (providerInfo == null) {
+                ProviderInfo providerInfo;
+                try {
+                    providerInfo = getProvider(mPackageManager, request);
+                    if (providerInfo == null) {
+                        receiver.send(RESULT_CODE_PROVIDER_NOT_FOUND, null);
+                        return;
+                    }
+                } catch (PackageManager.NameNotFoundException e) {
+                    receiver.send(RESULT_CODE_PROVIDER_NOT_FOUND, null);
                     return;
                 }
-                getFontFromProvider(request, receiver, providerInfo.authority);
+                FontInfo[] fonts;
+                try {
+                    fonts = getFontFromProvider(mContext, request, providerInfo.authority,
+                            null /* cancellation signal */);
+                } catch (InvalidFormatException e) {
+                    receiver.send(RESULT_CODE_PROVIDER_NOT_FOUND, null);
+                    return;
+                }
+
+                ArrayList<FontResult> result = new ArrayList<>();
+                int resultCode = -1;
+                for (FontInfo font : fonts) {
+                    try {
+                        resultCode = font.getResultCode();
+                        if (resultCode != Columns.RESULT_CODE_OK) {
+                            if (resultCode < 0) {
+                                // Negative values are reserved for the internal errors.
+                                resultCode = Columns.RESULT_CODE_FONT_NOT_FOUND;
+                            }
+                            for (int i = 0; i < result.size(); ++i) {
+                                try {
+                                    result.get(i).getFileDescriptor().close();
+                                } catch (IOException e) {
+                                    // Ignore, as we are closing fds for cleanup.
+                                }
+                            }
+                            receiver.send(resultCode, null);
+                            return;
+                        }
+                        ParcelFileDescriptor pfd = mContext.getContentResolver().openFileDescriptor(
+                                font.getUri(), "r");
+                        result.add(new FontResult(pfd, font.getTtcIndex(),
+                                FontVariationAxis.toFontVariationSettings(font.getAxes()),
+                                font.getWeight(), font.isItalic()));
+                    } catch (FileNotFoundException e) {
+                        Log.e(TAG, "FileNotFoundException raised when interacting with content "
+                                + "provider " + providerInfo.authority, e);
+                    }
+                }
+                if (!result.isEmpty()) {
+                    Bundle bundle = new Bundle();
+                    bundle.putParcelableArrayList(PARCEL_FONT_RESULTS, result);
+                    receiver.send(Columns.RESULT_CODE_OK, bundle);
+                    return;
+                }
+                receiver.send(Columns.RESULT_CODE_FONT_NOT_FOUND, null);
             });
             mHandler.removeCallbacks(mReplaceDispatcherThreadRunnable);
             mHandler.postDelayed(mReplaceDispatcherThreadRunnable, THREAD_RENEWAL_THRESHOLD_MS);
         }
     }
 
+    /**
+     * Fetch fonts given a font request.
+     *
+     * @param context A {@link Context} to be used for fetching fonts.
+     * @param cancellationSignal A signal to cancel the operation in progress, or null if none. If
+     *                           the operation is canceled, then {@link
+     *                           android.os.OperationCanceledException} will be thrown when the
+     *                           query is executed.
+     * @param request A {@link FontRequest} object that identifies the provider and query for the
+     *                request.
+     *
+     * @return {@link FontFamilyResult}
+     *
+     * @throws NameNotFoundException If requested package or authority was not found in system.
+     */
+    public static @NonNull FontFamilyResult fetchFonts(
+            @NonNull Context context, @Nullable CancellationSignal cancellationSignal,
+            @NonNull FontRequest request) throws NameNotFoundException {
+        ProviderInfo providerInfo = getProvider(context.getPackageManager(), request);
+        if (providerInfo == null) {
+            return new FontFamilyResult(FontFamilyResult.STATUS_WRONG_CERTIFICATES, null);
+
+        }
+        try {
+            FontInfo[] fonts = getFontFromProvider(
+                    context, request, providerInfo.authority, cancellationSignal);
+            return new FontFamilyResult(FontFamilyResult.STATUS_OK, fonts);
+        } catch (InvalidFormatException e) {
+            return new FontFamilyResult(FontFamilyResult.STATUS_UNEXPECTED_DATA_PROVIDED, null);
+        }
+    }
+
+    /**
+     * Build a Typeface from an array of {@link FontInfo}. Results that are marked as not ready
+     * will be skipped.
+     *
+     * @param context A {@link Context} that will be used to fetch the font contents.
+     * @param cancellationSignal A signal to cancel the operation in progress, or null if none. If
+     *                           the operation is canceled, then {@link
+     *                           android.os.OperationCanceledException} will be thrown.
+     * @param fonts An array of {@link FontInfo} to be used to create a Typeface.
+     * @param weight A weight value to be used for selecting a font from a font family.
+     * @param italic {@code true} if this font is of italic style. This will be used for font
+     *               selection from a font family.
+     * @param fallbackFontName A fallback font name used if this method fails to create the
+     *                         Typeface. By passing {@code null}, this method returns {@code null}
+     *                         if typeface creation fails.
+     * @return A Typeface object. May return {@code null} if that is the value passed to {@code
+     *         fallBackFontName}.
+     */
+    public static Typeface buildTypeface(@NonNull Context context,
+            @Nullable CancellationSignal cancellationSignal, @NonNull FontInfo[] fonts,
+            int weight, boolean italic, @Nullable String fallbackFontName) {
+        final Map<Uri, ByteBuffer> uriBuffer =
+                prepareFontData(context, fonts, cancellationSignal);
+        Typeface typeface = new Typeface.Builder(fonts, uriBuffer)
+            .setWeight(weight)
+            .setItalic(italic)
+            .build();
+        // TODO: Use Typeface fallback instead.
+        if (typeface == null) {
+            typeface = Typeface.create(fallbackFontName, Typeface.NORMAL);
+        }
+        return typeface;
+    }
+
+    /**
+     * Build a Typeface from an array of {@link FontInfo}
+     *
+     * Results that are marked as not ready will be skipped.
+     *
+     * @param context A {@link Context} that will be used to fetch the font contents.
+     * @param cancellationSignal A signal to cancel the operation in progress, or null if none. If
+     *                           the operation is canceled, then {@link
+     *                           android.os.OperationCanceledException} will be thrown.
+     * @param fonts An array of {@link FontInfo} to be used to create a Typeface.
+     * @return A Typeface object. Returns null if typeface creation fails.
+     */
+    public static Typeface buildTypeface(@NonNull Context context,
+            @Nullable CancellationSignal cancellationSignal, @NonNull FontInfo[] fonts) {
+        final Map<Uri, ByteBuffer> uriBuffer =
+                prepareFontData(context, fonts, cancellationSignal);
+        return new Typeface.Builder(fonts, uriBuffer).build();
+    }
+
+    /**
+     * A helper function to create a mapping from {@link Uri} to {@link ByteBuffer}.
+     *
+     * Skip if the file contents is not ready to be read.
+     *
+     * @param context A {@link Context} to be used for resolving content URI in
+     *                {@link FontInfo}.
+     * @param fonts An array of {@link FontInfo}.
+     * @return A map from {@link Uri} to {@link ByteBuffer}.
+     */
+    private static Map<Uri, ByteBuffer> prepareFontData(Context context, FontInfo[] fonts,
+            CancellationSignal cancellationSignal) {
+        final HashMap<Uri, ByteBuffer> out = new HashMap<>();
+        final ContentResolver resolver = context.getContentResolver();
+
+        for (FontInfo font : fonts) {
+            if (font.getResultCode() != Columns.RESULT_CODE_OK) {
+                continue;
+            }
+
+            final Uri uri = font.getUri();
+            if (out.containsKey(uri)) {
+                continue;
+            }
+
+            ByteBuffer buffer = null;
+            try (final ParcelFileDescriptor pfd =
+                    resolver.openFileDescriptor(uri, "r", cancellationSignal);
+                    final FileInputStream fis = new FileInputStream(pfd.getFileDescriptor())) {
+                final FileChannel fileChannel = fis.getChannel();
+                final long size = fileChannel.size();
+                buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, size);
+            } catch (IOException e) {
+                // ignore
+            }
+
+            // TODO: try other approach?, e.g. read all contents instead of mmap.
+
+            out.put(uri, buffer);
+        }
+        return Collections.unmodifiableMap(out);
+    }
+
     /** @hide */
     @VisibleForTesting
-    public ProviderInfo getProvider(FontRequest request, ResultReceiver receiver) {
+    public static @Nullable ProviderInfo getProvider(
+            PackageManager packageManager, FontRequest request) throws NameNotFoundException {
         String providerAuthority = request.getProviderAuthority();
-        ProviderInfo info = mPackageManager.resolveContentProvider(providerAuthority, 0);
+        ProviderInfo info = packageManager.resolveContentProvider(providerAuthority, 0);
         if (info == null) {
-            Log.e(TAG, "Can't find content provider " + providerAuthority);
-            receiver.send(RESULT_CODE_PROVIDER_NOT_FOUND, null);
-            return null;
+            throw new NameNotFoundException("No package found for authority: " + providerAuthority);
         }
 
         if (!info.packageName.equals(request.getProviderPackage())) {
-            Log.e(TAG, "Found content provider " + providerAuthority + ", but package was not "
-                    + request.getProviderPackage());
-            receiver.send(RESULT_CODE_PROVIDER_NOT_FOUND, null);
-            return null;
+            throw new NameNotFoundException("Found content provider " + providerAuthority
+                    + ", but package was not " + request.getProviderPackage());
         }
         // Trust system apps without signature checks
         if (info.applicationInfo.isSystemApp()) {
@@ -230,16 +541,11 @@
         }
 
         List<byte[]> signatures;
-        try {
-            PackageInfo packageInfo = mPackageManager.getPackageInfo(info.packageName,
-                    PackageManager.GET_SIGNATURES);
-            signatures = convertToByteArrayList(packageInfo.signatures);
-            Collections.sort(signatures, sByteArrayComparator);
-        } catch (PackageManager.NameNotFoundException e) {
-            Log.e(TAG, "Can't find content provider " + providerAuthority, e);
-            receiver.send(RESULT_CODE_PROVIDER_NOT_FOUND, null);
-            return null;
-        }
+        PackageInfo packageInfo = packageManager.getPackageInfo(info.packageName,
+                PackageManager.GET_SIGNATURES);
+        signatures = convertToByteArrayList(packageInfo.signatures);
+        Collections.sort(signatures, sByteArrayComparator);
+
         List<List<byte[]>> requestCertificatesList = request.getCertificates();
         for (int i = 0; i < requestCertificatesList.size(); ++i) {
             // Make a copy so we can sort it without modifying the incoming data.
@@ -249,8 +555,6 @@
                 return info;
             }
         }
-        Log.e(TAG, "Certificates don't match for given provider " + providerAuthority);
-        receiver.send(RESULT_CODE_WRONG_CERTIFICATES, null);
         return null;
     }
 
@@ -266,7 +570,8 @@
         return 0;
     };
 
-    private boolean equalsByteArrayList(List<byte[]> signatures, List<byte[]> requestSignatures) {
+    private static boolean equalsByteArrayList(
+            List<byte[]> signatures, List<byte[]> requestSignatures) {
         if (signatures.size() != requestSignatures.size()) {
             return false;
         }
@@ -278,7 +583,7 @@
         return true;
     }
 
-    private List<byte[]> convertToByteArrayList(Signature[] signatures) {
+    private static List<byte[]> convertToByteArrayList(Signature[] signatures) {
         List<byte[]> shas = new ArrayList<>();
         for (int i = 0; i < signatures.length; ++i) {
             shas.add(signatures[i].toByteArray());
@@ -288,9 +593,10 @@
 
     /** @hide */
     @VisibleForTesting
-    public void getFontFromProvider(FontRequest request, ResultReceiver receiver,
-            String authority) {
-        ArrayList<FontResult> result = null;
+    public static @NonNull FontInfo[] getFontFromProvider(
+            Context context, FontRequest request, String authority,
+            CancellationSignal cancellationSignal) throws InvalidFormatException {
+        ArrayList<FontInfo> result = new ArrayList<>();
         final Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
                 .authority(authority)
                 .build();
@@ -298,15 +604,14 @@
                 .authority(authority)
                 .appendPath("file")
                 .build();
-        try (Cursor cursor = mContext.getContentResolver().query(uri, new String[] { Columns._ID,
+        try (Cursor cursor = context.getContentResolver().query(uri, new String[] { Columns._ID,
                         Columns.FILE_ID, Columns.TTC_INDEX, Columns.VARIATION_SETTINGS,
                         Columns.STYLE, Columns.WEIGHT, Columns.ITALIC, Columns.RESULT_CODE },
-                "query = ?", new String[] { request.getQuery() }, null);) {
+                "query = ?", new String[] { request.getQuery() }, null, cancellationSignal);) {
             // TODO: Should we restrict the amount of fonts that can be returned?
             // TODO: Write documentation explaining that all results should be from the same family.
             if (cursor != null && cursor.getCount() > 0) {
                 final int resultCodeColumnIndex = cursor.getColumnIndex(Columns.RESULT_CODE);
-                int resultCode = -1;
                 result = new ArrayList<>();
                 final int idColumnIndex = cursor.getColumnIndexOrThrow(Columns._ID);
                 final int fileIdColumnIndex = cursor.getColumnIndex(Columns.FILE_ID);
@@ -316,23 +621,13 @@
                 final int italicColumnIndex = cursor.getColumnIndex(Columns.ITALIC);
                 final int styleColumnIndex = cursor.getColumnIndex(Columns.STYLE);
                 while (cursor.moveToNext()) {
-                    resultCode = resultCodeColumnIndex != -1
+                    int resultCode = resultCodeColumnIndex != -1
                             ? cursor.getInt(resultCodeColumnIndex) : Columns.RESULT_CODE_OK;
-                    if (resultCode != Columns.RESULT_CODE_OK) {
-                        if (resultCode < 0) {
-                            // Negative values are reserved for the internal errors.
-                            resultCode = Columns.RESULT_CODE_FONT_NOT_FOUND;
-                        }
-                        for (int i = 0; i < result.size(); ++i) {
-                            try {
-                                result.get(i).getFileDescriptor().close();
-                            } catch (IOException e) {
-                                // Ignore, as we are closing fds for cleanup.
-                            }
-                        }
-                        receiver.send(resultCode, null);
-                        return;
-                    }
+                    final int ttcIndex = ttcIndexColumnIndex != -1
+                            ? cursor.getInt(ttcIndexColumnIndex) : 0;
+                    final String variationSettings = vsColumnIndex != -1
+                            ? cursor.getString(vsColumnIndex) : null;
+
                     Uri fileUri;
                     if (fileIdColumnIndex == -1) {
                         long id = cursor.getLong(idColumnIndex);
@@ -341,42 +636,27 @@
                         long id = cursor.getLong(fileIdColumnIndex);
                         fileUri = ContentUris.withAppendedId(fileBaseUri, id);
                     }
-                    try {
-                        ParcelFileDescriptor pfd =
-                                mContext.getContentResolver().openFileDescriptor(fileUri, "r");
-                        final int ttcIndex = ttcIndexColumnIndex != -1
-                                ? cursor.getInt(ttcIndexColumnIndex) : 0;
-                        final String variationSettings = vsColumnIndex != -1
-                                ? cursor.getString(vsColumnIndex) : null;
-                        // TODO: Stop using STYLE column and enforce WEIGHT/ITALIC column.
-                        int weight;
-                        boolean italic;
-                        if (weightColumnIndex != -1 && italicColumnIndex != -1) {
-                            weight = cursor.getInt(weightColumnIndex);
-                            italic = cursor.getInt(italicColumnIndex) == 1;
-                        } else if (styleColumnIndex != -1) {
-                            final int style = cursor.getInt(styleColumnIndex);
-                            weight = (style & Typeface.BOLD) != 0 ? 700 : 400;
-                            italic = (style & Typeface.ITALIC) != 0;
-                        } else {
-                            weight = 400;
-                            italic = false;
-                        }
-                        result.add(
-                                new FontResult(pfd, ttcIndex, variationSettings, weight, italic));
-                    } catch (FileNotFoundException e) {
-                        Log.e(TAG, "FileNotFoundException raised when interacting with content "
-                                + "provider " + authority, e);
+                    // TODO: Stop using STYLE column and enforce WEIGHT/ITALIC column.
+                    int weight;
+                    boolean italic;
+                    if (weightColumnIndex != -1 && italicColumnIndex != -1) {
+                        weight = cursor.getInt(weightColumnIndex);
+                        italic = cursor.getInt(italicColumnIndex) == 1;
+                    } else if (styleColumnIndex != -1) {
+                        final int style = cursor.getInt(styleColumnIndex);
+                        weight = (style & Typeface.BOLD) != 0 ?
+                                Typeface.Builder.BOLD_WEIGHT : Typeface.Builder.NORMAL_WEIGHT;
+                        italic = (style & Typeface.ITALIC) != 0;
+                    } else {
+                        weight = Typeface.Builder.NORMAL_WEIGHT;
+                        italic = false;
                     }
+                    FontVariationAxis[] axes =
+                            FontVariationAxis.fromFontVariationSettings(variationSettings);
+                    result.add(new FontInfo(fileUri, ttcIndex, axes, weight, italic, resultCode));
                 }
             }
         }
-        if (result != null && !result.isEmpty()) {
-            Bundle bundle = new Bundle();
-            bundle.putParcelableArrayList(PARCEL_FONT_RESULTS, result);
-            receiver.send(Columns.RESULT_CODE_OK, bundle);
-            return;
-        }
-        receiver.send(Columns.RESULT_CODE_FONT_NOT_FOUND, null);
+        return result.toArray(new FontInfo[0]);
     }
 }
diff --git a/core/tests/coretests/src/android/provider/FontsContractTest.java b/core/tests/coretests/src/android/provider/FontsContractTest.java
index 1dd3ef6..ccc8c18 100644
--- a/core/tests/coretests/src/android/provider/FontsContractTest.java
+++ b/core/tests/coretests/src/android/provider/FontsContractTest.java
@@ -17,29 +17,29 @@
 
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
+import static android.provider.FontsContract.Columns.RESULT_CODE_OK;
+import static android.provider.FontsContract.Columns.RESULT_CODE_FONT_NOT_FOUND;
+import static android.provider.FontsContract.Columns.RESULT_CODE_FONT_UNAVAILABLE;
+import static android.provider.FontsContract.Columns.RESULT_CODE_MALFORMED_QUERY;
+
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManager;
 import android.content.pm.ProviderInfo;
 import android.content.pm.Signature;
 import android.database.MatrixCursor;
-import android.graphics.Typeface;
 import android.graphics.fonts.FontRequest;
-import android.graphics.fonts.FontResult;
-import android.os.Bundle;
-import android.os.ResultReceiver;
+import android.graphics.fonts.FontVariationAxis.InvalidFormatException;
+import android.graphics.fonts.FontVariationAxis;
+import android.provider.FontsContract.FontInfo;
 import android.support.test.filters.SmallTest;
 import android.test.ProviderTestCase2;
 import android.util.Base64;
 
-import org.mockito.ArgumentCaptor;
-
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -61,8 +61,6 @@
     private final FontRequest request = new FontRequest(
             TestFontsProvider.AUTHORITY, PACKAGE_NAME, "query");
     private TestFontsProvider mProvider;
-    private FontsContract mContract;
-    private ResultReceiver mResultReceiver;
     private PackageManager mPackageManager;
 
     public FontsContractTest() {
@@ -74,126 +72,178 @@
 
         mProvider = getProvider();
         mPackageManager = mock(PackageManager.class);
-        mContract = new FontsContract(getMockContext(), mPackageManager);
-        mResultReceiver = mock(ResultReceiver.class);
     }
 
-    public void testGetFontFromProvider_resultOK() {
-        mContract.getFontFromProvider(request, mResultReceiver, TestFontsProvider.AUTHORITY);
-
-        final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
-        verify(mResultReceiver).send(
-                eq(FontsContract.Columns.RESULT_CODE_OK), bundleCaptor.capture());
-
-        Bundle bundle = bundleCaptor.getValue();
-        assertNotNull(bundle);
-        List<FontResult> resultList =
-                bundle.getParcelableArrayList(FontsContract.PARCEL_FONT_RESULTS);
-        assertNotNull(resultList);
-        assertEquals(1, resultList.size());
-        FontResult fontResult = resultList.get(0);
-        assertEquals(TestFontsProvider.TTC_INDEX, fontResult.getTtcIndex());
-        assertEquals(TestFontsProvider.VARIATION_SETTINGS, fontResult.getFontVariationSettings());
-        assertEquals(TestFontsProvider.NORMAL_WEIGHT, fontResult.getWeight());
-        assertEquals(TestFontsProvider.ITALIC, fontResult.getItalic());
-        assertNotNull(fontResult.getFileDescriptor());
+    public void testGetFontFromProvider_resultOK() throws InvalidFormatException {
+        FontInfo[] fonts = FontsContract.getFontFromProvider(
+                getMockContext(), request, TestFontsProvider.AUTHORITY, null);
+        assertNotNull(fonts);
+        assertEquals(1, fonts.length);
+        FontInfo font = fonts[0];
+        assertEquals(TestFontsProvider.TTC_INDEX, font.getTtcIndex());
+        FontVariationAxis[] actual = font.getAxes();
+        assertEquals(1, actual.length);
+        assertEquals("wdth", actual[0].getTag());
+        assertEquals(1.0f, actual[0].getStyleValue(), 0);
+        assertEquals(TestFontsProvider.NORMAL_WEIGHT, font.getWeight());
+        assertEquals(TestFontsProvider.ITALIC, font.isItalic());
+        assertNotNull(font.getUri());
+        assertEquals(RESULT_CODE_OK, font.getResultCode());
     }
 
-    public void testGetFontFromProvider_providerDoesntReturnAllFields() {
+    public void testGetFontFromProvider_providerDoesntReturnAllFields()
+            throws InvalidFormatException {
         mProvider.setReturnAllFields(false);
 
-        final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
-        mContract.getFontFromProvider(request, mResultReceiver, TestFontsProvider.AUTHORITY);
-        verify(mResultReceiver).send(
-                eq(FontsContract.Columns.RESULT_CODE_OK), bundleCaptor.capture());
-
-        Bundle bundle = bundleCaptor.getValue();
-        assertNotNull(bundle);
-        List<FontResult> resultList =
-                bundle.getParcelableArrayList(FontsContract.PARCEL_FONT_RESULTS);
-        assertNotNull(resultList);
-        assertEquals(1, resultList.size());
-        FontResult fontResult = resultList.get(0);
-        assertEquals(0, fontResult.getTtcIndex());
-        assertNull(fontResult.getFontVariationSettings());
-        assertEquals(400, fontResult.getWeight());
-        assertFalse(fontResult.getItalic());
-        assertNotNull(fontResult.getFileDescriptor());
+        FontInfo[] fonts = FontsContract.getFontFromProvider(
+                getMockContext(), request, TestFontsProvider.AUTHORITY, null);
+        assertNotNull(fonts);
+        assertEquals(1, fonts.length);
+        FontInfo font = fonts[0];
+        assertEquals(0, font.getTtcIndex());
+        assertNull(font.getAxes());
+        assertEquals(400, font.getWeight());
+        assertFalse(font.isItalic());
+        assertNotNull(font.getUri());
+        assertEquals(RESULT_CODE_OK, font.getResultCode());
     }
 
-    public void testGetFontFromProvider_resultFontNotFound() {
+    public void testGetFontFromProvider_resultFontNotFound() throws InvalidFormatException {
         // Make the provider return unknown
-        mProvider.setResultCode(FontsContract.Columns.RESULT_CODE_FONT_NOT_FOUND);
-        mContract.getFontFromProvider(request, mResultReceiver, TestFontsProvider.AUTHORITY);
-
-        verify(mResultReceiver).send(FontsContract.Columns.RESULT_CODE_FONT_NOT_FOUND,null);
+        mProvider.setResultCode(RESULT_CODE_FONT_NOT_FOUND);
+        FontInfo[] fonts = FontsContract.getFontFromProvider(
+                getMockContext(), request, TestFontsProvider.AUTHORITY, null);
+        assertNotNull(fonts);
+        assertEquals(1, fonts.length);
+        FontInfo font = fonts[0];
+        assertEquals(TestFontsProvider.TTC_INDEX, font.getTtcIndex());
+        assertNotNull(font.getUri());
+        assertEquals(RESULT_CODE_FONT_NOT_FOUND, font.getResultCode());
     }
 
-    public void testGetFontFromProvider_resultFontUnavailable() {
+    public void testGetFontFromProvider_resultFontUnavailable() throws InvalidFormatException {
         // Make the provider return font unavailable
-        mProvider.setResultCode(FontsContract.Columns.RESULT_CODE_FONT_UNAVAILABLE);
-        mContract.getFontFromProvider(request, mResultReceiver, TestFontsProvider.AUTHORITY);
+        mProvider.setResultCode(RESULT_CODE_FONT_UNAVAILABLE);
+        FontInfo[] fonts = FontsContract.getFontFromProvider(
+                getMockContext(), request, TestFontsProvider.AUTHORITY, null);
 
-        verify(mResultReceiver).send(FontsContract.Columns.RESULT_CODE_FONT_UNAVAILABLE,null);
+        assertNotNull(fonts);
+        assertEquals(1, fonts.length);
+        FontInfo font = fonts[0];
+        assertEquals(TestFontsProvider.TTC_INDEX, font.getTtcIndex());
+        FontVariationAxis[] actual = font.getAxes();
+        assertEquals(1, actual.length);
+        assertEquals("wdth", actual[0].getTag());
+        assertEquals(1.0f, actual[0].getStyleValue(), 0);
+        assertEquals(TestFontsProvider.NORMAL_WEIGHT, font.getWeight());
+        assertEquals(TestFontsProvider.ITALIC, font.isItalic());
+        assertNotNull(font.getUri());
+        assertEquals(RESULT_CODE_FONT_UNAVAILABLE, font.getResultCode());
     }
 
-    public void testGetFontFromProvider_resultMalformedQuery() {
+    public void testGetFontFromProvider_resultMalformedQuery() throws InvalidFormatException {
         // Make the provider return font unavailable
-        mProvider.setResultCode(FontsContract.Columns.RESULT_CODE_MALFORMED_QUERY);
-        mContract.getFontFromProvider(request, mResultReceiver, TestFontsProvider.AUTHORITY);
+        mProvider.setResultCode(RESULT_CODE_MALFORMED_QUERY);
+        FontInfo[] fonts = FontsContract.getFontFromProvider(
+                getMockContext(), request, TestFontsProvider.AUTHORITY, null);
 
-        verify(mResultReceiver).send(FontsContract.Columns.RESULT_CODE_MALFORMED_QUERY,null);
+        assertNotNull(fonts);
+        assertEquals(1, fonts.length);
+        FontInfo font = fonts[0];
+        assertEquals(TestFontsProvider.TTC_INDEX, font.getTtcIndex());
+        FontVariationAxis[] actual = font.getAxes();
+        assertEquals(1, actual.length);
+        assertEquals("wdth", actual[0].getTag());
+        assertEquals(1.0f, actual[0].getStyleValue(), 0);
+        assertEquals(TestFontsProvider.NORMAL_WEIGHT, font.getWeight());
+        assertEquals(TestFontsProvider.ITALIC, font.isItalic());
+        assertNotNull(font.getUri());
+        assertEquals(RESULT_CODE_MALFORMED_QUERY, font.getResultCode());
     }
 
-    public void testGetFontFromProvider_resultFontNotFoundSecondRow() {
+    public void testGetFontFromProvider_resultFontNotFoundSecondRow()
+            throws InvalidFormatException {
         MatrixCursor cursor = new MatrixCursor(new String[] { FontsContract.Columns._ID,
                 FontsContract.Columns.TTC_INDEX, FontsContract.Columns.VARIATION_SETTINGS,
                 FontsContract.Columns.WEIGHT, FontsContract.Columns.ITALIC,
                 FontsContract.Columns.RESULT_CODE });
-        cursor.addRow(new Object[] { 1, 0, null, 400, 0, FontsContract.Columns.RESULT_CODE_OK});
+        cursor.addRow(new Object[] { 1, 0, null, 400, 0, RESULT_CODE_OK});
         cursor.addRow(new Object[] { 1, 0, null, 400, 0,
-                FontsContract.Columns.RESULT_CODE_FONT_NOT_FOUND});
+                RESULT_CODE_FONT_NOT_FOUND});
         mProvider.setCustomCursor(cursor);
-        mContract.getFontFromProvider(request, mResultReceiver, TestFontsProvider.AUTHORITY);
+        FontInfo[] fonts = FontsContract.getFontFromProvider(
+                getMockContext(), request, TestFontsProvider.AUTHORITY, null);
 
-        verify(mResultReceiver).send(FontsContract.Columns.RESULT_CODE_FONT_NOT_FOUND, null);
+        assertNotNull(fonts);
+        assertEquals(2, fonts.length);
+
+        FontInfo font = fonts[0];
+        assertEquals(0, font.getTtcIndex());
+        assertNull(font.getAxes());
+        assertEquals(400, font.getWeight());
+        assertFalse(font.isItalic());
+        assertNotNull(font.getUri());
+        assertEquals(RESULT_CODE_OK, font.getResultCode());
+
+        font = fonts[1];
+        assertEquals(0, font.getTtcIndex());
+        assertNull(font.getAxes());
+        assertEquals(400, font.getWeight());
+        assertFalse(font.isItalic());
+        assertNotNull(font.getUri());
+        assertEquals(RESULT_CODE_FONT_NOT_FOUND, font.getResultCode());
     }
 
-    public void testGetFontFromProvider_resultFontNotFoundOtherRow() {
+    public void testGetFontFromProvider_resultFontNotFoundOtherRow() throws InvalidFormatException {
         MatrixCursor cursor = new MatrixCursor(new String[] { FontsContract.Columns._ID,
                 FontsContract.Columns.TTC_INDEX, FontsContract.Columns.VARIATION_SETTINGS,
                 FontsContract.Columns.WEIGHT, FontsContract.Columns.ITALIC,
                 FontsContract.Columns.RESULT_CODE });
-        cursor.addRow(new Object[] { 1, 0, null, 400, 0, FontsContract.Columns.RESULT_CODE_OK});
+        cursor.addRow(new Object[] { 1, 0, null, 400, 0, RESULT_CODE_OK});
         cursor.addRow(new Object[] { 1, 0, null, 400, 0,
-                FontsContract.Columns.RESULT_CODE_FONT_NOT_FOUND});
-        cursor.addRow(new Object[] { 1, 0, null, 400, 0, FontsContract.Columns.RESULT_CODE_OK});
+                RESULT_CODE_FONT_NOT_FOUND});
+        cursor.addRow(new Object[] { 1, 0, null, 400, 0, RESULT_CODE_OK});
         mProvider.setCustomCursor(cursor);
-        mContract.getFontFromProvider(request, mResultReceiver, TestFontsProvider.AUTHORITY);
+        FontInfo[] fonts = FontsContract.getFontFromProvider(
+                getMockContext(), request, TestFontsProvider.AUTHORITY, null);
 
-        verify(mResultReceiver).send(FontsContract.Columns.RESULT_CODE_FONT_NOT_FOUND, null);
-    }
+        assertNotNull(fonts);
+        assertEquals(3, fonts.length);
 
-    public void testGetFontFromProvider_resultCodeIsNegativeNumber() {
-        MatrixCursor cursor = new MatrixCursor(new String[] { FontsContract.Columns._ID,
-                FontsContract.Columns.TTC_INDEX, FontsContract.Columns.VARIATION_SETTINGS,
-                FontsContract.Columns.WEIGHT, FontsContract.Columns.ITALIC,
-                FontsContract.Columns.RESULT_CODE });
-        cursor.addRow(new Object[] { 1, 0, null, 400, 0, FontsContract.Columns.RESULT_CODE_OK});
-        cursor.addRow(new Object[] { 1, 0, null, 400, 0, -5});
-        mProvider.setCustomCursor(cursor);
-        mContract.getFontFromProvider(request, mResultReceiver, TestFontsProvider.AUTHORITY);
+        FontInfo font = fonts[0];
+        assertEquals(0, font.getTtcIndex());
+        assertNull(font.getAxes());
+        assertEquals(400, font.getWeight());
+        assertFalse(font.isItalic());
+        assertNotNull(font.getUri());
+        assertEquals(RESULT_CODE_OK, font.getResultCode());
 
-        verify(mResultReceiver).send(FontsContract.Columns.RESULT_CODE_FONT_NOT_FOUND, null);
+        font = fonts[1];
+        assertEquals(0, font.getTtcIndex());
+        assertNull(font.getAxes());
+        assertEquals(400, font.getWeight());
+        assertFalse(font.isItalic());
+        assertNotNull(font.getUri());
+        assertEquals(RESULT_CODE_FONT_NOT_FOUND, font.getResultCode());
+
+        font = fonts[2];
+        assertEquals(0, font.getTtcIndex());
+        assertNull(font.getAxes());
+        assertEquals(400, font.getWeight());
+        assertFalse(font.isItalic());
+        assertNotNull(font.getUri());
+        assertEquals(RESULT_CODE_OK, font.getResultCode());
     }
 
     public void testGetProvider_providerNotFound() {
         when(mPackageManager.resolveContentProvider(anyString(), anyInt())).thenReturn(null);
 
-        ProviderInfo result = mContract.getProvider(request, mResultReceiver);
-
-        verify(mResultReceiver).send(FontsContract.RESULT_CODE_PROVIDER_NOT_FOUND, null);
-        assertNull(result);
+        try {
+            FontsContract.getProvider(mPackageManager, request);
+            fail();
+        } catch (NameNotFoundException e) {
+            // pass
+        }
     }
 
     public void testGetProvider_providerIsSystemApp() throws PackageManager.NameNotFoundException {
@@ -201,9 +251,7 @@
         info.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
         when(mPackageManager.resolveContentProvider(anyString(), anyInt())).thenReturn(info);
 
-        ProviderInfo result = mContract.getProvider(request, mResultReceiver);
-
-        verifyZeroInteractions(mResultReceiver);
+        ProviderInfo result = FontsContract.getProvider(mPackageManager, request);
         assertEquals(info, result);
     }
 
@@ -213,23 +261,22 @@
         info.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
         when(mPackageManager.resolveContentProvider(anyString(), anyInt())).thenReturn(info);
 
-        ProviderInfo result = mContract.getProvider(
-                new FontRequest(TestFontsProvider.AUTHORITY, "com.wrong.package", "query"),
-                mResultReceiver);
+        try {
+            FontsContract.getProvider(
+                mPackageManager,
+                new FontRequest(TestFontsProvider.AUTHORITY, "com.wrong.package", "query"));
+            fail();
+        } catch (NameNotFoundException e) {
+            // pass
+        }
 
-        verify(mResultReceiver).send(FontsContract.RESULT_CODE_PROVIDER_NOT_FOUND, null);
-        assertNull(result);
     }
 
     public void testGetProvider_providerIsNonSystemAppNoCerts()
             throws PackageManager.NameNotFoundException {
         setupPackageManager();
 
-        // The default request is missing the certificates info.
-        ProviderInfo result = mContract.getProvider(request, mResultReceiver);
-
-        verify(mResultReceiver).send(FontsContract.RESULT_CODE_WRONG_CERTIFICATES, null);
-        assertNull(result);
+        assertNull(FontsContract.getProvider(mPackageManager, request));
     }
 
     public void testGetProvider_providerIsNonSystemAppWrongCerts()
@@ -240,10 +287,8 @@
         List<byte[]> certList = Arrays.asList(wrongCert);
         FontRequest requestWrongCerts = new FontRequest(
                 TestFontsProvider.AUTHORITY, PACKAGE_NAME, "query", Arrays.asList(certList));
-        ProviderInfo result = mContract.getProvider(requestWrongCerts, mResultReceiver);
 
-        verify(mResultReceiver).send(FontsContract.RESULT_CODE_WRONG_CERTIFICATES, null);
-        assertNull(result);
+        assertNull(FontsContract.getProvider(mPackageManager, requestWrongCerts));
     }
 
     public void testGetProvider_providerIsNonSystemAppCorrectCerts()
@@ -253,9 +298,9 @@
         List<byte[]> certList = Arrays.asList(BYTE_ARRAY);
         FontRequest requestRightCerts = new FontRequest(
                 TestFontsProvider.AUTHORITY, PACKAGE_NAME, "query", Arrays.asList(certList));
-        ProviderInfo result = mContract.getProvider(requestRightCerts, mResultReceiver);
+        ProviderInfo result = FontsContract.getProvider(
+                mPackageManager, requestRightCerts);
 
-        verifyZeroInteractions(mResultReceiver);
         assertEquals(info, result);
     }
 
@@ -267,11 +312,7 @@
         List<byte[]> certList = Arrays.asList(wrongCert, BYTE_ARRAY);
         FontRequest requestRightCerts = new FontRequest(
                 TestFontsProvider.AUTHORITY, PACKAGE_NAME, "query", Arrays.asList(certList));
-        ProviderInfo result = mContract.getProvider(requestRightCerts, mResultReceiver);
-
-        // There is one too many certs, should fail as the set doesn't match.
-        verify(mResultReceiver).send(FontsContract.RESULT_CODE_WRONG_CERTIFICATES, null);
-        assertNull(result);
+        assertNull(FontsContract.getProvider(mPackageManager, requestRightCerts));
     }
 
     public void testGetProvider_providerIsNonSystemAppDuplicateCerts()
@@ -294,12 +335,7 @@
         List<byte[]> certList = Arrays.asList(BYTE_ARRAY_2, BYTE_ARRAY_COPY);
         FontRequest requestRightCerts = new FontRequest(
                 TestFontsProvider.AUTHORITY, PACKAGE_NAME, "query", Arrays.asList(certList));
-        ProviderInfo result = mContract.getProvider(requestRightCerts, mResultReceiver);
-
-        // The given list includes an extra cert and doesn't have a second copy of the cert like
-        // the provider does, so it should have failed.
-        verify(mResultReceiver).send(FontsContract.RESULT_CODE_WRONG_CERTIFICATES, null);
-        assertNull(result);
+        assertNull(FontsContract.getProvider(mPackageManager, requestRightCerts));
     }
 
     public void testGetProvider_providerIsNonSystemAppCorrectCertsSeveralSets()
@@ -312,9 +348,8 @@
         certList.add(Arrays.asList(BYTE_ARRAY));
         FontRequest requestRightCerts = new FontRequest(
                 TestFontsProvider.AUTHORITY, PACKAGE_NAME, "query", certList);
-        ProviderInfo result = mContract.getProvider(requestRightCerts, mResultReceiver);
+        ProviderInfo result = FontsContract.getProvider(mPackageManager, requestRightCerts);
 
-        verifyZeroInteractions(mResultReceiver);
         assertEquals(info, result);
     }
 
@@ -326,10 +361,12 @@
         certList.add(Arrays.asList(BYTE_ARRAY));
         FontRequest requestRightCerts = new FontRequest(
                 TestFontsProvider.AUTHORITY, "com.wrong.package.name", "query", certList);
-        ProviderInfo result = mContract.getProvider(requestRightCerts, mResultReceiver);
-
-        verify(mResultReceiver).send(FontsContract.RESULT_CODE_PROVIDER_NOT_FOUND, null);
-        assertNull(result);
+        try {
+            FontsContract.getProvider(mPackageManager, requestRightCerts);
+            fail();
+        } catch (NameNotFoundException e) {
+            // pass
+        }
     }
 
     private ProviderInfo setupPackageManager()
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 395dc07..21533f8 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -34,6 +34,7 @@
 import android.graphics.fonts.FontResult;
 import android.graphics.fonts.FontVariationAxis;
 import android.graphics.fonts.FontVariationAxis.InvalidFormatException;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.ParcelFileDescriptor;
@@ -509,6 +510,10 @@
      * </p>
      */
     public static final class Builder {
+        /** @hide */
+        public static final int NORMAL_WEIGHT = 400;
+        /** @hide */
+        public static final int BOLD_WEIGHT = 700;
 
         private int mTtcIndex;
         private FontVariationAxis[] mAxes;
@@ -517,6 +522,10 @@
         private String mPath;
         private FileDescriptor mFd;
 
+        private FontsContract.FontInfo[] mFonts;
+        private Map<Uri, ByteBuffer> mFontBuffers;
+        private String mFallbackFamilyName;
+
         private int mWeight = RESOLVE_BY_FONT_TABLE;
         private int mItalic = RESOLVE_BY_FONT_TABLE;
 
@@ -559,6 +568,25 @@
         }
 
         /**
+         * Constracts a builder from an array of FontsContract.FontInfo.
+         *
+         * Since {@link FontsContract.FontInfo} holds information about TTC indices and
+         * variation settings, there is no need to call {@link #setTtcIndex} or
+         * {@link #setFontVariationSettings}. Similary, {@link FontsContract.FontInfo} holds
+         * weight and italic information, so {@link #setWeight} and {@link #setItalic} are used
+         * for style matching during font selection.
+         *
+         * @param results The array of {@link FontsContract.FontInfo}
+         * @param buffers The mapping from URI to buffers to be used during building.
+         * @hide
+         */
+        public Builder(@NonNull FontsContract.FontInfo[] fonts,
+                @NonNull Map<Uri, ByteBuffer> buffers) {
+            mFonts = fonts;
+            mFontBuffers = buffers;
+        }
+
+        /**
          * Sets weight of the font.
          *
          * Tells the system the weight of the given font. If not provided, the system will resolve
@@ -590,6 +618,10 @@
          *                 collection, do not call this method or specify 0.
          */
         public Builder setTtcIndex(@IntRange(from = 0) int ttcIndex) {
+            if (mFonts != null) {
+                throw new IllegalArgumentException(
+                        "TTC index can not be specified for FontResult source.");
+            }
             mTtcIndex = ttcIndex;
             return this;
         }
@@ -603,6 +635,13 @@
          */
         public Builder setFontVariationSettings(@Nullable String variationSettings)
                 throws InvalidFormatException {
+            if (mFonts != null) {
+                throw new IllegalArgumentException(
+                        "Font variation settings can not be specified for FontResult source.");
+            }
+            if (mAxes != null) {
+                throw new IllegalStateException("Font variation settings are already set.");
+            }
             mAxes = FontVariationAxis.fromFontVariationSettings(variationSettings);
             return this;
         }
@@ -613,6 +652,13 @@
          * @param axes An array of font variation axis tag-value pairs.
          */
         public Builder setFontVariationSettings(@Nullable FontVariationAxis[] axes) {
+            if (mFonts != null) {
+                throw new IllegalArgumentException(
+                        "Font variation settings can not be specified for FontResult source.");
+            }
+            if (mAxes != null) {
+                throw new IllegalStateException("Font variation settings are already set.");
+            }
             mAxes = axes;
             return this;
         }
@@ -698,6 +744,32 @@
                 fontFamily.freeze();
                 FontFamily[] families = { fontFamily };
                 return createFromFamiliesWithDefault(families);
+            } else if (mFonts != null) {
+                final FontFamily fontFamily = new FontFamily();
+                boolean atLeastOneFont = false;
+                for (FontsContract.FontInfo font : mFonts) {
+                    final ByteBuffer fontBuffer = mFontBuffers.get(font.getUri());
+                    if (fontBuffer == null) {
+                        continue;  // skip
+                    }
+                    final boolean success = fontFamily.addFontFromBuffer(fontBuffer,
+                            font.getTtcIndex(), font.getAxes(), font.getWeight(),
+                            font.isItalic() ? STYLE_ITALIC : STYLE_NORMAL);
+                    if (!success) {
+                        fontFamily.abortCreation();
+                        return null;
+                    }
+                    atLeastOneFont = true;
+                }
+                if (!atLeastOneFont) {
+                    // No fonts are avaialble. No need to create new Typeface and returns fallback
+                    // Typeface instead.
+                    fontFamily.abortCreation();
+                    return null;
+                }
+                fontFamily.freeze();
+                FontFamily[] families = { fontFamily };
+                return createFromFamiliesWithDefault(families);
             }
 
             // Must not reach here.