field public static final int fontFamily = 16843692; // 0x10103ac
field public static final int fontFeatureSettings = 16843959; // 0x10104b7
field public static final int fontProviderAuthority = 16844114; // 0x1010552
+ field public static final int fontProviderPackage = 16844122; // 0x101055a
field public static final int fontProviderQuery = 16844115; // 0x1010553
field public static final int fontStyle = 16844081; // 0x1010531
field public static final int fontWeight = 16844083; // 0x1010533
package android.graphics.fonts {
public final class FontRequest implements android.os.Parcelable {
- ctor public FontRequest(java.lang.String, java.lang.String);
+ ctor public FontRequest(java.lang.String, java.lang.String, java.lang.String);
+ ctor public FontRequest(java.lang.String, java.lang.String, java.lang.String, java.util.List<java.util.List<byte[]>>);
method public int describeContents();
+ method public java.util.List<java.util.List<byte[]>> getCertificates();
method public java.lang.String getProviderAuthority();
+ method public java.lang.String getProviderPackage();
method public java.lang.String getQuery();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.graphics.fonts.FontRequest> CREATOR;
field public static final int fontFamily = 16843692; // 0x10103ac
field public static final int fontFeatureSettings = 16843959; // 0x10104b7
field public static final int fontProviderAuthority = 16844114; // 0x1010552
+ field public static final int fontProviderPackage = 16844122; // 0x101055a
field public static final int fontProviderQuery = 16844115; // 0x1010553
field public static final int fontStyle = 16844081; // 0x1010531
field public static final int fontWeight = 16844083; // 0x1010533
package android.graphics.fonts {
public final class FontRequest implements android.os.Parcelable {
- ctor public FontRequest(java.lang.String, java.lang.String);
+ ctor public FontRequest(java.lang.String, java.lang.String, java.lang.String);
+ ctor public FontRequest(java.lang.String, java.lang.String, java.lang.String, java.util.List<java.util.List<byte[]>>);
method public int describeContents();
+ method public java.util.List<java.util.List<byte[]>> getCertificates();
method public java.lang.String getProviderAuthority();
+ method public java.lang.String getProviderPackage();
method public java.lang.String getQuery();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.graphics.fonts.FontRequest> CREATOR;
field public static final int fontFamily = 16843692; // 0x10103ac
field public static final int fontFeatureSettings = 16843959; // 0x10104b7
field public static final int fontProviderAuthority = 16844114; // 0x1010552
+ field public static final int fontProviderPackage = 16844122; // 0x101055a
field public static final int fontProviderQuery = 16844115; // 0x1010553
field public static final int fontStyle = 16844081; // 0x1010531
field public static final int fontWeight = 16844083; // 0x1010533
package android.graphics.fonts {
public final class FontRequest implements android.os.Parcelable {
- ctor public FontRequest(java.lang.String, java.lang.String);
+ ctor public FontRequest(java.lang.String, java.lang.String, java.lang.String);
+ ctor public FontRequest(java.lang.String, java.lang.String, java.lang.String, java.util.List<java.util.List<byte[]>>);
method public int describeContents();
+ method public java.util.List<java.util.List<byte[]>> getCertificates();
method public java.lang.String getProviderAuthority();
+ method public java.lang.String getProviderPackage();
method public java.lang.String getQuery();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.graphics.fonts.FontRequest> CREATOR;
AttributeSet attrs = Xml.asAttributeSet(parser);
TypedArray array = resources.obtainAttributes(attrs, R.styleable.FontFamily);
String authority = array.getString(R.styleable.FontFamily_fontProviderAuthority);
+ String providerPackage = array.getString(R.styleable.FontFamily_fontProviderPackage);
String query = array.getString(R.styleable.FontFamily_fontProviderQuery);
array.recycle();
- if (authority != null && query != null) {
+ if (authority != null && providerPackage != null && query != null) {
while (parser.next() != XmlPullParser.END_TAG) {
skip(parser);
}
- return new FontConfig.Family(authority, query);
+ return new FontConfig.Family(authority, providerPackage, query);
}
List<FontConfig.Font> fonts = new ArrayList<>();
while (parser.next() != XmlPullParser.END_TAG) {
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
+import android.content.pm.Signature;
import android.database.Cursor;
+import android.graphics.Typeface;
import android.graphics.fonts.FontRequest;
import android.graphics.fonts.FontResult;
import android.net.Uri;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import java.io.FileNotFoundException;
import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
/**
* Utility class to deal with Font ContentProviders.
mPackageManager = mContext.getPackageManager();
}
+ /** @hide */
+ @VisibleForTesting
+ public FontsContract(Context context, PackageManager packageManager) {
+ mContext = context;
+ mPackageManager = packageManager;
+ }
+
// We use a background thread to post the content resolving work for all requests on. This
// thread should be quit/stopped after all requests are done.
private final Runnable mReplaceDispatcherThreadRunnable = new Runnable() {
mHandler = new Handler(mThread.getLooper());
}
mHandler.post(() -> {
- String providerAuthority = request.getProviderAuthority();
- // TODO: Implement cert checking for non-system apps
- ProviderInfo providerInfo = mPackageManager.resolveContentProvider(
- providerAuthority, PackageManager.MATCH_SYSTEM_ONLY);
+ ProviderInfo providerInfo = getProvider(request);
if (providerInfo == null) {
receiver.send(RESULT_CODE_PROVIDER_NOT_FOUND, null);
return;
}
- Bundle result = getFontFromProvider(request, receiver, providerInfo);
- if (result == null) {
- receiver.send(RESULT_CODE_FONT_NOT_FOUND, null);
- return;
- }
- receiver.send(RESULT_CODE_OK, result);
+ getFontFromProvider(request, receiver, providerInfo.authority);
});
mHandler.removeCallbacks(mReplaceDispatcherThreadRunnable);
mHandler.postDelayed(mReplaceDispatcherThreadRunnable, THREAD_RENEWAL_THRESHOLD_MS);
}
}
- private Bundle getFontFromProvider(FontRequest request, ResultReceiver receiver,
- ProviderInfo providerInfo) {
+ /** @hide */
+ @VisibleForTesting
+ public ProviderInfo getProvider(FontRequest request) {
+ String providerAuthority = request.getProviderAuthority();
+ ProviderInfo info = mPackageManager.resolveContentProvider(providerAuthority, 0);
+ if (info == null) {
+ Log.e(TAG, "Can't find content provider " + providerAuthority);
+ return null;
+ }
+
+ if (!info.packageName.equals(request.getProviderPackage())) {
+ Log.e(TAG, "Found content provider " + providerAuthority + ", but package was not "
+ + request.getProviderPackage());
+ return null;
+ }
+ // Trust system apps without signature checks
+ if (info.applicationInfo.isSystemApp()) {
+ return info;
+ }
+
+ Set<byte[]> signatures;
+ try {
+ PackageInfo packageInfo = mPackageManager.getPackageInfo(info.packageName,
+ PackageManager.GET_SIGNATURES);
+ signatures = convertToSet(packageInfo.signatures);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Can't find content provider " + providerAuthority, e);
+ return null;
+ }
+ List<List<byte[]>> requestCertificatesList = request.getCertificates();
+ for (int i = 0; i < requestCertificatesList.size(); ++i) {
+ final Set<byte[]> requestCertificates = convertToSet(requestCertificatesList.get(i));
+ if (signatures.equals(requestCertificates)) {
+ return info;
+ }
+ }
+ Log.e(TAG, "Certificates don't match for given provider " + providerAuthority);
+ return null;
+ }
+
+ private Set<byte[]> convertToSet(Signature[] signatures) {
+ Set<byte[]> shas = new HashSet<>();
+ for (int i = 0; i < signatures.length; ++i) {
+ shas.add(signatures[i].toByteArray());
+ }
+ return shas;
+ }
+
+ private Set<byte[]> convertToSet(List<byte[]> certs) {
+ Set<byte[]> shas = new HashSet<>();
+ shas.addAll(certs);
+ return shas;
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public void getFontFromProvider(FontRequest request, ResultReceiver receiver,
+ String authority) {
ArrayList<FontResult> result = null;
Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
- .authority(providerInfo.authority)
+ .authority(authority)
.build();
try (Cursor cursor = mContext.getContentResolver().query(uri, new String[] { Columns._ID,
Columns.TTC_INDEX, Columns.VARIATION_SETTINGS, Columns.STYLE },
try {
ParcelFileDescriptor pfd =
mContext.getContentResolver().openFileDescriptor(fileUri, "r");
- final int ttcIndex = cursor.getInt(ttcIndexColumnIndex);
- final String variationSettings = cursor.getString(vsColumnIndex);
- final int style = cursor.getInt(styleColumnIndex);
+ final int ttcIndex = ttcIndexColumnIndex != -1
+ ? cursor.getInt(ttcIndexColumnIndex) : 0;
+ final String variationSettings = vsColumnIndex != -1
+ ? cursor.getString(vsColumnIndex) : null;
+ final int style = styleColumnIndex != -1
+ ? cursor.getInt(styleColumnIndex) : Typeface.NORMAL;
result.add(new FontResult(pfd, ttcIndex, variationSettings, style));
} catch (FileNotFoundException e) {
Log.e(TAG, "FileNotFoundException raised when interacting with content "
- + "provider " + providerInfo.authority, e);
+ + "provider " + authority, e);
}
}
}
if (result != null && !result.isEmpty()) {
Bundle bundle = new Bundle();
bundle.putParcelableArrayList(PARCEL_FONT_RESULTS, result);
- return bundle;
+ receiver.send(RESULT_CODE_OK, bundle);
+ return;
}
- return null;
+ receiver.send(RESULT_CODE_FONT_NOT_FOUND, null);
}
}
private final String mLanguage;
private final String mVariant;
private final String mProviderAuthority;
+ private final String mProviderPackage;
private final String mQuery;
public Family(String name, List<Font> fonts, String language, String variant) {
mLanguage = language;
mVariant = variant;
mProviderAuthority = null;
+ mProviderPackage = null;
mQuery = null;
}
/**
* @hide
*/
- public Family(String providerAuthority, String query) {
+ public Family(String providerAuthority, String providerPackage, String query) {
mName = null;
mFonts = null;
mLanguage = null;
mVariant = null;
mProviderAuthority = providerAuthority;
+ mProviderPackage = providerPackage;
mQuery = query;
}
mFonts.add(new Font(origin.mFonts.get(i)));
}
mProviderAuthority = origin.mProviderAuthority;
+ mProviderPackage = origin.mProviderPackage;
mQuery = origin.mQuery;
}
/**
* @hide
*/
+ public String getProviderPackage() {
+ return mProviderPackage;
+ }
+
+ /**
+ * @hide
+ */
public String getQuery() {
return mQuery;
}
mProviderAuthority = null;
}
if (in.readInt() == 1) {
+ mProviderPackage = in.readString();
+ } else {
+ mProviderPackage = null;
+ }
+ if (in.readInt() == 1) {
mQuery = in.readString();
} else {
mQuery = null;
if (mProviderAuthority != null) {
out.writeString(mProviderAuthority);
}
+ out.writeInt(mProviderPackage == null ? 0 : 1);
+ if (mProviderPackage != null) {
+ out.writeString(mProviderPackage);
+ }
out.writeInt(mQuery == null ? 0 : 1);
if (mQuery != null) {
out.writeString(mQuery);
<!-- Attributes that are read when parsing a <fontfamily> tag. -->
<declare-styleable name="FontFamily">
<attr name="fontProviderAuthority" format="string" />
+ <attr name="fontProviderPackage" format="string" />
<attr name="fontProviderQuery" format="string" />
</declare-styleable>
<public name="requiredFeature" />
<public name="requiredNotFeature" />
<public name="autoFillHint" />
+ <public name="fontProviderPackage" />
</public-group>
<public-group type="style" first-id="0x010302e0">
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:android="http://schemas.android.com/apk/res/android"
- android:fontProviderAuthority="com.example.test.fontprovider"
+ android:fontProviderAuthority="com.example.test.fontprovider.authority"
+ android:fontProviderPackage="com.example.test.fontprovider.package"
android:fontProviderQuery="MyRequestedFont">
</font-family>
\ No newline at end of file
List<FontConfig.Family> families = result.getFamilies();
assertEquals(1, families.size());
FontConfig.Family family = families.get(0);
- assertEquals("com.example.test.fontprovider", family.getProviderAuthority());
+ assertEquals("com.example.test.fontprovider.authority", family.getProviderAuthority());
+ assertEquals("com.example.test.fontprovider.package", family.getProviderPackage());
assertEquals("MyRequestedFont", family.getQuery());
assertNull(family.getFonts());
}
--- /dev/null
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.provider;
+
+import static org.mockito.Matchers.any;
+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.when;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.content.pm.Signature;
+import android.graphics.Typeface;
+import android.graphics.fonts.FontRequest;
+import android.graphics.fonts.FontResult;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+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;
+
+/**
+ * Unit tests for {@link FontsContract}.
+ */
+@SmallTest
+public class FontsContractTest extends ProviderTestCase2<TestFontsProvider> {
+ private static final byte[] BYTE_ARRAY =
+ Base64.decode("e04fd020ea3a6910a2d808002b30", Base64.DEFAULT);
+ private static final String PACKAGE_NAME = "com.my.font.provider.package";
+
+ 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() {
+ super(TestFontsProvider.class, TestFontsProvider.AUTHORITY);
+ }
+
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mProvider = getProvider();
+ mPackageManager = mock(PackageManager.class);
+ mContract = new FontsContract(getMockContext(), mPackageManager);
+ mResultReceiver = mock(ResultReceiver.class);
+ }
+
+ public void testGetFontFromProvider() {
+ mContract.getFontFromProvider(request, mResultReceiver, TestFontsProvider.AUTHORITY);
+
+ final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+ verify(mResultReceiver).send(eq(FontsContract.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.STYLE, fontResult.getStyle());
+ assertNotNull(fontResult.getFileDescriptor());
+ }
+
+ public void testGetFontFromProvider_providerDoesntReturnAllFields() {
+ mProvider.setReturnAllFields(false);
+
+ final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+ mContract.getFontFromProvider(request, mResultReceiver, TestFontsProvider.AUTHORITY);
+ verify(mResultReceiver).send(eq(FontsContract.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(Typeface.NORMAL, fontResult.getStyle());
+ assertNotNull(fontResult.getFileDescriptor());
+ }
+
+ public void testGetProvider_providerNotFound() {
+ when(mPackageManager.resolveContentProvider(anyString(), anyInt())).thenReturn(null);
+
+ ProviderInfo result = mContract.getProvider(request);
+
+ assertNull(result);
+ }
+
+ public void testGetProvider_providerIsSystemApp() throws PackageManager.NameNotFoundException {
+ ProviderInfo info = setupPackageManager();
+ info.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
+ when(mPackageManager.resolveContentProvider(anyString(), anyInt())).thenReturn(info);
+
+ ProviderInfo result = mContract.getProvider(request);
+
+ assertEquals(info, result);
+ }
+
+ public void testGetProvider_providerIsSystemAppWrongPackage()
+ throws PackageManager.NameNotFoundException {
+ ProviderInfo info = setupPackageManager();
+ 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"));
+
+ assertNull(result);
+ }
+
+ public void testGetProvider_providerIsNonSystemAppNoCerts()
+ throws PackageManager.NameNotFoundException {
+ setupPackageManager();
+
+ // The default request is missing the certificates info.
+ ProviderInfo result = mContract.getProvider(request);
+
+ assertNull(result);
+ }
+
+ public void testGetProvider_providerIsNonSystemAppWrongCerts()
+ throws PackageManager.NameNotFoundException {
+ setupPackageManager();
+
+ byte[] wrongCert = Base64.decode("this is a wrong cert", Base64.DEFAULT);
+ List<byte[]> certList = Arrays.asList(wrongCert);
+ FontRequest requestWrongCerts = new FontRequest(
+ TestFontsProvider.AUTHORITY, PACKAGE_NAME, "query", Arrays.asList(certList));
+ ProviderInfo result = mContract.getProvider(requestWrongCerts);
+
+ assertNull(result);
+ }
+
+ public void testGetProvider_providerIsNonSystemAppCorrectCerts()
+ throws PackageManager.NameNotFoundException {
+ ProviderInfo info = setupPackageManager();
+
+ List<byte[]> certList = Arrays.asList(BYTE_ARRAY);
+ FontRequest requestRightCerts = new FontRequest(
+ TestFontsProvider.AUTHORITY, PACKAGE_NAME, "query", Arrays.asList(certList));
+ ProviderInfo result = mContract.getProvider(requestRightCerts);
+
+ assertEquals(info, result);
+ }
+
+ public void testGetProvider_providerIsNonSystemAppMoreCerts()
+ throws PackageManager.NameNotFoundException {
+ setupPackageManager();
+
+ byte[] wrongCert = Base64.decode("this is a wrong cert", Base64.DEFAULT);
+ 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);
+
+ // There is one too many certs, should fail as the set doesn't match.
+ assertNull(result);
+ }
+
+ public void testGetProvider_providerIsNonSystemAppCorrectCertsSeveralSets()
+ throws PackageManager.NameNotFoundException {
+ ProviderInfo info = setupPackageManager();
+
+ List<List<byte[]>> certList = new ArrayList<>();
+ byte[] wrongCert = Base64.decode("this is a wrong cert", Base64.DEFAULT);
+ certList.add(Arrays.asList(wrongCert));
+ certList.add(Arrays.asList(BYTE_ARRAY));
+ FontRequest requestRightCerts = new FontRequest(
+ TestFontsProvider.AUTHORITY, PACKAGE_NAME, "query", certList);
+ ProviderInfo result = mContract.getProvider(requestRightCerts);
+
+ assertEquals(info, result);
+ }
+
+ public void testGetProvider_providerIsNonSystemAppWrongPackage()
+ throws PackageManager.NameNotFoundException {
+ setupPackageManager();
+
+ List<List<byte[]>> certList = new ArrayList<>();
+ certList.add(Arrays.asList(BYTE_ARRAY));
+ FontRequest requestRightCerts = new FontRequest(
+ TestFontsProvider.AUTHORITY, "com.wrong.package.name", "query", certList);
+ ProviderInfo result = mContract.getProvider(requestRightCerts);
+
+ assertNull(result);
+ }
+
+ private ProviderInfo setupPackageManager()
+ throws PackageManager.NameNotFoundException {
+ ProviderInfo info = new ProviderInfo();
+ info.packageName = PACKAGE_NAME;
+ info.applicationInfo = new ApplicationInfo();
+ when(mPackageManager.resolveContentProvider(anyString(), anyInt())).thenReturn(info);
+ PackageInfo packageInfo = new PackageInfo();
+ Signature signature = mock(Signature.class);
+ when(signature.toByteArray()).thenReturn(BYTE_ARRAY);
+ packageInfo.packageName = PACKAGE_NAME;
+ packageInfo.signatures = new Signature[] { signature };
+ when(mPackageManager.getPackageInfo(anyString(), anyInt())).thenReturn(packageInfo);
+ return info;
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.provider;
+
+import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.graphics.Typeface;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Provides a test Content Provider implementing {@link FontsContract}.
+ */
+public class TestFontsProvider extends ContentProvider {
+ static final String AUTHORITY = "android.provider.TestFontsProvider";
+ static final int TTC_INDEX = 2;
+ static final String VARIATION_SETTINGS = "'wdth' 1";
+ static final int STYLE = Typeface.BOLD;
+
+ private ParcelFileDescriptor mPfd;
+ private boolean mReturnAllFields = true;
+
+ /**
+ * Used by tests to switch whether all fields should be returned or not.
+ */
+ void setReturnAllFields(boolean returnAllFields) {
+ mReturnAllFields = returnAllFields;
+ }
+
+ @Override
+ public boolean onCreate() {
+ mPfd = createFontFile();
+ return true;
+ }
+
+ @Override
+ public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection,
+ @Nullable String[] selectionArgs, @Nullable String sortOrder) {
+ MatrixCursor cursor;
+ if (mReturnAllFields) {
+ cursor = new MatrixCursor(new String[] { FontsContract.Columns._ID,
+ FontsContract.Columns.TTC_INDEX, FontsContract.Columns.VARIATION_SETTINGS,
+ FontsContract.Columns.STYLE });
+ cursor.addRow(new Object[] { 1, TTC_INDEX, VARIATION_SETTINGS, STYLE });
+ } else {
+ cursor = new MatrixCursor(new String[] { FontsContract.Columns._ID });
+ cursor.addRow(new Object[] { 1 });
+ }
+ return cursor;
+ }
+
+ @Override
+ public ParcelFileDescriptor openFile(Uri uri, String mode) {
+ try {
+ return mPfd.dup();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ @Override
+ public String getType(@NonNull Uri uri) {
+ return "application/x-font-ttf";
+ }
+
+ @Override
+ public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
+ return null;
+ }
+
+ @Override
+ public int delete(@NonNull Uri uri, @Nullable String selection,
+ @Nullable String[] selectionArgs) {
+ return 0;
+ }
+
+ @Override
+ public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection,
+ @Nullable String[] selectionArgs) {
+ return 0;
+ }
+
+ private ParcelFileDescriptor createFontFile() {
+ try {
+ final File file = new File(getContext().getCacheDir(), "font.ttf");
+ file.getParentFile().mkdirs();
+ file.createNewFile();
+ return ParcelFileDescriptor.open(file, MODE_READ_ONLY);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+}
}
// Downloaded font and it wasn't cached, request it again and return a
// default font instead (nothing we can do now).
- create(new FontRequest(family.getProviderAuthority(), family.getQuery()),
- NO_OP_REQUEST_CALLBACK);
+ create(new FontRequest(family.getProviderAuthority(), family.getProviderPackage(),
+ family.getQuery()), NO_OP_REQUEST_CALLBACK);
return DEFAULT;
}
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.Base64;
import com.android.internal.util.Preconditions;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
/**
* Information about a font request that may be sent to a Font Provider.
*/
public final class FontRequest implements Parcelable {
private final String mProviderAuthority;
+ private final String mProviderPackage;
private final String mQuery;
+ private final List<List<byte[]>> mCertificates;
+
+ /**
+ * @param providerAuthority The authority of the Font Provider to be used for the request. This
+ * should be a system installed app.
+ * @param providerPackage The package for the Font Provider to be used for the request. This is
+ * used to verify the identity of the provider.
+ * @param query The query to be sent over to the provider. Refer to your font provider's
+ * documentation on the format of this string.
+ */
+ public FontRequest(@NonNull String providerAuthority, @NonNull String providerPackage,
+ @NonNull String query) {
+ mProviderAuthority = Preconditions.checkNotNull(providerAuthority);
+ mQuery = Preconditions.checkNotNull(query);
+ mProviderPackage = Preconditions.checkNotNull(providerPackage);
+ mCertificates = Collections.emptyList();
+ }
/**
* @param providerAuthority The authority of the Font Provider to be used for the request.
* @param query The query to be sent over to the provider. Refer to your font provider's
- * documentation on the format of this string.
+ * documentation on the format of this string.
+ * @param providerPackage The package for the Font Provider to be used for the request. This is
+ * used to verify the identity of the provider.
+ * @param certificates The list of sets of hashes for the certificates the provider should be
+ * signed with. This is used to verify the identity of the provider. Each set in the
+ * list represents one collection of signature hashes. Refer to your font provider's
+ * documentation for these values.
*/
- public FontRequest(@NonNull String providerAuthority, @NonNull String query) {
+ public FontRequest(@NonNull String providerAuthority, @NonNull String providerPackage,
+ @NonNull String query, @NonNull List<List<byte[]>> certificates) {
mProviderAuthority = Preconditions.checkNotNull(providerAuthority);
+ mProviderPackage = Preconditions.checkNotNull(providerPackage);
mQuery = Preconditions.checkNotNull(query);
+ mCertificates = Preconditions.checkNotNull(certificates);
}
/**
}
/**
+ * Returns the selected font provider's package. This helps the system verify that the provider
+ * identified by the given authority is the one requested.
+ */
+ public String getProviderPackage() {
+ return mProviderPackage;
+ }
+
+ /**
* Returns the query string. Refer to your font provider's documentation on the format of this
* string.
*/
return mQuery;
}
+ /**
+ * Returns the list of certificate sets given for this provider. This helps the system verify
+ * that the provider identified by the given authority is the one requested.
+ */
+ public List<List<byte[]>> getCertificates() {
+ return mCertificates;
+ }
+
@Override
public int describeContents() {
return 0;
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mProviderAuthority);
+ dest.writeString(mProviderPackage);
dest.writeString(mQuery);
+ dest.writeList(mCertificates);
}
private FontRequest(Parcel in) {
mProviderAuthority = in.readString();
+ mProviderPackage = in.readString();
mQuery = in.readString();
+ mCertificates = new ArrayList<>();
+ in.readList(mCertificates, null);
}
public static final Parcelable.Creator<FontRequest> CREATOR =
@Override
public String toString() {
- return "FontRequest {"
+ StringBuilder builder = new StringBuilder();
+ builder.append("FontRequest {"
+ "mProviderAuthority: " + mProviderAuthority
+ + ", mProviderPackage: " + mProviderPackage
+ ", mQuery: " + mQuery
- + "}";
+ + ", mCertificates:");
+ for (int i = 0; i < mCertificates.size(); i++) {
+ builder.append(" [");
+ List<byte[]> set = mCertificates.get(i);
+ for (int j = 0; j < set.size(); j++) {
+ builder.append(" \"");
+ byte[] array = set.get(j);
+ builder.append(Base64.encodeToString(array, Base64.DEFAULT));
+ builder.append("\"");
+ }
+ builder.append(" ]");
+ }
+ builder.append("}");
+ return builder.toString();
}
}