OSDN Git Service

Support non-system Font Providers
authorClara Bayarri <clarabayarri@google.com>
Tue, 7 Feb 2017 15:33:40 +0000 (15:33 +0000)
committerClara Bayarri <clarabayarri@google.com>
Tue, 7 Mar 2017 14:59:33 +0000 (14:59 +0000)
To do this, the developer must specify the set of certificate
hashes that represent the authority's app. This allows us to
verify that the authority we find is indeed the one intended
by the developer.

Bug: 35025705
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
CTS attached to topic
Change-Id: I605f9a93bbca8705936ead08efb4a5b4fdcc4882

14 files changed:
api/current.txt
api/system-current.txt
api/test-current.txt
core/java/android/content/res/FontResourcesParser.java
core/java/android/provider/FontsContract.java
core/java/android/text/FontConfig.java
core/res/res/values/attrs.xml
core/res/res/values/public.xml
core/tests/coretests/res/font/samplexmldownloadedfont.xml
core/tests/coretests/src/android/content/res/FontResourcesParserTest.java
core/tests/coretests/src/android/provider/FontsContractTest.java [new file with mode: 0644]
core/tests/coretests/src/android/provider/TestFontsProvider.java [new file with mode: 0644]
graphics/java/android/graphics/Typeface.java
graphics/java/android/graphics/fonts/FontRequest.java

index 38a9be8..f22f1af 100644 (file)
@@ -607,6 +607,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 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
@@ -14089,9 +14090,12 @@ package android.graphics.drawable.shapes {
 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;
index 6d6b321..5fbc11c 100644 (file)
@@ -719,6 +719,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 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
@@ -14806,9 +14807,12 @@ package android.graphics.drawable.shapes {
 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;
index 70eecde..f3d3dae 100644 (file)
@@ -607,6 +607,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 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
@@ -14128,9 +14129,12 @@ package android.graphics.drawable.shapes {
 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;
index 3f8f90e..50fc344 100644 (file)
@@ -67,13 +67,14 @@ public class FontResourcesParser {
         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) {
index 90e710f..96dd76b 100644 (file)
@@ -19,9 +19,12 @@ import android.app.ActivityThread;
 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;
@@ -34,9 +37,13 @@ import android.os.ResultReceiver;
 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.
@@ -107,6 +114,13 @@ public class FontsContract {
         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() {
@@ -133,31 +147,79 @@ public class FontsContract {
                 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 },
@@ -176,13 +238,16 @@ public class FontsContract {
                     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);
                     }
                 }
             }
@@ -190,8 +255,9 @@ public class FontsContract {
         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);
     }
 }
index 82e44dc..1087851 100644 (file)
@@ -403,6 +403,7 @@ public final class FontConfig implements Parcelable {
         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) {
@@ -411,18 +412,20 @@ public final class FontConfig implements Parcelable {
             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;
         }
 
@@ -435,6 +438,7 @@ public final class FontConfig implements Parcelable {
                 mFonts.add(new Font(origin.mFonts.get(i)));
             }
             mProviderAuthority = origin.mProviderAuthority;
+            mProviderPackage = origin.mProviderPackage;
             mQuery = origin.mQuery;
         }
 
@@ -476,6 +480,13 @@ public final class FontConfig implements Parcelable {
         /**
          * @hide
          */
+        public String getProviderPackage() {
+            return mProviderPackage;
+        }
+
+        /**
+         * @hide
+         */
         public String getQuery() {
             return mQuery;
         }
@@ -498,6 +509,11 @@ public final class FontConfig implements Parcelable {
                 mProviderAuthority = null;
             }
             if (in.readInt() == 1) {
+                mProviderPackage = in.readString();
+            } else {
+                mProviderPackage = null;
+            }
+            if (in.readInt() == 1) {
                 mQuery = in.readString();
             } else {
                 mQuery = null;
@@ -517,6 +533,10 @@ public final class FontConfig implements Parcelable {
             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);
index 55b154a..f8d5241 100644 (file)
     <!-- 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>
 
index df3962c..0d90cd2 100644 (file)
         <public name="requiredFeature" />
         <public name="requiredNotFeature" />
         <public name="autoFillHint" />
+        <public name="fontProviderPackage" />
     </public-group>
 
     <public-group type="style" first-id="0x010302e0">
index 35391bd..f1bdc47 100644 (file)
@@ -1,5 +1,6 @@
 <?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
index 8b536a7..23d3aa5 100644 (file)
@@ -91,7 +91,8 @@ public class FontResourcesParserTest {
         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());
     }
diff --git a/core/tests/coretests/src/android/provider/FontsContractTest.java b/core/tests/coretests/src/android/provider/FontsContractTest.java
new file mode 100644 (file)
index 0000000..db623a4
--- /dev/null
@@ -0,0 +1,235 @@
+/*
+ * 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;
+    }
+}
diff --git a/core/tests/coretests/src/android/provider/TestFontsProvider.java b/core/tests/coretests/src/android/provider/TestFontsProvider.java
new file mode 100644 (file)
index 0000000..6d40f37
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * 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;
+    }
+}
index 52e18a9..8b30903 100644 (file)
@@ -186,8 +186,8 @@ public class Typeface {
                 }
                 // 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;
             }
 
index e50df6f..c7a5830 100644 (file)
@@ -18,24 +18,56 @@ package android.graphics.fonts;
 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);
     }
 
     /**
@@ -47,6 +79,14 @@ public final class FontRequest implements Parcelable {
     }
 
     /**
+     * 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.
      */
@@ -54,6 +94,14 @@ public final class FontRequest implements Parcelable {
         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;
@@ -62,12 +110,17 @@ public final class FontRequest implements Parcelable {
     @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 =
@@ -85,9 +138,24 @@ public final class FontRequest implements Parcelable {
 
     @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();
     }
 }