Tests for READ_EXTERNAL permission.

Verify that app with no permissions is allowed (and blocked) from
reading external storage.  Verify that app with only WRITE_EXTERNAL
is always able to read/write.

Bug: 6315473
Change-Id: I53483110c2d296c53350b48d38967b0bf3f38148
diff --git a/CtsTestCaseList.mk b/CtsTestCaseList.mk
index 2feb76b..e525c7c 100644
--- a/CtsTestCaseList.mk
+++ b/CtsTestCaseList.mk
@@ -15,6 +15,7 @@
 cts_security_apps_list := \
 	CtsAppAccessData \
 	CtsAppWithData \
+	CtsExternalStorageApp \
 	CtsInstrumentationAppDiffCert \
 	CtsPermissionDeclareApp \
 	CtsSharedUidInstall \
@@ -22,7 +23,8 @@
 	CtsSimpleAppInstall \
 	CtsSimpleAppInstallDiffCert \
 	CtsTargetInstrumentationApp \
-	CtsUsePermissionDiffCert
+	CtsUsePermissionDiffCert \
+	CtsWriteExternalStorageApp
 
 cts_support_packages := \
 	CtsAccelerationTestStubs \
diff --git a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/AppSecurityTests.java b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/AppSecurityTests.java
index 7eb3a33..dc75e7d 100644
--- a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/AppSecurityTests.java
+++ b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/AppSecurityTests.java
@@ -21,6 +21,7 @@
 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.result.CollectingTestListener;
 import com.android.tradefed.result.TestRunResult;
 import com.android.tradefed.testtype.DeviceTestCase;
@@ -58,6 +59,17 @@
     private static final String APP_ACCESS_DATA_APK = "CtsAppAccessData.apk";
     private static final String APP_ACCESS_DATA_PKG = "com.android.cts.appaccessdata";
 
+    // External storage constants
+    private static final String EXTERNAL_STORAGE_APP_APK = "CtsExternalStorageApp.apk";
+    private static final String EXTERNAL_STORAGE_APP_PKG = "com.android.cts.externalstorageapp";
+    private static final String EXTERNAL_STORAGE_APP_CLASS = EXTERNAL_STORAGE_APP_PKG
+            + ".ExternalStorageTest";
+    private static final String WRITE_EXTERNAL_STORAGE_APP_APK = "CtsWriteExternalStorageApp.apk";
+    private static final String
+            WRITE_EXTERNAL_STORAGE_APP_PKG = "com.android.cts.writeexternalstorageapp";
+    private static final String WRITE_EXTERNAL_STORAGE_APP_CLASS = WRITE_EXTERNAL_STORAGE_APP_PKG
+            + ".WriteExternalStorageTest";
+
     // testInstrumentationDiffCert constants
     private static final String TARGET_INSTRUMENT_APK = "CtsTargetInstrumentationApp.apk";
     private static final String TARGET_INSTRUMENT_PKG = "com.android.cts.targetinstrumentationapp";
@@ -72,6 +84,8 @@
     private static final String PERMISSION_DIFF_CERT_PKG =
         "com.android.cts.usespermissiondiffcertapp";
 
+    private static final String READ_EXTERNAL_STORAGE = "android.permission.READ_EXTERNAL_STORAGE";
+
     private static final String LOG_TAG = "AppSecurityTests";
 
     private CtsBuildHelper mCtsBuild;
