Loading api/current.txt +32 −0 Original line number Diff line number Diff line Loading @@ -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); Loading @@ -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(); } Loading Loading @@ -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 { Loading Loading @@ -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"; api/system-current.txt +32 −0 Original line number Diff line number Diff line Loading @@ -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); Loading @@ -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(); } Loading Loading @@ -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 { Loading Loading @@ -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"; api/test-current.txt +32 −0 Original line number Diff line number Diff line Loading @@ -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); Loading @@ -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(); } Loading Loading @@ -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 { Loading Loading @@ -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"; core/java/android/provider/FontsContract.java 0 → 100644 +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; } } graphics/java/android/graphics/Typeface.java +153 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. Loading @@ -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"; Loading Loading @@ -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 Loading
api/current.txt +32 −0 Original line number Diff line number Diff line Loading @@ -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); Loading @@ -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(); } Loading Loading @@ -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 { Loading Loading @@ -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";
api/system-current.txt +32 −0 Original line number Diff line number Diff line Loading @@ -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); Loading @@ -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(); } Loading Loading @@ -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 { Loading Loading @@ -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";
api/test-current.txt +32 −0 Original line number Diff line number Diff line Loading @@ -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); Loading @@ -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(); } Loading Loading @@ -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 { Loading Loading @@ -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";
core/java/android/provider/FontsContract.java 0 → 100644 +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; } }
graphics/java/android/graphics/Typeface.java +153 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. Loading @@ -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"; Loading Loading @@ -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