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