am 9410c3b9: am 166f0aac: Merge "Test new READ/WRITE_EXTERNAL_STORAGE behavior." into klp-dev

* commit '9410c3b924943e9e6da633939a45c462c2de4954':
  Test new READ/WRITE_EXTERNAL_STORAGE behavior.
diff --git a/CtsTestCaseList.mk b/CtsTestCaseList.mk
index 755cb45..5d23eb5 100644
--- a/CtsTestCaseList.mk
+++ b/CtsTestCaseList.mk
@@ -19,6 +19,7 @@
 	CtsInstrumentationAppDiffCert \
 	CtsPermissionDeclareApp \
 	CtsPermissionDeclareAppCompat \
+	CtsReadExternalStorageApp \
 	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 3779db9..88b05fb 100644
--- a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/AppSecurityTests.java
+++ b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/AppSecurityTests.java
@@ -17,7 +17,6 @@
 package com.android.cts.appsecurity;
 
 import com.android.cts.tradefed.build.CtsBuildHelper;
-import com.android.ddmlib.IDevice;
 import com.android.ddmlib.Log;
 import com.android.ddmlib.testrunner.InstrumentationResultParser;
 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
@@ -66,15 +65,16 @@
     private static final String APP_ACCESS_DATA_PKG = "com.android.cts.appaccessdata";
 
     // External storage constants
+    private static final String COMMON_EXTERNAL_STORAGE_APP_CLASS = "com.android.cts.externalstorageapp.CommonExternalStorageTest";
     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 EXTERNAL_STORAGE_APP_CLASS = EXTERNAL_STORAGE_APP_PKG + ".ExternalStorageTest";
+    private static final String READ_EXTERNAL_STORAGE_APP_APK = "CtsReadExternalStorageApp.apk";
+    private static final String READ_EXTERNAL_STORAGE_APP_PKG = "com.android.cts.readexternalstorageapp";
+    private static final String READ_EXTERNAL_STORAGE_APP_CLASS = READ_EXTERNAL_STORAGE_APP_PKG + ".ReadExternalStorageTest";
     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";
+    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";
@@ -207,20 +207,89 @@
     }
 
     /**
-     * Verify that legacy filesystem paths continue working, and that they all
-     * point to same location.
+     * Verify that app with no external storage permissions works correctly.
      */
