Add unittests for "ambiguous" content providers

Add CTS tests describing the behavior of "ambiguous" content providers.
These are content providers where the provider doesn't specify an
android:exported line.

For API versions 16 and below, the default value of android:exported
is "true". Add "PermissionDeclareAppCompat", with target API level
16, and verify that it's content providers are visible.

Update the current tests, and verify that content providers which
don't have an "android:exported" line are not exported.

Bug: 3306452

Change-Id: Id7e8b0721bbc53d8e610a053c3f545fabd4ae596
diff --git a/CtsTestCaseList.mk b/CtsTestCaseList.mk
index e525c7c..60a8cf2 100644
--- a/CtsTestCaseList.mk
+++ b/CtsTestCaseList.mk
@@ -18,6 +18,7 @@
 	CtsExternalStorageApp \
 	CtsInstrumentationAppDiffCert \
 	CtsPermissionDeclareApp \
+	CtsPermissionDeclareAppCompat \
 	CtsSharedUidInstall \
 	CtsSharedUidInstallDiffCert \
 	CtsSimpleAppInstall \
diff --git a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/AppSecurityTests.java b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/AppSecurityTests.java
index 23d353e..e42ac3b 100644
--- a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/AppSecurityTests.java
+++ b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/AppSecurityTests.java
@@ -84,6 +84,9 @@
     // testPermissionDiffCert constants
     private static final String DECLARE_PERMISSION_APK = "CtsPermissionDeclareApp.apk";
     private static final String DECLARE_PERMISSION_PKG = "com.android.cts.permissiondeclareapp";
+    private static final String DECLARE_PERMISSION_COMPAT_APK = "CtsPermissionDeclareAppCompat.apk";
+    private static final String DECLARE_PERMISSION_COMPAT_PKG = "com.android.cts.permissiondeclareappcompat";
+
     private static final String PERMISSION_DIFF_CERT_APK = "CtsUsePermissionDiffCert.apk";
     private static final String PERMISSION_DIFF_CERT_PKG =
         "com.android.cts.usespermissiondiffcertapp";
@@ -311,6 +314,7 @@
         try {
             // cleanup test app that might be installed from previous partial test run
             getDevice().uninstallPackage(DECLARE_PERMISSION_PKG);
+            getDevice().uninstallPackage(DECLARE_PERMISSION_COMPAT_PKG);
             getDevice().uninstallPackage(PERMISSION_DIFF_CERT_PKG);
 
             String installResult = getDevice().installPackage(
@@ -318,6 +322,11 @@
             assertNull(String.format("failed to install declare permission app. Reason: %s",
                     installResult), installResult);
 
+            installResult = getDevice().installPackage(
+                    getTestAppFile(DECLARE_PERMISSION_COMPAT_APK), false);
+            assertNull(String.format("failed to install declare permission compat app. Reason: %s",
+                    installResult), installResult);
+
             // the app will install, but will get error at runtime
             installResult = getDevice().installPackage(getTestAppFile(PERMISSION_DIFF_CERT_APK),
                     false);
@@ -329,6 +338,7 @@
         }
         finally {
             getDevice().uninstallPackage(DECLARE_PERMISSION_PKG);
+            getDevice().uninstallPackage(DECLARE_PERMISSION_COMPAT_PKG);
             getDevice().uninstallPackage(PERMISSION_DIFF_CERT_PKG);
         }
     }
diff --git a/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/AndroidManifest.xml
index 00c996c..ad7a640 100644
--- a/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/AndroidManifest.xml
@@ -41,7 +41,8 @@
         <provider android:name="PermissionContentProvider"
                 android:authorities="ctspermissionwithsignature"
                 android:readPermission="com.android.cts.permissionWithSignature"
-                android:writePermission="com.android.cts.permissionWithSignature">
+                android:writePermission="com.android.cts.permissionWithSignature"
+                android:exported="true">
         </provider>
 
         <!-- Need a way for another app to try to access the permission, but will
@@ -49,7 +50,8 @@
         <provider android:name="PermissionContentProviderGranting"
                 android:authorities="ctspermissionwithsignaturegranting"
                 android:readPermission="com.android.cts.permissionWithSignature"
-                android:writePermission="com.android.cts.permissionWithSignature">
+                android:writePermission="com.android.cts.permissionWithSignature"
+                android:exported="true">
             <grant-uri-permission android:pathPattern="/foo.*" />
             <grant-uri-permission android:pathPattern="/yes.*" />
         </provider>
@@ -68,12 +70,19 @@
             <grant-uri-permission android:pathPattern="/yes.*" />
         </provider>
 
