Merge "Introduce FontsContract.fetchFonts and expose URI for watching." into oc-dev

am: 061db23b79

Change-Id: I7a7d47db8dedd6f75d4884d635cbfcf15949ac43
This commit is contained in:
Seigo Nonaka
2017-04-13 13:27:57 +00:00
committed by android-build-merger
6 changed files with 660 additions and 211 deletions

View File

@@ -34502,6 +34502,9 @@ package android.provider {
}
public class FontsContract {
method public static android.graphics.Typeface buildTypeface(android.content.Context, android.os.CancellationSignal, android.provider.FontsContract.FontInfo[], int, boolean, java.lang.String);
method public static android.graphics.Typeface buildTypeface(android.content.Context, android.os.CancellationSignal, android.provider.FontsContract.FontInfo[]);
method public static android.provider.FontsContract.FontFamilyResult fetchFonts(android.content.Context, android.os.CancellationSignal, android.graphics.fonts.FontRequest) throws android.content.pm.PackageManager.NameNotFoundException;
}
public static final class FontsContract.Columns implements android.provider.BaseColumns {
@@ -34518,6 +34521,23 @@ package android.provider {
field public static final java.lang.String WEIGHT = "font_weight";
}
public static class FontsContract.FontFamilyResult {
method public android.provider.FontsContract.FontInfo[] getFonts();
method public int getStatusCode();
field public static final int STATUS_OK = 0; // 0x0
field public static final int STATUS_UNEXPECTED_DATA_PROVIDED = 2; // 0x2
field public static final int STATUS_WRONG_CERTIFICATES = 1; // 0x1
}
public static class FontsContract.FontInfo {
method public android.graphics.fonts.FontVariationAxis[] getAxes();
method public int getResultCode();
method public int getTtcIndex();
method public android.net.Uri getUri();
method public int getWeight();
method public boolean isItalic();
}
public final deprecated class LiveFolders implements android.provider.BaseColumns {
field public static final java.lang.String ACTION_CREATE_LIVE_FOLDER = "android.intent.action.CREATE_LIVE_FOLDER";
field public static final java.lang.String DESCRIPTION = "description";

View File

@@ -37479,6 +37479,9 @@ package android.provider {
}
public class FontsContract {
method public static android.graphics.Typeface buildTypeface(android.content.Context, android.os.CancellationSignal, android.provider.FontsContract.FontInfo[], int, boolean, java.lang.String);
method public static android.graphics.Typeface buildTypeface(android.content.Context, android.os.CancellationSignal, android.provider.FontsContract.FontInfo[]);
method public static android.provider.FontsContract.FontFamilyResult fetchFonts(android.content.Context, android.os.CancellationSignal, android.graphics.fonts.FontRequest) throws android.content.pm.PackageManager.NameNotFoundException;
}
public static final class FontsContract.Columns implements android.provider.BaseColumns {
@@ -37495,6 +37498,23 @@ package android.provider {
field public static final java.lang.String WEIGHT = "font_weight";
}
public static class FontsContract.FontFamilyResult {
method public android.provider.FontsContract.FontInfo[] getFonts();
method public int getStatusCode();
field public static final int STATUS_OK = 0; // 0x0
field public static final int STATUS_UNEXPECTED_DATA_PROVIDED = 2; // 0x2
field public static final int STATUS_WRONG_CERTIFICATES = 1; // 0x1
}
public static class FontsContract.FontInfo {
method public android.graphics.fonts.FontVariationAxis[] getAxes();
method public int getResultCode();
method public int getTtcIndex();
method public android.net.Uri getUri();
method public int getWeight();
method public boolean isItalic();
}
public final deprecated class LiveFolders implements android.provider.BaseColumns {
field public static final java.lang.String ACTION_CREATE_LIVE_FOLDER = "android.intent.action.CREATE_LIVE_FOLDER";
field public static final java.lang.String DESCRIPTION = "description";

View File

@@ -34645,6 +34645,9 @@ package android.provider {
}
public class FontsContract {
method public static android.graphics.Typeface buildTypeface(android.content.Context, android.os.CancellationSignal, android.provider.FontsContract.FontInfo[], int, boolean, java.lang.String);
method public static android.graphics.Typeface buildTypeface(android.content.Context, android.os.CancellationSignal, android.provider.FontsContract.FontInfo[]);
method public static android.provider.FontsContract.FontFamilyResult fetchFonts(android.content.Context, android.os.CancellationSignal, android.graphics.fonts.FontRequest) throws android.content.pm.PackageManager.NameNotFoundException;
}
public static final class FontsContract.Columns implements android.provider.BaseColumns {
@@ -34661,6 +34664,23 @@ package android.provider {
field public static final java.lang.String WEIGHT = "font_weight";
}
public static class FontsContract.FontFamilyResult {
method public android.provider.FontsContract.FontInfo[] getFonts();
method public int getStatusCode();
field public static final int STATUS_OK = 0; // 0x0
field public static final int STATUS_UNEXPECTED_DATA_PROVIDED = 2; // 0x2
field public static final int STATUS_WRONG_CERTIFICATES = 1; // 0x1
}
public static class FontsContract.FontInfo {
method public android.graphics.fonts.FontVariationAxis[] getAxes();
method public int getResultCode();
method public int getTtcIndex();
method public android.net.Uri getUri();
method public int getWeight();
method public boolean isItalic();
}
public final deprecated class LiveFolders implements android.provider.BaseColumns {
field public static final java.lang.String ACTION_CREATE_LIVE_FOLDER = "android.intent.action.CREATE_LIVE_FOLDER";
field public static final java.lang.String DESCRIPTION = "description";

View File

@@ -15,10 +15,18 @@
*/
package android.provider;
import static android.graphics.fonts.FontVariationAxis.InvalidFormatException;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.content.pm.Signature;
@@ -26,8 +34,10 @@ import android.database.Cursor;
import android.graphics.Typeface;
import android.graphics.fonts.FontRequest;
import android.graphics.fonts.FontResult;
import android.graphics.fonts.FontVariationAxis;
import android.net.Uri;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.ParcelFileDescriptor;
@@ -37,14 +47,22 @@ import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Utility class to deal with Font ContentProviders.
@@ -140,7 +158,6 @@ public class FontsContract {
* @hide
*/
public static final String PARCEL_FONT_RESULTS = "font_results";
// Error codes internal to the system, which can not come from a provider. To keep the number
// space open for new provider codes, these should all be negative numbers.
/** @hide */
@@ -165,11 +182,128 @@ public class FontsContract {
mPackageManager = mContext.getPackageManager();
}
/** @hide */
@VisibleForTesting
public FontsContract(Context context, PackageManager packageManager) {
mContext = context;
mPackageManager = packageManager;
/**
* Object represent a font entry in the family returned from {@link #fetchFonts}.
*/
public static class FontInfo {
private final Uri mUri;
private final int mTtcIndex;
private final FontVariationAxis[] mAxes;
private final int mWeight;
private final boolean mItalic;
private final int mResultCode;
/**
* Creates a Font with all the information needed about a provided font.
* @param uri A URI associated to the font file.
* @param ttcIndex If providing a TTC_INDEX file, the index to point to. Otherwise, 0.
* @param axes If providing a variation font, the settings for it. May be null.
* @param weight An integer that indicates the font weight.
* @param italic A boolean that indicates the font is italic style or not.
* @param resultCode A boolean that indicates the font contents is ready.
*/
/** @hide */
public FontInfo(@NonNull Uri uri, @IntRange(from = 0) int ttcIndex,
@Nullable FontVariationAxis[] axes, @IntRange(from = 1, to = 1000) int weight,
boolean italic, int resultCode) {
mUri = Preconditions.checkNotNull(uri);
mTtcIndex = ttcIndex;
mAxes = axes;
mWeight = weight;
mItalic = italic;
mResultCode = resultCode;
}
/**
* Returns a URI associated to this record.
*/
public @NonNull Uri getUri() {
return mUri;
}
/**
* Returns the index to be used to access this font when accessing a TTC file.
*/
public @IntRange(from = 0) int getTtcIndex() {
return mTtcIndex;
}
/**
* Returns the list of axes associated to this font.
*/
public @Nullable FontVariationAxis[] getAxes() {
return mAxes;
}
/**
* Returns the weight value for this font.
*/
public @IntRange(from = 1, to = 1000) int getWeight() {
return mWeight;
}
/**
* Returns whether this font is italic.
*/
public boolean isItalic() {
return mItalic;
}
/**
* Returns result code.
*
* {@link FontsContract.Columns#RESULT_CODE}
*/
public int getResultCode() {
return mResultCode;
}
}
/**
* Object returned from {@link #fetchFonts}.
*/
public static class FontFamilyResult {
/**
* Constant represents that the font was successfully retrieved. Note that when this value
* is set and {@link #getFonts} returns an empty array, it means there were no fonts
* matching the given query.
*/
public static final int STATUS_OK = 0;
/**
* Constant represents that the given certificate was not matched with the provider's
* signature. {@link #getFonts} returns null if this status was set.
*/
public static final int STATUS_WRONG_CERTIFICATES = 1;
/**
* Constant represents that the provider returns unexpected data. {@link #getFonts} returns
* null if this status was set. For example, this value is set when the font provider
* gives invalid format of variation settings.
*/
public static final int STATUS_UNEXPECTED_DATA_PROVIDED = 2;
/** @hide */
@IntDef({STATUS_OK, STATUS_WRONG_CERTIFICATES, STATUS_UNEXPECTED_DATA_PROVIDED})
@Retention(RetentionPolicy.SOURCE)
@interface FontResultStatus {}
private final @FontResultStatus int mStatusCode;
private final FontInfo[] mFonts;
/** @hide */
public FontFamilyResult(@FontResultStatus int statusCode, @Nullable FontInfo[] fonts) {
mStatusCode = statusCode;
mFonts = fonts;
}
public @FontResultStatus int getStatusCode() {
return mStatusCode;
}
public @NonNull FontInfo[] getFonts() {
return mFonts;
}
}
// We use a background thread to post the content resolving work for all requests on. This
@@ -196,33 +330,210 @@ public class FontsContract {
mHandler = new Handler(mThread.getLooper());
}
mHandler.post(() -> {
ProviderInfo providerInfo = getProvider(request, receiver);
if (providerInfo == null) {
ProviderInfo providerInfo;
try {
providerInfo = getProvider(mPackageManager, request);
if (providerInfo == null) {
receiver.send(RESULT_CODE_PROVIDER_NOT_FOUND, null);
return;
}
} catch (PackageManager.NameNotFoundException e) {
receiver.send(RESULT_CODE_PROVIDER_NOT_FOUND, null);
return;
}
getFontFromProvider(request, receiver, providerInfo.authority);
FontInfo[] fonts;
try {
fonts = getFontFromProvider(mContext, request, providerInfo.authority,
null /* cancellation signal */);
} catch (InvalidFormatException e) {
receiver.send(RESULT_CODE_PROVIDER_NOT_FOUND, null);
return;
}
ArrayList<FontResult> result = new ArrayList<>();
int resultCode = -1;
for (FontInfo font : fonts) {
try {
resultCode = font.getResultCode();
if (resultCode != Columns.RESULT_CODE_OK) {
if (resultCode < 0) {
// Negative values are reserved for the internal errors.
resultCode = Columns.RESULT_CODE_FONT_NOT_FOUND;
}
for (int i = 0; i < result.size(); ++i) {
try {
result.get(i).getFileDescriptor().close();
} catch (IOException e) {
// Ignore, as we are closing fds for cleanup.
}
}
receiver.send(resultCode, null);
return;
}
ParcelFileDescriptor pfd = mContext.getContentResolver().openFileDescriptor(
font.getUri(), "r");
result.add(new FontResult(pfd, font.getTtcIndex(),
FontVariationAxis.toFontVariationSettings(font.getAxes()),
font.getWeight(), font.isItalic()));
} catch (FileNotFoundException e) {
Log.e(TAG, "FileNotFoundException raised when interacting with content "
+ "provider " + providerInfo.authority, e);
}
}
if (!result.isEmpty()) {
Bundle bundle = new Bundle();
bundle.putParcelableArrayList(PARCEL_FONT_RESULTS, result);
receiver.send(Columns.RESULT_CODE_OK, bundle);
return;
}
receiver.send(Columns.RESULT_CODE_FONT_NOT_FOUND, null);
});
mHandler.removeCallbacks(mReplaceDispatcherThreadRunnable);
mHandler.postDelayed(mReplaceDispatcherThreadRunnable, THREAD_RENEWAL_THRESHOLD_MS);
}
}
/**
* Fetch fonts given a font request.
*
* @param context A {@link Context} to be used for fetching fonts.
* @param cancellationSignal A signal to cancel the operation in progress, or null if none. If
* the operation is canceled, then {@link
* android.os.OperationCanceledException} will be thrown when the
* query is executed.
* @param request A {@link FontRequest} object that identifies the provider and query for the
* request.
*
* @return {@link FontFamilyResult}
*
* @throws NameNotFoundException If requested package or authority was not found in system.
*/
public static @NonNull FontFamilyResult fetchFonts(
@NonNull Context context, @Nullable CancellationSignal cancellationSignal,
@NonNull FontRequest request) throws NameNotFoundException {
ProviderInfo providerInfo = getProvider(context.getPackageManager(), request);
if (providerInfo == null) {
return new FontFamilyResult(FontFamilyResult.STATUS_WRONG_CERTIFICATES, null);
}
try {
FontInfo[] fonts = getFontFromProvider(
context, request, providerInfo.authority, cancellationSignal);
return new FontFamilyResult(FontFamilyResult.STATUS_OK, fonts);
} catch (InvalidFormatException e) {
return new FontFamilyResult(FontFamilyResult.STATUS_UNEXPECTED_DATA_PROVIDED, null);
}
}
/**
* Build a Typeface from an array of {@link FontInfo}. Results that are marked as not ready
* will be skipped.
*
* @param context A {@link Context} that will be used to fetch the font contents.
* @param cancellationSignal A signal to cancel the operation in progress, or null if none. If
* the operation is canceled, then {@link
* android.os.OperationCanceledException} will be thrown.
* @param fonts An array of {@link FontInfo} to be used to create a Typeface.
* @param weight A weight value to be used for selecting a font from a font family.
* @param italic {@code true} if this font is of italic style. This will be used for font
* selection from a font family.
* @param fallbackFontName A fallback font name used if this method fails to create the
* Typeface. By passing {@code null}, this method returns {@code null}
* if typeface creation fails.
* @return A Typeface object. May return {@code null} if that is the value passed to {@code
* fallBackFontName}.
*/
public static Typeface buildTypeface(@NonNull Context context,
@Nullable CancellationSignal cancellationSignal, @NonNull FontInfo[] fonts,
int weight, boolean italic, @Nullable String fallbackFontName) {
final Map<Uri, ByteBuffer> uriBuffer =
prepareFontData(context, fonts, cancellationSignal);
Typeface typeface = new Typeface.Builder(fonts, uriBuffer)
.setWeight(weight)
.setItalic(italic)
.build();
// TODO: Use Typeface fallback instead.
if (typeface == null) {
typeface = Typeface.create(fallbackFontName, Typeface.NORMAL);
}
return typeface;
}
/**
* Build a Typeface from an array of {@link FontInfo}
*
* Results that are marked as not ready will be skipped.
*
* @param context A {@link Context} that will be used to fetch the font contents.
* @param cancellationSignal A signal to cancel the operation in progress, or null if none. If
* the operation is canceled, then {@link
* android.os.OperationCanceledException} will be thrown.
* @param fonts An array of {@link FontInfo} to be used to create a Typeface.
* @return A Typeface object. Returns null if typeface creation fails.
*/
public static Typeface buildTypeface(@NonNull Context context,
@Nullable CancellationSignal cancellationSignal, @NonNull FontInfo[] fonts) {
final Map<Uri, ByteBuffer> uriBuffer =
prepareFontData(context, fonts, cancellationSignal);
return new Typeface.Builder(fonts, uriBuffer).build();
}
/**
* A helper function to create a mapping from {@link Uri} to {@link ByteBuffer}.
*
* Skip if the file contents is not ready to be read.
*
* @param context A {@link Context} to be used for resolving content URI in
* {@link FontInfo}.
* @param fonts An array of {@link FontInfo}.
* @return A map from {@link Uri} to {@link ByteBuffer}.
*/
private static Map<Uri, ByteBuffer> prepareFontData(Context context, FontInfo[] fonts,
CancellationSignal cancellationSignal) {
final HashMap<Uri, ByteBuffer> out = new HashMap<>();
final ContentResolver resolver = context.getContentResolver();
for (FontInfo font : fonts) {
if (font.getResultCode() != Columns.RESULT_CODE_OK) {
continue;
}
final Uri uri = font.getUri();
if (out.containsKey(uri)) {
continue;
}
ByteBuffer buffer = null;
try (final ParcelFileDescriptor pfd =
resolver.openFileDescriptor(uri, "r", cancellationSignal);
final FileInputStream fis = new FileInputStream(pfd.getFileDescriptor())) {
final FileChannel fileChannel = fis.getChannel();
final long size = fileChannel.size();
buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, size);
} catch (IOException e) {
// ignore
}
// TODO: try other approach?, e.g. read all contents instead of mmap.
out.put(uri, buffer);
}
return Collections.unmodifiableMap(out);
}
/** @hide */
@VisibleForTesting
public ProviderInfo getProvider(FontRequest request, ResultReceiver receiver) {
public static @Nullable ProviderInfo getProvider(
PackageManager packageManager, FontRequest request) throws NameNotFoundException {
String providerAuthority = request.getProviderAuthority();
ProviderInfo info = mPackageManager.resolveContentProvider(providerAuthority, 0);
ProviderInfo info = packageManager.resolveContentProvider(providerAuthority, 0);
if (info == null) {
Log.e(TAG, "Can't find content provider " + providerAuthority);
receiver.send(RESULT_CODE_PROVIDER_NOT_FOUND, null);
return null;
throw new NameNotFoundException("No package found for authority: " + providerAuthority);
}
if (!info.packageName.equals(request.getProviderPackage())) {
Log.e(TAG, "Found content provider " + providerAuthority + ", but package was not "
+ request.getProviderPackage());
receiver.send(RESULT_CODE_PROVIDER_NOT_FOUND, null);
return null;
throw new NameNotFoundException("Found content provider " + providerAuthority
+ ", but package was not " + request.getProviderPackage());
}
// Trust system apps without signature checks
if (info.applicationInfo.isSystemApp()) {
@@ -230,16 +541,11 @@ public class FontsContract {
}
List<byte[]> signatures;
try {
PackageInfo packageInfo = mPackageManager.getPackageInfo(info.packageName,
PackageManager.GET_SIGNATURES);
signatures = convertToByteArrayList(packageInfo.signatures);
Collections.sort(signatures, sByteArrayComparator);
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Can't find content provider " + providerAuthority, e);
receiver.send(RESULT_CODE_PROVIDER_NOT_FOUND, null);
return null;
}
PackageInfo packageInfo = packageManager.getPackageInfo(info.packageName,
PackageManager.GET_SIGNATURES);
signatures = convertToByteArrayList(packageInfo.signatures);
Collections.sort(signatures, sByteArrayComparator);
List<List<byte[]>> requestCertificatesList = request.getCertificates();
for (int i = 0; i < requestCertificatesList.size(); ++i) {
// Make a copy so we can sort it without modifying the incoming data.
@@ -249,8 +555,6 @@ public class FontsContract {
return info;
}
}
Log.e(TAG, "Certificates don't match for given provider " + providerAuthority);
receiver.send(RESULT_CODE_WRONG_CERTIFICATES, null);
return null;
}
@@ -266,7 +570,8 @@ public class FontsContract {
return 0;
};
private boolean equalsByteArrayList(List<byte[]> signatures, List<byte[]> requestSignatures) {
private static boolean equalsByteArrayList(
List<byte[]> signatures, List<byte[]> requestSignatures) {
if (signatures.size() != requestSignatures.size()) {
return false;
}
@@ -278,7 +583,7 @@ public class FontsContract {
return true;
}
private List<byte[]> convertToByteArrayList(Signature[] signatures) {
private static List<byte[]> convertToByteArrayList(Signature[] signatures) {
List<byte[]> shas = new ArrayList<>();
for (int i = 0; i < signatures.length; ++i) {
shas.add(signatures[i].toByteArray());
@@ -288,9 +593,10 @@ public class FontsContract {
/** @hide */
@VisibleForTesting
public void getFontFromProvider(FontRequest request, ResultReceiver receiver,
String authority) {
ArrayList<FontResult> result = null;
public static @NonNull FontInfo[] getFontFromProvider(
Context context, FontRequest request, String authority,
CancellationSignal cancellationSignal) throws InvalidFormatException {
ArrayList<FontInfo> result = new ArrayList<>();
final Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
.authority(authority)
.build();
@@ -298,15 +604,14 @@ public class FontsContract {
.authority(authority)
.appendPath("file")
.build();
try (Cursor cursor = mContext.getContentResolver().query(uri, new String[] { Columns._ID,
try (Cursor cursor = context.getContentResolver().query(uri, new String[] { Columns._ID,
Columns.FILE_ID, Columns.TTC_INDEX, Columns.VARIATION_SETTINGS,
Columns.STYLE, Columns.WEIGHT, Columns.ITALIC, Columns.RESULT_CODE },
"query = ?", new String[] { request.getQuery() }, null);) {
"query = ?", new String[] { request.getQuery() }, null, cancellationSignal);) {
// TODO: Should we restrict the amount of fonts that can be returned?
// TODO: Write documentation explaining that all results should be from the same family.
if (cursor != null && cursor.getCount() > 0) {
final int resultCodeColumnIndex = cursor.getColumnIndex(Columns.RESULT_CODE);
int resultCode = -1;
result = new ArrayList<>();
final int idColumnIndex = cursor.getColumnIndexOrThrow(Columns._ID);
final int fileIdColumnIndex = cursor.getColumnIndex(Columns.FILE_ID);
@@ -316,23 +621,13 @@ public class FontsContract {
final int italicColumnIndex = cursor.getColumnIndex(Columns.ITALIC);
final int styleColumnIndex = cursor.getColumnIndex(Columns.STYLE);
while (cursor.moveToNext()) {
resultCode = resultCodeColumnIndex != -1
int resultCode = resultCodeColumnIndex != -1
? cursor.getInt(resultCodeColumnIndex) : Columns.RESULT_CODE_OK;
if (resultCode != Columns.RESULT_CODE_OK) {
if (resultCode < 0) {
// Negative values are reserved for the internal errors.
resultCode = Columns.RESULT_CODE_FONT_NOT_FOUND;
}
for (int i = 0; i < result.size(); ++i) {
try {
result.get(i).getFileDescriptor().close();
} catch (IOException e) {
// Ignore, as we are closing fds for cleanup.
}
}
receiver.send(resultCode, null);
return;
}
final int ttcIndex = ttcIndexColumnIndex != -1
? cursor.getInt(ttcIndexColumnIndex) : 0;
final String variationSettings = vsColumnIndex != -1
? cursor.getString(vsColumnIndex) : null;
Uri fileUri;
if (fileIdColumnIndex == -1) {
long id = cursor.getLong(idColumnIndex);
@@ -341,42 +636,27 @@ public class FontsContract {
long id = cursor.getLong(fileIdColumnIndex);
fileUri = ContentUris.withAppendedId(fileBaseUri, id);
}
try {
ParcelFileDescriptor pfd =
mContext.getContentResolver().openFileDescriptor(fileUri, "r");
final int ttcIndex = ttcIndexColumnIndex != -1
? cursor.getInt(ttcIndexColumnIndex) : 0;
final String variationSettings = vsColumnIndex != -1
? cursor.getString(vsColumnIndex) : null;
// TODO: Stop using STYLE column and enforce WEIGHT/ITALIC column.
int weight;
boolean italic;
if (weightColumnIndex != -1 && italicColumnIndex != -1) {
weight = cursor.getInt(weightColumnIndex);
italic = cursor.getInt(italicColumnIndex) == 1;
} else if (styleColumnIndex != -1) {
final int style = cursor.getInt(styleColumnIndex);
weight = (style & Typeface.BOLD) != 0 ? 700 : 400;
italic = (style & Typeface.ITALIC) != 0;
} else {
weight = 400;
italic = false;
}
result.add(
new FontResult(pfd, ttcIndex, variationSettings, weight, italic));
} catch (FileNotFoundException e) {
Log.e(TAG, "FileNotFoundException raised when interacting with content "
+ "provider " + authority, e);
// TODO: Stop using STYLE column and enforce WEIGHT/ITALIC column.
int weight;
boolean italic;
if (weightColumnIndex != -1 && italicColumnIndex != -1) {
weight = cursor.getInt(weightColumnIndex);
italic = cursor.getInt(italicColumnIndex) == 1;
} else if (styleColumnIndex != -1) {
final int style = cursor.getInt(styleColumnIndex);
weight = (style & Typeface.BOLD) != 0 ?
Typeface.Builder.BOLD_WEIGHT : Typeface.Builder.NORMAL_WEIGHT;
italic = (style & Typeface.ITALIC) != 0;
} else {
weight = Typeface.Builder.NORMAL_WEIGHT;
italic = false;
}
FontVariationAxis[] axes =
FontVariationAxis.fromFontVariationSettings(variationSettings);
result.add(new FontInfo(fileUri, ttcIndex, axes, weight, italic, resultCode));
}
}
}
if (result != null && !result.isEmpty()) {
Bundle bundle = new Bundle();
bundle.putParcelableArrayList(PARCEL_FONT_RESULTS, result);
receiver.send(Columns.RESULT_CODE_OK, bundle);
return;
}
receiver.send(Columns.RESULT_CODE_FONT_NOT_FOUND, null);
return result.toArray(new FontInfo[0]);
}
}

View File

@@ -17,29 +17,29 @@ package android.provider;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static android.provider.FontsContract.Columns.RESULT_CODE_OK;
import static android.provider.FontsContract.Columns.RESULT_CODE_FONT_NOT_FOUND;
import static android.provider.FontsContract.Columns.RESULT_CODE_FONT_UNAVAILABLE;
import static android.provider.FontsContract.Columns.RESULT_CODE_MALFORMED_QUERY;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.content.pm.Signature;
import android.database.MatrixCursor;
import android.graphics.Typeface;
import android.graphics.fonts.FontRequest;
import android.graphics.fonts.FontResult;
import android.os.Bundle;
import android.os.ResultReceiver;
import android.graphics.fonts.FontVariationAxis.InvalidFormatException;
import android.graphics.fonts.FontVariationAxis;
import android.provider.FontsContract.FontInfo;
import android.support.test.filters.SmallTest;
import android.test.ProviderTestCase2;
import android.util.Base64;
import org.mockito.ArgumentCaptor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -61,8 +61,6 @@ public class FontsContractTest extends ProviderTestCase2<TestFontsProvider> {
private final FontRequest request = new FontRequest(
TestFontsProvider.AUTHORITY, PACKAGE_NAME, "query");
private TestFontsProvider mProvider;
private FontsContract mContract;
private ResultReceiver mResultReceiver;
private PackageManager mPackageManager;
public FontsContractTest() {
@@ -74,126 +72,178 @@ public class FontsContractTest extends ProviderTestCase2<TestFontsProvider> {
mProvider = getProvider();
mPackageManager = mock(PackageManager.class);
mContract = new FontsContract(getMockContext(), mPackageManager);
mResultReceiver = mock(ResultReceiver.class);
}
public void testGetFontFromProvider_resultOK() {
mContract.getFontFromProvider(request, mResultReceiver, TestFontsProvider.AUTHORITY);
final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
verify(mResultReceiver).send(
eq(FontsContract.Columns.RESULT_CODE_OK), bundleCaptor.capture());
Bundle bundle = bundleCaptor.getValue();
assertNotNull(bundle);
List<FontResult> resultList =
bundle.getParcelableArrayList(FontsContract.PARCEL_FONT_RESULTS);
assertNotNull(resultList);
assertEquals(1, resultList.size());
FontResult fontResult = resultList.get(0);
assertEquals(TestFontsProvider.TTC_INDEX, fontResult.getTtcIndex());
assertEquals(TestFontsProvider.VARIATION_SETTINGS, fontResult.getFontVariationSettings());
assertEquals(TestFontsProvider.NORMAL_WEIGHT, fontResult.getWeight());
assertEquals(TestFontsProvider.ITALIC, fontResult.getItalic());
assertNotNull(fontResult.getFileDescriptor());
public void testGetFontFromProvider_resultOK() throws InvalidFormatException {
FontInfo[] fonts = FontsContract.getFontFromProvider(
getMockContext(), request, TestFontsProvider.AUTHORITY, null);
assertNotNull(fonts);
assertEquals(1, fonts.length);
FontInfo font = fonts[0];
assertEquals(TestFontsProvider.TTC_INDEX, font.getTtcIndex());
FontVariationAxis[] actual = font.getAxes();
assertEquals(1, actual.length);
assertEquals("wdth", actual[0].getTag());
assertEquals(1.0f, actual[0].getStyleValue(), 0);
assertEquals(TestFontsProvider.NORMAL_WEIGHT, font.getWeight());
assertEquals(TestFontsProvider.ITALIC, font.isItalic());
assertNotNull(font.getUri());
assertEquals(RESULT_CODE_OK, font.getResultCode());
}
public void testGetFontFromProvider_providerDoesntReturnAllFields() {
public void testGetFontFromProvider_providerDoesntReturnAllFields()
throws InvalidFormatException {
mProvider.setReturnAllFields(false);
final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
mContract.getFontFromProvider(request, mResultReceiver, TestFontsProvider.AUTHORITY);
verify(mResultReceiver).send(
eq(FontsContract.Columns.RESULT_CODE_OK), bundleCaptor.capture());
Bundle bundle = bundleCaptor.getValue();
assertNotNull(bundle);
List<FontResult> resultList =
bundle.getParcelableArrayList(FontsContract.PARCEL_FONT_RESULTS);
assertNotNull(resultList);
assertEquals(1, resultList.size());
FontResult fontResult = resultList.get(0);
assertEquals(0, fontResult.getTtcIndex());
assertNull(fontResult.getFontVariationSettings());
assertEquals(400, fontResult.getWeight());
assertFalse(fontResult.getItalic());
assertNotNull(fontResult.getFileDescriptor());
FontInfo[] fonts = FontsContract.getFontFromProvider(
getMockContext(), request, TestFontsProvider.AUTHORITY, null);
assertNotNull(fonts);
assertEquals(1, fonts.length);
FontInfo font = fonts[0];
assertEquals(0, font.getTtcIndex());
assertNull(font.getAxes());
assertEquals(400, font.getWeight());
assertFalse(font.isItalic());
assertNotNull(font.getUri());
assertEquals(RESULT_CODE_OK, font.getResultCode());
}
public void testGetFontFromProvider_resultFontNotFound() {
public void testGetFontFromProvider_resultFontNotFound() throws InvalidFormatException {
// Make the provider return unknown
mProvider.setResultCode(FontsContract.Columns.RESULT_CODE_FONT_NOT_FOUND);
mContract.getFontFromProvider(request, mResultReceiver, TestFontsProvider.AUTHORITY);
verify(mResultReceiver).send(FontsContract.Columns.RESULT_CODE_FONT_NOT_FOUND,null);
mProvider.setResultCode(RESULT_CODE_FONT_NOT_FOUND);
FontInfo[] fonts = FontsContract.getFontFromProvider(
getMockContext(), request, TestFontsProvider.AUTHORITY, null);
assertNotNull(fonts);
assertEquals(1, fonts.length);
FontInfo font = fonts[0];
assertEquals(TestFontsProvider.TTC_INDEX, font.getTtcIndex());
assertNotNull(font.getUri());
assertEquals(RESULT_CODE_FONT_NOT_FOUND, font.getResultCode());
}
public void testGetFontFromProvider_resultFontUnavailable() {
public void testGetFontFromProvider_resultFontUnavailable() throws InvalidFormatException {
// Make the provider return font unavailable
mProvider.setResultCode(FontsContract.Columns.RESULT_CODE_FONT_UNAVAILABLE);
mContract.getFontFromProvider(request, mResultReceiver, TestFontsProvider.AUTHORITY);
mProvider.setResultCode(RESULT_CODE_FONT_UNAVAILABLE);
FontInfo[] fonts = FontsContract.getFontFromProvider(
getMockContext(), request, TestFontsProvider.AUTHORITY, null);
verify(mResultReceiver).send(FontsContract.Columns.RESULT_CODE_FONT_UNAVAILABLE,null);
assertNotNull(fonts);
assertEquals(1, fonts.length);
FontInfo font = fonts[0];
assertEquals(TestFontsProvider.TTC_INDEX, font.getTtcIndex());
FontVariationAxis[] actual = font.getAxes();
assertEquals(1, actual.length);
assertEquals("wdth", actual[0].getTag());
assertEquals(1.0f, actual[0].getStyleValue(), 0);
assertEquals(TestFontsProvider.NORMAL_WEIGHT, font.getWeight());
assertEquals(TestFontsProvider.ITALIC, font.isItalic());
assertNotNull(font.getUri());
assertEquals(RESULT_CODE_FONT_UNAVAILABLE, font.getResultCode());
}
public void testGetFontFromProvider_resultMalformedQuery() {
public void testGetFontFromProvider_resultMalformedQuery() throws InvalidFormatException {
// Make the provider return font unavailable
mProvider.setResultCode(FontsContract.Columns.RESULT_CODE_MALFORMED_QUERY);
mContract.getFontFromProvider(request, mResultReceiver, TestFontsProvider.AUTHORITY);
mProvider.setResultCode(RESULT_CODE_MALFORMED_QUERY);
FontInfo[] fonts = FontsContract.getFontFromProvider(
getMockContext(), request, TestFontsProvider.AUTHORITY, null);
verify(mResultReceiver).send(FontsContract.Columns.RESULT_CODE_MALFORMED_QUERY,null);
assertNotNull(fonts);
assertEquals(1, fonts.length);
FontInfo font = fonts[0];
assertEquals(TestFontsProvider.TTC_INDEX, font.getTtcIndex());
FontVariationAxis[] actual = font.getAxes();
assertEquals(1, actual.length);
assertEquals("wdth", actual[0].getTag());
assertEquals(1.0f, actual[0].getStyleValue(), 0);
assertEquals(TestFontsProvider.NORMAL_WEIGHT, font.getWeight());
assertEquals(TestFontsProvider.ITALIC, font.isItalic());
assertNotNull(font.getUri());
assertEquals(RESULT_CODE_MALFORMED_QUERY, font.getResultCode());
}
public void testGetFontFromProvider_resultFontNotFoundSecondRow() {
public void testGetFontFromProvider_resultFontNotFoundSecondRow()
throws InvalidFormatException {
MatrixCursor cursor = new MatrixCursor(new String[] { FontsContract.Columns._ID,
FontsContract.Columns.TTC_INDEX, FontsContract.Columns.VARIATION_SETTINGS,
FontsContract.Columns.WEIGHT, FontsContract.Columns.ITALIC,
FontsContract.Columns.RESULT_CODE });
cursor.addRow(new Object[] { 1, 0, null, 400, 0, FontsContract.Columns.RESULT_CODE_OK});
cursor.addRow(new Object[] { 1, 0, null, 400, 0, RESULT_CODE_OK});
cursor.addRow(new Object[] { 1, 0, null, 400, 0,
FontsContract.Columns.RESULT_CODE_FONT_NOT_FOUND});
RESULT_CODE_FONT_NOT_FOUND});
mProvider.setCustomCursor(cursor);
mContract.getFontFromProvider(request, mResultReceiver, TestFontsProvider.AUTHORITY);
FontInfo[] fonts = FontsContract.getFontFromProvider(
getMockContext(), request, TestFontsProvider.AUTHORITY, null);
verify(mResultReceiver).send(FontsContract.Columns.RESULT_CODE_FONT_NOT_FOUND, null);
assertNotNull(fonts);
assertEquals(2, fonts.length);
FontInfo font = fonts[0];
assertEquals(0, font.getTtcIndex());
assertNull(font.getAxes());
assertEquals(400, font.getWeight());
assertFalse(font.isItalic());
assertNotNull(font.getUri());
assertEquals(RESULT_CODE_OK, font.getResultCode());
font = fonts[1];
assertEquals(0, font.getTtcIndex());
assertNull(font.getAxes());
assertEquals(400, font.getWeight());
assertFalse(font.isItalic());
assertNotNull(font.getUri());
assertEquals(RESULT_CODE_FONT_NOT_FOUND, font.getResultCode());
}
public void testGetFontFromProvider_resultFontNotFoundOtherRow() {
public void testGetFontFromProvider_resultFontNotFoundOtherRow() throws InvalidFormatException {
MatrixCursor cursor = new MatrixCursor(new String[] { FontsContract.Columns._ID,
FontsContract.Columns.TTC_INDEX, FontsContract.Columns.VARIATION_SETTINGS,
FontsContract.Columns.WEIGHT, FontsContract.Columns.ITALIC,
FontsContract.Columns.RESULT_CODE });
cursor.addRow(new Object[] { 1, 0, null, 400, 0, FontsContract.Columns.RESULT_CODE_OK});
cursor.addRow(new Object[] { 1, 0, null, 400, 0, RESULT_CODE_OK});
cursor.addRow(new Object[] { 1, 0, null, 400, 0,
FontsContract.Columns.RESULT_CODE_FONT_NOT_FOUND});
cursor.addRow(new Object[] { 1, 0, null, 400, 0, FontsContract.Columns.RESULT_CODE_OK});
RESULT_CODE_FONT_NOT_FOUND});
cursor.addRow(new Object[] { 1, 0, null, 400, 0, RESULT_CODE_OK});
mProvider.setCustomCursor(cursor);
mContract.getFontFromProvider(request, mResultReceiver, TestFontsProvider.AUTHORITY);
FontInfo[] fonts = FontsContract.getFontFromProvider(
getMockContext(), request, TestFontsProvider.AUTHORITY, null);
verify(mResultReceiver).send(FontsContract.Columns.RESULT_CODE_FONT_NOT_FOUND, null);
}
assertNotNull(fonts);
assertEquals(3, fonts.length);
public void testGetFontFromProvider_resultCodeIsNegativeNumber() {
MatrixCursor cursor = new MatrixCursor(new String[] { FontsContract.Columns._ID,
FontsContract.Columns.TTC_INDEX, FontsContract.Columns.VARIATION_SETTINGS,
FontsContract.Columns.WEIGHT, FontsContract.Columns.ITALIC,
FontsContract.Columns.RESULT_CODE });
cursor.addRow(new Object[] { 1, 0, null, 400, 0, FontsContract.Columns.RESULT_CODE_OK});
cursor.addRow(new Object[] { 1, 0, null, 400, 0, -5});
mProvider.setCustomCursor(cursor);
mContract.getFontFromProvider(request, mResultReceiver, TestFontsProvider.AUTHORITY);
FontInfo font = fonts[0];
assertEquals(0, font.getTtcIndex());
assertNull(font.getAxes());
assertEquals(400, font.getWeight());
assertFalse(font.isItalic());
assertNotNull(font.getUri());
assertEquals(RESULT_CODE_OK, font.getResultCode());
verify(mResultReceiver).send(FontsContract.Columns.RESULT_CODE_FONT_NOT_FOUND, null);
font = fonts[1];
assertEquals(0, font.getTtcIndex());
assertNull(font.getAxes());
assertEquals(400, font.getWeight());
assertFalse(font.isItalic());
assertNotNull(font.getUri());
assertEquals(RESULT_CODE_FONT_NOT_FOUND, font.getResultCode());
font = fonts[2];
assertEquals(0, font.getTtcIndex());
assertNull(font.getAxes());
assertEquals(400, font.getWeight());
assertFalse(font.isItalic());
assertNotNull(font.getUri());
assertEquals(RESULT_CODE_OK, font.getResultCode());
}
public void testGetProvider_providerNotFound() {
when(mPackageManager.resolveContentProvider(anyString(), anyInt())).thenReturn(null);
ProviderInfo result = mContract.getProvider(request, mResultReceiver);
verify(mResultReceiver).send(FontsContract.RESULT_CODE_PROVIDER_NOT_FOUND, null);
assertNull(result);
try {
FontsContract.getProvider(mPackageManager, request);
fail();
} catch (NameNotFoundException e) {
// pass
}
}
public void testGetProvider_providerIsSystemApp() throws PackageManager.NameNotFoundException {
@@ -201,9 +251,7 @@ public class FontsContractTest extends ProviderTestCase2<TestFontsProvider> {
info.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
when(mPackageManager.resolveContentProvider(anyString(), anyInt())).thenReturn(info);
ProviderInfo result = mContract.getProvider(request, mResultReceiver);
verifyZeroInteractions(mResultReceiver);
ProviderInfo result = FontsContract.getProvider(mPackageManager, request);
assertEquals(info, result);
}
@@ -213,23 +261,22 @@ public class FontsContractTest extends ProviderTestCase2<TestFontsProvider> {
info.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
when(mPackageManager.resolveContentProvider(anyString(), anyInt())).thenReturn(info);
ProviderInfo result = mContract.getProvider(
new FontRequest(TestFontsProvider.AUTHORITY, "com.wrong.package", "query"),
mResultReceiver);
try {
FontsContract.getProvider(
mPackageManager,
new FontRequest(TestFontsProvider.AUTHORITY, "com.wrong.package", "query"));
fail();
} catch (NameNotFoundException e) {
// pass
}
verify(mResultReceiver).send(FontsContract.RESULT_CODE_PROVIDER_NOT_FOUND, null);
assertNull(result);
}
public void testGetProvider_providerIsNonSystemAppNoCerts()
throws PackageManager.NameNotFoundException {
setupPackageManager();
// The default request is missing the certificates info.
ProviderInfo result = mContract.getProvider(request, mResultReceiver);
verify(mResultReceiver).send(FontsContract.RESULT_CODE_WRONG_CERTIFICATES, null);
assertNull(result);
assertNull(FontsContract.getProvider(mPackageManager, request));
}
public void testGetProvider_providerIsNonSystemAppWrongCerts()
@@ -240,10 +287,8 @@ public class FontsContractTest extends ProviderTestCase2<TestFontsProvider> {
List<byte[]> certList = Arrays.asList(wrongCert);
FontRequest requestWrongCerts = new FontRequest(
TestFontsProvider.AUTHORITY, PACKAGE_NAME, "query", Arrays.asList(certList));
ProviderInfo result = mContract.getProvider(requestWrongCerts, mResultReceiver);
verify(mResultReceiver).send(FontsContract.RESULT_CODE_WRONG_CERTIFICATES, null);
assertNull(result);
assertNull(FontsContract.getProvider(mPackageManager, requestWrongCerts));
}
public void testGetProvider_providerIsNonSystemAppCorrectCerts()
@@ -253,9 +298,9 @@ public class FontsContractTest extends ProviderTestCase2<TestFontsProvider> {
List<byte[]> certList = Arrays.asList(BYTE_ARRAY);
FontRequest requestRightCerts = new FontRequest(
TestFontsProvider.AUTHORITY, PACKAGE_NAME, "query", Arrays.asList(certList));
ProviderInfo result = mContract.getProvider(requestRightCerts, mResultReceiver);
ProviderInfo result = FontsContract.getProvider(
mPackageManager, requestRightCerts);
verifyZeroInteractions(mResultReceiver);
assertEquals(info, result);
}
@@ -267,11 +312,7 @@ public class FontsContractTest extends ProviderTestCase2<TestFontsProvider> {
List<byte[]> certList = Arrays.asList(wrongCert, BYTE_ARRAY);
FontRequest requestRightCerts = new FontRequest(
TestFontsProvider.AUTHORITY, PACKAGE_NAME, "query", Arrays.asList(certList));
ProviderInfo result = mContract.getProvider(requestRightCerts, mResultReceiver);
// There is one too many certs, should fail as the set doesn't match.
verify(mResultReceiver).send(FontsContract.RESULT_CODE_WRONG_CERTIFICATES, null);
assertNull(result);
assertNull(FontsContract.getProvider(mPackageManager, requestRightCerts));
}
public void testGetProvider_providerIsNonSystemAppDuplicateCerts()
@@ -294,12 +335,7 @@ public class FontsContractTest extends ProviderTestCase2<TestFontsProvider> {
List<byte[]> certList = Arrays.asList(BYTE_ARRAY_2, BYTE_ARRAY_COPY);
FontRequest requestRightCerts = new FontRequest(
TestFontsProvider.AUTHORITY, PACKAGE_NAME, "query", Arrays.asList(certList));
ProviderInfo result = mContract.getProvider(requestRightCerts, mResultReceiver);
// The given list includes an extra cert and doesn't have a second copy of the cert like
// the provider does, so it should have failed.
verify(mResultReceiver).send(FontsContract.RESULT_CODE_WRONG_CERTIFICATES, null);
assertNull(result);
assertNull(FontsContract.getProvider(mPackageManager, requestRightCerts));
}
public void testGetProvider_providerIsNonSystemAppCorrectCertsSeveralSets()
@@ -312,9 +348,8 @@ public class FontsContractTest extends ProviderTestCase2<TestFontsProvider> {
certList.add(Arrays.asList(BYTE_ARRAY));
FontRequest requestRightCerts = new FontRequest(
TestFontsProvider.AUTHORITY, PACKAGE_NAME, "query", certList);
ProviderInfo result = mContract.getProvider(requestRightCerts, mResultReceiver);
ProviderInfo result = FontsContract.getProvider(mPackageManager, requestRightCerts);
verifyZeroInteractions(mResultReceiver);
assertEquals(info, result);
}
@@ -326,10 +361,12 @@ public class FontsContractTest extends ProviderTestCase2<TestFontsProvider> {
certList.add(Arrays.asList(BYTE_ARRAY));
FontRequest requestRightCerts = new FontRequest(
TestFontsProvider.AUTHORITY, "com.wrong.package.name", "query", certList);
ProviderInfo result = mContract.getProvider(requestRightCerts, mResultReceiver);
verify(mResultReceiver).send(FontsContract.RESULT_CODE_PROVIDER_NOT_FOUND, null);
assertNull(result);
try {
FontsContract.getProvider(mPackageManager, requestRightCerts);
fail();
} catch (NameNotFoundException e) {
// pass
}
}
private ProviderInfo setupPackageManager()

View File

@@ -34,6 +34,7 @@ import android.graphics.fonts.FontRequest;
import android.graphics.fonts.FontResult;
import android.graphics.fonts.FontVariationAxis;
import android.graphics.fonts.FontVariationAxis.InvalidFormatException;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.ParcelFileDescriptor;
@@ -509,6 +510,10 @@ public class Typeface {
* </p>
*/
public static final class Builder {
/** @hide */
public static final int NORMAL_WEIGHT = 400;
/** @hide */
public static final int BOLD_WEIGHT = 700;
private int mTtcIndex;
private FontVariationAxis[] mAxes;
@@ -517,6 +522,10 @@ public class Typeface {
private String mPath;
private FileDescriptor mFd;
private FontsContract.FontInfo[] mFonts;
private Map<Uri, ByteBuffer> mFontBuffers;
private String mFallbackFamilyName;
private int mWeight = RESOLVE_BY_FONT_TABLE;
private int mItalic = RESOLVE_BY_FONT_TABLE;
@@ -558,6 +567,25 @@ public class Typeface {
mPath = Preconditions.checkStringNotEmpty(path);
}
/**
* Constracts a builder from an array of FontsContract.FontInfo.
*
* Since {@link FontsContract.FontInfo} holds information about TTC indices and
* variation settings, there is no need to call {@link #setTtcIndex} or
* {@link #setFontVariationSettings}. Similary, {@link FontsContract.FontInfo} holds
* weight and italic information, so {@link #setWeight} and {@link #setItalic} are used
* for style matching during font selection.
*
* @param results The array of {@link FontsContract.FontInfo}
* @param buffers The mapping from URI to buffers to be used during building.
* @hide
*/
public Builder(@NonNull FontsContract.FontInfo[] fonts,
@NonNull Map<Uri, ByteBuffer> buffers) {
mFonts = fonts;
mFontBuffers = buffers;
}
/**
* Sets weight of the font.
*
@@ -590,6 +618,10 @@ public class Typeface {
* collection, do not call this method or specify 0.
*/
public Builder setTtcIndex(@IntRange(from = 0) int ttcIndex) {
if (mFonts != null) {
throw new IllegalArgumentException(
"TTC index can not be specified for FontResult source.");
}
mTtcIndex = ttcIndex;
return this;
}
@@ -603,6 +635,13 @@ public class Typeface {
*/
public Builder setFontVariationSettings(@Nullable String variationSettings)
throws InvalidFormatException {
if (mFonts != null) {
throw new IllegalArgumentException(
"Font variation settings can not be specified for FontResult source.");
}
if (mAxes != null) {
throw new IllegalStateException("Font variation settings are already set.");
}
mAxes = FontVariationAxis.fromFontVariationSettings(variationSettings);
return this;
}
@@ -613,6 +652,13 @@ public class Typeface {
* @param axes An array of font variation axis tag-value pairs.
*/
public Builder setFontVariationSettings(@Nullable FontVariationAxis[] axes) {
if (mFonts != null) {
throw new IllegalArgumentException(
"Font variation settings can not be specified for FontResult source.");
}
if (mAxes != null) {
throw new IllegalStateException("Font variation settings are already set.");
}
mAxes = axes;
return this;
}
@@ -698,6 +744,32 @@ public class Typeface {
fontFamily.freeze();
FontFamily[] families = { fontFamily };
return createFromFamiliesWithDefault(families);
} else if (mFonts != null) {
final FontFamily fontFamily = new FontFamily();
boolean atLeastOneFont = false;
for (FontsContract.FontInfo font : mFonts) {
final ByteBuffer fontBuffer = mFontBuffers.get(font.getUri());
if (fontBuffer == null) {
continue; // skip
}
final boolean success = fontFamily.addFontFromBuffer(fontBuffer,
font.getTtcIndex(), font.getAxes(), font.getWeight(),
font.isItalic() ? STYLE_ITALIC : STYLE_NORMAL);
if (!success) {
fontFamily.abortCreation();
return null;
}
atLeastOneFont = true;
}
if (!atLeastOneFont) {
// No fonts are avaialble. No need to create new Typeface and returns fallback
// Typeface instead.
fontFamily.abortCreation();
return null;
}
fontFamily.freeze();
FontFamily[] families = { fontFamily };
return createFromFamiliesWithDefault(families);
}
// Must not reach here.