Home | History | Annotate | Download | only in writeexternalstorageapp
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.cts.writeexternalstorageapp;
     18 
     19 import static android.test.MoreAsserts.assertNotEqual;
     20 
     21 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_NONE;
     22 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.TAG;
     23 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirNoWriteAccess;
     24 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirReadOnlyAccess;
     25 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirReadWriteAccess;
     26 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.buildCommonChildDirs;
     27 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.buildProbeFile;
     28 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.deleteContents;
     29 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.getAllPackageSpecificPathsExceptMedia;
     30 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.getMountPaths;
     31 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.getPrimaryPackageSpecificPaths;
     32 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.getSecondaryPackageSpecificPaths;
     33 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.readInt;
     34 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.writeInt;
     35 
     36 import android.os.Environment;
     37 import android.os.SystemClock;
     38 import android.system.Os;
     39 import android.test.AndroidTestCase;
     40 import android.text.format.DateUtils;
     41 import android.util.Log;
     42 
     43 import com.android.cts.externalstorageapp.CommonExternalStorageTest;
     44 
     45 import java.io.File;
     46 import java.util.List;
     47 import java.util.Random;
     48 
     49 /**
     50  * Test external storage from an application that has
     51  * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE}.
     52  */
     53 public class WriteExternalStorageTest extends AndroidTestCase {
     54 
     55     private static final File TEST_FILE = new File(
     56             Environment.getExternalStorageDirectory(), "meow");
     57 
     58     /**
     59      * Set of file paths that should all refer to the same location to verify
     60      * support for legacy paths.
     61      */
     62     private static final File[] IDENTICAL_FILES = {
     63             new File("/sdcard/caek"),
     64             new File(System.getenv("EXTERNAL_STORAGE"), "caek"),
     65             new File(Environment.getExternalStorageDirectory(), "caek"),
     66     };
     67 
     68     @Override
     69     protected void tearDown() throws Exception {
     70         try {
     71             TEST_FILE.delete();
     72             for (File file : IDENTICAL_FILES) {
     73                 file.delete();
     74             }
     75         } finally {
     76             super.tearDown();
     77         }
     78     }
     79 
     80     private void assertExternalStorageMounted() {
     81         assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
     82     }
     83 
     84     public void testReadExternalStorage() throws Exception {
     85         assertExternalStorageMounted();
     86         Environment.getExternalStorageDirectory().list();
     87     }
     88 
     89     public void testWriteExternalStorage() throws Exception {
     90         final long testValue = 12345000;
     91         assertExternalStorageMounted();
     92 
     93         // Write a value and make sure we can read it back
     94         writeInt(TEST_FILE, 32);
     95         assertEquals(readInt(TEST_FILE), 32);
     96 
     97         assertTrue("Must be able to set last modified", TEST_FILE.setLastModified(testValue));
     98         assertEquals(testValue, TEST_FILE.lastModified());
     99     }
    100 
    101     public void testWriteExternalStorageDirs() throws Exception {
    102         final File probe = new File(
    103                 Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM),
    104                 "100CTS");
    105 
    106         assertFalse(probe.exists());
    107         assertTrue(probe.mkdirs());
    108 
    109         assertDirReadWriteAccess(probe);
    110     }
    111 
    112     /**
    113      * Verify that legacy filesystem paths continue working, and that they all
    114      * point to same location.
    115      */
    116     public void testLegacyPaths() throws Exception {
    117         final Random r = new Random();
    118         for (File target : IDENTICAL_FILES) {
    119             // Ensure we're starting with clean slate
    120             for (File file : IDENTICAL_FILES) {
    121                 file.delete();
    122             }
    123 
    124             // Write value to our current target
    125             final int value = r.nextInt();
    126             writeInt(target, value);
    127 
    128             // Ensure that identical files all contain the value
    129             for (File file : IDENTICAL_FILES) {
    130                 assertEquals(readInt(file), value);
    131             }
    132         }
    133     }
    134 
    135     public void testPrimaryReadWrite() throws Exception {
    136         assertDirReadWriteAccess(Environment.getExternalStorageDirectory());
    137     }
    138 
    139     /**
    140      * Verify that above our package directories (on primary storage) we always
    141      * have write access.
    142      */
    143     public void testPrimaryWalkingUpTreeReadWrite() throws Exception {
    144         final List<File> paths = getPrimaryPackageSpecificPaths(getContext());
    145         final String packageName = getContext().getPackageName();
    146 
    147         for (File path : paths) {
    148             assertNotNull("Valid media must be inserted during CTS", path);
    149             assertEquals("Valid media must be inserted during CTS", Environment.MEDIA_MOUNTED,
    150                     Environment.getExternalStorageState(path));
    151 
    152             assertTrue(path.getAbsolutePath().contains(packageName));
    153 
    154             // Walk until we leave device, writing the whole way
    155             while (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState(path))) {
    156                 assertDirReadWriteAccess(path);
    157                 path = path.getParentFile();
    158             }
    159         }
    160     }
    161 
    162     /**
    163      * Verify that we have write access in other packages on primary external
    164      * storage.
    165      */
    166     public void testPrimaryOtherPackageWriteAccess() throws Exception {
    167         final File ourCache = getContext().getExternalCacheDir();
    168         final File otherCache = new File(ourCache.getAbsolutePath()
    169                 .replace(getContext().getPackageName(), PACKAGE_NONE));
    170         deleteContents(otherCache);
    171         otherCache.delete();
    172 
    173         assertTrue(otherCache.mkdirs());
    174         assertDirReadWriteAccess(otherCache);
    175     }
    176 
    177     /**
    178      * Verify we have valid mount status until we leave the device.
    179      */
    180     public void testMountStatusWalkingUpTree() {
    181         final File top = Environment.getExternalStorageDirectory();
    182         File path = getContext().getExternalCacheDir();
    183 
    184         int depth = 0;
    185         while (depth++ < 32) {
    186             assertDirReadWriteAccess(path);
    187             assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState(path));
    188 
    189             if (path.getAbsolutePath().equals(top.getAbsolutePath())) {
    190                 break;
    191             }
    192 
    193             path = path.getParentFile();
    194         }
    195 
    196         // Make sure we hit the top
    197         assertEquals(top.getAbsolutePath(), path.getAbsolutePath());
    198 
    199         // And going one step further should be outside our reach
    200         path = path.getParentFile();
    201         assertDirNoWriteAccess(path);
    202         assertEquals(Environment.MEDIA_UNKNOWN, Environment.getExternalStorageState(path));
    203     }
    204 
    205     /**
    206      * Verify mount status for random paths.
    207      */
    208     public void testMountStatus() {
    209         assertEquals(Environment.MEDIA_UNKNOWN,
    210                 Environment.getExternalStorageState(new File("/meow-should-never-exist")));
    211 
    212         // Internal data isn't a mount point
    213         assertEquals(Environment.MEDIA_UNKNOWN,
    214                 Environment.getExternalStorageState(getContext().getCacheDir()));
    215     }
    216 
    217     /**
    218      * Verify that we have write access in our package-specific directories on
    219      * secondary storage devices, but it becomes read-only access above them.
    220      */
    221     public void testSecondaryWalkingUpTreeReadOnly() throws Exception {
    222         final List<File> paths = getSecondaryPackageSpecificPaths(getContext());
    223         final String packageName = getContext().getPackageName();
    224 
    225         for (File path : paths) {
    226             assertNotNull("Valid media must be inserted during CTS", path);
    227             assertEquals("Valid media must be inserted during CTS", Environment.MEDIA_MOUNTED,
    228                     Environment.getExternalStorageState(path));
    229 
    230             assertTrue(path.getAbsolutePath().contains(packageName));
    231 
    232             // Walk up until we drop our package
    233             while (path.getAbsolutePath().contains(packageName)) {
    234                 assertDirReadWriteAccess(path);
    235                 path = path.getParentFile();
    236             }
    237 
    238             // Walk all the way up to root
    239             while (path != null) {
    240                 if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState(path))) {
    241                     assertDirReadOnlyAccess(path);
    242                 } else {
    243                     assertDirNoWriteAccess(path);
    244                 }
    245                 assertDirNoWriteAccess(buildCommonChildDirs(path));
    246                 path = path.getParentFile();
    247             }
    248         }
    249     }
    250 
    251     /**
    252      * Verify that .nomedia is created correctly.
    253      */
    254     public void testVerifyNoMediaCreated() throws Exception {
    255         for (File file : getAllPackageSpecificPathsExceptMedia(getContext())) {
    256             deleteContents(file);
    257         }
    258         final List<File> paths = getAllPackageSpecificPathsExceptMedia(getContext());
    259 
    260         // Require that .nomedia was created somewhere above each dir
    261         for (File path : paths) {
    262             assertNotNull("Valid media must be inserted during CTS", path);
    263             assertEquals("Valid media must be inserted during CTS", Environment.MEDIA_MOUNTED,
    264                     Environment.getExternalStorageState(path));
    265 
    266             final File start = path;
    267 
    268             boolean found = false;
    269             while (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState(path))) {
    270                 final File test = new File(path, ".nomedia");
    271                 if (test.exists()) {
    272                     found = true;
    273                     break;
    274                 }
    275                 path = path.getParentFile();
    276             }
    277 
    278             if (!found) {
    279                 fail("Missing .nomedia file above package-specific directory " + start
    280                         + "; gave up at " + path);
    281             }
    282         }
    283     }
    284 
    285     /**
    286      * Secondary external storage mount points must always be read-only, per
    287      * CDD, <em>except</em> for the package specific directories tested by
    288      * {@link CommonExternalStorageTest#testAllPackageDirsWritable()}.
    289      */
    290     public void testSecondaryMountPointsNotWritable() throws Exception {
    291         // Probe path could be /storage/emulated/0 or /storage/1234-5678
    292         final File probe = buildProbeFile(Environment.getExternalStorageDirectory());
    293         assertTrue(probe.createNewFile());
    294 
    295         final String userId = Integer.toString(android.os.Process.myUid() / 100000);
    296         final List<File> mountPaths = getMountPaths();
    297         for (File path : mountPaths) {
    298             // Mount points could be multi-user aware, so try probing both top
    299             // level and user-specific directory.
    300             final File userPath = new File(path, userId);
    301 
    302             final File testProbe = new File(path, probe.getName());
    303             final File testUserProbe = new File(userPath, probe.getName());
    304 
    305             if (testProbe.exists() || testUserProbe.exists()) {
    306                 Log.d(TAG, "Primary external mountpoint " + path);
    307             } else {
    308                 // This mountpoint is not primary external storage; we must
    309                 // not be able to write.
    310                 Log.d(TAG, "Other mountpoint " + path);
    311                 assertDirNoWriteAccess(path);
    312                 assertDirNoWriteAccess(userPath);
    313             }
    314         }
    315     }
    316 
    317     /**
    318      * Verify that moving around package-specific directories causes permissions
    319      * to be updated.
    320      */
    321     public void testMovePackageSpecificPaths() throws Exception {
    322         final File before = getContext().getExternalCacheDir();
    323         final File beforeFile = new File(before, "test.probe");
    324         assertTrue(beforeFile.createNewFile());
    325         assertEquals(Os.getuid(), Os.stat(before.getAbsolutePath()).st_uid);
    326         assertEquals(Os.getuid(), Os.stat(beforeFile.getAbsolutePath()).st_uid);
    327 
    328         final File after = new File(before.getAbsolutePath()
    329                 .replace(getContext().getPackageName(), "com.example.does.not.exist"));
    330         after.getParentFile().mkdirs();
    331 
    332         Os.rename(before.getAbsolutePath(), after.getAbsolutePath());
    333 
    334         // Sit around long enough for VFS cache to expire
    335         SystemClock.sleep(15 * DateUtils.SECOND_IN_MILLIS);
    336 
    337         final File afterFile = new File(after, "test.probe");
    338         assertNotEqual(Os.getuid(), Os.stat(after.getAbsolutePath()).st_uid);
    339         assertNotEqual(Os.getuid(), Os.stat(afterFile.getAbsolutePath()).st_uid);
    340     }
    341 }
    342