+        <!-- An ambiguous content provider, where "exported" was not specified.
+             Nobody should get access to this. -->
+        <provider android:name="AmbiguousContentProvider"
+                android:authorities="ctsambiguousprovider">
+        </provider>
+
         <!-- Target for tests about how path permissions interact with granting
              URI permissions. -->
         <provider android:name="PermissionContentProviderPath"
                 android:authorities="ctspermissionwithsignaturepath"
                 android:readPermission="com.android.cts.permissionNotUsedWithSignature"
-                android:writePermission="com.android.cts.permissionNotUsedWithSignature">
+                android:writePermission="com.android.cts.permissionNotUsedWithSignature"
+                android:exported="true">
             <path-permission
                     android:pathPrefix="/foo"
                     android:readPermission="com.android.cts.permissionWithSignature"
@@ -88,7 +97,8 @@
         <!-- Target for tests that verify path permissions can restrict access
              when no default top-level permission. -->
         <provider android:name="PermissionContentProviderPathRestricting"
-                android:authorities="ctspermissionwithsignaturepathrestricting">
+                android:authorities="ctspermissionwithsignaturepathrestricting"
+                android:exported="true">
             <!-- Require signature permission to get into path. -->
             <path-permission
                     android:pathPrefix="/foo"
diff --git a/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/AmbiguousContentProvider.java b/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/AmbiguousContentProvider.java
new file mode 100644
index 0000000..3536979
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/AmbiguousContentProvider.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2012 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 com.android.cts.permissiondeclareapp;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+/**
+ * Empty content provider, manifest did not declare exported=true nor exported=false.
+ */
+public class AmbiguousContentProvider extends ContentProvider {
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        // do nothing
+        return 0;
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        return "got/theMIME";
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        return null;
+    }
+
+    @Override
+    public boolean onCreate() {
+        return false;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder) {
+        return null;
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection,
+            String[] selectionArgs) {
+        return 0;
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/PermissionDeclareAppCompat/Android.mk b/hostsidetests/appsecurity/test-apps/PermissionDeclareAppCompat/Android.mk
new file mode 100644
index 0000000..acdc20f
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/PermissionDeclareAppCompat/Android.mk
@@ -0,0 +1,32 @@
+# Copyright (C) 2012 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := 16
+
+LOCAL_PACKAGE_NAME := CtsPermissionDeclareAppCompat
+
+# sign this app with a different cert than CtsUsePermissionDiffCert
+LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
+
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/PermissionDeclareAppCompat/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/PermissionDeclareAppCompat/AndroidManifest.xml
new file mode 100644
index 0000000..5bbf93f
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/PermissionDeclareAppCompat/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.permissiondeclareappcompat">
+
+    <application>
+        <!--
+             This provider doesn't specify an android:exported line. Because we specify
+             LOCAL_SDK_VERSION:=16 in Android.mk, we preserve the old behavior of defaulting
+             android:exported="true".
+         -->
+        <provider android:name="AmbiguousContentProvider"
+                android:authorities="ctsambiguousprovidercompat">
+        </provider>
+
+    </application>
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/PermissionDeclareAppCompat/src/com/android/cts/permissiondeclareappcompat/AmbiguousContentProvider.java b/hostsidetests/appsecurity/test-apps/PermissionDeclareAppCompat/src/com/android/cts/permissiondeclareappcompat/AmbiguousContentProvider.java
new file mode 100644
index 0000000..8665b70
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/PermissionDeclareAppCompat/src/com/android/cts/permissiondeclareappcompat/AmbiguousContentProvider.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2012 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 com.android.cts.permissiondeclareappcompat;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+/**
+ * Empty content provider, all permissions are enforced in manifest
+ */
+public class AmbiguousContentProvider extends ContentProvider {
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        // do nothing
+        return 0;
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        return "got/theUnspecifiedMIME";
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        return null;
+    }
+
+    @Override
+    public boolean onCreate() {
+        return false;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder) {
+        return null;
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection,
+            String[] selectionArgs) {
+        return 0;
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
index 9ef3977..a6495a9 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
@@ -45,9 +45,12 @@
             "content://ctspermissionwithsignaturepathrestricting");
     private static final Uri PRIV_URI = Uri.parse("content://ctsprivateprovider");
     private static final Uri PRIV_URI_GRANTING = Uri.parse("content://ctsprivateprovidergranting");
-
     private static final String EXPECTED_MIME_TYPE = "got/theMIME";
-    
+
+    private static final Uri AMBIGUOUS_URI_COMPAT = Uri.parse("content://ctsambiguousprovidercompat");
+    private static final String EXPECTED_MIME_TYPE_AMBIGUOUS = "got/theUnspecifiedMIME";
+    private static final Uri AMBIGUOUS_URI = Uri.parse("content://ctsambiguousprovider");
+
     private void assertReadingContentUriNotAllowed(Uri uri, String msg) {
         try {
             getContext().getContentResolver().query(uri, null, null, null, null);
@@ -61,7 +64,7 @@
         try {
             getContext().getContentResolver().query(uri, null, null, null, null);
         } catch (SecurityException e) {
-            fail("unexpected SecurityException reading " + uri);
+            fail("unexpected SecurityException reading " + uri + ": " + e.getMessage());
         }
     }
 
@@ -98,7 +101,7 @@
         try {
             getContext().getContentResolver().insert(uri, new ContentValues());
         } catch (SecurityException e) {
-            fail("unexpected SecurityException writing " + uri);
+            fail("unexpected SecurityException writing " + uri + ": " + e.getMessage());
         }
     }
 
@@ -145,8 +148,35 @@
      * since it is not exported from its app.
      */
     public void testReadProviderWhenPrivate() {
-        assertReadingContentUriNotAllowed(PRIV_URI,
-                "shouldn't read private provider");
+        assertReadingContentUriNotAllowed(PRIV_URI, "shouldn't read private provider");
+    }
+
+    /**
+     * Test that the ctsambiguousprovider content provider cannot be read,
+     * since it doesn't have an "exported=" line.
+     */
+    public void testReadProviderWhenAmbiguous() {
+        assertReadingContentUriNotAllowed(AMBIGUOUS_URI, "shouldn't read ambiguous provider");
+    }
+
+    /**
+     * Old App Compatibility Test
+     *
+     * Test that the ctsambiguousprovidercompat content provider can be read for older
+     * API versions, because it didn't specify either exported=true or exported=false.
+     */
+    public void testReadProviderWhenAmbiguousCompat() {
+        assertReadingContentUriAllowed(AMBIGUOUS_URI_COMPAT);
+    }
+
+    /**
+     * Old App Compatibility Test
+     *
+     * Test that the ctsambiguousprovidercompat content provider can be written for older
+     * API versions, because it didn't specify either exported=true or exported=false.
+     */
+    public void testWriteProviderWhenAmbiguousCompat() {
+        assertWritingContentUriAllowed(AMBIGUOUS_URI_COMPAT);
     }
 
     /**
@@ -154,8 +184,15 @@
      * since it is not exported from its app.
      */
     public void testWriteProviderWhenPrivate() {
-        assertWritingContentUriNotAllowed(PRIV_URI,
-                "shouldn't write private provider");
+        assertWritingContentUriNotAllowed(PRIV_URI, "shouldn't write private provider");
+    }
+
+    /**
+     * Test that the ctsambiguousprovider content provider cannot be written,
+     * since it doesn't have an exported= line.
+     */
+    public void testWriteProviderWhenAmbiguous() {
+        assertWritingContentUriNotAllowed(AMBIGUOUS_URI, "shouldn't write ambiguous provider");
     }
 
     private static ClipData makeSingleClipData(Uri uri) {
@@ -450,6 +487,14 @@
     }
 
     /**
+     * Test that the ctsambiguousprovider content provider can not grant
+     * URI permissions to others.
+     */
+    public void testGrantAmbiguousNonGrantingFail() {
+        doTestGrantUriPermissionFail(AMBIGUOUS_URI);
+    }
+
+    /**
      * Test that the ctsprivateprovidergranting content provider can not grant
      * URI permissions to paths outside of the grant tree
      */
@@ -1084,4 +1129,26 @@
         // All apps should be able to get MIME type even if provider is private.
         assertEquals(getContext().getContentResolver().getType(PRIV_URI), EXPECTED_MIME_TYPE);
     }
+
+    public void testGetMimeTypeAmbiguous() {
+        // Precondition: no current access.
+        assertReadingContentUriNotAllowed(AMBIGUOUS_URI, "shouldn't read when starting test");
+        assertWritingContentUriNotAllowed(AMBIGUOUS_URI, "shouldn't write when starting test");
+
+        // All apps should be able to get MIME type even if provider is private.
+        assertEquals(getContext().getContentResolver().getType(AMBIGUOUS_URI), EXPECTED_MIME_TYPE);
+    }
+
+    /**
+     * Old App Compatibility Test
+     *
+     * We should be able to access the mime type of a content provider of an older
+     * application, even if that application didn't explicitly declare either
+     * exported=true or exported=false
+     */
+    public void testGetMimeTypeAmbiguousCompat() {
+        // All apps should be able to get MIME type even if provider is private.
+        assertEquals(EXPECTED_MIME_TYPE_AMBIGUOUS,
+                getContext().getContentResolver().getType(AMBIGUOUS_URI_COMPAT));
+    }
 }