Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d3902a34 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Introduce Fonts Content Provider"

parents 9e2a47d2 b0812a30
Loading
Loading
Loading
Loading
+32 −0
Original line number Diff line number Diff line
@@ -13227,6 +13227,7 @@ package android.graphics {
  }
  public class Typeface {
    method public static 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);
@@ -13247,6 +13248,14 @@ package android.graphics {
    field public static final android.graphics.Typeface SERIF;
  }
  public static abstract 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 = 1; // 0x1
    field public static final int FAIL_REASON_FONT_NOT_FOUND = 2; // 0x2
    field public static final int FAIL_REASON_PROVIDER_NOT_FOUND = 0; // 0x0
  }
  public class Xfermode {
    ctor public Xfermode();
  }
@@ -13801,6 +13810,19 @@ package android.graphics.drawable.shapes {
}
package android.graphics.fonts {
  public final class FontRequest implements android.os.Parcelable {
    ctor public FontRequest(java.lang.String, java.lang.String);
    method public int describeContents();
    method public java.lang.String getProviderAuthority();
    method public java.lang.String getQuery();
    method public void writeToParcel(android.os.Parcel, int);
    field public static final android.os.Parcelable.Creator<android.graphics.fonts.FontRequest> CREATOR;
  }
}
package android.graphics.pdf {
  public class PdfDocument {
@@ -33046,6 +33068,16 @@ package android.provider {
    method public final int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]);
  }
  public class FontsContract {
  }
  public static final class FontsContract.Columns implements android.provider.BaseColumns {
    ctor public FontsContract.Columns();
    field public static final java.lang.String STYLE = "font_style";
    field public static final java.lang.String TTC_INDEX = "font_ttc_index";
    field public static final java.lang.String VARIATION_SETTINGS = "font_variation_settings";
  }
  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";
+32 −0
Original line number Diff line number Diff line
@@ -13774,6 +13774,7 @@ package android.graphics {
  }
  public class Typeface {
    method public static 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);
@@ -13794,6 +13795,14 @@ package android.graphics {
    field public static final android.graphics.Typeface SERIF;
  }
  public static abstract 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 = 1; // 0x1
    field public static final int FAIL_REASON_FONT_NOT_FOUND = 2; // 0x2
    field public static final int FAIL_REASON_PROVIDER_NOT_FOUND = 0; // 0x0
  }
  public class Xfermode {
    ctor public Xfermode();
  }
@@ -14348,6 +14357,19 @@ package android.graphics.drawable.shapes {
}
package android.graphics.fonts {
  public final class FontRequest implements android.os.Parcelable {
    ctor public FontRequest(java.lang.String, java.lang.String);
    method public int describeContents();
    method public java.lang.String getProviderAuthority();
    method public java.lang.String getQuery();
    method public void writeToParcel(android.os.Parcel, int);
    field public static final android.os.Parcelable.Creator<android.graphics.fonts.FontRequest> CREATOR;
  }
}
package android.graphics.pdf {
  public class PdfDocument {
@@ -35914,6 +35936,16 @@ package android.provider {
    method public final int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]);
  }
  public class FontsContract {
  }
  public static final class FontsContract.Columns implements android.provider.BaseColumns {
    ctor public FontsContract.Columns();
    field public static final java.lang.String STYLE = "font_style";
    field public static final java.lang.String TTC_INDEX = "font_ttc_index";
    field public static final java.lang.String VARIATION_SETTINGS = "font_variation_settings";
  }
  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";
+32 −0
Original line number Diff line number Diff line
@@ -13259,6 +13259,7 @@ package android.graphics {
  }
  public class Typeface {
    method public static 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);
@@ -13279,6 +13280,14 @@ package android.graphics {
    field public static final android.graphics.Typeface SERIF;
  }
  public static abstract 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 = 1; // 0x1
    field public static final int FAIL_REASON_FONT_NOT_FOUND = 2; // 0x2
    field public static final int FAIL_REASON_PROVIDER_NOT_FOUND = 0; // 0x0
  }
  public class Xfermode {
    ctor public Xfermode();
  }
