Merge "Introduce FontsContract.fetchFonts and expose URI for watching." into oc-dev
am: 061db23b79
Change-Id: I7a7d47db8dedd6f75d4884d635cbfcf15949ac43
This commit is contained in:
@@ -34502,6 +34502,9 @@ package android.provider {
|
||||
}
|
||||
|
||||
public class FontsContract {
|
||||
method public static android.graphics.Typeface buildTypeface(android.content.Context, android.os.CancellationSignal, android.provider.FontsContract.FontInfo[], int, boolean, java.lang.String);
|
||||
method public static android.graphics.Typeface buildTypeface(android.content.Context, android.os.CancellationSignal, android.provider.FontsContract.FontInfo[]);
|
||||
method public static android.provider.FontsContract.FontFamilyResult fetchFonts(android.content.Context, android.os.CancellationSignal, android.graphics.fonts.FontRequest) throws android.content.pm.PackageManager.NameNotFoundException;
|
||||
}
|
||||
|
||||
public static final class FontsContract.Columns implements android.provider.BaseColumns {
|
||||
@@ -34518,6 +34521,23 @@ package android.provider {
|
||||
field public static final java.lang.String WEIGHT = "font_weight";
|
||||
}
|
||||
|
||||
public static class FontsContract.FontFamilyResult {
|
||||
method public android.provider.FontsContract.FontInfo[] getFonts();
|
||||
method public int getStatusCode();
|
||||
field public static final int STATUS_OK = 0; // 0x0
|
||||
field public static final int STATUS_UNEXPECTED_DATA_PROVIDED = 2; // 0x2
|
||||
field public static final int STATUS_WRONG_CERTIFICATES = 1; // 0x1
|
||||
}
|
||||
|
||||
public static class FontsContract.FontInfo {
|
||||
method public android.graphics.fonts.FontVariationAxis[] getAxes();
|
||||
method public int getResultCode();
|
||||
method public int getTtcIndex();
|
||||
method public android.net.Uri getUri();
|
||||
method public int getWeight();
|
||||
method public boolean isItalic();
|
||||
}
|
||||
|
||||
public final deprecated class LiveFolders implements android.provider.BaseColumns {
|
||||
field public static final java.lang.String ACTION_CREATE_LIVE_FOLDER = "android.intent.action.CREATE_LIVE_FOLDER";
|
||||
field public static final java.lang.String DESCRIPTION = "description";
|
||||
|
||||
@@ -37479,6 +37479,9 @@ package android.provider {
|
||||
}
|
||||
|
||||
public class FontsContract {
|
||||
method public static android.graphics.Typeface buildTypeface(android.content.Context, android.os.CancellationSignal, android.provider.FontsContract.FontInfo[], int, boolean, java.lang.String);
|
||||
method public static android.graphics.Typeface buildTypeface(android.content.Context, android.os.CancellationSignal, android.provider.FontsContract.FontInfo[]);
|
||||
method public static android.provider.FontsContract.FontFamilyResult fetchFonts(android.content.Context, android.os.CancellationSignal, android.graphics.fonts.FontRequest) throws android.content.pm.PackageManager.NameNotFoundException;
|
||||
}
|
||||
|
||||
public static final class FontsContract.Columns implements android.provider.BaseColumns {
|
||||
@@ -37495,6 +37498,23 @@ package android.provider {
|
||||
field public static final java.lang.String WEIGHT = "font_weight";
|
||||
}
|
||||
|
||||
public static class FontsContract.FontFamilyResult {
|
||||
method public android.provider.FontsContract.FontInfo[] getFonts();
|
||||
method public int getStatusCode();
|
||||
field public static final int STATUS_OK = 0; // 0x0
|
||||
field public static final int STATUS_UNEXPECTED_DATA_PROVIDED = 2; // 0x2
|
||||
field public static final int STATUS_WRONG_CERTIFICATES = 1; // 0x1
|
||||
}
|
||||
|
||||
public static class FontsContract.FontInfo {
|
||||
method public android.graphics.fonts.FontVariationAxis[] getAxes();
|
||||
method public int getResultCode();
|
||||
method public int getTtcIndex();
|
||||
method public android.net.Uri getUri();
|
||||
method public int getWeight();
|
||||
method public boolean isItalic();
|
||||
}
|
||||
|
||||
public final deprecated class LiveFolders implements android.provider.BaseColumns {
|
||||
field public static final java.lang.String ACTION_CREATE_LIVE_FOLDER = "android.intent.action.CREATE_LIVE_FOLDER";
|
||||
field public static final java.lang.String DESCRIPTION = "description";
|
||||
|
||||
@@ -34645,6 +34645,9 @@ package android.provider {
|
||||
}
|
||||
|
||||
public class FontsContract {
|
||||
method public static android.graphics.Typeface buildTypeface(android.content.Context, android.os.CancellationSignal, android.provider.FontsContract.FontInfo[], int, boolean, java.lang.String);
|
||||
method public static android.graphics.Typeface buildTypeface(android.content.Context, android.os.CancellationSignal, android.provider.FontsContract.FontInfo[]);
|
||||
method public static android.provider.FontsContract.FontFamilyResult fetchFonts(android.content.Context, android.os.CancellationSignal, android.graphics.fonts.FontRequest) throws android.content.pm.PackageManager.NameNotFoundException;
|
||||
}
|
||||
|
||||
public static final class FontsContract.Columns implements android.provider.BaseColumns {
|
||||
@@ -34661,6 +34664,23 @@ package android.provider {
|
||||
field public static final java.lang.String WEIGHT = "font_weight";
|
||||
}
|
||||
|
||||
public static class FontsContract.FontFamilyResult {
|
||||
method public android.provider.FontsContract.FontInfo[] getFonts();
|
||||
method public int getStatusCode();
|
||||
field public static final int STATUS_OK = 0; // 0x0
|
||||
field public static final int STATUS_UNEXPECTED_DATA_PROVIDED = 2; // 0x2
|
||||
field public static final int STATUS_WRONG_CERTIFICATES = 1; // 0x1
|
||||
}
|
||||
|
||||
public static class FontsContract.FontInfo {
|
||||
method public android.graphics.fonts.FontVariationAxis[] getAxes();
|
||||
method public int getResultCode();
|
||||
method public int getTtcIndex();
|
||||
method public android.net.Uri getUri();
|
||||
method public int getWeight();
|
||||
method public boolean isItalic();
|
||||
}
|
||||
|
||||
public final deprecated class LiveFolders implements android.provider.BaseColumns {
|
||||
field public static final java.lang.String ACTION_CREATE_LIVE_FOLDER = "android.intent.action.CREATE_LIVE_FOLDER";
|
||||
field public static final java.lang.String DESCRIPTION = "description";
|
||||
|
||||
@@ -15,10 +15,18 @@
|
||||
*/
|
||||
package android.provider;
|
||||
|
||||
import static android.graphics.fonts.FontVariationAxis.InvalidFormatException;
|
||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.IntRange;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ProviderInfo;
|
||||
import android.content.pm.Signature;
|
||||
@@ -26,8 +34,10 @@ import android.database.Cursor;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.fonts.FontRequest;
|
||||
import android.graphics.fonts.FontResult;
|
||||
import android.graphics.fonts.FontVariationAxis;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
@@ -37,14 +47,22 @@ import android.util.Log;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Utility class to deal with Font ContentProviders.
|
||||
@@ -140,7 +158,6 @@ public class FontsContract {
|
||||
* @hide
|
||||
*/
|
||||
public static final String PARCEL_FONT_RESULTS = "font_results";
|
||||
|
||||
// Error codes internal to the system, which can not come from a provider. To keep the number
|
||||
// space open for new provider codes, these should all be negative numbers.
|
||||
/** @hide */
|
||||
@@ -165,11 +182,128 @@ public class FontsContract {
|
||||
mPackageManager = mContext.getPackageManager();
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@VisibleForTesting
|
||||
public FontsContract(Context context, PackageManager packageManager) {
|
||||
mContext = context;
|
||||
mPackageManager = packageManager;
|
||||
/**
|
||||
* Object represent a font entry in the family returned from {@link #fetchFonts}.
|
||||
*/
|
||||
public static class FontInfo {
|
||||
private final Uri mUri;
|
||||
private final int mTtcIndex;
|
||||
private final FontVariationAxis[] mAxes;
|
||||
private final int mWeight;
|
||||
private final boolean mItalic;
|
||||
private final int mResultCode;
|
||||
|
||||
/**
|
||||
* Creates a Font with all the information needed about a provided font.
|
||||
* @param uri A URI associated to the font file.
|
||||
* @param ttcIndex If providing a TTC_INDEX file, the index to point to. Otherwise, 0.
|
||||
* @param axes If providing a variation font, the settings for it. May be null.
|
||||
* @param weight An integer that indicates the font weight.
|
||||
* @param italic A boolean that indicates the font is italic style or not.
|
||||
* @param resultCode A boolean that indicates the font contents is ready.
|
||||
*/
|
||||
/** @hide */
|
||||
public FontInfo(@NonNull Uri uri, @IntRange(from = 0) int ttcIndex,
|
||||
@Nullable FontVariationAxis[] axes, @IntRange(from = 1, to = 1000) int weight,
|
||||
boolean italic, int resultCode) {
|
||||
mUri = Preconditions.checkNotNull(uri);
|
||||
mTtcIndex = ttcIndex;
|
||||
mAxes = axes;
|
||||
mWeight = weight;
|
||||
mItalic = italic;
|
||||
mResultCode = resultCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a URI associated to this record.
|
||||
*/
|
||||
public @NonNull Uri getUri() {
|
||||
return mUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index to be used to access this font when accessing a TTC file.
|
||||
*/
|
||||
public @IntRange(from = 0) int getTtcIndex() {
|
||||
return mTtcIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of axes associated to this font.
|
||||
*/
|
||||
public @Nullable FontVariationAxis[] getAxes() {
|
||||
return mAxes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the weight value for this font.
|
||||
*/
|
||||
public @IntRange(from = 1, to = 1000) int getWeight() {
|
||||
return mWeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this font is italic.
|
||||
*/
|
||||
public boolean isItalic() {
|
||||
return mItalic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns result code.
|
||||
*
|
||||
* {@link FontsContract.Columns#RESULT_CODE}
|
||||
*/
|
||||
public int getResultCode() {
|
||||
return mResultCode;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Object returned from {@link #fetchFonts}.
|
||||
*/
|
||||
public static class FontFamilyResult {
|
||||
/**
|
||||
* Constant represents that the font was successfully retrieved. Note that when this value
|
||||
* is set and {@link #getFonts} returns an empty array, it means there were no fonts
|
||||
* matching the given query.
|
||||
*/
|
||||
public static final int STATUS_OK = 0;
|
||||
|
||||
/**
|
||||
* Constant represents that the given certificate was not matched with the provider's
|
||||
* signature. {@link #getFonts} returns null if this status was set.
|
||||
*/
|
||||
public static final int STATUS_WRONG_CERTIFICATES = 1;
|
||||
|
||||
/**
|
||||
* Constant represents that the provider returns unexpected data. {@link #getFonts} returns
|
||||
* null if this status was set. For example, this value is set when the font provider
|
||||
* gives invalid format of variation settings.
|
||||
*/
|
||||
public static final int STATUS_UNEXPECTED_DATA_PROVIDED = 2;
|
||||
|
||||
/** @hide */
|
||||
@IntDef({STATUS_OK, STATUS_WRONG_CERTIFICATES, STATUS_UNEXPECTED_DATA_PROVIDED})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@interface FontResultStatus {}
|
||||
|
||||
private final @FontResultStatus int mStatusCode;
|
||||
private final FontInfo[] mFonts;
|
||||
|
||||
/** @hide */
|
||||
public FontFamilyResult(@FontResultStatus int statusCode, @Nullable FontInfo[] fonts) {
|
||||
mStatusCode = statusCode;
|
||||
mFonts = fonts;
|
||||
}
|
||||
|
||||
public @FontResultStatus int getStatusCode() {
|
||||
return mStatusCode;
|
||||
}
|
||||
|
||||
public @NonNull FontInfo[] getFonts() {
|
||||
return mFonts;
|
||||
}
|
||||
}
|
||||
|
||||
// We use a background thread to post the content resolving work for all requests on. This
|
||||
@@ -196,33 +330,210 @@ public class FontsContract {
|
||||
mHandler = new Handler(mThread.getLooper());
|
||||
}
|
||||
mHandler.post(() -> {
|
||||
ProviderInfo providerInfo = getProvider(request, receiver);
|
||||
if (providerInfo == null) {
|
||||
ProviderInfo providerInfo;
|
||||
try {
|
||||
providerInfo = getProvider(mPackageManager, request);
|
||||
if (providerInfo == null) {
|
||||
receiver.send(RESULT_CODE_PROVIDER_NOT_FOUND, null);
|
||||
return;
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
receiver.send(RESULT_CODE_PROVIDER_NOT_FOUND, null);
|
||||
return;
|
||||
}
|
||||
getFontFromProvider(request, receiver, providerInfo.authority);
|
||||
FontInfo[] fonts;
|
||||
try {
|
||||
fonts = getFontFromProvider(mContext, request, providerInfo.authority,
|
||||
null /* cancellation signal */);
|
||||
} catch (InvalidFormatException e) {
|
||||
receiver.send(RESULT_CODE_PROVIDER_NOT_FOUND, null);
|
||||
return;
|
||||
}
|
||||
|
||||
ArrayList<FontResult> result = new ArrayList<>();
|
||||
int resultCode = -1;
|
||||
for (FontInfo font : fonts) {
|
||||
try {
|
||||
resultCode = font.getResultCode();
|
||||
if (resultCode != Columns.RESULT_CODE_OK) {
|
||||
if (resultCode < 0) {
|
||||
// Negative values are reserved for the internal errors.
|
||||
resultCode = Columns.RESULT_CODE_FONT_NOT_FOUND;
|
||||
}
|
||||
for (int i = 0; i < result.size(); ++i) {
|
||||
try {
|
||||
result.get(i).getFileDescriptor().close();
|
||||
} catch (IOException e) {
|
||||
// Ignore, as we are closing fds for cleanup.
|
||||
}
|
||||
}
|
||||
receiver.send(resultCode, null);
|
||||
return;
|
||||
}
|
||||
ParcelFileDescriptor pfd = mContext.getContentResolver().openFileDescriptor(
|
||||
font.getUri(), "r");
|
||||
result.add(new FontResult(pfd, font.getTtcIndex(),
|
||||
FontVariationAxis.toFontVariationSettings(font.getAxes()),
|
||||
font.getWeight(), font.isItalic()));
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.e(TAG, "FileNotFoundException raised when interacting with content "
|
||||
+ "provider " + providerInfo.authority, e);
|
||||
}
|
||||
}
|
||||
if (!result.isEmpty()) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelableArrayList(PARCEL_FONT_RESULTS, result);
|
||||
receiver.send(Columns.RESULT_CODE_OK, bundle);
|
||||
return;
|
||||
}
|
||||
receiver.send(Columns.RESULT_CODE_FONT_NOT_FOUND, null);
|
||||
});
|
||||
mHandler.removeCallbacks(mReplaceDispatcherThreadRunnable);
|
||||
mHandler.postDelayed(mReplaceDispatcherThreadRunnable, THREAD_RENEWAL_THRESHOLD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch fonts given a font request.
|
||||
*
|
||||
* @param context A {@link Context} to be used for fetching fonts.
|
||||
* @param cancellationSignal A signal to cancel the operation in progress, or null if none. If
|
||||
* the operation is canceled, then {@link
|
||||
* android.os.OperationCanceledException} will be thrown when the
|
||||
* query is executed.
|
||||
* @param request A {@link FontRequest} object that identifies the provider and query for the
|
||||
* request.
|
||||
*
|
||||
* @return {@link FontFamilyResult}
|
||||
*
|
||||
* @throws NameNotFoundException If requested package or authority was not found in system.
|
||||
*/
|
||||
public static @NonNull FontFamilyResult fetchFonts(
|
||||
@NonNull Context context, @Nullable CancellationSignal cancellationSignal,
|
||||
@NonNull FontRequest request) throws NameNotFoundException {
|
||||
ProviderInfo providerInfo = getProvider(context.getPackageManager(), request);
|
||||
if (providerInfo == null) {
|
||||
return new FontFamilyResult(FontFamilyResult.STATUS_WRONG_CERTIFICATES, null);
|
||||
|
||||
}
|
||||
try {
|
||||
FontInfo[] fonts = getFontFromProvider(
|
||||
context, request, providerInfo.authority, cancellationSignal);
|
||||
return new FontFamilyResult(FontFamilyResult.STATUS_OK, fonts);
|
||||
} catch (InvalidFormatException e) {
|
||||
return new FontFamilyResult(FontFamilyResult.STATUS_UNEXPECTED_DATA_PROVIDED, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a Typeface from an array of {@link FontInfo}. Results that are marked as not ready
|
||||
* will be skipped.
|
||||
*
|
||||
* @param context A {@link Context} that will be used to fetch the font contents.
|
||||
* @param cancellationSignal A signal to cancel the operation in progress, or null if none. If
|
||||
* the operation is canceled, then {@link
|
||||
* android.os.OperationCanceledException} will be thrown.
|
||||
* @param fonts An array of {@link FontInfo} to be used to create a Typeface.
|
||||
* @param weight A weight value to be used for selecting a font from a font family.
|
||||
* @param italic {@code true} if this font is of italic style. This will be used for font
|
||||
* selection from a font family.
|
||||
* @param fallbackFontName A fallback font name used if this method fails to create the
|
||||
* Typeface. By passing {@code null}, this method returns {@code null}
|
||||
* if typeface creation fails.
|
||||
* @return A Typeface object. May return {@code null} if that is the value passed to {@code
|
||||
* fallBackFontName}.
|
||||
*/
|
||||
public static Typeface buildTypeface(@NonNull Context context,
|
||||
@Nullable CancellationSignal cancellationSignal, @NonNull FontInfo[] fonts,
|
||||
int weight, boolean italic, @Nullable String fallbackFontName) {
|
||||
final Map<Uri, ByteBuffer> uriBuffer =
|
||||
prepareFontData(context, fonts, cancellationSignal);
|
||||
Typeface typeface = new Typeface.Builder(fonts, uriBuffer)
|
||||
.setWeight(weight)
|
||||
.setItalic(italic)
|
||||
.build();
|
||||
// TODO: Use Typeface fallback instead.
|
||||
if (typeface == null) {
|
||||
typeface = Typeface.create(fallbackFontName, Typeface.NORMAL);
|
||||
}
|
||||
return typeface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a Typeface from an array of {@link FontInfo}
|
||||
*
|
||||
* Results that are marked as not ready will be skipped.
|
||||
*
|
||||
* @param context A {@link Context} that will be used to fetch the font contents.
|
||||
* @param cancellationSignal A signal to cancel the operation in progress, or null if none. If
|
||||
* the operation is canceled, then {@link
|
||||
* android.os.OperationCanceledException} will be thrown.
|
||||
* @param fonts An array of {@link FontInfo} to be used to create a Typeface.
|
||||
* @return A Typeface object. Returns null if typeface creation fails.
|
||||
*/
|
||||
public static Typeface buildTypeface(@NonNull Context context,
|
||||
@Nullable CancellationSignal cancellationSignal, @NonNull FontInfo[] fonts) {
|
||||
final Map<Uri, ByteBuffer> uriBuffer =
|
||||
prepareFontData(context, fonts, cancellationSignal);
|
||||
return new Typeface.Builder(fonts, uriBuffer).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper function to create a mapping from {@link Uri} to {@link ByteBuffer}.
|
||||
*
|
||||
* Skip if the file contents is not ready to be read.
|
||||
*
|
||||
* @param context A {@link Context} to be used for resolving content URI in
|
||||
* {@link FontInfo}.
|
||||
* @param fonts An array of {@link FontInfo}.
|
||||
* @return A map from {@link Uri} to {@link ByteBuffer}.
|
||||
*/
|
||||
private static Map<Uri, ByteBuffer> prepareFontData(Context context, FontInfo[] fonts,
|
||||
CancellationSignal cancellationSignal) {
|
||||
final HashMap<Uri, ByteBuffer> out = new HashMap<>();
|
||||
final ContentResolver resolver = context.getContentResolver();
|
||||
|
||||
for (FontInfo font : fonts) {
|
||||
if (font.getResultCode() != Columns.RESULT_CODE_OK) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final Uri uri = font.getUri();
|
||||
if (out.containsKey(uri)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ByteBuffer buffer = null;
|
||||
try (final ParcelFileDescriptor pfd =
|
||||
resolver.openFileDescriptor(uri, "r", cancellationSignal);
|
||||
final FileInputStream fis = new FileInputStream(pfd.getFileDescriptor())) {
|
||||
final FileChannel fileChannel = fis.getChannel();
|
||||
final long size = fileChannel.size();
|
||||
buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, size);
|
||||
} catch (IOException e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
// TODO: try other approach?, e.g. read all contents instead of mmap.
|
||||
|
||||
out.put(uri, buffer);
|
||||
}
|
||||
return Collections.unmodifiableMap(out);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@VisibleForTesting
|
||||
public ProviderInfo getProvider(FontRequest request, ResultReceiver receiver) {
|
||||
public static @Nullable ProviderInfo getProvider(
|
||||
PackageManager packageManager, FontRequest request) throws NameNotFoundException {
|
||||
String providerAuthority = request.getProviderAuthority();
|
||||
ProviderInfo info = mPackageManager.resolveContentProvider(providerAuthority, 0);
|
||||
ProviderInfo info = packageManager.resolveContentProvider(providerAuthority, 0);
|
||||
if (info == null) {
|
||||
Log.e(TAG, "Can't find content provider " + providerAuthority);
|
||||
receiver.send(RESULT_CODE_PROVIDER_NOT_FOUND, null);
|
||||
return null;
|
||||
throw new NameNotFoundException("No package found for authority: " + providerAuthority);
|
||||
}
|
||||
|
||||
if (!info.packageName.equals(request.getProviderPackage())) {
|
||||
Log.e(TAG, "Found content provider " + providerAuthority + ", but package was not "
|
||||
+ request.getProviderPackage());
|
||||
receiver.send(RESULT_CODE_PROVIDER_NOT_FOUND, null);
|
||||
return null;
|
||||
throw new NameNotFoundException("Found content provider " + providerAuthority
|
||||
+ ", but package was not " + request.getProviderPackage());
|
||||
}
|
||||
// Trust system apps without signature checks
|
||||
if (info.applicationInfo.isSystemApp()) {
|
||||
@@ -230,16 +541,11 @@ public class FontsContract {
|
||||
}
|
||||
|
||||
List<byte[]> signatures;
|
||||
try {
|
||||
PackageInfo packageInfo = mPackageManager.getPackageInfo(info.packageName,
|
||||
PackageManager.GET_SIGNATURES);
|
||||
signatures = convertToByteArrayList(packageInfo.signatures);
|
||||
Collections.sort(signatures, sByteArrayComparator);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Log.e(TAG, "Can't find content provider " + providerAuthority, e);
|
||||
receiver.send(RESULT_CODE_PROVIDER_NOT_FOUND, null);
|
||||
return null;
|
||||
}
|
||||
PackageInfo packageInfo = packageManager.getPackageInfo(info.packageName,
|
||||
PackageManager.GET_SIGNATURES);
|
||||
signatures = convertToByteArrayList(packageInfo.signatures);
|
||||
Collections.sort(signatures, sByteArrayComparator);
|
||||
|
||||
List<List<byte[]>> requestCertificatesList = request.getCertificates();
|
||||
for (int i = 0; i < requestCertificatesList.size(); ++i) {
|
||||
// Make a copy so we can sort it without modifying the incoming data.
|
||||
@@ -249,8 +555,6 @@ public class FontsContract {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
Log.e(TAG, "Certificates don't match for given provider " + providerAuthority);
|
||||
receiver.send(RESULT_CODE_WRONG_CERTIFICATES, null);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -266,7 +570,8 @@ public class FontsContract {
|
||||
return 0;
|
||||
};
|
||||
|
||||
private boolean equalsByteArrayList(List<byte[]> signatures, List<byte[]> requestSignatures) {
|
||||
private static boolean equalsByteArrayList(
|
||||
List<byte[]> signatures, List<byte[]> requestSignatures) {
|
||||
if (signatures.size() != requestSignatures.size()) {
|
||||
return false;
|
||||
}
|
||||
@@ -278,7 +583,7 @@ public class FontsContract {
|
||||
return true;
|
||||
}
|
||||
|
||||
private List<byte[]> convertToByteArrayList(Signature[] signatures) {
|
||||
private static List<byte[]> convertToByteArrayList(Signature[] signatures) {
|
||||
List<byte[]> shas = new ArrayList<>();
|
||||
for (int i = 0; i < signatures.length; ++i) {
|
||||
shas.add(signatures[i].toByteArray());
|
||||
@@ -288,9 +593,10 @@ public class FontsContract {
|
||||
|
||||
/** @hide */
|
||||
@VisibleForTesting
|
||||
public void getFontFromProvider(FontRequest request, ResultReceiver receiver,
|
||||
String authority) {
|
||||
ArrayList<FontResult> result = null;
|
||||
public static @NonNull FontInfo[] getFontFromProvider(
|
||||
Context context, FontRequest request, String authority,
|
||||
CancellationSignal cancellationSignal) throws InvalidFormatException {
|
||||
ArrayList<FontInfo> result = new ArrayList<>();
|
||||
final Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
|
||||
.authority(authority)
|
||||
.build();
|
||||
@@ -298,15 +604,14 @@ public class FontsContract {
|
||||
.authority(authority)
|
||||
.appendPath("file")
|
||||
.build();
|
||||
try (Cursor cursor = mContext.getContentResolver().query(uri, new String[] { Columns._ID,
|
||||
try (Cursor cursor = context.getContentResolver().query(uri, new String[] { Columns._ID,
|
||||
Columns.FILE_ID, Columns.TTC_INDEX, Columns.VARIATION_SETTINGS,
|
||||
Columns.STYLE, Columns.WEIGHT, Columns.ITALIC, Columns.RESULT_CODE },
|
||||
"query = ?", new String[] { request.getQuery() }, null);) {
|
||||
"query = ?", new String[] { request.getQuery() }, null, cancellationSignal);) {
|
||||
// TODO: Should we restrict the amount of fonts that can be returned?
|
||||
// TODO: Write documentation explaining that all results should be from the same family.
|
||||
if (cursor != null && cursor.getCount() > 0) {
|
||||
final int resultCodeColumnIndex = cursor.getColumnIndex(Columns.RESULT_CODE);
|
||||
int resultCode = -1;
|
||||
result = new ArrayList<>();
|
||||
final int idColumnIndex = cursor.getColumnIndexOrThrow(Columns._ID);
|
||||
final int fileIdColumnIndex = cursor.getColumnIndex(Columns.FILE_ID);
|
||||
@@ -316,23 +621,13 @@ public class FontsContract {
|
||||
final int italicColumnIndex = cursor.getColumnIndex(Columns.ITALIC);
|
||||
final int styleColumnIndex = cursor.getColumnIndex(Columns.STYLE);
|
||||
while (cursor.moveToNext()) {
|
||||
resultCode = resultCodeColumnIndex != -1
|
||||
int resultCode = resultCodeColumnIndex != -1
|
||||
? cursor.getInt(resultCodeColumnIndex) : Columns.RESULT_CODE_OK;
|
||||
if (resultCode != Columns.RESULT_CODE_OK) {
|
||||
if (resultCode < 0) {
|
||||
// Negative values are reserved for the internal errors.
|
||||
resultCode = Columns.RESULT_CODE_FONT_NOT_FOUND;
|
||||
}
|
||||
for (int i = 0; i < result.size(); ++i) {
|
||||
try {
|
||||
result.get(i).getFileDescriptor().close();
|
||||
} catch (IOException e) {
|
||||
// Ignore, as we are closing fds for cleanup.
|
||||
}
|
||||
}
|
||||
receiver.send(resultCode, null);
|
||||
return;
|
||||
}
|
||||
final int ttcIndex = ttcIndexColumnIndex != -1
|
||||
? cursor.getInt(ttcIndexColumnIndex) : 0;
|
||||
final String variationSettings = vsColumnIndex != -1
|
||||
? cursor.getString(vsColumnIndex) : null;
|
||||
|
||||
Uri fileUri;
|
||||
if (fileIdColumnIndex == -1) {
|
||||
long id = cursor.getLong(idColumnIndex);
|
||||
@@ -341,42 +636,27 @@ public class FontsContract {
|
||||
long id = cursor.getLong(fileIdColumnIndex);
|
||||
fileUri = ContentUris.withAppendedId(fileBaseUri, id);
|
||||
}
|
||||
try {
|
||||
ParcelFileDescriptor pfd =
|
||||
mContext.getContentResolver().openFileDescriptor(fileUri, "r");
|
||||
final int ttcIndex = ttcIndexColumnIndex != -1
|
||||
? cursor.getInt(ttcIndexColumnIndex) : 0;
|
||||
final String variationSettings = vsColumnIndex != -1
|
||||
? cursor.getString(vsColumnIndex) : null;
|
||||
// TODO: Stop using STYLE column and enforce WEIGHT/ITALIC column.
|
||||
int weight;
|
||||
boolean italic;
|
||||
if (weightColumnIndex != -1 && italicColumnIndex != -1) {
|
||||
weight = cursor.getInt(weightColumnIndex);
|
||||
italic = cursor.getInt(italicColumnIndex) == 1;
|
||||
} else if (styleColumnIndex != -1) {
|
||||
final int style = cursor.getInt(styleColumnIndex);
|
||||
weight = (style & Typeface.BOLD) != 0 ? 700 : 400;
|
||||
italic = (style & Typeface.ITALIC) != 0;
|
||||
} else {
|
||||
weight = 400;
|
||||
italic = false;
|
||||
}
|
||||
result.add(
|
||||
new FontResult(pfd, ttcIndex, variationSettings, weight, italic));
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.e(TAG, "FileNotFoundException raised when interacting with content "
|
||||
+ "provider " + authority, e);
|
||||
// TODO: Stop using STYLE column and enforce WEIGHT/ITALIC column.
|
||||
int weight;
|
||||
boolean italic;
|
||||
if (weightColumnIndex != -1 && italicColumnIndex != -1) {
|
||||
weight = cursor.getInt(weightColumnIndex);
|
||||
italic = cursor.getInt(italicColumnIndex) == 1;
|
||||
} else if (styleColumnIndex != -1) {
|
||||
final int style = cursor.getInt(styleColumnIndex);
|
||||
weight = (style & Typeface.BOLD) != 0 ?
|
||||
Typeface.Builder.BOLD_WEIGHT : Typeface.Builder.NORMAL_WEIGHT;
|
||||
italic = (style & Typeface.ITALIC) != 0;
|
||||
} else {
|
||||
weight = Typeface.Builder.NORMAL_WEIGHT;
|
||||
italic = false;
|
||||
}
|
||||
FontVariationAxis[] axes =
|
||||
FontVariationAxis.fromFontVariationSettings(variationSettings);
|
||||
result.add(new FontInfo(fileUri, ttcIndex, axes, weight, italic, resultCode));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result != null && !result.isEmpty()) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelableArrayList(PARCEL_FONT_RESULTS, result);
|
||||
receiver.send(Columns.RESULT_CODE_OK, bundle);
|
||||
return;
|
||||
}
|
||||
receiver.send(Columns.RESULT_CODE_FONT_NOT_FOUND, null);
|
||||
return result.toArray(new FontInfo[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,29 +17,29 @@ package android.provider;
|
||||
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import static android.provider.FontsContract.Columns.RESULT_CODE_OK;
|
||||
import static android.provider.FontsContract.Columns.RESULT_CODE_FONT_NOT_FOUND;
|
||||
import static android.provider.FontsContract.Columns.RESULT_CODE_FONT_UNAVAILABLE;
|
||||
import static android.provider.FontsContract.Columns.RESULT_CODE_MALFORMED_QUERY;
|
||||
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ProviderInfo;
|
||||
import android.content.pm.Signature;
|
||||
import android.database.MatrixCursor;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.fonts.FontRequest;
|
||||
import android.graphics.fonts.FontResult;
|
||||
import android.os.Bundle;
|
||||
import android.os.ResultReceiver;
|
||||
import android.graphics.fonts.FontVariationAxis.InvalidFormatException;
|
||||
import android.graphics.fonts.FontVariationAxis;
|
||||
import android.provider.FontsContract.FontInfo;
|
||||
import android.support.test.filters.SmallTest;
|
||||
import android.test.ProviderTestCase2;
|
||||
import android.util.Base64;
|
||||
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -61,8 +61,6 @@ public class FontsContractTest extends ProviderTestCase2<TestFontsProvider> {
|
||||
private final FontRequest request = new FontRequest(
|
||||
TestFontsProvider.AUTHORITY, PACKAGE_NAME, "query");
|
||||
private TestFontsProvider mProvider;
|
||||
private FontsContract mContract;
|
||||
private ResultReceiver mResultReceiver;
|
||||
private PackageManager mPackageManager;
|
||||
|
||||
public FontsContractTest() {
|
||||
@@ -74,126 +72,178 @@ public class FontsContractTest extends ProviderTestCase2<TestFontsProvider> {
|
||||
|
||||
mProvider = getProvider();
|
||||
mPackageManager = mock(PackageManager.class);
|
||||
mContract = new FontsContract(getMockContext(), mPackageManager);
|
||||
mResultReceiver = mock(ResultReceiver.class);
|
||||
}
|
||||
|
||||
public void testGetFontFromProvider_resultOK() {
|
||||
mContract.getFontFromProvider(request, mResultReceiver, TestFontsProvider.AUTHORITY);
|
||||
|
||||
final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
|
||||
verify(mResultReceiver).send(
|
||||
eq(FontsContract.Columns.RESULT_CODE_OK), bundleCaptor.capture());
|
||||
|
||||
Bundle bundle = bundleCaptor.getValue();
|
||||
assertNotNull(bundle);
|
||||
List<FontResult> resultList =
|
||||
bundle.getParcelableArrayList(FontsContract.PARCEL_FONT_RESULTS);
|
||||
assertNotNull(resultList);
|
||||
assertEquals(1, resultList.size());
|
||||
FontResult fontResult = resultList.get(0);
|
||||
assertEquals(TestFontsProvider.TTC_INDEX, fontResult.getTtcIndex());
|
||||
assertEquals(TestFontsProvider.VARIATION_SETTINGS, fontResult.getFontVariationSettings());
|
||||
assertEquals(TestFontsProvider.NORMAL_WEIGHT, fontResult.getWeight());
|
||||
assertEquals(TestFontsProvider.ITALIC, fontResult.getItalic());
|
||||
assertNotNull(fontResult.getFileDescriptor());
|
||||
public void testGetFontFromProvider_resultOK() throws InvalidFormatException {
|
||||
FontInfo[] fonts = FontsContract.getFontFromProvider(
|
||||
getMockContext(), request, TestFontsProvider.AUTHORITY, null);
|
||||
assertNotNull(fonts);
|
||||
assertEquals(1, fonts.length);
|
||||
FontInfo font = fonts[0];
|
||||
assertEquals(TestFontsProvider.TTC_INDEX, font.getTtcIndex());
|
||||
FontVariationAxis[] actual = font.getAxes();
|
||||
assertEquals(1, actual.length);
|
||||
assertEquals("wdth", actual[0].getTag());
|
||||
assertEquals(1.0f, actual[0].getStyleValue(), 0);
|
||||
assertEquals(TestFontsProvider.NORMAL_WEIGHT, font.getWeight());
|
||||
assertEquals(TestFontsProvider.ITALIC, font.isItalic());
|
||||
assertNotNull(font.getUri());
|
||||
assertEquals(RESULT_CODE_OK, font.getResultCode());
|
||||
}
|
||||
|
||||
public void testGetFontFromProvider_providerDoesntReturnAllFields() {
|
||||
public void testGetFontFromProvider_providerDoesntReturnAllFields()
|
||||
throws InvalidFormatException {
|
||||
mProvider.setReturnAllFields(false);
|
||||
|
||||
final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
|
||||
mContract.getFontFromProvider(request, mResultReceiver, TestFontsProvider.AUTHORITY);
|
||||
verify(mResultReceiver).send(
|
||||
eq(FontsContract.Columns.RESULT_CODE_OK), bundleCaptor.capture());
|
||||
|
||||
Bundle bundle = bundleCaptor.getValue();
|
||||
assertNotNull(bundle);
|
||||
List<FontResult> resultList =
|
||||
bundle.getParcelableArrayList(FontsContract.PARCEL_FONT_RESULTS);
|
||||
assertNotNull(resultList);
|
||||
assertEquals(1, resultList.size());
|
||||
FontResult fontResult = resultList.get(0);
|
||||
assertEquals(0, fontResult.getTtcIndex());
|
||||
assertNull(fontResult.getFontVariationSettings());
|
||||
assertEquals(400, fontResult.getWeight());
|
||||
assertFalse(fontResult.getItalic());
|
||||
assertNotNull(fontResult.getFileDescriptor());
|
||||
FontInfo[] fonts = FontsContract.getFontFromProvider(
|
||||
getMockContext(), request, TestFontsProvider.AUTHORITY, null);
|
||||
assertNotNull(fonts);
|
||||
assertEquals(1, fonts.length);
|
||||
FontInfo font = fonts[0];
|
||||
assertEquals(0, font.getTtcIndex());
|
||||
assertNull(font.getAxes());
|
||||
assertEquals(400, font.getWeight());
|
||||
assertFalse(font.isItalic());
|
||||
assertNotNull(font.getUri());
|
||||
assertEquals(RESULT_CODE_OK, font.getResultCode());
|
||||
}
|
||||
|
||||
public void testGetFontFromProvider_resultFontNotFound() {
|
||||
public void testGetFontFromProvider_resultFontNotFound() throws InvalidFormatException {
|
||||
// Make the provider return unknown
|
||||
mProvider.setResultCode(FontsContract.Columns.RESULT_CODE_FONT_NOT_FOUND);
|
||||
mContract.getFontFromProvider(request, mResultReceiver, TestFontsProvider.AUTHORITY);
|
||||
|
||||
verify(mResultReceiver).send(FontsContract.Columns.RESULT_CODE_FONT_NOT_FOUND,null);
|
||||
mProvider.setResultCode(RESULT_CODE_FONT_NOT_FOUND);
|
||||
FontInfo[] fonts = FontsContract.getFontFromProvider(
|
||||
getMockContext(), request, TestFontsProvider.AUTHORITY, null);
|
||||
assertNotNull(fonts);
|
||||
assertEquals(1, fonts.length);
|
||||
FontInfo font = fonts[0];
|
||||
assertEquals(TestFontsProvider.TTC_INDEX, font.getTtcIndex());
|
||||
assertNotNull(font.getUri());
|
||||
assertEquals(RESULT_CODE_FONT_NOT_FOUND, font.getResultCode());
|
||||
}
|
||||
|
||||
public void testGetFontFromProvider_resultFontUnavailable() {
|
||||
public void testGetFontFromProvider_resultFontUnavailable() throws InvalidFormatException {
|
||||
// Make the provider return font unavailable
|
||||
mProvider.setResultCode(FontsContract.Columns.RESULT_CODE_FONT_UNAVAILABLE);
|
||||
mContract.getFontFromProvider(request, mResultReceiver, TestFontsProvider.AUTHORITY);
|
||||
mProvider.setResultCode(RESULT_CODE_FONT_UNAVAILABLE);
|
||||
FontInfo[] fonts = FontsContract.getFontFromProvider(
|
||||
getMockContext(), request, TestFontsProvider.AUTHORITY, null);
|
||||
|
||||
verify(mResultReceiver).send(FontsContract.Columns.RESULT_CODE_FONT_UNAVAILABLE,null);
|
||||
assertNotNull(fonts);
|
||||
assertEquals(1, fonts.length);
|
||||
FontInfo font = fonts[0];
|
||||
assertEquals(TestFontsProvider.TTC_INDEX, font.getTtcIndex());
|
||||
FontVariationAxis[] actual = font.getAxes();
|
||||
assertEquals(1, actual.length);
|
||||
assertEquals("wdth", actual[0].getTag());
|
||||
assertEquals(1.0f, actual[0].getStyleValue(), 0);
|
||||
assertEquals(TestFontsProvider.NORMAL_WEIGHT, font.getWeight());
|
||||
assertEquals(TestFontsProvider.ITALIC, font.isItalic());
|
||||
assertNotNull(font.getUri());
|
||||
assertEquals(RESULT_CODE_FONT_UNAVAILABLE, font.getResultCode());
|
||||
}
|
||||
|
||||
public void testGetFontFromProvider_resultMalformedQuery() {
|
||||
public void testGetFontFromProvider_resultMalformedQuery() throws InvalidFormatException {
|
||||
// Make the provider return font unavailable
|
||||
mProvider.setResultCode(FontsContract.Columns.RESULT_CODE_MALFORMED_QUERY);
|
||||
mContract.getFontFromProvider(request, mResultReceiver, TestFontsProvider.AUTHORITY);
|
||||
mProvider.setResultCode(RESULT_CODE_MALFORMED_QUERY);
|
||||
FontInfo[] fonts = FontsContract.getFontFromProvider(
|
||||
getMockContext(), request, TestFontsProvider.AUTHORITY, null);
|
||||
|
||||
verify(mResultReceiver).send(FontsContract.Columns.RESULT_CODE_MALFORMED_QUERY,null);
|
||||
assertNotNull(fonts);
|
||||
assertEquals(1, fonts.length);
|
||||
FontInfo font = fonts[0];
|
||||
assertEquals(TestFontsProvider.TTC_INDEX, font.getTtcIndex());
|
||||
FontVariationAxis[] actual = font.getAxes();
|
||||
assertEquals(1, actual.length);
|
||||
assertEquals("wdth", actual[0].getTag());
|
||||
assertEquals(1.0f, actual[0].getStyleValue(), 0);
|
||||
assertEquals(TestFontsProvider.NORMAL_WEIGHT, font.getWeight());
|
||||
assertEquals(TestFontsProvider.ITALIC, font.isItalic());
|
||||
assertNotNull(font.getUri());
|
||||
assertEquals(RESULT_CODE_MALFORMED_QUERY, font.getResultCode());
|
||||
}
|
||||
|
||||
public void testGetFontFromProvider_resultFontNotFoundSecondRow() {
|
||||
public void testGetFontFromProvider_resultFontNotFoundSecondRow()
|
||||
throws InvalidFormatException {
|
||||
MatrixCursor cursor = new MatrixCursor(new String[] { FontsContract.Columns._ID,
|
||||
FontsContract.Columns.TTC_INDEX, FontsContract.Columns.VARIATION_SETTINGS,
|
||||
FontsContract.Columns.WEIGHT, FontsContract.Columns.ITALIC,
|
||||
FontsContract.Columns.RESULT_CODE });
|
||||
cursor.addRow(new Object[] { 1, 0, null, 400, 0, FontsContract.Columns.RESULT_CODE_OK});
|
||||
cursor.addRow(new Object[] { 1, 0, null, 400, 0, RESULT_CODE_OK});
|
||||
cursor.addRow(new Object[] { 1, 0, null, 400, 0,
|
||||
FontsContract.Columns.RESULT_CODE_FONT_NOT_FOUND});
|
||||
RESULT_CODE_FONT_NOT_FOUND});
|
||||
mProvider.setCustomCursor(cursor);
|
||||
mContract.getFontFromProvider(request, mResultReceiver, TestFontsProvider.AUTHORITY);
|
||||
FontInfo[] fonts = FontsContract.getFontFromProvider(
|
||||
getMockContext(), request, TestFontsProvider.AUTHORITY, null);
|
||||
|
||||
verify(mResultReceiver).send(FontsContract.Columns.RESULT_CODE_FONT_NOT_FOUND, null);
|
||||
assertNotNull(fonts);
|
||||
assertEquals(2, fonts.length);
|
||||
|
||||
FontInfo font = fonts[0];
|
||||
assertEquals(0, font.getTtcIndex());
|
||||
assertNull(font.getAxes());
|
||||
assertEquals(400, font.getWeight());
|
||||
assertFalse(font.isItalic());
|
||||
assertNotNull(font.getUri());
|
||||
assertEquals(RESULT_CODE_OK, font.getResultCode());
|
||||
|
||||
font = fonts[1];
|
||||
assertEquals(0, font.getTtcIndex());
|
||||
assertNull(font.getAxes());
|
||||
assertEquals(400, font.getWeight());
|
||||
assertFalse(font.isItalic());
|
||||
assertNotNull(font.getUri());
|
||||
assertEquals(RESULT_CODE_FONT_NOT_FOUND, font.getResultCode());
|
||||
}
|
||||
|
||||
public void testGetFontFromProvider_resultFontNotFoundOtherRow() {
|
||||
public void testGetFontFromProvider_resultFontNotFoundOtherRow() throws InvalidFormatException {
|
||||
MatrixCursor cursor = new MatrixCursor(new String[] { FontsContract.Columns._ID,
|
||||
FontsContract.Columns.TTC_INDEX, FontsContract.Columns.VARIATION_SETTINGS,
|
||||
FontsContract.Columns.WEIGHT, FontsContract.Columns.ITALIC,
|
||||
FontsContract.Columns.RESULT_CODE });
|
||||
cursor.addRow(new Object[] { 1, 0, null, 400, 0, FontsContract.Columns.RESULT_CODE_OK});
|
||||
cursor.addRow(new Object[] { 1, 0, null, 400, 0, RESULT_CODE_OK});
|
||||
cursor.addRow(new Object[] { 1, 0, null, 400, 0,
|
||||
FontsContract.Columns.RESULT_CODE_FONT_NOT_FOUND});
|
||||
cursor.addRow(new Object[] { 1, 0, null, 400, 0, FontsContract.Columns.RESULT_CODE_OK});
|
||||
RESULT_CODE_FONT_NOT_FOUND});
|
||||
cursor.addRow(new Object[] { 1, 0, null, 400, 0, RESULT_CODE_OK});
|
||||
mProvider.setCustomCursor(cursor);
|
||||
mContract.getFontFromProvider(request, mResultReceiver, TestFontsProvider.AUTHORITY);
|
||||
FontInfo[] fonts = FontsContract.getFontFromProvider(
|
||||
getMockContext(), request, TestFontsProvider.AUTHORITY, null);
|
||||
|
||||
verify(mResultReceiver).send(FontsContract.Columns.RESULT_CODE_FONT_NOT_FOUND, null);
|
||||
}
|
||||
assertNotNull(fonts);
|
||||
assertEquals(3, fonts.length);
|
||||
|
||||
public void testGetFontFromProvider_resultCodeIsNegativeNumber() {
|
||||
MatrixCursor cursor = new MatrixCursor(new String[] { FontsContract.Columns._ID,
|
||||
FontsContract.Columns.TTC_INDEX, FontsContract.Columns.VARIATION_SETTINGS,
|
||||
FontsContract.Columns.WEIGHT, FontsContract.Columns.ITALIC,
|
||||
FontsContract.Columns.RESULT_CODE });
|
||||
cursor.addRow(new Object[] { 1, 0, null, 400, 0, FontsContract.Columns.RESULT_CODE_OK});
|
||||
cursor.addRow(new Object[] { 1, 0, null, 400, 0, -5});
|
||||
mProvider.setCustomCursor(cursor);
|
||||
mContract.getFontFromProvider(request, mResultReceiver, TestFontsProvider.AUTHORITY);
|
||||
FontInfo font = fonts[0];
|
||||
assertEquals(0, font.getTtcIndex());
|
||||
assertNull(font.getAxes());
|
||||
assertEquals(400, font.getWeight());
|
||||
assertFalse(font.isItalic());
|
||||
assertNotNull(font.getUri());
|
||||
assertEquals(RESULT_CODE_OK, font.getResultCode());
|
||||
|
||||
verify(mResultReceiver).send(FontsContract.Columns.RESULT_CODE_FONT_NOT_FOUND, null);
|
||||
font = fonts[1];
|
||||
assertEquals(0, font.getTtcIndex());
|
||||
assertNull(font.getAxes());
|
||||
assertEquals(400, font.getWeight());
|
||||
assertFalse(font.isItalic());
|
||||
assertNotNull(font.getUri());
|
||||
assertEquals(RESULT_CODE_FONT_NOT_FOUND, font.getResultCode());
|
||||
|
||||
font = fonts[2];
|
||||
assertEquals(0, font.getTtcIndex());
|
||||
assertNull(font.getAxes());
|
||||
assertEquals(400, font.getWeight());
|
||||
assertFalse(font.isItalic());
|
||||
assertNotNull(font.getUri());
|
||||
assertEquals(RESULT_CODE_OK, font.getResultCode());
|
||||
}
|
||||
|
||||
public void testGetProvider_providerNotFound() {
|
||||
when(mPackageManager.resolveContentProvider(anyString(), anyInt())).thenReturn(null);
|
||||
|
||||
ProviderInfo result = mContract.getProvider(request, mResultReceiver);
|
||||
|
||||
verify(mResultReceiver).send(FontsContract.RESULT_CODE_PROVIDER_NOT_FOUND, null);
|
||||
assertNull(result);
|
||||
try {
|
||||
FontsContract.getProvider(mPackageManager, request);
|
||||
fail();
|
||||
} catch (NameNotFoundException e) {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
|
||||
public void testGetProvider_providerIsSystemApp() throws PackageManager.NameNotFoundException {
|
||||
@@ -201,9 +251,7 @@ public class FontsContractTest extends ProviderTestCase2<TestFontsProvider> {
|
||||
info.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
|
||||
when(mPackageManager.resolveContentProvider(anyString(), anyInt())).thenReturn(info);
|
||||
|
||||
ProviderInfo result = mContract.getProvider(request, mResultReceiver);
|
||||
|
||||
verifyZeroInteractions(mResultReceiver);
|
||||
ProviderInfo result = FontsContract.getProvider(mPackageManager, request);
|
||||
assertEquals(info, result);
|
||||
}
|
||||
|
||||
@@ -213,23 +261,22 @@ public class FontsContractTest extends ProviderTestCase2<TestFontsProvider> {
|
||||
info.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
|
||||
when(mPackageManager.resolveContentProvider(anyString(), anyInt())).thenReturn(info);
|
||||
|
||||
ProviderInfo result = mContract.getProvider(
|
||||
new FontRequest(TestFontsProvider.AUTHORITY, "com.wrong.package", "query"),
|
||||
mResultReceiver);
|
||||
try {
|
||||
FontsContract.getProvider(
|
||||
mPackageManager,
|
||||
new FontRequest(TestFontsProvider.AUTHORITY, "com.wrong.package", "query"));
|
||||
fail();
|
||||
} catch (NameNotFoundException e) {
|
||||
// pass
|
||||
}
|
||||
|
||||
verify(mResultReceiver).send(FontsContract.RESULT_CODE_PROVIDER_NOT_FOUND, null);
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
public void testGetProvider_providerIsNonSystemAppNoCerts()
|
||||
throws PackageManager.NameNotFoundException {
|
||||
setupPackageManager();
|
||||
|
||||
// The default request is missing the certificates info.
|
||||
ProviderInfo result = mContract.getProvider(request, mResultReceiver);
|
||||
|
||||
verify(mResultReceiver).send(FontsContract.RESULT_CODE_WRONG_CERTIFICATES, null);
|
||||
assertNull(result);
|
||||
assertNull(FontsContract.getProvider(mPackageManager, request));
|
||||
}
|
||||
|
||||
public void testGetProvider_providerIsNonSystemAppWrongCerts()
|
||||
@@ -240,10 +287,8 @@ public class FontsContractTest extends ProviderTestCase2<TestFontsProvider> {
|
||||
List<byte[]> certList = Arrays.asList(wrongCert);
|
||||
FontRequest requestWrongCerts = new FontRequest(
|
||||
TestFontsProvider.AUTHORITY, PACKAGE_NAME, "query", Arrays.asList(certList));
|
||||
ProviderInfo result = mContract.getProvider(requestWrongCerts, mResultReceiver);
|
||||
|
||||
verify(mResultReceiver).send(FontsContract.RESULT_CODE_WRONG_CERTIFICATES, null);
|
||||
assertNull(result);
|
||||
assertNull(FontsContract.getProvider(mPackageManager, requestWrongCerts));
|
||||
}
|
||||
|
||||
public void testGetProvider_providerIsNonSystemAppCorrectCerts()
|
||||
@@ -253,9 +298,9 @@ public class FontsContractTest extends ProviderTestCase2<TestFontsProvider> {
|
||||
List<byte[]> certList = Arrays.asList(BYTE_ARRAY);
|
||||
FontRequest requestRightCerts = new FontRequest(
|
||||
TestFontsProvider.AUTHORITY, PACKAGE_NAME, "query", Arrays.asList(certList));
|
||||
ProviderInfo result = mContract.getProvider(requestRightCerts, mResultReceiver);
|
||||
ProviderInfo result = FontsContract.getProvider(
|
||||
mPackageManager, requestRightCerts);
|
||||
|
||||
verifyZeroInteractions(mResultReceiver);
|
||||
assertEquals(info, result);
|
||||
}
|
||||
|
||||
@@ -267,11 +312,7 @@ public class FontsContractTest extends ProviderTestCase2<TestFontsProvider> {
|
||||
List<byte[]> certList = Arrays.asList(wrongCert, BYTE_ARRAY);
|
||||
FontRequest requestRightCerts = new FontRequest(
|
||||
TestFontsProvider.AUTHORITY, PACKAGE_NAME, "query", Arrays.asList(certList));
|
||||
ProviderInfo result = mContract.getProvider(requestRightCerts, mResultReceiver);
|
||||
|
||||
// There is one too many certs, should fail as the set doesn't match.
|
||||
verify(mResultReceiver).send(FontsContract.RESULT_CODE_WRONG_CERTIFICATES, null);
|
||||
assertNull(result);
|
||||
assertNull(FontsContract.getProvider(mPackageManager, requestRightCerts));
|
||||
}
|
||||
|
||||
public void testGetProvider_providerIsNonSystemAppDuplicateCerts()
|
||||
@@ -294,12 +335,7 @@ public class FontsContractTest extends ProviderTestCase2<TestFontsProvider> {
|
||||
List<byte[]> certList = Arrays.asList(BYTE_ARRAY_2, BYTE_ARRAY_COPY);
|
||||
FontRequest requestRightCerts = new FontRequest(
|
||||
TestFontsProvider.AUTHORITY, PACKAGE_NAME, "query", Arrays.asList(certList));
|
||||
ProviderInfo result = mContract.getProvider(requestRightCerts, mResultReceiver);
|
||||
|
||||
// The given list includes an extra cert and doesn't have a second copy of the cert like
|
||||
// the provider does, so it should have failed.
|
||||
verify(mResultReceiver).send(FontsContract.RESULT_CODE_WRONG_CERTIFICATES, null);
|
||||
assertNull(result);
|
||||
assertNull(FontsContract.getProvider(mPackageManager, requestRightCerts));
|
||||
}
|
||||
|
||||
public void testGetProvider_providerIsNonSystemAppCorrectCertsSeveralSets()
|
||||
@@ -312,9 +348,8 @@ public class FontsContractTest extends ProviderTestCase2<TestFontsProvider> {
|
||||
certList.add(Arrays.asList(BYTE_ARRAY));
|
||||
FontRequest requestRightCerts = new FontRequest(
|
||||
TestFontsProvider.AUTHORITY, PACKAGE_NAME, "query", certList);
|
||||
ProviderInfo result = mContract.getProvider(requestRightCerts, mResultReceiver);
|
||||
ProviderInfo result = FontsContract.getProvider(mPackageManager, requestRightCerts);
|
||||
|
||||
verifyZeroInteractions(mResultReceiver);
|
||||
assertEquals(info, result);
|
||||
}
|
||||
|
||||
@@ -326,10 +361,12 @@ public class FontsContractTest extends ProviderTestCase2<TestFontsProvider> {
|
||||
certList.add(Arrays.asList(BYTE_ARRAY));
|
||||
FontRequest requestRightCerts = new FontRequest(
|
||||
TestFontsProvider.AUTHORITY, "com.wrong.package.name", "query", certList);
|
||||
ProviderInfo result = mContract.getProvider(requestRightCerts, mResultReceiver);
|
||||
|
||||
verify(mResultReceiver).send(FontsContract.RESULT_CODE_PROVIDER_NOT_FOUND, null);
|
||||
assertNull(result);
|
||||
try {
|
||||
FontsContract.getProvider(mPackageManager, requestRightCerts);
|
||||
fail();
|
||||
} catch (NameNotFoundException e) {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
|
||||
private ProviderInfo setupPackageManager()
|
||||
|
||||
@@ -34,6 +34,7 @@ import android.graphics.fonts.FontRequest;
|
||||
import android.graphics.fonts.FontResult;
|
||||
import android.graphics.fonts.FontVariationAxis;
|
||||
import android.graphics.fonts.FontVariationAxis.InvalidFormatException;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
@@ -509,6 +510,10 @@ public class Typeface {
|
||||
* </p>
|
||||
*/
|
||||
public static final class Builder {
|
||||
/** @hide */
|
||||
public static final int NORMAL_WEIGHT = 400;
|
||||
/** @hide */
|
||||
public static final int BOLD_WEIGHT = 700;
|
||||
|
||||
private int mTtcIndex;
|
||||
private FontVariationAxis[] mAxes;
|
||||
@@ -517,6 +522,10 @@ public class Typeface {
|
||||
private String mPath;
|
||||
private FileDescriptor mFd;
|
||||
|
||||
private FontsContract.FontInfo[] mFonts;
|
||||
private Map<Uri, ByteBuffer> mFontBuffers;
|
||||
private String mFallbackFamilyName;
|
||||
|
||||
private int mWeight = RESOLVE_BY_FONT_TABLE;
|
||||
private int mItalic = RESOLVE_BY_FONT_TABLE;
|
||||
|
||||
@@ -558,6 +567,25 @@ public class Typeface {
|
||||
mPath = Preconditions.checkStringNotEmpty(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constracts a builder from an array of FontsContract.FontInfo.
|
||||
*
|
||||
* Since {@link FontsContract.FontInfo} holds information about TTC indices and
|
||||
* variation settings, there is no need to call {@link #setTtcIndex} or
|
||||
* {@link #setFontVariationSettings}. Similary, {@link FontsContract.FontInfo} holds
|
||||
* weight and italic information, so {@link #setWeight} and {@link #setItalic} are used
|
||||
* for style matching during font selection.
|
||||
*
|
||||
* @param results The array of {@link FontsContract.FontInfo}
|
||||
* @param buffers The mapping from URI to buffers to be used during building.
|
||||
* @hide
|
||||
*/
|
||||
public Builder(@NonNull FontsContract.FontInfo[] fonts,
|
||||
@NonNull Map<Uri, ByteBuffer> buffers) {
|
||||
mFonts = fonts;
|
||||
mFontBuffers = buffers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets weight of the font.
|
||||
*
|
||||
@@ -590,6 +618,10 @@ public class Typeface {
|
||||
* collection, do not call this method or specify 0.
|
||||
*/
|
||||
public Builder setTtcIndex(@IntRange(from = 0) int ttcIndex) {
|
||||
if (mFonts != null) {
|
||||
throw new IllegalArgumentException(
|
||||
"TTC index can not be specified for FontResult source.");
|
||||
}
|
||||
mTtcIndex = ttcIndex;
|
||||
return this;
|
||||
}
|
||||
@@ -603,6 +635,13 @@ public class Typeface {
|
||||
*/
|
||||
public Builder setFontVariationSettings(@Nullable String variationSettings)
|
||||
throws InvalidFormatException {
|
||||
if (mFonts != null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Font variation settings can not be specified for FontResult source.");
|
||||
}
|
||||
if (mAxes != null) {
|
||||
throw new IllegalStateException("Font variation settings are already set.");
|
||||
}
|
||||
mAxes = FontVariationAxis.fromFontVariationSettings(variationSettings);
|
||||
return this;
|
||||
}
|
||||
@@ -613,6 +652,13 @@ public class Typeface {
|
||||
* @param axes An array of font variation axis tag-value pairs.
|
||||
*/
|
||||
public Builder setFontVariationSettings(@Nullable FontVariationAxis[] axes) {
|
||||
if (mFonts != null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Font variation settings can not be specified for FontResult source.");
|
||||
}
|
||||
if (mAxes != null) {
|
||||
throw new IllegalStateException("Font variation settings are already set.");
|
||||
}
|
||||
mAxes = axes;
|
||||
return this;
|
||||
}
|
||||
@@ -698,6 +744,32 @@ public class Typeface {
|
||||
fontFamily.freeze();
|
||||
FontFamily[] families = { fontFamily };
|
||||
return createFromFamiliesWithDefault(families);
|
||||
} else if (mFonts != null) {
|
||||
final FontFamily fontFamily = new FontFamily();
|
||||
boolean atLeastOneFont = false;
|
||||
for (FontsContract.FontInfo font : mFonts) {
|
||||
final ByteBuffer fontBuffer = mFontBuffers.get(font.getUri());
|
||||
if (fontBuffer == null) {
|
||||
continue; // skip
|
||||
}
|
||||
final boolean success = fontFamily.addFontFromBuffer(fontBuffer,
|
||||
font.getTtcIndex(), font.getAxes(), font.getWeight(),
|
||||
font.isItalic() ? STYLE_ITALIC : STYLE_NORMAL);
|
||||
if (!success) {
|
||||
fontFamily.abortCreation();
|
||||
return null;
|
||||
}
|
||||
atLeastOneFont = true;
|
||||
}
|
||||
if (!atLeastOneFont) {
|
||||
// No fonts are avaialble. No need to create new Typeface and returns fallback
|
||||
// Typeface instead.
|
||||
fontFamily.abortCreation();
|
||||
return null;
|
||||
}
|
||||
fontFamily.freeze();
|
||||
FontFamily[] families = { fontFamily };
|
||||
return createFromFamiliesWithDefault(families);
|
||||
}
|
||||
|
||||
// Must not reach here.
|
||||
|
||||
Reference in New Issue
Block a user