Introduce Fonts Content Provider

This change exposes to developers the ability to request fonts
from a provider via Typeface.

Until further security is implemented, only system apps can
provide fonts.

Test: See topic for CTS change
Change-Id: Ic7d5e2648340ee561f4d4c2d73a673748d2af076
This commit is contained in:
Clara Bayarri
2016-10-20 10:42:13 +01:00
parent 2cff9319e8
commit b0812a3049
8 changed files with 665 additions and 2 deletions

View File

@ -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";
@ -124,7 +143,7 @@ public class Typeface {
FontFamily fontFamily = new FontFamily();
if (fontFamily.addFontFromAssetManager(mgr, path, cookie, false /* isAsset */)) {
FontFamily[] families = { fontFamily };
FontFamily[] families = {fontFamily};
typeface = createFromFamiliesWithDefault(families);
sDynamicTypefaceCache.put(key, typeface);
return typeface;
@ -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.

View File

@ -0,0 +1,93 @@
/*
* 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.graphics.fonts;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.internal.util.Preconditions;
/**
* Information about a font request that may be sent to a Font Provider.
*/
public final class FontRequest implements Parcelable {
private final String mProviderAuthority;
private final String mQuery;
/**
* @param providerAuthority The authority of the Font Provider to be used for the request.
* @param query The query to be sent over to the provider. Refer to your font provider's
* documentation on the format of this string.
*/
public FontRequest(@NonNull String providerAuthority, @NonNull String query) {
mProviderAuthority = Preconditions.checkNotNull(providerAuthority);
mQuery = Preconditions.checkNotNull(query);
}
/**
* Returns the selected font provider's authority. This tells the system what font provider
* it should request the font from.
*/
public String getProviderAuthority() {
return mProviderAuthority;
}
/**
* Returns the query string. Refer to your font provider's documentation on the format of this
* string.
*/
public String getQuery() {
return mQuery;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mProviderAuthority);
dest.writeString(mQuery);
}
private FontRequest(Parcel in) {
mProviderAuthority = in.readString();
mQuery = in.readString();
}
public static final Parcelable.Creator<FontRequest> CREATOR =
new Parcelable.Creator<FontRequest>() {
@Override
public FontRequest createFromParcel(Parcel in) {
return new FontRequest(in);
}
@Override
public FontRequest[] newArray(int size) {
return new FontRequest[size];
}
};
@Override
public String toString() {
return "FontRequest {"
+ "mProviderAuthority: " + mProviderAuthority
+ ", mQuery: " + mQuery
+ "}";
}
}

View File

@ -0,0 +1,108 @@
/*
* 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.graphics.fonts;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Paint;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import com.android.internal.util.Preconditions;
import java.io.FileDescriptor;
import java.io.IOException;
/**
* Results returned from a Font Provider to the system.
* @hide
*/
public final class FontResult implements Parcelable {
private final ParcelFileDescriptor mFileDescriptor;
private final int mTtcIndex;
private final String mFontVariationSettings;
private final int mStyle;
/**
* Creates a FontResult with all the information needed about a provided font.
* @param fileDescriptor A ParcelFileDescriptor pointing to the font file. This shoult 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.
* @param ttcIndex If providing a TTC_INDEX file, the index to point to. Otherwise, 0.
* @param fontVariationSettings If providing a variation font, the settings for it. May be null.
* @param style 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 FontResult(@NonNull ParcelFileDescriptor fileDescriptor, int ttcIndex,
@Nullable String fontVariationSettings, int style) {
mFileDescriptor = Preconditions.checkNotNull(fileDescriptor);
mTtcIndex = ttcIndex;
mFontVariationSettings = fontVariationSettings;
mStyle = style;
}
public ParcelFileDescriptor getFileDescriptor() {
return mFileDescriptor;
}
public int getTtcIndex() {
return mTtcIndex;
}
public String getFontVariationSettings() {
return mFontVariationSettings;
}
public int getStyle() {
return mStyle;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(mFileDescriptor, flags);
dest.writeInt(mTtcIndex);
dest.writeString(mFontVariationSettings);
dest.writeInt(mStyle);
}
private FontResult(Parcel in) {
mFileDescriptor = in.readParcelable(null);
mTtcIndex = in.readInt();
mFontVariationSettings = in.readString();
mStyle = in.readInt();
}
public static final Parcelable.Creator<FontResult> CREATOR =
new Parcelable.Creator<FontResult>() {
@Override
public FontResult createFromParcel(Parcel in) {
return new FontResult(in);
}
@Override
public FontResult[] newArray(int size) {
return new FontResult[size];
}
};
}

View File

@ -0,0 +1,18 @@
/* Copyright 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.graphics.fonts;
parcelable FontSpec;