@@ -13833,6 +13842,19 @@ package android.graphics.drawable.shapes {
}
package android.graphics.fonts {
  public final class FontRequest implements android.os.Parcelable {
    ctor public FontRequest(java.lang.String, java.lang.String);
    method public int describeContents();
    method public java.lang.String getProviderAuthority();
    method public java.lang.String getQuery();
    method public void writeToParcel(android.os.Parcel, int);
    field public static final android.os.Parcelable.Creator<android.graphics.fonts.FontRequest> CREATOR;
  }
}
package android.graphics.pdf {
  public class PdfDocument {
@@ -33162,6 +33184,16 @@ package android.provider {
    method public final int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]);
  }
  public class FontsContract {
  }
  public static final class FontsContract.Columns implements android.provider.BaseColumns {
    ctor public FontsContract.Columns();
    field public static final java.lang.String STYLE = "font_style";
    field public static final java.lang.String TTC_INDEX = "font_ttc_index";
    field public static final java.lang.String VARIATION_SETTINGS = "font_variation_settings";
  }
  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";
+197 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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.provider;

import android.app.ActivityThread;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.database.Cursor;
import android.graphics.fonts.FontRequest;
import android.graphics.fonts.FontResult;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.ResultReceiver;
import android.util.Log;

import com.android.internal.annotations.GuardedBy;

import java.io.FileNotFoundException;
import java.util.ArrayList;

/**
 * Utility class to deal with Font ContentProviders.
 */
public class FontsContract {
    private static final String TAG = "FontsContract";

    /**
     * Defines the constants used in a response from a Font Provider. The cursor returned from the
     * query should have the ID column populated with the content uri ID for the resulting font.
     * This should point to a real file or shared memory, as the client will mmap the given file
     * descriptor. Pipes, sockets and other non-mmap-able file descriptors will fail to load in the
     * client application.
     */
    public static final class Columns implements BaseColumns {
        /**
         * Constant used to request data from a font provider. The cursor returned from the query
         * should have this column populated with an int for the ttc index for the resulting font.
         */
        public static final String TTC_INDEX = "font_ttc_index";
        /**
         * Constant used to request data from a font provider. The cursor returned from the query
         * may populate this column with the font variation settings String information for the
         * font.
         */
        public static final String VARIATION_SETTINGS = "font_variation_settings";
        /**
         * Constant used to request data from a font provider. The cursor returned from the query
         * should have this column populated with the int style for the resulting font. This should
         * be one of {@link android.graphics.Typeface#NORMAL},
         * {@link android.graphics.Typeface#BOLD}, {@link android.graphics.Typeface#ITALIC} or
         * {@link android.graphics.Typeface#BOLD_ITALIC}
         */
        public static final String STYLE = "font_style";
    }

    /**
     * Constant used to identify the List of {@link ParcelFileDescriptor} item in the Bundle
     * returned to the ResultReceiver in getFont.
     * @hide
     */
    public static final String PARCEL_FONT_RESULTS = "font_results";

    /** @hide */
    public static final int RESULT_CODE_OK = 0;
    /** @hide */
    public static final int RESULT_CODE_FONT_NOT_FOUND = 1;
    /** @hide */
    public static final int RESULT_CODE_PROVIDER_NOT_FOUND = 2;

    private static final int THREAD_RENEWAL_THRESHOLD_MS = 10000;

    private final Context mContext;
    private final PackageManager mPackageManager;
    private final Object mLock = new Object();
    @GuardedBy("mLock")
    private Handler mHandler;
    @GuardedBy("mLock")
    private HandlerThread mThread;

    /** @hide */
    public FontsContract() {
        // TODO: investigate if the system context is the best option here. ApplicationContext or
        // the one passed by developer?
        // TODO: Looks like ActivityThread.currentActivityThread() can return null. Check when it
        // returns null and check if we need to handle null case.
        mContext = ActivityThread.currentActivityThread().getSystemContext();
        mPackageManager = mContext.getPackageManager();
    }

    // We use a background thread to post the content resolving work for all requests on. This
    // thread should be quit/stopped after all requests are done.
    private final Runnable mReplaceDispatcherThreadRunnable = new Runnable() {
        @Override
        public void run() {
            synchronized (mLock) {
                if (mThread != null) {
                    mThread.quitSafely();
                    mThread = null;
                    mHandler = null;
                }
            }
        }
    };

