diff options
| -rw-r--r-- | api/current.txt | 20 | ||||
| -rw-r--r-- | api/system-current.txt | 20 | ||||
| -rw-r--r-- | api/test-current.txt | 20 | ||||
| -rw-r--r-- | core/java/android/provider/FontsContract.java | 454 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/provider/FontsContractTest.java | 297 | ||||
| -rw-r--r-- | graphics/java/android/graphics/Typeface.java | 72 |
6 files changed, 666 insertions, 217 deletions
diff --git a/api/current.txt b/api/current.txt index 5955bd4c32f9..292470760a42 100644 --- a/api/current.txt +++ b/api/current.txt @@ -34491,6 +34491,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 { @@ -34507,6 +34510,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"; diff --git a/api/system-current.txt b/api/system-current.txt index d565b41f80db..8f53d82bb4cf 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -37467,6 +37467,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 { @@ -37483,6 +37486,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"; diff --git a/api/test-current.txt b/api/test-current.txt index 9ba8e5706444..cc219d42105f 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -34632,6 +34632,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 { @@ -34648,6 +34651,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"; diff --git a/core/java/android/provider/FontsContract.java b/core/java/android/provider/FontsContract.java index f2aed5d965d1..f9508902d49f 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.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; + } + FontInfo[] fonts; + try { + fonts = getFontFromProvider(mContext, request, providerInfo.authority, + null /* cancellation signal */); + } catch (InvalidFormatException e) { + receiver.send(RESULT_CODE_PROVIDER_NOT_FOUND, null); return; } - getFontFromProvider(request, receiver, providerInfo.authority); + + 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]); } } diff --git a/core/tests/coretests/src/android/provider/FontsContractTest.java b/core/tests/coretests/src/android/provider/FontsContractTest.java index 1dd3ef6f012e..ccc8c184fdf7 100644 --- a/core/tests/coretests/src/android/provider/FontsContractTest.java +++ b/core/tests/coretests/src/android/provider/FontsContractTest.java @@ -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); - - verify(mResultReceiver).send(FontsContract.Columns.RESULT_CODE_FONT_UNAVAILABLE,null); + mProvider.setResultCode(RESULT_CODE_FONT_UNAVAILABLE); + 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_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); - - verify(mResultReceiver).send(FontsContract.Columns.RESULT_CODE_MALFORMED_QUERY,null); + mProvider.setResultCode(RESULT_CODE_MALFORMED_QUERY); + 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_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); - - verify(mResultReceiver).send(FontsContract.Columns.RESULT_CODE_FONT_NOT_FOUND, null); + FontInfo[] fonts = FontsContract.getFontFromProvider( + getMockContext(), request, TestFontsProvider.AUTHORITY, 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); - - verify(mResultReceiver).send(FontsContract.Columns.RESULT_CODE_FONT_NOT_FOUND, null); - } - - 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); - - verify(mResultReceiver).send(FontsContract.Columns.RESULT_CODE_FONT_NOT_FOUND, null); + FontInfo[] fonts = FontsContract.getFontFromProvider( + getMockContext(), request, TestFontsProvider.AUTHORITY, null); + + assertNotNull(fonts); + assertEquals(3, 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()); + + 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() diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index 395dc07b4dc5..21533f8ce6c9 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -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; @@ -559,6 +568,25 @@ public class Typeface { } /** + * 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 @@ 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. |