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.externalstorageapp; 18 19 import android.content.ContentResolver; 20 import android.content.ContentValues; 21 import android.content.Context; 22 import android.net.Uri; 23 import android.os.Environment; 24 import android.provider.MediaStore; 25 import android.provider.MediaStore.Images; 26 import android.test.AndroidTestCase; 27 import android.util.Log; 28 29 import java.io.BufferedReader; 30 import java.io.ByteArrayOutputStream; 31 import java.io.DataInputStream; 32 import java.io.DataOutputStream; 33 import java.io.File; 34 import java.io.FileInputStream; 35 import java.io.FileOutputStream; 36 import java.io.FileReader; 37 import java.io.IOException; 38 import java.io.InputStream; 39 import java.io.OutputStream; 40 import java.util.ArrayList; 41 import java.util.Arrays; 42 import java.util.Collections; 43 import java.util.List; 44 45 /** 46 * Tests common functionality that should be supported regardless of external 47 * storage status. 48 */ 49 public class CommonExternalStorageTest extends AndroidTestCase { 50 public static final String TAG = "CommonExternalStorageTest"; 51 52 public static final String PACKAGE_NONE = "com.android.cts.externalstorageapp"; 53 public static final String PACKAGE_READ = "com.android.cts.readexternalstorageapp"; 54 public static final String PACKAGE_WRITE = "com.android.cts.writeexternalstorageapp"; 55 56 /** 57 * Dump helpful debugging details. 58 */ testDumpDebug()59 public void testDumpDebug() throws Exception { 60 logCommand("/system/bin/id"); 61 logCommand("/system/bin/cat", "/proc/self/mountinfo"); 62 } 63 64 /** 65 * Primary storage must always be mounted. 66 */ testExternalStorageMounted()67 public void testExternalStorageMounted() { 68 assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState()); 69 } 70 71 /** 72 * Verify that single path is always first item in multiple. 73 */ testMultipleCacheDirs()74 public void testMultipleCacheDirs() throws Exception { 75 final File single = getContext().getExternalCacheDir(); 76 assertNotNull("Primary storage must always be available", single); 77 final File firstMultiple = getContext().getExternalCacheDirs()[0]; 78 assertEquals(single, firstMultiple); 79 } 80 81 /** 82 * Verify that single path is always first item in multiple. 83 */ testMultipleFilesDirs()84 public void testMultipleFilesDirs() throws Exception { 85 final File single = getContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES); 86 assertNotNull("Primary storage must always be available", single); 87 final File firstMultiple = getContext() 88 .getExternalFilesDirs(Environment.DIRECTORY_PICTURES)[0]; 89 assertEquals(single, firstMultiple); 90 } 91 92 /** 93 * Verify that single path is always first item in multiple. 94 */ testMultipleObbDirs()95 public void testMultipleObbDirs() throws Exception { 96 final File single = getContext().getObbDir(); 97 assertNotNull("Primary storage must always be available", single); 98 final File firstMultiple = getContext().getObbDirs()[0]; 99 assertEquals(single, firstMultiple); 100 } 101 102 /** 103 * Verify we can write to our own package dirs. 104 */ testAllPackageDirsWritable()105 public void testAllPackageDirsWritable() throws Exception { 106 final long testValue = 1234500000000L; 107 final List<File> paths = getAllPackageSpecificPaths(getContext()); 108 for (File path : paths) { 109 assertNotNull("Valid media must be inserted during CTS", path); 110 assertEquals("Valid media must be inserted during CTS", Environment.MEDIA_MOUNTED, 111 Environment.getExternalStorageState(path)); 112 113 assertDirReadWriteAccess(path); 114 115 final File directChild = new File(path, "directChild"); 116 final File subdir = new File(path, "subdir"); 117 final File subdirChild = new File(path, "subdirChild"); 118 119 writeInt(directChild, 32); 120 subdir.mkdirs(); 121 assertDirReadWriteAccess(subdir); 122 writeInt(subdirChild, 64); 123 124 assertEquals(32, readInt(directChild)); 125 assertEquals(64, readInt(subdirChild)); 126 127 assertTrue("Must be able to set last modified", directChild.setLastModified(testValue)); 128 assertTrue("Must be able to set last modified", subdirChild.setLastModified(testValue)); 129 130 assertEquals(testValue, directChild.lastModified()); 131 assertEquals(testValue, subdirChild.lastModified()); 132 } 133 134 for (File path : paths) { 135 deleteContents(path); 136 } 137 } 138 139 /** 140 * Return a set of several package-specific external storage paths. 141 */ getAllPackageSpecificPaths(Context context)142 public static List<File> getAllPackageSpecificPaths(Context context) { 143 final List<File> paths = new ArrayList<File>(); 144 Collections.addAll(paths, context.getExternalCacheDirs()); 145 Collections.addAll(paths, context.getExternalFilesDirs(null)); 146 Collections.addAll(paths, context.getExternalFilesDirs(Environment.DIRECTORY_PICTURES)); 147 Collections.addAll(paths, context.getExternalMediaDirs()); 148 Collections.addAll(paths, context.getObbDirs()); 149 return paths; 150 } 151 getAllPackageSpecificPathsExceptMedia(Context context)152 public static List<File> getAllPackageSpecificPathsExceptMedia(Context context) { 153 final List<File> paths = new ArrayList<File>(); 154 Collections.addAll(paths, context.getExternalCacheDirs()); 155 Collections.addAll(paths, context.getExternalFilesDirs(null)); 156 Collections.addAll(paths, context.getExternalFilesDirs(Environment.DIRECTORY_PICTURES)); 157 Collections.addAll(paths, context.getObbDirs()); 158 return paths; 159 } 160 getAllPackageSpecificPathsExceptObb(Context context)161 public static List<File> getAllPackageSpecificPathsExceptObb(Context context) { 162 final List<File> paths = new ArrayList<File>(); 163 Collections.addAll(paths, context.getExternalCacheDirs()); 164 Collections.addAll(paths, context.getExternalFilesDirs(null)); 165 Collections.addAll(paths, context.getExternalFilesDirs(Environment.DIRECTORY_PICTURES)); 166 Collections.addAll(paths, context.getExternalMediaDirs()); 167 return paths; 168 } 169 getAllPackageSpecificObbGiftPaths(Context context, String targetPackageName)170 public static List<File> getAllPackageSpecificObbGiftPaths(Context context, 171 String targetPackageName) { 172 final List<File> targetFiles = new ArrayList<>(); 173 final File obbDir = context.getObbDir(); 174 final File targetObbDir = new File( 175 obbDir.getAbsolutePath().replace(context.getPackageName(), targetPackageName)); 176 targetFiles.add(new File(targetObbDir, targetPackageName + ".gift")); 177 return targetFiles; 178 } 179 180 /** 181 * Return a set of several package-specific external storage paths pointing 182 * at "gift" files designed to be exchanged with the target package in Q. 183 * These directories can't be used to exchange "gift" files in R. 184 */ getAllPackageSpecificNoGiftPaths(Context context, String targetPackageName)185 public static List<File> getAllPackageSpecificNoGiftPaths(Context context, 186 String targetPackageName) { 187 final List<File> files = getPrimaryPackageSpecificPathsExceptMedia(context); 188 final List<File> targetFiles = new ArrayList<>(); 189 for (File file : files) { 190 final File targetFile = new File( 191 file.getAbsolutePath().replace(context.getPackageName(), targetPackageName)); 192 targetFiles.add(new File(targetFile, targetPackageName + ".gift")); 193 } 194 return targetFiles; 195 } 196 getPrimaryPackageSpecificPathsExceptMedia(Context context)197 public static List<File> getPrimaryPackageSpecificPathsExceptMedia(Context context) { 198 final List<File> paths = new ArrayList<File>(); 199 Collections.addAll(paths, context.getExternalCacheDir()); 200 Collections.addAll(paths, context.getExternalFilesDir(null)); 201 Collections.addAll(paths, context.getObbDir()); 202 return paths; 203 } 204 getPrimaryPackageSpecificPaths(Context context)205 public static List<File> getPrimaryPackageSpecificPaths(Context context) { 206 final List<File> paths = new ArrayList<File>(); 207 Collections.addAll(paths, context.getExternalCacheDir()); 208 Collections.addAll(paths, context.getExternalFilesDir(null)); 209 Collections.addAll(paths, context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)); 210 Collections.addAll(paths, context.getObbDir()); 211 return paths; 212 } 213 getSecondaryPackageSpecificPaths(Context context)214 public static List<File> getSecondaryPackageSpecificPaths(Context context) { 215 final List<File> paths = new ArrayList<File>(); 216 Collections.addAll(paths, dropFirst(context.getExternalCacheDirs())); 217 Collections.addAll(paths, dropFirst(context.getExternalFilesDirs(null))); 218 Collections.addAll( 219 paths, dropFirst(context.getExternalFilesDirs(Environment.DIRECTORY_PICTURES))); 220 Collections.addAll(paths, dropFirst(context.getObbDirs())); 221 return paths; 222 } 223 getMountPaths()224 public static List<File> getMountPaths() throws IOException { 225 final List<File> paths = new ArrayList<>(); 226 final BufferedReader br = new BufferedReader(new FileReader("/proc/self/mounts")); 227 try { 228 String line; 229 while ((line = br.readLine()) != null) { 230 final String[] fields = line.split(" "); 231 paths.add(new File(fields[1])); 232 } 233 } finally { 234 br.close(); 235 } 236 return paths; 237 } 238 dropFirst(File[] before)239 private static File[] dropFirst(File[] before) { 240 final File[] after = new File[before.length - 1]; 241 System.arraycopy(before, 1, after, 0, after.length); 242 return after; 243 } 244 buildProbeFile(File dir)245 public static File buildProbeFile(File dir) { 246 return new File(dir, ".probe_" + System.nanoTime()); 247 } 248 buildCommonChildDirs(File dir)249 public static File[] buildCommonChildDirs(File dir) { 250 return new File[] { 251 new File(dir, Environment.DIRECTORY_MUSIC), 252 new File(dir, Environment.DIRECTORY_PODCASTS), 253 new File(dir, Environment.DIRECTORY_ALARMS), 254 new File(dir, Environment.DIRECTORY_RINGTONES), 255 new File(dir, Environment.DIRECTORY_NOTIFICATIONS), 256 new File(dir, Environment.DIRECTORY_PICTURES), 257 new File(dir, Environment.DIRECTORY_MOVIES), 258 new File(dir, Environment.DIRECTORY_DOWNLOADS), 259 new File(dir, Environment.DIRECTORY_DCIM), 260 new File(dir, Environment.DIRECTORY_DOCUMENTS), 261 }; 262 } 263 assertDirReadOnlyAccess(File path)264 public static void assertDirReadOnlyAccess(File path) { 265 Log.d(TAG, "Asserting read-only access to " + path); 266 267 assertTrue("exists", path.exists()); 268 assertTrue("execute", path.canExecute()); 269 270 try { 271 final File probe = buildProbeFile(path); 272 assertFalse(probe.createNewFile()); 273 assertFalse(probe.exists()); 274 assertFalse(probe.delete()); 275 fail("able to create probe!"); 276 } catch (IOException e) { 277 // expected 278 } 279 } 280 assertDirReadWriteAccess(File[] paths)281 public static void assertDirReadWriteAccess(File[] paths) { 282 for (File path : paths) { 283 assertDirReadWriteAccess(path); 284 } 285 } 286 assertDirReadWriteAccess(File path)287 public static void assertDirReadWriteAccess(File path) { 288 Log.d(TAG, "Asserting read/write access to " + path); 289 290 assertTrue("exists", path.exists()); 291 assertTrue("read", path.canRead()); 292 assertTrue("execute", path.canExecute()); 293 assertNotNull("list", path.list()); 294 295 try { 296 final File probe = buildProbeFile(path); 297 assertTrue(probe.createNewFile()); 298 assertTrue(probe.exists()); 299 assertTrue(probe.delete()); 300 assertFalse(probe.exists()); 301 } catch (IOException e) { 302 fail("failed to create probe!"); 303 } 304 } 305 assertDirNoAccess(File path)306 public static void assertDirNoAccess(File path) { 307 Log.d(TAG, "Asserting no access to " + path); 308 309 try { 310 final File probe = buildProbeFile(path); 311 assertFalse(probe.createNewFile()); 312 assertFalse(probe.exists()); 313 assertFalse(probe.delete()); 314 fail("able to create probe!"); 315 } catch (IOException e) { 316 // expected 317 } 318 } 319 assertDirNoWriteAccess(File[] paths)320 public static void assertDirNoWriteAccess(File[] paths) { 321 for (File path : paths) { 322 assertDirNoWriteAccess(path); 323 } 324 } 325 assertDirNoWriteAccess(File path)326 public static void assertDirNoWriteAccess(File path) { 327 Log.d(TAG, "Asserting no write access to " + path); 328 329 try { 330 final File probe = buildProbeFile(path); 331 assertFalse(probe.createNewFile()); 332 assertFalse(probe.exists()); 333 assertFalse(probe.delete()); 334 fail("able to create probe!"); 335 } catch (IOException e) { 336 // expected 337 } 338 } 339 assertFileReadWriteAccess(File path)340 public static void assertFileReadWriteAccess(File path) { 341 try { 342 new FileInputStream(path).close(); 343 } catch (IOException e) { 344 fail("failed to read!"); 345 } 346 347 try { 348 new FileOutputStream(path, true).close(); 349 } catch (IOException e) { 350 fail("failed to write!"); 351 } 352 } 353 assertFileNoAccess(File path)354 public static void assertFileNoAccess(File path) { 355 try { 356 new FileInputStream(path).close(); 357 fail("able to read!"); 358 } catch (IOException e) { 359 // expected 360 } 361 362 try { 363 new FileOutputStream(path, true).close(); 364 fail("able to write!"); 365 } catch (IOException e) { 366 // expected 367 } 368 } 369 assertFileNotPresent(File path)370 public static void assertFileNotPresent(File path) { 371 assertFalse(path + " exists!", path.exists()); 372 } 373 assertMediaNoAccess(ContentResolver resolver, boolean legacyApp)374 public static void assertMediaNoAccess(ContentResolver resolver, boolean legacyApp) 375 throws Exception { 376 final ContentValues values = new ContentValues(); 377 values.put(Images.Media.MIME_TYPE, "image/jpeg"); 378 values.put(Images.Media.DATA, 379 buildProbeFile(Environment.getExternalStorageDirectory()).getAbsolutePath()); 380 381 try { 382 Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); 383 if (legacyApp) { 384 // For legacy apps we do not crash - just make the operation do nothing 385 assertEquals(MediaStore.Images.Media.EXTERNAL_CONTENT_URI 386 .buildUpon().appendPath("0").build().toString(), uri.toString()); 387 } else { 388 fail("Expected access to be blocked"); 389 } 390 } catch (Exception expected) { 391 } 392 } 393 assertMediaReadWriteAccess(ContentResolver resolver)394 public static void assertMediaReadWriteAccess(ContentResolver resolver) throws Exception { 395 final ContentValues values = new ContentValues(); 396 values.put(Images.Media.MIME_TYPE, "image/jpeg"); 397 values.put(Images.Media.DATA, 398 buildProbeFile(Environment.getExternalStorageDirectory()).getAbsolutePath()); 399 400 final Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); 401 try { 402 resolver.openFileDescriptor(uri, "rw").close(); 403 resolver.openFileDescriptor(uri, "w").close(); 404 resolver.openFileDescriptor(uri, "r").close(); 405 } finally { 406 resolver.delete(uri, null, null); 407 } 408 } 409 isAllowList(File file)410 private static boolean isAllowList(File file) { 411 final String[] allowLists = { 412 "autorun.inf", ".android_secure", "android_secure" 413 }; 414 if (file.getParentFile().getAbsolutePath().equals( 415 Environment.getExternalStorageDirectory().getAbsolutePath())) { 416 for (String allowList : allowLists) { 417 if (file.getName().equalsIgnoreCase(allowList)) { 418 return true; 419 } 420 } 421 } 422 return false; 423 } 424 removeAllowList(File[] files)425 private static File[] removeAllowList(File[] files) { 426 List<File> fileList = new ArrayList<File>(); 427 if (files == null) { 428 return null; 429 } 430 431 for (File file : files) { 432 if (!isAllowList(file)) { 433 fileList.add(file); 434 } 435 } 436 return fileList.toArray(new File[fileList.size()]); 437 } 438 deleteContents(File dir)439 public static void deleteContents(File dir) throws IOException { 440 File[] files = dir.listFiles(); 441 files = removeAllowList(files); 442 if (files != null) { 443 for (File file : files) { 444 if (file.isDirectory()) { 445 deleteContents(file); 446 } 447 assertTrue(file.delete()); 448 } 449 450 File[] dirs = removeAllowList(dir.listFiles()); 451 if (dirs.length != 0) { 452 fail("Expected wiped storage but found: " + Arrays.toString(dirs)); 453 } 454 } 455 } 456 writeInt(File file, int value)457 public static void writeInt(File file, int value) throws IOException { 458 final DataOutputStream os = new DataOutputStream(new FileOutputStream(file)); 459 try { 460 os.writeInt(value); 461 } finally { 462 os.close(); 463 } 464 } 465 readInt(File file)466 public static int readInt(File file) throws IOException { 467 final DataInputStream is = new DataInputStream(new FileInputStream(file)); 468 try { 469 return is.readInt(); 470 } finally { 471 is.close(); 472 } 473 } 474 logCommand(String... cmd)475 public static void logCommand(String... cmd) throws Exception { 476 final Process proc = new ProcessBuilder(cmd).redirectErrorStream(true).start(); 477 478 final ByteArrayOutputStream buf = new ByteArrayOutputStream(); 479 copy(proc.getInputStream(), buf); 480 final int res = proc.waitFor(); 481 482 Log.d(TAG, Arrays.toString(cmd) + " result " + res + ":"); 483 Log.d(TAG, buf.toString()); 484 } 485 486 /** Shamelessly lifted from libcore.io.Streams */ copy(InputStream in, OutputStream out)487 public static int copy(InputStream in, OutputStream out) throws IOException { 488 int total = 0; 489 byte[] buffer = new byte[8192]; 490 int c; 491 while ((c = in.read(buffer)) != -1) { 492 total += c; 493 out.write(buffer, 0, c); 494 } 495 return total; 496 } 497 } 498