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 com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_NONE; 20 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_READ; 21 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_WRITE; 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.assertFileReadWriteAccess; 27 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.buildGiftForPackage; 28 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.buildProbeFile; 29 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.deleteContents; 30 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.getAllPackageSpecificPaths; 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.test.AndroidTestCase; 38 import android.util.Log; 39 40 import com.android.cts.externalstorageapp.CommonExternalStorageTest; 41 42 import java.io.BufferedReader; 43 import java.io.File; 44 import java.io.FileReader; 45 import java.util.List; 46 import java.util.Random; 47 48 /** 49 * Test external storage from an application that has 50 * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE}. 51 */ 52 public class WriteExternalStorageTest extends AndroidTestCase { 53 54 private static final File TEST_FILE = new File( 55 Environment.getExternalStorageDirectory(), "meow"); 56 57 /** 58 * Set of file paths that should all refer to the same location to verify 59 * support for legacy paths. 60 */ 61 private static final File[] IDENTICAL_FILES = { 62 new File("/sdcard/caek"), 63 new File(System.getenv("EXTERNAL_STORAGE"), "caek"), 64 new File(Environment.getExternalStorageDirectory(), "caek"), 65 }; 66 67 @Override tearDown()68 protected void tearDown() throws Exception { 69 try { 70 TEST_FILE.delete(); 71 for (File file : IDENTICAL_FILES) { 72 file.delete(); 73 } 74 } finally { 75 super.tearDown(); 76 } 77 } 78 assertExternalStorageMounted()79 private void assertExternalStorageMounted() { 80 assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState()); 81 } 82 testReadExternalStorage()83 public void testReadExternalStorage() throws Exception { 84 assertExternalStorageMounted(); 85 Environment.getExternalStorageDirectory().list(); 86 } 87 testWriteExternalStorage()88 public void testWriteExternalStorage() throws Exception { 89 assertExternalStorageMounted(); 90 91 // Write a value and make sure we can read it back 92 writeInt(TEST_FILE, 32); 93 assertEquals(readInt(TEST_FILE), 32); 94 } 95 96 /** 97 * Verify that legacy filesystem paths continue working, and that they all 98 * point to same location. 99 */ testLegacyPaths()100 public void testLegacyPaths() throws Exception { 101 final Random r = new Random(); 102 for (File target : IDENTICAL_FILES) { 103 // Ensure we're starting with clean slate 104 for (File file : IDENTICAL_FILES) { 105 file.delete(); 106 } 107 108 // Write value to our current target 109 final int value = r.nextInt(); 110 writeInt(target, value); 111 112 // Ensure that identical files all contain the value 113 for (File file : IDENTICAL_FILES) { 114 assertEquals(readInt(file), value); 115 } 116 } 117 } 118 testPrimaryReadWrite()119 public void testPrimaryReadWrite() throws Exception { 120 assertDirReadWriteAccess(Environment.getExternalStorageDirectory()); 121 } 122 123 /** 124 * Verify that above our package directories (on primary storage) we always 125 * have write access. 126 */ testPrimaryWalkingUpTreeReadWrite()127 public void testPrimaryWalkingUpTreeReadWrite() throws Exception { 128 final List<File> paths = getPrimaryPackageSpecificPaths(getContext()); 129 final String packageName = getContext().getPackageName(); 130 131 for (File path : paths) { 132 assertNotNull("Valid media must be inserted during CTS", path); 133 assertEquals("Valid media must be inserted during CTS", Environment.MEDIA_MOUNTED, 134 Environment.getStorageState(path)); 135 136 assertTrue(path.getAbsolutePath().contains(packageName)); 137 138 // Walk until we leave device, writing the whole way 139 while (Environment.MEDIA_MOUNTED.equals(Environment.getStorageState(path))) { 140 assertDirReadWriteAccess(path); 141 path = path.getParentFile(); 142 } 143 } 144 } 145 146 /** 147 * Verify that we have write access in other packages on primary external 148 * storage. 149 */ testPrimaryOtherPackageWriteAccess()150 public void testPrimaryOtherPackageWriteAccess() throws Exception { 151 deleteContents(Environment.getExternalStorageDirectory()); 152 153 final File ourCache = getContext().getExternalCacheDir(); 154 final File otherCache = new File(ourCache.getAbsolutePath() 155 .replace(getContext().getPackageName(), PACKAGE_NONE)); 156 157 assertTrue(otherCache.mkdirs()); 158 assertDirReadWriteAccess(otherCache); 159 } 160 161 /** 162 * Verify we have valid mount status until we leave the device. 163 */ testMountStatusWalkingUpTree()164 public void testMountStatusWalkingUpTree() { 165 final File top = Environment.getExternalStorageDirectory(); 166 File path = getContext().getExternalCacheDir(); 167 168 int depth = 0; 169 while (depth++ < 32) { 170 assertDirReadWriteAccess(path); 171 assertEquals(Environment.MEDIA_MOUNTED, Environment.getStorageState(path)); 172 173 if (path.getAbsolutePath().equals(top.getAbsolutePath())) { 174 break; 175 } 176 177 path = path.getParentFile(); 178 } 179 180 // Make sure we hit the top 181 assertEquals(top.getAbsolutePath(), path.getAbsolutePath()); 182 183 // And going one step further should be outside our reach 184 path = path.getParentFile(); 185 assertDirNoWriteAccess(path); 186 assertEquals(Environment.MEDIA_UNKNOWN, Environment.getStorageState(path)); 187 } 188 189 /** 190 * Verify mount status for random paths. 191 */ testMountStatus()192 public void testMountStatus() { 193 assertEquals(Environment.MEDIA_UNKNOWN, 194 Environment.getStorageState(new File("/meow-should-never-exist"))); 195 196 // Internal data isn't a mount point 197 assertEquals(Environment.MEDIA_UNKNOWN, 198 Environment.getStorageState(getContext().getCacheDir())); 199 } 200 201 /** 202 * Verify that we have write access in our package-specific directories on 203 * secondary storage devices, but it becomes read-only access above them. 204 */ testSecondaryWalkingUpTreeReadOnly()205 public void testSecondaryWalkingUpTreeReadOnly() throws Exception { 206 final List<File> paths = getSecondaryPackageSpecificPaths(getContext()); 207 final String packageName = getContext().getPackageName(); 208 209 for (File path : paths) { 210 assertNotNull("Valid media must be inserted during CTS", path); 211 assertEquals("Valid media must be inserted during CTS", Environment.MEDIA_MOUNTED, 212 Environment.getStorageState(path)); 213 214 assertTrue(path.getAbsolutePath().contains(packageName)); 215 216 // Walk up until we drop our package 217 while (path.getAbsolutePath().contains(packageName)) { 218 assertDirReadWriteAccess(path); 219 path = path.getParentFile(); 220 } 221 222 // Keep walking up until we leave device 223 while (Environment.MEDIA_MOUNTED.equals(Environment.getStorageState(path))) { 224 assertDirReadOnlyAccess(path); 225 path = path.getParentFile(); 226 } 227 } 228 } 229 230 /** 231 * Verify that .nomedia is created correctly. 232 */ testVerifyNoMediaCreated()233 public void testVerifyNoMediaCreated() throws Exception { 234 deleteContents(Environment.getExternalStorageDirectory()); 235 236 final List<File> paths = getAllPackageSpecificPaths(getContext()); 237 238 // Require that .nomedia was created somewhere above each dir 239 for (File path : paths) { 240 assertNotNull("Valid media must be inserted during CTS", path); 241 assertEquals("Valid media must be inserted during CTS", Environment.MEDIA_MOUNTED, 242 Environment.getStorageState(path)); 243 244 final File start = path; 245 246 boolean found = false; 247 while (Environment.MEDIA_MOUNTED.equals(Environment.getStorageState(path))) { 248 final File test = new File(path, ".nomedia"); 249 if (test.exists()) { 250 found = true; 251 break; 252 } 253 path = path.getParentFile(); 254 } 255 256 if (!found) { 257 fail("Missing .nomedia file above package-specific directory " + start 258 + "; gave up at " + path); 259 } 260 } 261 } 262 263 /** 264 * Secondary external storage mount points must always be read-only, per 265 * CDD, <em>except</em> for the package specific directories tested by 266 * {@link CommonExternalStorageTest#testAllPackageDirsWritable()}. 267 */ testSecondaryMountPointsNotWritable()268 public void testSecondaryMountPointsNotWritable() throws Exception { 269 final File probe = buildProbeFile(Environment.getExternalStorageDirectory()); 270 assertTrue(probe.createNewFile()); 271 272 final BufferedReader br = new BufferedReader(new FileReader("/proc/self/mounts")); 273 try { 274 String line; 275 while ((line = br.readLine()) != null) { 276 final String[] fields = line.split(" "); 277 final File testMount = new File(fields[1]); 278 final File testProbe = new File(testMount, probe.getName()); 279 if (testProbe.exists()) { 280 Log.d(TAG, "Primary external mountpoint " + testMount); 281 } else { 282 // This mountpoint is not primary external storage; we must 283 // not be able to write. 284 Log.d(TAG, "Other mountpoint " + testMount); 285 assertDirNoWriteAccess(testProbe.getParentFile()); 286 } 287 } 288 } finally { 289 br.close(); 290 probe.delete(); 291 } 292 } 293 294 /** 295 * Leave gifts for other packages in their primary external cache dirs. 296 */ doWriteGifts()297 public void doWriteGifts() throws Exception { 298 final File none = buildGiftForPackage(getContext(), PACKAGE_NONE); 299 none.getParentFile().mkdirs(); 300 none.createNewFile(); 301 assertFileReadWriteAccess(none); 302 303 writeInt(none, 100); 304 assertEquals(100, readInt(none)); 305 306 final File read = buildGiftForPackage(getContext(), PACKAGE_READ); 307 read.getParentFile().mkdirs(); 308 read.createNewFile(); 309 assertFileReadWriteAccess(read); 310 311 writeInt(read, 101); 312 assertEquals(101, readInt(read)); 313 314 final File write = buildGiftForPackage(getContext(), PACKAGE_WRITE); 315 write.getParentFile().mkdirs(); 316 write.createNewFile(); 317 assertFileReadWriteAccess(write); 318 319 writeInt(write, 102); 320 assertEquals(102, readInt(write)); 321 } 322 } 323