XML Support for non system font providers

The certs needed to identify non preinstalled providers
can now be declared in XML, using a resources array.

Test: runtest --path frameworks/base/core/tests/coretests/src/android/provider/FontsContractTest.java
runtest --path frameworks/base/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java
Bug: 35025705
Change-Id: Ibf12ad409aa5a873ddb32b1383c147728e664c23
This commit is contained in:
Clara Bayarri
2017-02-21 18:19:11 +00:00
parent c0e002b430
commit fb483cc90c
13 changed files with 200 additions and 26 deletions

View File

@@ -608,6 +608,7 @@ package android {
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 fontProviderCerts = 16844128; // 0x1010560
field public static final int fontProviderPackage = 16844122; // 0x101055a
field public static final int fontProviderQuery = 16844115; // 0x1010553
field public static final int fontStyle = 16844095; // 0x101053f

View File

@@ -721,6 +721,7 @@ package android {
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 fontProviderCerts = 16844128; // 0x1010560
field public static final int fontProviderPackage = 16844122; // 0x101055a
field public static final int fontProviderQuery = 16844115; // 0x1010553
field public static final int fontStyle = 16844095; // 0x101053f

View File

@@ -608,6 +608,7 @@ package android {
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 fontProviderCerts = 16844128; // 0x1010560
field public static final int fontProviderPackage = 16844122; // 0x101055a
field public static final int fontProviderQuery = 16844115; // 0x1010553
field public static final int fontStyle = 16844095; // 0x101053f

View File

@@ -26,6 +26,7 @@ import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
@@ -44,12 +45,14 @@ public class FontResourcesParser {
private final @NonNull String mProviderAuthority;
private final @NonNull String mProviderPackage;
private final @NonNull String mQuery;
private final @Nullable List<List<String>> mCerts;
public ProviderResourceEntry(@NonNull String authority, @NonNull String pkg,
@NonNull String query) {
@NonNull String query, @Nullable List<List<String>> certs) {
mProviderAuthority = authority;
mProviderPackage = pkg;
mQuery = query;
mCerts = certs;
}
public @NonNull String getAuthority() {
@@ -63,6 +66,10 @@ public class FontResourcesParser {
public @NonNull String getQuery() {
return mQuery;
}
public @Nullable List<List<String>> getCerts() {
return mCerts;
}
}
// A class represents font element in xml file which points a file in resource.
@@ -144,12 +151,33 @@ public class FontResourcesParser {
String authority = array.getString(R.styleable.FontFamily_fontProviderAuthority);
String providerPackage = array.getString(R.styleable.FontFamily_fontProviderPackage);
String query = array.getString(R.styleable.FontFamily_fontProviderQuery);
int certsId = array.getResourceId(R.styleable.FontFamily_fontProviderCerts, 0);
array.recycle();
if (authority != null && providerPackage != null && query != null) {
while (parser.next() != XmlPullParser.END_TAG) {
skip(parser);
}
return new ProviderResourceEntry(authority, providerPackage, query);
List<List<String>> certs = null;
if (certsId != 0) {
TypedArray typedArray = resources.obtainTypedArray(certsId);
if (typedArray.length() > 0) {
certs = new ArrayList<>();
boolean isArrayOfArrays = typedArray.getResourceId(0, 0) != 0;
if (isArrayOfArrays) {
for (int i = 0; i < typedArray.length(); i++) {
int certId = typedArray.getResourceId(i, 0);
String[] certsArray = resources.getStringArray(certId);
List<String> certsList = Arrays.asList(certsArray);
certs.add(certsList);
}
} else {
String[] certsArray = resources.getStringArray(certsId);
List<String> certsList = Arrays.asList(certsArray);
certs.add(certsList);
}
}
}
return new ProviderResourceEntry(authority, providerPackage, query, certs);
}
List<FontFileResourceEntry> fonts = new ArrayList<>();
while (parser.next() != XmlPullParser.END_TAG) {

View File

@@ -15,7 +15,6 @@
*/
package android.provider;
import android.app.ActivityThread;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
@@ -42,9 +41,10 @@ import com.android.internal.annotations.VisibleForTesting;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
/**
* Utility class to deal with Font ContentProviders.
@@ -207,11 +207,12 @@ public class FontsContract {
return info;
}
Set<byte[]> signatures;
List<byte[]> signatures;
try {
PackageInfo packageInfo = mPackageManager.getPackageInfo(info.packageName,
PackageManager.GET_SIGNATURES);
signatures = convertToSet(packageInfo.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);
@@ -219,8 +220,10 @@ public class FontsContract {
}
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)) {
// Make a copy so we can sort it without modifying the incoming data.
List<byte[]> requestSignatures = new ArrayList<>(requestCertificatesList.get(i));
Collections.sort(requestSignatures, sByteArrayComparator);
if (equalsByteArrayList(signatures, requestSignatures)) {
return info;
}
}
@@ -229,20 +232,38 @@ public class FontsContract {
return null;
}
private Set<byte[]> convertToSet(Signature[] signatures) {
Set<byte[]> shas = new HashSet<>();
private static final Comparator<byte[]> sByteArrayComparator = (l, r) -> {
if (l.length != r.length) {
return l.length - r.length;
}
for (int i = 0; i < l.length; ++i) {
if (l[i] != r[i]) {
return l[i] - r[i];
}
}
return 0;
};
private boolean equalsByteArrayList(List<byte[]> signatures, List<byte[]> requestSignatures) {
if (signatures.size() != requestSignatures.size()) {
return false;
}
for (int i = 0; i < signatures.size(); ++i) {
if (!Arrays.equals(signatures.get(i), requestSignatures.get(i))) {
return false;
}
}
return true;
}
private List<byte[]> convertToByteArrayList(Signature[] signatures) {
List<byte[]> shas = new ArrayList<>();
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,

View File

@@ -8569,6 +8569,7 @@
<attr name="fontProviderAuthority" format="string" />
<attr name="fontProviderPackage" format="string" />
<attr name="fontProviderQuery" format="string" />
<attr name="fontProviderCerts" format="reference" />
</declare-styleable>
<!-- @hide -->

View File

@@ -2809,6 +2809,7 @@
<public name="isStatic" />
<public name="isFeatureSplit" />
<public name="singleLineTitle" />
<public name="fontProviderCerts" />
</public-group>
<public-group type="style" first-id="0x010302e0">

View File

@@ -0,0 +1,7 @@
<?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:fontProviderQuery="MyRequestedFont"
android:fontProviderPackage="com.example.test.fontprovider.package"
android:fontProviderCerts="@array/certarray">
</font-family>

View File

@@ -0,0 +1,7 @@
<?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:fontProviderQuery="MyRequestedFont"
android:fontProviderPackage="com.example.test.fontprovider.package"
android:fontProviderCerts="@array/certs1">
</font-family>

View File

@@ -31,4 +31,19 @@
<item>2 days</item>
<item>1 week</item>
</string-array>
<string-array name="certs1">
<item>123456789</item>
<item>987654321</item>
</string-array>
<string-array name="certs2">
<item>abcdefg</item>
<item>gfedcba</item>
</string-array>
<array name="certarray">
<item>@array/certs1</item>
<item>@array/certs2</item>
</array>
</resources>

View File

@@ -15,21 +15,20 @@
*/
package android.content.res;
import static junit.framework.Assert.assertNull;
import static android.content.res.FontResourcesParser.FamilyResourceEntry;
import static android.content.res.FontResourcesParser.FontFamilyFilesResourceEntry;
import static android.content.res.FontResourcesParser.FontFileResourceEntry;
import static android.content.res.FontResourcesParser.ProviderResourceEntry;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static android.content.res.FontResourcesParser.FamilyResourceEntry;
import static android.content.res.FontResourcesParser.ProviderResourceEntry;
import static android.content.res.FontResourcesParser.FontFileResourceEntry;
import static android.content.res.FontResourcesParser.FontFamilyFilesResourceEntry;
import android.app.Instrumentation;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.text.FontConfig;
import com.android.frameworks.coretests.R;
@@ -97,4 +96,50 @@ public class FontResourcesParserTest {
assertEquals("com.example.test.fontprovider.package", providerEntry.getPackage());
assertEquals("MyRequestedFont", providerEntry.getQuery());
}
@Test
public void testParseDownloadableFont_singleCerts() throws IOException, XmlPullParserException {
XmlResourceParser parser = mResources.getXml(R.font.samplexmldownloadedfontsinglecerts);
FamilyResourceEntry result = FontResourcesParser.parse(parser, mResources);
assertNotNull(result);
assertTrue(result instanceof ProviderResourceEntry);
ProviderResourceEntry providerResourceEntry = (ProviderResourceEntry) result;
assertEquals("com.example.test.fontprovider", providerResourceEntry.getAuthority());
assertEquals("MyRequestedFont", providerResourceEntry.getQuery());
assertEquals("com.example.test.fontprovider.package", providerResourceEntry.getPackage());
List<List<String>> certList = providerResourceEntry.getCerts();
assertNotNull(certList);
assertEquals(1, certList.size());
List<String> certs = certList.get(0);
assertEquals(2, certs.size());
assertEquals("123456789", certs.get(0));
assertEquals("987654321", certs.get(1));
}
@Test
public void testParseDownloadableFont_multipleCerts() throws IOException, XmlPullParserException {
XmlResourceParser parser = mResources.getXml(R.font.samplexmldownloadedfontmulticerts);
FamilyResourceEntry result = FontResourcesParser.parse(parser, mResources);
assertNotNull(result);
assertTrue(result instanceof ProviderResourceEntry);
ProviderResourceEntry providerResourceEntry = (ProviderResourceEntry) result;
assertEquals("com.example.test.fontprovider", providerResourceEntry.getAuthority());
assertEquals("MyRequestedFont", providerResourceEntry.getQuery());
assertEquals("com.example.test.fontprovider.package", providerResourceEntry.getPackage());
List<List<String>> certList = providerResourceEntry.getCerts();
assertNotNull(certList);
assertEquals(2, certList.size());
List<String> certs1 = certList.get(0);
assertEquals(2, certs1.size());
assertEquals("123456789", certs1.get(0));
assertEquals("987654321", certs1.get(1));
List<String> certs2 = certList.get(1);
assertEquals(2, certs2.size());
assertEquals("abcdefg", certs2.get(0));
assertEquals("gfedcba", certs2.get(1));
}
}

View File

@@ -51,6 +51,11 @@ import java.util.List;
public class FontsContractTest extends ProviderTestCase2<TestFontsProvider> {
private static final byte[] BYTE_ARRAY =
Base64.decode("e04fd020ea3a6910a2d808002b30", Base64.DEFAULT);
// Use a different instance to test byte array comparison
private static final byte[] BYTE_ARRAY_COPY =
Base64.decode("e04fd020ea3a6910a2d808002b30", Base64.DEFAULT);
private static final byte[] BYTE_ARRAY_2 =
Base64.decode("e04fd020ea3a6910a2d808002b32", Base64.DEFAULT);
private static final String PACKAGE_NAME = "com.my.font.provider.package";
private final FontRequest request = new FontRequest(
@@ -268,6 +273,34 @@ public class FontsContractTest extends ProviderTestCase2<TestFontsProvider> {
assertNull(result);
}
public void testGetProvider_providerIsNonSystemAppDuplicateCerts()
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_COPY);
Signature signature2 = mock(Signature.class);
when(signature2.toByteArray()).thenReturn(BYTE_ARRAY_COPY);
packageInfo.packageName = PACKAGE_NAME;
packageInfo.signatures = new Signature[] { signature, signature2 };
when(mPackageManager.getPackageInfo(anyString(), anyInt())).thenReturn(packageInfo);
// The provider has {BYTE_ARRAY_COPY, BYTE_ARRAY_COPY}, the request has
// {BYTE_ARRAY_2, BYTE_ARRAY_COPY}.
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);
}
public void testGetProvider_providerIsNonSystemAppCorrectCertsSeveralSets()
throws PackageManager.NameNotFoundException {
ProviderInfo info = setupPackageManager();
@@ -306,7 +339,7 @@ public class FontsContractTest extends ProviderTestCase2<TestFontsProvider> {
when(mPackageManager.resolveContentProvider(anyString(), anyInt())).thenReturn(info);
PackageInfo packageInfo = new PackageInfo();
Signature signature = mock(Signature.class);
when(signature.toByteArray()).thenReturn(BYTE_ARRAY);
when(signature.toByteArray()).thenReturn(BYTE_ARRAY_COPY);
packageInfo.packageName = PACKAGE_NAME;
packageInfo.signatures = new Signature[] { signature };
when(mPackageManager.getPackageInfo(anyString(), anyInt())).thenReturn(packageInfo);

View File

@@ -38,6 +38,7 @@ import android.os.ParcelFileDescriptor;
import android.os.ResultReceiver;
import android.provider.FontsContract;
import android.text.FontConfig;
import android.util.Base64;
import android.util.Log;
import android.util.LongSparseArray;
import android.util.LruCache;
@@ -196,10 +197,22 @@ public class Typeface {
if (typeface != null) {
return typeface;
}
List<List<String>> givenCerts = providerEntry.getCerts();
List<List<byte[]>> certs = new ArrayList<>();
if (givenCerts != null) {
for (int i = 0; i < givenCerts.size(); i++) {
List<String> certSet = givenCerts.get(i);
List<byte[]> byteArraySet = new ArrayList<>();
for (int j = 0; j < certSet.size(); j++) {
byteArraySet.add(Base64.decode(certSet.get(j), Base64.DEFAULT));
}
certs.add(byteArraySet);
}
}
// Downloaded font and it wasn't cached, request it again and return a
// default font instead (nothing we can do now).
create(new FontRequest(providerEntry.getAuthority(), providerEntry.getPackage(),
providerEntry.getQuery()), NO_OP_REQUEST_CALLBACK);
providerEntry.getQuery(), certs), NO_OP_REQUEST_CALLBACK);
return DEFAULT;
}