-    public void testExternalStorageLegacyPaths() throws Exception {
+    public void testExternalStorageNone() throws Exception {
         try {
+            wipePrimaryExternalStorage(getDevice());
+
+            getDevice().uninstallPackage(EXTERNAL_STORAGE_APP_PKG);
+            assertNull(getDevice()
+                    .installPackage(getTestAppFile(EXTERNAL_STORAGE_APP_APK), false));
+            assertTrue("Failed external storage with no permissions",
+                    runDeviceTests(EXTERNAL_STORAGE_APP_PKG));
+        } finally {
+            getDevice().uninstallPackage(EXTERNAL_STORAGE_APP_PKG);
+        }
+    }
+
+    /**
+     * Verify that app with
+     * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} works
+     * correctly.
+     */
+    public void testExternalStorageRead() throws Exception {
+        try {
+            wipePrimaryExternalStorage(getDevice());
+
+            getDevice().uninstallPackage(READ_EXTERNAL_STORAGE_APP_PKG);
+            assertNull(getDevice()
+                    .installPackage(getTestAppFile(READ_EXTERNAL_STORAGE_APP_APK), false));
+            assertTrue("Failed external storage with read permissions",
+                    runDeviceTests(READ_EXTERNAL_STORAGE_APP_PKG));
+        } finally {
+            getDevice().uninstallPackage(READ_EXTERNAL_STORAGE_APP_PKG);
+        }
+    }
+
+    /**
+     * Verify that app with
+     * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} works
+     * correctly.
+     */
+    public void testExternalStorageWrite() throws Exception {
+        try {
+            wipePrimaryExternalStorage(getDevice());
+
             getDevice().uninstallPackage(WRITE_EXTERNAL_STORAGE_APP_PKG);
             assertNull(getDevice()
                     .installPackage(getTestAppFile(WRITE_EXTERNAL_STORAGE_APP_APK), false));
+            assertTrue("Failed external storage with write permissions",
+                    runDeviceTests(WRITE_EXTERNAL_STORAGE_APP_PKG));
+        } finally {
+            getDevice().uninstallPackage(WRITE_EXTERNAL_STORAGE_APP_PKG);
+        }
+    }
 
-            assertTrue("Failed to verify legacy filesystem paths", runDeviceTests(
-                    WRITE_EXTERNAL_STORAGE_APP_PKG, WRITE_EXTERNAL_STORAGE_APP_CLASS,
-                    "testLegacyPaths"));
+    /**
+     * Verify that app with WRITE_EXTERNAL can leave gifts in external storage
+     * directories belonging to other apps, and those apps can read.
+     */
+    public void testExternalStorageGifts() throws Exception {
+        try {
+            wipePrimaryExternalStorage(getDevice());
+
+            getDevice().uninstallPackage(EXTERNAL_STORAGE_APP_PKG);
+            getDevice().uninstallPackage(READ_EXTERNAL_STORAGE_APP_PKG);
+            getDevice().uninstallPackage(WRITE_EXTERNAL_STORAGE_APP_PKG);
+            assertNull(getDevice()
+                    .installPackage(getTestAppFile(EXTERNAL_STORAGE_APP_APK), false));
+            assertNull(getDevice()
+                    .installPackage(getTestAppFile(READ_EXTERNAL_STORAGE_APP_APK), false));
+            assertNull(getDevice()
+                    .installPackage(getTestAppFile(WRITE_EXTERNAL_STORAGE_APP_APK), false));
+
+            assertTrue("Failed to write gifts", runDeviceTests(WRITE_EXTERNAL_STORAGE_APP_PKG,
+                    WRITE_EXTERNAL_STORAGE_APP_CLASS, "doWriteGifts"));
+
+            assertTrue("Read failed to verify gifts", runDeviceTests(READ_EXTERNAL_STORAGE_APP_PKG,
+                    READ_EXTERNAL_STORAGE_APP_CLASS, "doVerifyGifts"));
+            assertTrue("None failed to verify gifts", runDeviceTests(EXTERNAL_STORAGE_APP_PKG,
+                    EXTERNAL_STORAGE_APP_CLASS, "doVerifyGifts"));
 
         } finally {
+            getDevice().uninstallPackage(EXTERNAL_STORAGE_APP_PKG);
+            getDevice().uninstallPackage(READ_EXTERNAL_STORAGE_APP_PKG);
             getDevice().uninstallPackage(WRITE_EXTERNAL_STORAGE_APP_PKG);
         }
     }
@@ -500,4 +569,9 @@
         getDevice().executeShellCommand(cmd, parser);
         return listener.getCurrentRunResults();
     }
+
+    private static void wipePrimaryExternalStorage(ITestDevice device)
+            throws DeviceNotAvailableException {
+        device.executeShellCommand("rm -rf /sdcard/*");
+    }
 }
