Introduce FontsContract.requestFont and deprecate Typeface.create

Since background fetch should be synchronized with life cycle of
Activities, so should not create new background thread inside this
method. Not to break existing apps, mark as deprecated and will be
removed from public API untile next release.

New API accepts Handler so that the developer can pass their own thread
.

Test: TBD
Bug: 37253785
Change-Id: I87fdc6a354a829db679253824f42489f25698fa0
diff --git a/api/current.txt b/api/current.txt
index 0873990..0db689c 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -13761,7 +13761,7 @@
   }
 
   public class Typeface {
-    method public static void create(android.graphics.fonts.FontRequest, android.graphics.Typeface.FontRequestCallback);
+    method public static deprecated void create(android.graphics.fonts.FontRequest, android.graphics.Typeface.FontRequestCallback);
     method public static android.graphics.Typeface create(java.lang.String, int);
     method public static android.graphics.Typeface create(android.graphics.Typeface, int);
     method public static android.graphics.Typeface createFromAsset(android.content.res.AssetManager, java.lang.String);
@@ -13795,7 +13795,7 @@
     method public android.graphics.Typeface.Builder setWeight(int);
   }
 
-  public static abstract interface Typeface.FontRequestCallback {
+  public static abstract deprecated interface Typeface.FontRequestCallback {
     method public abstract void onTypefaceRequestFailed(int);
     method public abstract void onTypefaceRetrieved(android.graphics.Typeface);
     field public static final int FAIL_REASON_FONT_LOAD_ERROR = -3; // 0xfffffffd
@@ -34504,6 +34504,7 @@
     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;
+    method public static void requestFont(android.content.Context, android.graphics.fonts.FontRequest, android.provider.FontsContract.FontRequestCallback, android.os.Handler);
   }
 
   public static final class FontsContract.Columns implements android.provider.BaseColumns {
@@ -34537,6 +34538,18 @@
     method public boolean isItalic();
   }
 
+  public static class FontsContract.FontRequestCallback {
+    ctor public FontsContract.FontRequestCallback();
+    method public void onTypefaceRequestFailed(int);
+    method public void onTypefaceRetrieved(android.graphics.Typeface);
+    field public static final int FAIL_REASON_FONT_LOAD_ERROR = -3; // 0xfffffffd
+    field public static final int FAIL_REASON_FONT_NOT_FOUND = 1; // 0x1
+    field public static final int FAIL_REASON_FONT_UNAVAILABLE = 2; // 0x2
+    field public static final int FAIL_REASON_MALFORMED_QUERY = 3; // 0x3
+    field public static final int FAIL_REASON_PROVIDER_NOT_FOUND = -1; // 0xffffffff
+    field public static final int FAIL_REASON_WRONG_CERTIFICATES = -2; // 0xfffffffe
+  }
+
   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 5cd3b0a..30c08a7 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -14528,7 +14528,7 @@
   }
 
   public class Typeface {
-    method public static void create(android.graphics.fonts.FontRequest, android.graphics.Typeface.FontRequestCallback);
+    method public static deprecated void create(android.graphics.fonts.FontRequest, android.graphics.Typeface.FontRequestCallback);
     method public static android.graphics.Typeface create(java.lang.String, int);
     method public static android.graphics.Typeface create(android.graphics.Typeface, int);
     method public static android.graphics.Typeface createFromAsset(android.content.res.AssetManager, java.lang.String);
@@ -14562,7 +14562,7 @@
     method public android.graphics.Typeface.Builder setWeight(int);
   }
 
-  public static abstract interface Typeface.FontRequestCallback {
+  public static abstract deprecated interface Typeface.FontRequestCallback {
     method public abstract void onTypefaceRequestFailed(int);
     method public abstract void onTypefaceRetrieved(android.graphics.Typeface);
     field public static final int FAIL_REASON_FONT_LOAD_ERROR = -3; // 0xfffffffd
@@ -37481,6 +37481,7 @@
     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;
+    method public static void requestFont(android.content.Context, android.graphics.fonts.FontRequest, android.provider.FontsContract.FontRequestCallback, android.os.Handler);
   }
 
   public static final class FontsContract.Columns implements android.provider.BaseColumns {
@@ -37514,6 +37515,18 @@
     method public boolean isItalic();
   }
 
+  public static class FontsContract.FontRequestCallback {
+    ctor public FontsContract.FontRequestCallback();
+    method public void onTypefaceRequestFailed(int);
+    method public void onTypefaceRetrieved(android.graphics.Typeface);
+    field public static final int FAIL_REASON_FONT_LOAD_ERROR = -3; // 0xfffffffd
+    field public static final int FAIL_REASON_FONT_NOT_FOUND = 1; // 0x1
+    field public static final int FAIL_REASON_FONT_UNAVAILABLE = 2; // 0x2
+    field public static final int FAIL_REASON_MALFORMED_QUERY = 3; // 0x3
+    field public static final int FAIL_REASON_PROVIDER_NOT_FOUND = -1; // 0xffffffff
+    field public static final int FAIL_REASON_WRONG_CERTIFICATES = -2; // 0xfffffffe
+  }
+
   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 c65d8da..b21aa5a 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -13812,7 +13812,7 @@
   }
 
   public class Typeface {
-    method public static void create(android.graphics.fonts.FontRequest, android.graphics.Typeface.FontRequestCallback);
+    method public static deprecated void create(android.graphics.fonts.FontRequest, android.graphics.Typeface.FontRequestCallback);
     method public static android.graphics.Typeface create(java.lang.String, int);
     method public static android.graphics.Typeface create(android.graphics.Typeface, int);
     method public static android.graphics.Typeface createFromAsset(android.content.res.AssetManager, java.lang.String);
@@ -13846,7 +13846,7 @@
     method public android.graphics.Typeface.Builder setWeight(int);
   }
 