    /**
     * @hide
     */
    public void getFont(FontRequest request, ResultReceiver receiver) {
        synchronized (mLock) {
            if (mHandler == null) {
                mThread = new HandlerThread("fonts", Process.THREAD_PRIORITY_BACKGROUND);
                mThread.start();
                mHandler = new Handler(mThread.getLooper());
            }
            mHandler.post(() -> {
                String providerAuthority = request.getProviderAuthority();
                // TODO: Implement cert checking for non-system apps
                ProviderInfo providerInfo = mPackageManager.resolveContentProvider(
                        providerAuthority, PackageManager.MATCH_SYSTEM_ONLY);
                if (providerInfo == null) {
                    receiver.send(RESULT_CODE_PROVIDER_NOT_FOUND, null);
                    return;
                }
                Bundle result = getFontFromProvider(request, receiver, providerInfo);
                if (result == null) {
                    receiver.send(RESULT_CODE_FONT_NOT_FOUND, null);
                    return;
                }
                receiver.send(RESULT_CODE_OK, result);
            });
            mHandler.removeCallbacks(mReplaceDispatcherThreadRunnable);
            mHandler.postDelayed(mReplaceDispatcherThreadRunnable, THREAD_RENEWAL_THRESHOLD_MS);
        }
    }

    private Bundle getFontFromProvider(FontRequest request, ResultReceiver receiver,
            ProviderInfo providerInfo) {
        ArrayList<FontResult> result = null;
        Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
                .authority(providerInfo.authority)
                .build();
        try (Cursor cursor = mContext.getContentResolver().query(uri, new String[] { Columns._ID,
                        Columns.TTC_INDEX, Columns.VARIATION_SETTINGS, Columns.STYLE },
                "query = ?", new String[] { request.getQuery() }, null);) {
            // 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) {
                result = new ArrayList<>();
                final int idColumnIndex = cursor.getColumnIndex(Columns._ID);
                final int ttcIndexColumnIndex = cursor.getColumnIndex(Columns.TTC_INDEX);
                final int vsColumnIndex = cursor.getColumnIndex(Columns.VARIATION_SETTINGS);
                final int styleColumnIndex = cursor.getColumnIndex(Columns.STYLE);
                while (cursor.moveToNext()) {
                    long id = cursor.getLong(idColumnIndex);
                    Uri fileUri = ContentUris.withAppendedId(uri, id);
                    try {
                        ParcelFileDescriptor pfd =
                                mContext.getContentResolver().openFileDescriptor(fileUri, "r");
                        final int ttcIndex = cursor.getInt(ttcIndexColumnIndex);
                        final String variationSettings = cursor.getString(vsColumnIndex);
                        final int style = cursor.getInt(styleColumnIndex);
                        result.add(new FontResult(pfd, ttcIndex, variationSettings, style));
                    } catch (FileNotFoundException e) {
                        Log.e(TAG, "FileNotFoundException raised when interacting with content "
                                + "provider " + providerInfo.authority, e);
                    }
                }
            }
        }
        if (result != null && !result.isEmpty()) {
            Bundle bundle = new Bundle();
            bundle.putParcelableArrayList(PARCEL_FONT_RESULTS, result);
            return bundle;
        }
        return null;
    }
}
+153 −2
Original line number Diff line number Diff line
@@ -16,20 +16,34 @@

package android.graphics;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.content.res.AssetManager;
import android.graphics.fonts.FontRequest;
import android.graphics.fonts.FontResult;
import android.os.Bundle;
import android.os.Handler;
import android.os.ParcelFileDescriptor;
import android.os.ResultReceiver;
import android.provider.FontsContract;
import android.text.FontConfig;
import android.util.Log;
import android.util.LongSparseArray;
import android.util.LruCache;
import android.util.SparseArray;

import com.android.internal.annotations.GuardedBy;

import libcore.io.IoUtils;

import org.xmlpull.v1.XmlPullParserException;