diff --git a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/Android.mk b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/Android.mk
index 91d6ccf..bc99560 100644
--- a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/Android.mk
@@ -17,7 +17,7 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE_TAGS := tests
-LOCAL_SDK_VERSION := 10
+LOCAL_SDK_VERSION := current
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_PACKAGE_NAME := CtsExternalStorageApp
diff --git a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java
new file mode 100644
index 0000000..340ebed
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java
@@ -0,0 +1,284 @@
+/*
+ * 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.content.Context;
+import android.os.Environment;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Tests common functionality that should be supported regardless of external
+ * storage status.
+ */
+public class CommonExternalStorageTest extends AndroidTestCase {
+    private static final String TAG = "CommonExternalStorageTest";
+
+    public static final String PACKAGE_NONE = "com.android.cts.externalstorageapp";
+    public static final String PACKAGE_READ = "com.android.cts.readexternalstorageapp";
+    public static final String PACKAGE_WRITE = "com.android.cts.writeexternalstorageapp";
+
+    /**
+     * Primary storage must always be mounted.
+     */
+    public void testExternalStorageMounted() {
+        assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
+    }
+
+    /**
+     * Verify that single path is always first item in multiple.
+     */
+    public void testMultipleCacheDirs() throws Exception {
+        final File single = getContext().getExternalCacheDir();
+        assertNotNull("Primary storage must always be available", single);
+        final File firstMultiple = getContext().getExternalCacheDirs()[0];
+        assertEquals(single, firstMultiple);
+    }
+
+    /**
+     * Verify that single path is always first item in multiple.
+     */
+    public void testMultipleFilesDirs() throws Exception {
+        final File single = getContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES);
+        assertNotNull("Primary storage must always be available", single);
+        final File firstMultiple = getContext()
+                .getExternalFilesDirs(Environment.DIRECTORY_PICTURES)[0];
+        assertEquals(single, firstMultiple);
+    }
+
+    /**
+     * Verify that single path is always first item in multiple.
+     */
+    public void testMultipleObbDirs() throws Exception {
+        final File single = getContext().getObbDir();
+        assertNotNull("Primary storage must always be available", single);
+        final File firstMultiple = getContext().getObbDirs()[0];
+        assertEquals(single, firstMultiple);
+    }
+
+    /**
+     * Verify we can write to our own package dirs.
+     */
+    public void testPackageDirs() throws Exception {
+        final List<File> paths = getAllPackageSpecificPaths(getContext());
+        for (File path : paths) {
+            if (path == null) continue;
+
+            assertEquals(Environment.MEDIA_MOUNTED, Environment.getStorageState(path));
+            assertDirReadWriteAccess(path);
+
+            final File directChild = new File(path, "directChild");
+            final File subdir = new File(path, "subdir");
+            final File subdirChild = new File(path, "subdirChild");
+
+            writeInt(directChild, 32);
+            subdir.mkdirs();
+            assertDirReadWriteAccess(subdir);
+            writeInt(subdirChild, 64);
+
+            assertEquals(32, readInt(directChild));
+            assertEquals(64, readInt(subdirChild));
+        }
+
+        for (File path : paths) {
+            deleteContents(path);
+        }
+    }
+
+    /**
+     * Return a set of several package-specific external storage paths.
+     */
+    public static List<File> getAllPackageSpecificPaths(Context context) {
+        final List<File> paths = new ArrayList<File>();
+        Collections.addAll(paths, context.getExternalCacheDirs());
+        Collections.addAll(paths, context.getExternalFilesDirs(null));
+        Collections.addAll(paths, context.getExternalFilesDirs(Environment.DIRECTORY_PICTURES));
+        Collections.addAll(paths, context.getObbDirs());
+        return paths;
+    }
+
+    public static List<File> getPrimaryPackageSpecificPaths(Context context) {
+        final List<File> paths = new ArrayList<File>();
+        Collections.addAll(paths, context.getExternalCacheDir());
+        Collections.addAll(paths, context.getExternalFilesDir(null));
+        Collections.addAll(paths, context.getExternalFilesDir(Environment.DIRECTORY_PICTURES));
+        Collections.addAll(paths, context.getObbDir());
+        return paths;
+    }
+
+    public static List<File> getSecondaryPackageSpecificPaths(Context context) {
+        final List<File> paths = new ArrayList<File>();
+        Collections.addAll(paths, dropFirst(context.getExternalCacheDirs()));
+        Collections.addAll(paths, dropFirst(context.getExternalFilesDirs(null)));
+        Collections.addAll(
+                paths, dropFirst(context.getExternalFilesDirs(Environment.DIRECTORY_PICTURES)));
+        Collections.addAll(paths, dropFirst(context.getObbDirs()));
+        return paths;
+    }
+
+    private static File[] dropFirst(File[] before) {
+        final File[] after = new File[before.length - 1];
+        System.arraycopy(before, 1, after, 0, after.length);
+        return after;
+    }
+
+    public static File buildGiftForPackage(Context context, String packageName) {
+        final File myCache = context.getExternalCacheDir();
+        return new File(myCache.getAbsolutePath().replace(context.getPackageName(), packageName),
+                packageName + ".gift");
+    }
+
+    public static void assertDirReadOnlyAccess(File path) {
+        Log.d(TAG, "Asserting read-only access to " + path);
+
+        assertTrue("exists", path.exists());
+        assertTrue("read", path.canRead());
+        assertTrue("execute", path.canExecute());
+        assertNotNull("list", path.list());
+
+        try {
+            final File probe = new File(path, ".probe");
+            assertTrue(!probe.exists());
+            probe.createNewFile();
+            probe.delete();
+            fail("able to create probe!");
+        } catch (IOException e) {
+            // expected
+        }
+    }
+
+    public static void assertDirReadWriteAccess(File path) {
+        Log.d(TAG, "Asserting read/write access to " + path);
+
+        assertTrue("exists", path.exists());
+        assertTrue("read", path.canRead());
+        assertTrue("execute", path.canExecute());
+        assertNotNull("list", path.list());
+
+        try {
+            final File probe = new File(path, ".probe");
+            assertTrue(!probe.exists());
+            probe.createNewFile();
+            probe.delete();
+        } catch (IOException e) {
+            fail("failed to create probe!");
+        }
+    }
+
+    public static void assertDirNoAccess(File path) {
+        Log.d(TAG, "Asserting no access to " + path);
+
+        assertFalse("read", path.canRead());
+        assertNull("list", path.list());
+
+        try {
+            final File probe = new File(path, ".probe");
+            assertTrue(!probe.exists());
+            probe.createNewFile();
+            probe.delete();
+            fail("able to create probe!");
+        } catch (IOException e) {
+            // expected
+        }
+    }
+
+    public static void assertFileReadOnlyAccess(File path) {
+        try {
+            new FileInputStream(path).close();
+        } catch (IOException e) {
+            fail("failed to read!");
+        }
+
+        try {
+            new FileOutputStream(path, true).close();
+            fail("able to write!");
+        } catch (IOException e) {
+            // expected
+        }
+    }
+
+    public static void assertFileReadWriteAccess(File path) {
+        try {
+            new FileInputStream(path).close();
+        } catch (IOException e) {
+            fail("failed to read!");
+        }
+
+        try {
+            new FileOutputStream(path, true).close();
+        } catch (IOException e) {
+            fail("failed to write!");
+        }
+    }
+
+    public static void assertFileNoAccess(File path) {
+        try {
+            new FileInputStream(path).close();
+            fail("able to read!");
+        } catch (IOException e) {
+            // expected
+        }
+
+        try {
+            new FileOutputStream(path, true).close();
+            fail("able to write!");
+        } catch (IOException e) {
+            // expected
+        }
+    }
+
+    public static void deleteContents(File dir) throws IOException {
+        File[] files = dir.listFiles();
+        if (files != null) {
+            for (File file : files) {
+                if (file.isDirectory()) {
+                    deleteContents(file);
+                }
+                assertTrue(file.delete());
+            }
+            assertEquals(0, dir.listFiles().length);
+        }
+    }
+
+    public static void writeInt(File file, int value) throws IOException {
+        final DataOutputStream os = new DataOutputStream(new FileOutputStream(file));
+        try {
+            os.writeInt(value);
+        } finally {
+            os.close();
+        }
+    }
+
+    public static int readInt(File file) throws IOException {
+        final DataInputStream is = new DataInputStream(new FileInputStream(file));
+        try {
+            return is.readInt();
+        } finally {
+            is.close();
+        }
+    }
+}
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
index a3fcf4a..9d24f98 100644
--- 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
@@ -16,43 +16,69 @@
 
 package com.android.cts.externalstorageapp;
 
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_NONE;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_READ;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_WRITE;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirNoAccess;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirReadWriteAccess;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertFileNoAccess;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertFileReadWriteAccess;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.buildGiftForPackage;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.getAllPackageSpecificPaths;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.readInt;
+
 import android.os.Environment;
 import android.test.AndroidTestCase;
 
 import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