@@ -179,6 +193,86 @@
     }
 
     /**
+     * Test behavior when
+     * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} is unenforced.
+     */
+    public void testReadExternalStorageUnenforced() throws Exception {
+        try {
+            getDevice().uninstallPackage(EXTERNAL_STORAGE_APP_PKG);
+            getDevice().uninstallPackage(WRITE_EXTERNAL_STORAGE_APP_PKG);
+
+            // stage test file on external storage
+            getDevice().pushString("CAEK", "/sdcard/meow");
+
+            // mark permission as not enforced
+            setPermissionEnforced(getDevice(), READ_EXTERNAL_STORAGE, false);
+
+            // install apps and run test
+            assertNull(getDevice()
+                    .installPackage(getTestAppFile(EXTERNAL_STORAGE_APP_APK), false));
+            assertNull(getDevice()
+                    .installPackage(getTestAppFile(WRITE_EXTERNAL_STORAGE_APP_APK), false));
+
+            // normal app should be able to read
+            assertTrue("Normal app unable to read external storage", runDeviceTests(
+                    EXTERNAL_STORAGE_APP_PKG, EXTERNAL_STORAGE_APP_CLASS,
+                    "testReadExternalStorage"));
+
+            // WRITE_EXTERNAL app should be able to read and write
+            assertTrue("WRITE_EXTERNAL app unable to read external storage", runDeviceTests(
+                    WRITE_EXTERNAL_STORAGE_APP_PKG, WRITE_EXTERNAL_STORAGE_APP_CLASS,
+                    "testReadExternalStorage"));
+            assertTrue("WRITE_EXTERNAL app unable to write external storage", runDeviceTests(
+                    WRITE_EXTERNAL_STORAGE_APP_PKG, WRITE_EXTERNAL_STORAGE_APP_CLASS,
+                    "testWriteExternalStorage"));
+
+        } finally {
+            getDevice().uninstallPackage(EXTERNAL_STORAGE_APP_PKG);
+            getDevice().uninstallPackage(WRITE_EXTERNAL_STORAGE_APP_PKG);
+        }
+    }
+
+    /**
+     * Test behavior when
+     * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} is enforced.
+     */
+    public void testReadExternalStorageEnforced() throws Exception {
+        try {
+            getDevice().uninstallPackage(EXTERNAL_STORAGE_APP_PKG);
+            getDevice().uninstallPackage(WRITE_EXTERNAL_STORAGE_APP_PKG);
+
+            // stage test file on external storage
+            getDevice().pushString("CAEK", "/sdcard/meow");
+
+            // mark permission as enforced
+            setPermissionEnforced(getDevice(), READ_EXTERNAL_STORAGE, true);
+
+            // install apps and run test
+            assertNull(getDevice()
+                    .installPackage(getTestAppFile(EXTERNAL_STORAGE_APP_APK), false));
+            assertNull(getDevice()
+                    .installPackage(getTestAppFile(WRITE_EXTERNAL_STORAGE_APP_APK), false));
+
+            // normal app should not be able to read
+            assertTrue("Normal app able to read external storage", runDeviceTests(
+                    EXTERNAL_STORAGE_APP_PKG, EXTERNAL_STORAGE_APP_CLASS,
+                    "testFailReadExternalStorage"));
+
+            // WRITE_EXTERNAL app should be able to read and write
+            assertTrue("WRITE_EXTERNAL app unable to read external storage", runDeviceTests(
+                    WRITE_EXTERNAL_STORAGE_APP_PKG, WRITE_EXTERNAL_STORAGE_APP_CLASS,
+                    "testReadExternalStorage"));
+            assertTrue("WRITE_EXTERNAL app unable to write external storage", runDeviceTests(
+                    WRITE_EXTERNAL_STORAGE_APP_PKG, WRITE_EXTERNAL_STORAGE_APP_CLASS,
+                    "testWriteExternalStorage"));
+
+        } finally {
+            getDevice().uninstallPackage(EXTERNAL_STORAGE_APP_PKG);
+            getDevice().uninstallPackage(WRITE_EXTERNAL_STORAGE_APP_PKG);
+        }
+    }
+
+    /**
      * Test that uninstall of an app removes its private data.
      */
     public void testUninstallRemovesData() throws Exception {
@@ -319,4 +413,10 @@
         return listener.getCurrentRunResults();
     }
 
+    private static void setPermissionEnforced(
+            ITestDevice device, String permission, boolean enforced)
+            throws DeviceNotAvailableException {
+        device.executeShellCommand("pm set-permission-enforced " + permission + " "
+                + Boolean.toString(enforced));
+    }
 }
