From fb483cc90c27a9c0fcafa28343a8fd644f8384a4 Mon Sep 17 00:00:00 2001 From: Clara Bayarri Date: Tue, 21 Feb 2017 18:19:11 +0000 Subject: [PATCH] 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 --- api/current.txt | 1 + api/system-current.txt | 1 + api/test-current.txt | 1 + .../content/res/FontResourcesParser.java | 32 +++++++++- core/java/android/provider/FontsContract.java | 51 +++++++++++----- core/res/res/values/attrs.xml | 1 + core/res/res/values/public.xml | 1 + .../samplexmldownloadedfontmulticerts.xml | 7 +++ .../samplexmldownloadedfontsinglecerts.xml | 7 +++ core/tests/coretests/res/values/arrays.xml | 15 +++++ .../content/res/FontResourcesParserTest.java | 59 ++++++++++++++++--- .../android/provider/FontsContractTest.java | 35 ++++++++++- graphics/java/android/graphics/Typeface.java | 15 ++++- 13 files changed, 200 insertions(+), 26 deletions(-) create mode 100644 core/tests/coretests/res/font/samplexmldownloadedfontmulticerts.xml create mode 100644 core/tests/coretests/res/font/samplexmldownloadedfontsinglecerts.xml diff --git a/api/current.txt b/api/current.txt index ee4cadab8155a..006fa519577f7 100644 --- a/api/current.txt +++ b/api/current.txt @@ -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 diff --git a/api/system-current.txt b/api/system-current.txt index bf39eda22e1eb..caae0146e3407 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -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 diff --git a/api/test-current.txt b/api/test-current.txt index 40be895dff902..49310067efee6 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -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 diff --git a/core/java/android/content/res/FontResourcesParser.java b/core/java/android/content/res/FontResourcesParser.java index 091cc263f5b45..0edbc70c88395 100644 --- a/core/java/android/content/res/FontResourcesParser.java +++ b/core/java/android/content/res/FontResourcesParser.java @@ -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> mCerts; public ProviderResourceEntry(@NonNull String authority, @NonNull String pkg, - @NonNull String query) { + @NonNull String query, @Nullable List> 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> 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> 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 certsList = Arrays.asList(certsArray); + certs.add(certsList); + } + } else { + String[] certsArray = resources.getStringArray(certsId); + List certsList = Arrays.asList(certsArray); + certs.add(certsList); + } + } + } + return new ProviderResourceEntry(authority, providerPackage, query, certs); } List fonts = new ArrayList<>(); while (parser.next() != XmlPullParser.END_TAG) { diff --git a/core/java/android/provider/FontsContract.java b/core/java/android/provider/FontsContract.java index 84443e9e3f550..fd9d4db9842ec 100644 --- a/core/java/android/provider/FontsContract.java +++ b/core/java/android/provider/FontsContract.java @@ -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 signatures; + List 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> requestCertificatesList = request.getCertificates(); for (int i = 0; i < requestCertificatesList.size(); ++i) { - final Set requestCertificates = convertToSet(requestCertificatesList.get(i)); - if (signatures.equals(requestCertificates)) { + // Make a copy so we can sort it without modifying the incoming data. + List 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 convertToSet(Signature[] signatures) { - Set shas = new HashSet<>(); + private static final Comparator 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 signatures, List 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 convertToByteArrayList(Signature[] signatures) { + List shas = new ArrayList<>(); for (int i = 0; i < signatures.length; ++i) { shas.add(signatures[i].toByteArray()); } return shas; } - private Set convertToSet(List certs) { - Set shas = new HashSet<>(); - shas.addAll(certs); - return shas; - } - /** @hide */ @VisibleForTesting public void getFontFromProvider(FontRequest request, ResultReceiver receiver, diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index eecb021383e53..e1068ead93a94 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -8569,6 +8569,7 @@ + diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 8b1b9d39a4716..baad14812be39 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2809,6 +2809,7 @@ + diff --git a/core/tests/coretests/res/font/samplexmldownloadedfontmulticerts.xml b/core/tests/coretests/res/font/samplexmldownloadedfontmulticerts.xml new file mode 100644 index 0000000000000..7a753c3be538e --- /dev/null +++ b/core/tests/coretests/res/font/samplexmldownloadedfontmulticerts.xml @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/core/tests/coretests/res/font/samplexmldownloadedfontsinglecerts.xml b/core/tests/coretests/res/font/samplexmldownloadedfontsinglecerts.xml new file mode 100644 index 0000000000000..b834771af705e --- /dev/null +++ b/core/tests/coretests/res/font/samplexmldownloadedfontsinglecerts.xml @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/core/tests/coretests/res/values/arrays.xml b/core/tests/coretests/res/values/arrays.xml index f76da8579fc96..7a25707ca8a01 100644 --- a/core/tests/coretests/res/values/arrays.xml +++ b/core/tests/coretests/res/values/arrays.xml @@ -31,4 +31,19 @@ 2 days 1 week + + + 123456789 + 987654321 + + + + abcdefg + gfedcba + + + + @array/certs1 + @array/certs2 + diff --git a/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java b/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java index 82f46909cfa89..5e426e817a7d6 100644 --- a/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java +++ b/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java @@ -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> certList = providerResourceEntry.getCerts(); + assertNotNull(certList); + assertEquals(1, certList.size()); + List 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> certList = providerResourceEntry.getCerts(); + assertNotNull(certList); + assertEquals(2, certList.size()); + List certs1 = certList.get(0); + assertEquals(2, certs1.size()); + assertEquals("123456789", certs1.get(0)); + assertEquals("987654321", certs1.get(1)); + List certs2 = certList.get(1); + assertEquals(2, certs2.size()); + assertEquals("abcdefg", certs2.get(0)); + assertEquals("gfedcba", certs2.get(1)); + } } diff --git a/core/tests/coretests/src/android/provider/FontsContractTest.java b/core/tests/coretests/src/android/provider/FontsContractTest.java index d90fc2b0a9aa6..6820e92866a21 100644 --- a/core/tests/coretests/src/android/provider/FontsContractTest.java +++ b/core/tests/coretests/src/android/provider/FontsContractTest.java @@ -51,6 +51,11 @@ import java.util.List; public class FontsContractTest extends ProviderTestCase2 { 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 { 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 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 { 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); diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index 2afe3756a355a..c74f65b543f7c 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -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> givenCerts = providerEntry.getCerts(); + List> certs = new ArrayList<>(); + if (givenCerts != null) { + for (int i = 0; i < givenCerts.size(); i++) { + List certSet = givenCerts.get(i); + List 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; }