-  public static abstract interface Typeface.FontRequestCallback {
+  public static abstract deprecated interface Typeface.FontRequestCallback {
     method public abstract void onTypefaceRequestFailed(int);
     method public abstract void onTypefaceRetrieved(android.graphics.Typeface);
     field public static final int FAIL_REASON_FONT_LOAD_ERROR = -3; // 0xfffffffd
@@ -34647,6 +34647,7 @@
     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;
+    method public static void requestFont(android.content.Context, android.graphics.fonts.FontRequest, android.provider.FontsContract.FontRequestCallback, android.os.Handler);
   }
 
   public static final class FontsContract.Columns implements android.provider.BaseColumns {
@@ -34680,6 +34681,18 @@
     method public boolean isItalic();
   }
 
+  public static class FontsContract.FontRequestCallback {
+    ctor public FontsContract.FontRequestCallback();
+    method public void onTypefaceRequestFailed(int);
+    method public void onTypefaceRetrieved(android.graphics.Typeface);
+    field public static final int FAIL_REASON_FONT_LOAD_ERROR = -3; // 0xfffffffd
+    field public static final int FAIL_REASON_FONT_NOT_FOUND = 1; // 0x1
+    field public static final int FAIL_REASON_FONT_UNAVAILABLE = 2; // 0x2
+    field public static final int FAIL_REASON_MALFORMED_QUERY = 3; // 0x3
+    field public static final int FAIL_REASON_PROVIDER_NOT_FOUND = -1; // 0xffffffff
+    field public static final int FAIL_REASON_WRONG_CERTIFICATES = -2; // 0xfffffffe
+  }
+
   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 f9508902..7e56ff9 100644
--- a/core/java/android/provider/FontsContract.java
+++ b/core/java/android/provider/FontsContract.java
@@ -394,6 +394,150 @@
     }
 
     /**
+     * Interface used to receive asynchronously fetched typefaces.
+     */
+    public static class FontRequestCallback {
+        /**
+         * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given
+         * provider was not found on the device.
+         */
+        public static final int FAIL_REASON_PROVIDER_NOT_FOUND = RESULT_CODE_PROVIDER_NOT_FOUND;
+        /**
+         * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given
+         * provider must be authenticated and the given certificates do not match its signature.
+         */
+        public static final int FAIL_REASON_WRONG_CERTIFICATES = RESULT_CODE_WRONG_CERTIFICATES;
+        /**
+         * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the font
+         * returned by the provider was not loaded properly.
+         */
+        public static final int FAIL_REASON_FONT_LOAD_ERROR = -3;
+        /**
+         * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the font
+         * provider did not return any results for the given query.
+         */
+        public static final int FAIL_REASON_FONT_NOT_FOUND = Columns.RESULT_CODE_FONT_NOT_FOUND;
+        /**
+         * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the font
+         * provider found the queried font, but it is currently unavailable.
+         */
+        public static final int FAIL_REASON_FONT_UNAVAILABLE = Columns.RESULT_CODE_FONT_UNAVAILABLE;
+        /**
+         * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given
+         * query was not supported by the provider.
+         */
+        public static final int FAIL_REASON_MALFORMED_QUERY = Columns.RESULT_CODE_MALFORMED_QUERY;
+
+        /** @hide */
+        @IntDef({ FAIL_REASON_PROVIDER_NOT_FOUND, FAIL_REASON_FONT_LOAD_ERROR,
+                FAIL_REASON_FONT_NOT_FOUND, FAIL_REASON_FONT_UNAVAILABLE,
+                FAIL_REASON_MALFORMED_QUERY })
+        @Retention(RetentionPolicy.SOURCE)
+        @interface FontRequestFailReason {}
+
+        public FontRequestCallback() {}
+
+        /**
+         * Called then a Typeface request done via {@link Typeface#create(FontRequest,
+         * FontRequestCallback)} is complete. Note that this method will not be called if
+         * {@link #onTypefaceRequestFailed(int)} is called instead.
+         * @param typeface  The Typeface object retrieved.
+         */
+        public void onTypefaceRetrieved(Typeface typeface) {}
+
+        /**
+         * Called when a Typeface request done via {@link Typeface#create(FontRequest,
+         * FontRequestCallback)} fails.
+         * @param reason One of {@link #FAIL_REASON_PROVIDER_NOT_FOUND},
+         *               {@link #FAIL_REASON_FONT_NOT_FOUND},
+         *               {@link #FAIL_REASON_FONT_LOAD_ERROR},
+         *               {@link #FAIL_REASON_FONT_UNAVAILABLE} or
+         *               {@link #FAIL_REASON_MALFORMED_QUERY}.
+         */
+        public void onTypefaceRequestFailed(@FontRequestFailReason int reason) {}
+    }
+
+    /**
+     * Create a typeface object given a font request. The font will be asynchronously fetched,
+     * therefore the result is delivered to the given callback. See {@link FontRequest}.
+     * Only one of the methods in callback will be invoked, depending on whether the request
+     * succeeds or fails. These calls will happen on the caller thread.
+     * @param context A context to be used for fetching from font provider.
+     * @param request A {@link FontRequest} object that identifies the provider and query for the
+     *                request. May not be null.
+     * @param callback A callback that will be triggered when results are obtained. May not be null.
+     * @param handler A handler to be processed the font fetching.
+     */
+    public static void requestFont(@NonNull Context context, @NonNull FontRequest request,
+            @NonNull FontRequestCallback callback, @NonNull Handler handler) {
+
+        final Handler callerThreadHandler = new Handler();
+        handler.post(() -> {
+            // TODO: Cache the result.
+            FontFamilyResult result;
+            try {
+                result = fetchFonts(context, null /* cancellation signal */, request);
+            } catch (NameNotFoundException e) {
+                callerThreadHandler.post(() -> callback.onTypefaceRequestFailed(
+                        FontRequestCallback.FAIL_REASON_PROVIDER_NOT_FOUND));
+                return;
+            }
+
+            if (result.getStatusCode() != FontFamilyResult.STATUS_OK) {
+                switch (result.getStatusCode()) {
+                    case FontFamilyResult.STATUS_WRONG_CERTIFICATES:
+                        callerThreadHandler.post(() -> callback.onTypefaceRequestFailed(
+                                FontRequestCallback.FAIL_REASON_WRONG_CERTIFICATES));
+                        return;
+                    case FontFamilyResult.STATUS_UNEXPECTED_DATA_PROVIDED:
+                        callerThreadHandler.post(() -> callback.onTypefaceRequestFailed(
+                                FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR));
+                        return;
+                    default:
+                        // fetchFont returns unexpected status type. Fallback to load error.
+                        callerThreadHandler.post(() -> callback.onTypefaceRequestFailed(
+                                FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR));
+                        return;
+                }
+            }
+
+            final FontInfo[] fonts = result.getFonts();
+            if (fonts == null || fonts.length == 0) {
+                callerThreadHandler.post(() -> callback.onTypefaceRequestFailed(
+                        FontRequestCallback.FAIL_REASON_FONT_NOT_FOUND));
+                return;
+            }
+            for (final FontInfo font : fonts) {
+                if (font.getResultCode() != Columns.RESULT_CODE_OK) {
+                    // We proceed if all font entry is ready to use. Otherwise report the first
+                    // error.
+                    final int resultCode = font.getResultCode();
+                    if (resultCode < 0) {
+                        // Negative values are reserved for internal errors. Fallback to load error.
+                        callerThreadHandler.post(() -> callback.onTypefaceRequestFailed(
+                                FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR));
+                    } else {
+                        callerThreadHandler.post(() -> callback.onTypefaceRequestFailed(
+                                resultCode));
+                    }
+                    return;
+                }
+            }
+
+            final Typeface typeface = buildTypeface(context, null /* cancellation signal */, fonts);
+            if (typeface == null) {
+                // Something went wrong during reading font files. This happens if the given font
+                // file is an unsupported font type.
+                callerThreadHandler.post(() -> callback.onTypefaceRequestFailed(
+                        FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR));
+                return;
+            }
+
+            callerThreadHandler.post(() -> callback.onTypefaceRetrieved(typeface));
+        });
+    }
+
+    /**
      * Fetch fonts given a font request.
      *
      * @param context A {@link Context} to be used for fetching fonts.
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 21533f8..c3588dc 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -292,6 +292,7 @@
      *                request. May not be null.
      * @param callback A callback that will be triggered when results are obtained. May not be null.
      */
+    @Deprecated
     public static void create(@NonNull FontRequest request, @NonNull FontRequestCallback callback) {
         // Check the cache first
         // TODO: would the developer want to avoid a cache hit and always ask for the freshest
@@ -404,6 +405,7 @@
     /**
      * Interface used to receive asynchronously fetched typefaces.
      */
+    @Deprecated
     public interface FontRequestCallback {
         /**
          * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given