Introduce FontsContract.fetchFonts and expose URI for watching.
FontsContract.fetchFonts provides a low level font access with fonts
provider.
This CL also includes:
- Introduce new class FontFamilyResult/Font as the inner static class
of FontsContract which are used to for result value of fetchFont..
- Introduce a functionality to FontsContract to be able to create
Typeface from an array of FontResult.
- Expose URI of each file entries to be able to register ContentObserver
Bug: 36494487
Bug: 36085028
Test: android.provider.FontsContract passes
Test: android.graphics.cts.TypefaceTest passes
Test: android.graphics.fonts.cts.FontResultTest passes
Change-Id: Id6f85039d0e86be063ef099d7ec6bfd97e4424c5
diff --git a/api/current.txt b/api/current.txt
index 5955bd4..2924707 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -34491,6 +34491,9 @@
}
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 {
@@ -34507,6 +34510,23 @@
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";
diff --git a/api/system-current.txt b/api/system-current.txt
index d565b41..8f53d82 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -37467,6 +37467,9 @@
}
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 {
@@ -37483,6 +37486,23 @@
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";
diff --git a/api/test-current.txt b/api/test-current.txt
index 9ba8e57..cc219d4 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -34632,6 +34632,9 @@
}
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 {
@@ -34648,6 +34651,23 @@
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";
diff --git a/core/java/android/provider/FontsContract.java b/core/java/android/provider/FontsContract.java
index f2aed5d..f9508902 100644
--- a/core/java/android/provider/FontsContract.java
+++ b/core/java/android/provider/FontsContract.java
@@ -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.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 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 @@
* @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 @@
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 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
/** @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 @@
.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 @@
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 @@
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]);
}
}
diff --git a/core/tests/coretests/src/android/provider/FontsContractTest.java b/core/tests/coretests/src/android/provider/FontsContractTest.java
index 1dd3ef6..ccc8c18 100644
--- a/core/tests/coretests/src/android/provider/FontsContractTest.java
+++ b/core/tests/coretests/src/android/provider/FontsContractTest.java
@@ -17,29 +17,29 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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()
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 395dc07..21533f8 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -34,6 +34,7 @@
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 @@
* </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 @@
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;
@@ -559,6 +568,25 @@
}
/**
+ * 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.
*
* Tells the system the weight of the given font. If not provided, the system will resolve
@@ -590,6 +618,10 @@
* 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 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 @@
* @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 @@
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.