+import java.util.List;
 
 /**
- * Test if {@link Environment#getExternalStorageDirectory()} is readable.
+ * Test external storage from an application that has no external storage
+ * permissions.
  */
 public class ExternalStorageTest extends AndroidTestCase {
 
-    private static final String TEST_FILE = "meow";
-
-    private void assertExternalStorageMounted() {
-        assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
+    public void testPrimaryNoAccess() throws Exception {
+        assertDirNoAccess(Environment.getExternalStorageDirectory());
     }
 
-    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();
+    /**
+     * Verify that above our package directories we always have no access.
+     */
+    public void testAllWalkingUpTreeNoAccess() throws Exception {
+        final List<File> paths = getAllPackageSpecificPaths(getContext());
+        final String packageName = getContext().getPackageName();
+
+        for (File path : paths) {
+            assertTrue(path.getAbsolutePath().contains(packageName));
+
+            // Walk up until we drop our package
+            while (path.getAbsolutePath().contains(packageName)) {
+                assertDirReadWriteAccess(path);
+                path = path.getParentFile();
+            }
+
+            // Keep walking up until we leave device
+            while (Environment.MEDIA_MOUNTED.equals(Environment.getStorageState(path))) {
+                assertDirNoAccess(path);
+                path = path.getParentFile();
+            }
         }
     }
 
-    public void testFailReadExternalStorage() throws Exception {
-        assertExternalStorageMounted();
-        try {
-            readExternalStorage();
-            fail("able read external file");
-        } catch (IOException e) {
-            // expected
-            e.printStackTrace();
-        }
+    /**
+     * Verify we can read only our gifts.
+     */
+    public void doVerifyGifts() throws Exception {
+        final File none = buildGiftForPackage(getContext(), PACKAGE_NONE);
+        assertFileReadWriteAccess(none);
+        assertEquals(100, readInt(none));
+
+        final File read = buildGiftForPackage(getContext(), PACKAGE_READ);
+        assertFileNoAccess(read);
+
+        final File write = buildGiftForPackage(getContext(), PACKAGE_WRITE);
+        assertFileNoAccess(write);
     }
 }
diff --git a/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/Android.mk b/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/Android.mk
new file mode 100644
index 0000000..44e4bef
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/Android.mk
@@ -0,0 +1,29 @@
+# Copyright (C) 2013 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 := current
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+    ../ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java
+
+LOCAL_PACKAGE_NAME := CtsReadExternalStorageApp
+
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/AndroidManifest.xml
new file mode 100644
index 0000000..f6582b9
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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.readexternalstorageapp">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.test.InstrumentationTestRunner"
+        android:targetPackage="com.android.cts.readexternalstorageapp" />
+
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/src/com/android/cts/readexternalstorageapp/ReadExternalStorageTest.java b/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/src/com/android/cts/readexternalstorageapp/ReadExternalStorageTest.java
new file mode 100644
index 0000000..d315651
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/src/com/android/cts/readexternalstorageapp/ReadExternalStorageTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.readexternalstorageapp;
+
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_NONE;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_READ;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_WRITE;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirReadOnlyAccess;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirReadWriteAccess;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertFileReadOnlyAccess;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertFileReadWriteAccess;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.buildGiftForPackage;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.getAllPackageSpecificPaths;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.readInt;
+
+import android.os.Environment;
+import android.test.AndroidTestCase;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * Test external storage from an application that has
+ * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE}.
+ */
+public class ReadExternalStorageTest extends AndroidTestCase {
+
+    public void testPrimaryReadOnly() throws Exception {
+        assertDirReadOnlyAccess(Environment.getExternalStorageDirectory());
+    }
+
+    /**
+     * Verify that above our package directories we always have read only
+     * access.
+     */
+    public void testAllWalkingUpTreeReadOnly() throws Exception {
+        final List<File> paths = getAllPackageSpecificPaths(getContext());
+        final String packageName = getContext().getPackageName();
+
+        for (File path : paths) {
+            assertTrue(path.getAbsolutePath().contains(packageName));
+
+            // Walk up until we drop our package
+            while (path.getAbsolutePath().contains(packageName)) {
+                assertDirReadWriteAccess(path);
+                path = path.getParentFile();
+            }
+
+            // Keep walking up until we leave device
+            while (Environment.MEDIA_MOUNTED.equals(Environment.getStorageState(path))) {
+                assertDirReadOnlyAccess(path);
+                path = path.getParentFile();
+            }
+        }
+    }
+
+    /**
+     * Verify we can read all gifts.
+     */
+    public void doVerifyGifts() throws Exception {
+        final File none = buildGiftForPackage(getContext(), PACKAGE_NONE);
+        assertFileReadOnlyAccess(none);
+        assertEquals(100, readInt(none));
+
+        final File read = buildGiftForPackage(getContext(), PACKAGE_READ);
+        assertFileReadWriteAccess(read);
+        assertEquals(101, readInt(read));
+
+        final File write = buildGiftForPackage(getContext(), PACKAGE_WRITE);
+        assertFileReadOnlyAccess(write);
+        assertEquals(102, readInt(write));
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/Android.mk b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/Android.mk
index 9e056a9..4352bfb 100644
--- a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/Android.mk
@@ -17,9 +17,11 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE_TAGS := tests
-LOCAL_SDK_VERSION := 10
+LOCAL_SDK_VERSION := current
 
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+    ../ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java
+
 LOCAL_PACKAGE_NAME := CtsWriteExternalStorageApp
 
 LOCAL_DEX_PREOPT := false
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
index 3f103b6..ff2f1b7 100644
--- 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
@@ -16,19 +16,30 @@
 
 package com.android.cts.writeexternalstorageapp;
 
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_NONE;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_READ;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_WRITE;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirReadOnlyAccess;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirReadWriteAccess;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertFileReadWriteAccess;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.buildGiftForPackage;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.deleteContents;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.getAllPackageSpecificPaths;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.getPrimaryPackageSpecificPaths;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.getSecondaryPackageSpecificPaths;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.readInt;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.writeInt;
+
 import android.os.Environment;
 import android.test.AndroidTestCase;
 
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
 import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
+import java.util.List;
 import java.util.Random;
 
 /**
- * Test if {@link Environment#getExternalStorageDirectory()} is writable.
+ * Test external storage from an application that has
+ * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE}.
  */
 public class WriteExternalStorageTest extends AndroidTestCase {
 
@@ -57,6 +68,10 @@
         }
     }
 
+    private void assertExternalStorageMounted() {
+        assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
+    }
+
     public void testReadExternalStorage() throws Exception {
         assertExternalStorageMounted();
         Environment.getExternalStorageDirectory().list();
@@ -93,25 +108,124 @@
         }
     }
 
-    private static void assertExternalStorageMounted() {
-        assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
+    public void testPrimaryReadWrite() throws Exception {
+        assertDirReadWriteAccess(Environment.getExternalStorageDirectory());
     }
 
-    private static void writeInt(File file, int value) throws IOException {
-        final DataOutputStream os = new DataOutputStream(new FileOutputStream(file));
-        try {
-            os.writeInt(value);
-        } finally {
-            os.close();
+    /**
+     * Verify that above our package directories (on primary storage) we always
+     * have write access.
+     */
+    public void testPrimaryWalkingUpTreeReadWrite() throws Exception {
+        final List<File> paths = getPrimaryPackageSpecificPaths(getContext());
+        final String packageName = getContext().getPackageName();
+
+        for (File path : paths) {
+            assertTrue(path.getAbsolutePath().contains(packageName));
+
+            // Walk until we leave device, writing the whole way
+            while (Environment.MEDIA_MOUNTED.equals(Environment.getStorageState(path))) {
+                assertDirReadWriteAccess(path);
+                path = path.getParentFile();
+            }
         }
     }
 
-    private static int readInt(File file) throws IOException {
-        final DataInputStream is = new DataInputStream(new FileInputStream(file));
-        try {
-            return is.readInt();
-        } finally {
-            is.close();
+    /**
+     * Verify that we have write access in other packages on primary external
+     * storage.
+     */
+    public void testPrimaryOtherPackageWriteAccess() throws Exception {
+        deleteContents(Environment.getExternalStorageDirectory());
+
+        final File ourCache = getContext().getExternalCacheDir();
+        final File otherCache = new File(ourCache.getAbsolutePath()
+                .replace(getContext().getPackageName(), PACKAGE_NONE));
+
+        assertTrue(otherCache.mkdirs());
+        assertDirReadWriteAccess(otherCache);
+    }
+
+    /**
+     * Verify that we have write access in our package-specific directories on
+     * secondary storage devices, but it becomes read-only access above them.
+     */
+    public void testSecondaryWalkingUpTreeReadOnly() throws Exception {
+        final List<File> paths = getSecondaryPackageSpecificPaths(getContext());
+        final String packageName = getContext().getPackageName();
+
+        for (File path : paths) {
+            assertTrue(path.getAbsolutePath().contains(packageName));
+
+            // Walk up until we drop our package
+            while (path.getAbsolutePath().contains(packageName)) {
+                assertDirReadWriteAccess(path);
+                path = path.getParentFile();
+            }
+
+            // Keep walking up until we leave device
+            while (Environment.MEDIA_MOUNTED.equals(Environment.getStorageState(path))) {
+                assertDirReadOnlyAccess(path);
+                path = path.getParentFile();
+            }
         }
     }
+
+    /**
+     * Verify that .nomedia is created correctly.
+     */
+    public void testVerifyNoMediaCreated() throws Exception {
+        deleteContents(Environment.getExternalStorageDirectory());
+
+        final List<File> paths = getAllPackageSpecificPaths(getContext());
+
+        // Require that .nomedia was created somewhere above each dir
+        for (File path : paths) {
+            final File start = path;
+
+            boolean found = false;
+            while (Environment.MEDIA_MOUNTED.equals(Environment.getStorageState(path))) {
+                final File test = new File(path, ".nomedia");
+                if (test.exists()) {
+                    found = true;
+                    break;
+                }
+                path = path.getParentFile();
+            }
+
+            if (!found) {
+                fail("Missing .nomedia file above package-specific directory " + start
+                        + "; gave up at " + path);
+            }
+        }
+    }
+
+    /**
+     * Leave gifts for other packages in their primary external cache dirs.
+     */
+    public void doWriteGifts() throws Exception {
+        final File none = buildGiftForPackage(getContext(), PACKAGE_NONE);
+        none.getParentFile().mkdirs();
+        none.createNewFile();
+        assertFileReadWriteAccess(none);
+
+        writeInt(none, 100);
+        assertEquals(100, readInt(none));
+
+        final File read = buildGiftForPackage(getContext(), PACKAGE_READ);
+        read.getParentFile().mkdirs();
+        read.createNewFile();
+        assertFileReadWriteAccess(read);
+
+        writeInt(read, 101);
+        assertEquals(101, readInt(read));
+
+        final File write = buildGiftForPackage(getContext(), PACKAGE_WRITE);
+        write.getParentFile().mkdirs();
+        write.createNewFile();
+        assertFileReadWriteAccess(write);
+
+        writeInt(write, 102);
+        assertEquals(102, readInt(write));
+    }
 }