import java.io.File;
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;
@@ -64,7 +78,11 @@ public class Typeface {

    static Typeface[] sDefaults;
    private static final LongSparseArray<SparseArray<Typeface>> sTypefaceCache =
            new LongSparseArray<SparseArray<Typeface>>(3);
            new LongSparseArray<>(3);
    @GuardedBy("sLock")
    private static FontsContract sFontsContract;
    @GuardedBy("sLock")
    private static Handler mHandler;

    /**
     * Cache for Typeface objects dynamically loaded from assets. Currently max size is 16.
@@ -74,6 +92,7 @@ public class Typeface {
    static Typeface sDefaultTypeface;
    static Map<String, Typeface> sSystemFontMap;
    static FontFamily[] sFallbackFonts;
    private static final Object sLock = new Object();

    static final String FONTS_CONFIG = "fonts.xml";

@@ -134,6 +153,138 @@ public class Typeface {
        throw new RuntimeException("Font resource not found " + path);
    }

    /**
     * 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 main thread.
     * @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.
     */
    public static void create(@NonNull FontRequest request, @NonNull FontRequestCallback callback) {
        synchronized (sLock) {
            if (sFontsContract == null) {
                sFontsContract = new FontsContract();
                mHandler = new Handler();
            }
            final ResultReceiver receiver = new ResultReceiver(null) {
                @Override
                public void onReceiveResult(int resultCode, Bundle resultData) {
                    mHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            receiveResult(request, callback, resultCode, resultData);
                        }
                    });
                }
            };
            sFontsContract.getFont(request, receiver);
        }
    }

    private static void receiveResult(FontRequest request, FontRequestCallback callback,
            int resultCode, Bundle resultData) {
        if (resultCode == FontsContract.RESULT_CODE_PROVIDER_NOT_FOUND) {
            callback.onTypefaceRequestFailed(
                    FontRequestCallback.FAIL_REASON_PROVIDER_NOT_FOUND);
            return;
        }
        if (resultCode == FontsContract.RESULT_CODE_FONT_NOT_FOUND
                || resultData == null) {
            callback.onTypefaceRequestFailed(
                    FontRequestCallback.FAIL_REASON_FONT_NOT_FOUND);
            return;
        }
        List<FontResult> resultList =
                resultData.getParcelableArrayList(FontsContract.PARCEL_FONT_RESULTS);
        if (resultList == null || resultList.isEmpty()) {
            callback.onTypefaceRequestFailed(
                    FontRequestCallback.FAIL_REASON_FONT_NOT_FOUND);
            return;
        }
        FontFamily fontFamily = new FontFamily();
        for (int i = 0; i < resultList.size(); ++i) {
            FontResult result = resultList.get(i);
            ParcelFileDescriptor fd = result.getFileDescriptor();
            if (fd == null) {
                callback.onTypefaceRequestFailed(
                        FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR);
                return;
            }
            try (FileInputStream is = new FileInputStream(fd.getFileDescriptor())) {
                FileChannel fileChannel = is.getChannel();
                long fontSize = fileChannel.size();
                ByteBuffer fontBuffer = fileChannel.map(
                        FileChannel.MapMode.READ_ONLY, 0, fontSize);
                int style = result.getStyle();
                int weight = (style & BOLD) != 0 ? 700 : 400;
                // TODO: this method should be
                // create(fd, ttcIndex, fontVariationSettings, style).
                if (!fontFamily.addFontWeightStyle(fontBuffer, result.getTtcIndex(),
                                null, weight, (style & ITALIC) != 0)) {
                    Log.e(TAG, "Error creating font " + request.getQuery());
                    callback.onTypefaceRequestFailed(
                            FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR);
                    return;
                }
            } catch (IOException e) {
                Log.e(TAG, "Error reading font " + request.getQuery(), e);
                callback.onTypefaceRequestFailed(
                        FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR);
                return;
            } finally {
                IoUtils.closeQuietly(fd);
            }
        }
        callback.onTypefaceRetrieved(Typeface.createFromFamiliesWithDefault(
                new FontFamily[] {fontFamily}));
    }

    /**
     * Interface used to receive asynchronously fetched typefaces.
     */
    public interface FontRequestCallback {
        /**
         * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given
         * provider was not found on the device.
         */
        int FAIL_REASON_PROVIDER_NOT_FOUND = 0;
        /**
         * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the font
         * returned by the provider was not loaded properly.
         */
        int FAIL_REASON_FONT_LOAD_ERROR = 1;
        /**
         * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given
         * provider did not return any results for the given query.
         */
        int FAIL_REASON_FONT_NOT_FOUND = 2;

        /** @hide */
        @IntDef({FAIL_REASON_PROVIDER_NOT_FOUND, FAIL_REASON_FONT_LOAD_ERROR,
                FAIL_REASON_FONT_NOT_FOUND})
        @Retention(RetentionPolicy.SOURCE)
        @interface FontRequestFailReason {}

        /**
         * 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.
         */
        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} or
         *               {@link #FAIL_REASON_FONT_LOAD_ERROR}.
         */
        void onTypefaceRequestFailed(@FontRequestFailReason int reason);
    }

    /**
     * Create a typeface object given a family name, and option style information.
     * If null is passed for the name, then the "default" font will be chosen.
Loading