diff --git a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/Android.mk b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/Android.mk
new file mode 100644
index 0000000..3c687f1
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/Android.mk
@@ -0,0 +1,25 @@
+# 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_SDK_VERSION := 10
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_PACKAGE_NAME := CtsExternalStorageApp
+
+include $(BUILD_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/AndroidManifest.xml
new file mode 100644
index 0000000..0ba6684
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?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.externalstorageapp">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.test.InstrumentationTestRunner"
+        android:targetPackage="com.android.cts.externalstorageapp" />
+
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/ExternalStorageTest.java b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/ExternalStorageTest.java
new file mode 100644
index 0000000..d42353d
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/ExternalStorageTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.externalstorageapp;
+
+import android.os.Environment;
+import android.test.AndroidTestCase;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Test if {@link Environment#getExternalStorageDirectory()} is readable.
+ */
+public class ExternalStorageTest extends AndroidTestCase {
+
+    private static final String TEST_FILE = "meow";
+
+    private void assertExternalStorageMounted() {
+        assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
+    }
+
+    private void readExternalStorage() throws IOException {
+        final File file = new File(Environment.getExternalStorageDirectory(), TEST_FILE);
+        final InputStream is = new FileInputStream(file);
+        try {
+            is.read();
+        } finally {
+            is.close();
+        }
+    }
+
+    public void testReadExternalStorage() throws Exception {
+        assertExternalStorageMounted();
+        try {
+            readExternalStorage();
+        } catch (IOException e) {
+            fail("unable to read external file");
+        }
+    }
+
+    public void testFailReadExternalStorage() throws Exception {
+        assertExternalStorageMounted();
+        try {
+            readExternalStorage();
+            fail("able read external file");
+        } catch (IOException e) {
+            // expected
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/Android.mk b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/Android.mk
new file mode 100644
index 0000000..bdb2887
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/Android.mk
@@ -0,0 +1,25 @@
+# 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_SDK_VERSION := 10
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_PACKAGE_NAME := CtsWriteExternalStorageApp
+
+include $(BUILD_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/AndroidManifest.xml
new file mode 100644
index 0000000..82910aa
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?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.writeexternalstorageapp">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.test.InstrumentationTestRunner"
+        android:targetPackage="com.android.cts.writeexternalstorageapp" />
+
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/src/com/android/cts/writeexternalstorageapp/WriteExternalStorageTest.java b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/src/com/android/cts/writeexternalstorageapp/WriteExternalStorageTest.java
new file mode 100644
index 0000000..b899bb0
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/src/com/android/cts/writeexternalstorageapp/WriteExternalStorageTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.writeexternalstorageapp;
+
+import android.os.Environment;
+import android.test.AndroidTestCase;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Test if {@link Environment#getExternalStorageDirectory()} is writable.
+ */
+public class WriteExternalStorageTest extends AndroidTestCase {
+
+    private static final String TEST_FILE = "meow";
+
+    private void assertExternalStorageMounted() {
+        assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
+    }
+
+    private void readExternalStorage() throws IOException {
+        final File file = new File(Environment.getExternalStorageDirectory(), TEST_FILE);
+        final InputStream is = new FileInputStream(file);
+        try {
+            is.read();
+        } finally {
+            is.close();
+        }
+    }
+
+    private void writeExternalStorage() throws IOException {
+        final File file = new File(Environment.getExternalStorageDirectory(), TEST_FILE);
+        final OutputStream os = new FileOutputStream(file);
+        try {
+            os.write(32);
+        } finally {
+            os.close();
+        }
+    }
+
+    public void testReadExternalStorage() throws Exception {
+        assertExternalStorageMounted();
+        try {
+            readExternalStorage();
+        } catch (IOException e) {
+            fail("unable to read external file");
+        }
+    }
+
+    public void testWriteExternalStorage() throws Exception {
+        assertExternalStorageMounted();
+        try {
+            writeExternalStorage();
+        } catch (IOException e) {
+            fail("unable to read external file");
+        }
+    }
+
+}