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 List<File> paths = getAllPackageSpecificPaths(getContext()); 107 for (File path : paths) { 108 assertNotNull("Valid media must be inserted during CTS", path); 109 assertEquals("Valid media must be inserted during CTS", Environment.MEDIA_MOUNTED, 110 Environment.getExternalStorageState(path)); 111 112 assertDirReadWriteAccess(path); 113 114 final File directChild = new File(path, "directChild"); 115 final File subdir = new File(path, "subdir"); 116 final File subdirChild = new File(path, "subdirChild"); 117 118 writeInt(directChild, 32); 119 subdir.mkdirs(); 120 assertDirReadWriteAccess(subdir); 121 writeInt(subdirChild, 64); 122 123 assertEquals(32, readInt(directChild)); 124 assertEquals(64, readInt(subdirChild)); 125 } 126 127 for (File path : paths) { 128 deleteContents(path); 129 } 130 } 131 132 /** 133 * Return a set of several package-specific external storage paths. 134 */ getAllPackageSpecificPaths(Context context)135 public static List<File> getAllPackageSpecificPaths(Context context) { 136 final List<File> paths = new ArrayList<File>(); 137 Collections.addAll(paths, context.getExternalCacheDirs()); 138 Collections.addAll(paths, context.getExternalFilesDirs(null)); 139 Collections.addAll(paths, context.getExternalFilesDirs(Environment.DIRECTORY_PICTURES)); 140 Collections.addAll(paths, context.getExternalMediaDirs()); 141 Collections.addAll(paths, context.getObbDirs()); 142 return paths; 143 } 144 getAllPackageSpecificPathsExceptMedia(Context context)145 public static List<File> getAllPackageSpecificPathsExceptMedia(Context context) { 146 final List<File> paths = new ArrayList<File>(); 147 Collections.addAll(paths, context.getExternalCacheDirs()); 148 Collections.addAll(paths, context.getExternalFilesDirs(null)); 149 Collections.addAll(paths, context.getExternalFilesDirs(Environment.DIRECTORY_PICTURES)); 150 Collections.addAll(paths, context.getObbDirs()); 151 return paths; 152 } 153 getAllPackageSpecificPathsExceptObb(Context context)154 public static List<File> getAllPackageSpecificPathsExceptObb(Context context) { 155 final List<File> paths = new ArrayList<File>(); 156 Collections.addAll(paths, context.getExternalCacheDirs()); 157 Collections.addAll(paths, context.getExternalFilesDirs(null)); 158 Collections.addAll(paths, context.getExternalFilesDirs(Environment.DIRECTORY_PICTURES)); 159 Collections.addAll(paths, context.getExternalMediaDirs()); 160 return paths; 161 } 162 163 /** 164 * Return a set of several package-specific external storage paths pointing 165 * at "gift" files designed to be exchanged with the target package. 166 */ getAllPackageSpecificGiftPaths(Context context, String targetPackageName)167 public static List<File> getAllPackageSpecificGiftPaths(Context context, 168 String targetPackageName) { 169 final List<File> files = getPrimaryPackageSpecificPaths(context); 170 final List<File> targetFiles = new ArrayList<>(); 171 for (File file : files) { 172 final File targetFile = new File( 173 file.getAbsolutePath().replace(context.getPackageName(), targetPackageName)); 174 targetFiles.add(new File(targetFile, targetPackageName + ".gift")); 175 } 176 return targetFiles; 177 } 178 getPrimaryPackageSpecificPaths(Context context)179 public static List<File> getPrimaryPackageSpecificPaths(Context context) { 180 final List<File> paths = new ArrayList<File>(); 181 Collections.addAll(paths, context.getExternalCacheDir()); 182 Collections.addAll(paths, context.getExternalFilesDir(null)); 183 Collections.addAll(paths, context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)); 184 Collections.addAll(paths, context.getObbDir()); 185 return paths; 186 } 187 getSecondaryPackageSpecificPaths(Context context)188 public static List<File> getSecondaryPackageSpecificPaths(Context context) { 189 final List<File> paths = new ArrayList<File>(); 190 Collections.addAll(paths, dropFirst(context.getExternalCacheDirs())); 191 Collections.addAll(paths, dropFirst(context.getExternalFilesDirs(null))); 192 Collections.addAll( 193 paths, dropFirst(context.getExternalFilesDirs(Environment.DIRECTORY_PICTURES))); 194 Collections.addAll(paths, dropFirst(context.getObbDirs())); 195 return paths; 196 } 197 getMountPaths()198 public static List<File> getMountPaths() throws IOException { 199 final List<File> paths = new ArrayList<>(); 200 final BufferedReader br = new BufferedReader(new FileReader("/proc/self/mounts")); 201 try { 202 String line; 203 while ((line = br.readLine()) != null) { 204 final String[] fields = line.split(" "); 205 paths.add(new File(fields[1])); 206 } 207 } finally { 208 br.close(); 209 } 210 return paths; 211 } 212 dropFirst(File[] before)213 private static File[] dropFirst(File[] before) { 214 final File[] after = new File[before.length - 1]; 215 System.arraycopy(before, 1, after, 0, after.length); 216 return after; 217 } 218 buildProbeFile(File dir)219 public static File buildProbeFile(File dir) { 220 return new File(dir, ".probe_" + System.nanoTime()); 221 } 222 buildCommonChildDirs(File dir)223 public static File[] buildCommonChildDirs(File dir) { 224 return new File[] { 225 new File(dir, Environment.DIRECTORY_MUSIC), 226 new File(dir, Environment.DIRECTORY_PODCASTS), 227 new File(dir, Environment.DIRECTORY_ALARMS), 228 new File(dir, Environment.DIRECTORY_RINGTONES), 229 new File(dir, Environment.DIRECTORY_NOTIFICATIONS), 230 new File(dir, Environment.DIRECTORY_PICTURES), 231 new File(dir, Environment.DIRECTORY_MOVIES), 232 new File(dir, Environment.DIRECTORY_DOWNLOADS), 233 new File(dir, Environment.DIRECTORY_DCIM), 234 new File(dir, Environment.DIRECTORY_DOCUMENTS), 235 }; 236 } 237 assertDirReadOnlyAccess(File path)238 public static void assertDirReadOnlyAccess(File path) { 239 Log.d(TAG, "Asserting read-only access to " + path); 240 241 assertTrue("exists", path.exists()); 242 assertTrue("read", path.canRead()); 243 assertTrue("execute", path.canExecute()); 244 assertNotNull("list", path.list()); 245 246 try { 247 final File probe = buildProbeFile(path); 248 assertFalse(probe.createNewFile()); 249 assertFalse(probe.exists()); 250 assertFalse(probe.delete()); 251 fail("able to create probe!"); 252 } catch (IOException e) { 253 // expected 254 } 255 } 256 assertDirReadWriteAccess(File path)257 public static void assertDirReadWriteAccess(File path) { 258 Log.d(TAG, "Asserting read/write access to " + path); 259 260 assertTrue("exists", path.exists()); 261 assertTrue("read", path.canRead()); 262 assertTrue("execute", path.canExecute()); 263 assertNotNull("list", path.list()); 264 265 try { 266 final File probe = buildProbeFile(path); 267 assertTrue(probe.createNewFile()); 268 assertTrue(probe.exists()); 269 assertTrue(probe.delete()); 270 assertFalse(probe.exists()); 271 } catch (IOException e) { 272 fail("failed to create probe!"); 273 } 274 } 275 assertDirNoAccess(File path)276 public static void assertDirNoAccess(File path) { 277 Log.d(TAG, "Asserting no access to " + path); 278 279 assertFalse("read", path.canRead()); 280 assertNull("list", path.list()); 281 282 try { 283 final File probe = buildProbeFile(path); 284 assertFalse(probe.createNewFile()); 285 assertFalse(probe.exists()); 286 assertFalse(probe.delete()); 287 fail("able to create probe!"); 288 } catch (IOException e) { 289 // expected 290 } 291 } 292 assertDirNoWriteAccess(File[] paths)293 public static void assertDirNoWriteAccess(File[] paths) { 294 for (File path : paths) { 295 assertDirNoWriteAccess(path); 296 } 297 } 298 assertDirNoWriteAccess(File path)299 public static void assertDirNoWriteAccess(File path) { 300 Log.d(TAG, "Asserting no write access to " + path); 301 302 try { 303 final File probe = buildProbeFile(path); 304 assertFalse(probe.createNewFile()); 305 assertFalse(probe.exists()); 306 assertFalse(probe.delete()); 307 fail("able to create probe!"); 308 } catch (IOException e) { 309 // expected 310 } 311 } 312 assertFileReadOnlyAccess(File path)313 public static void assertFileReadOnlyAccess(File path) { 314 try { 315 new FileInputStream(path).close(); 316 } catch (IOException e) { 317 fail("failed to read!"); 318 } 319 320 try { 321 new FileOutputStream(path, true).close(); 322 fail("able to write!"); 323 } catch (IOException e) { 324 // expected 325 } 326 } 327 assertFileReadWriteAccess(File path)328 public static void assertFileReadWriteAccess(File path) { 329 try { 330 new FileInputStream(path).close(); 331 } catch (IOException e) { 332 fail("failed to read!"); 333 } 334 335 try { 336 new FileOutputStream(path, true).close(); 337 } catch (IOException e) { 338 fail("failed to write!"); 339 } 340 } 341 assertFileNoAccess(File path)342 public static void assertFileNoAccess(File path) { 343 try { 344 new FileInputStream(path).close(); 345 fail("able to read!"); 346 } catch (IOException e) { 347 // expected 348 } 349 350 try { 351 new FileOutputStream(path, true).close(); 352 fail("able to write!"); 353 } catch (IOException e) { 354 // expected 355 } 356 } 357 assertMediaNoAccess(ContentResolver resolver, boolean legacyApp)358 public static void assertMediaNoAccess(ContentResolver resolver, boolean legacyApp) 359 throws Exception { 360 final ContentValues values = new ContentValues(); 361 values.put(Images.Media.MIME_TYPE, "image/jpeg"); 362 values.put(Images.Media.DATA, 363 buildProbeFile(Environment.getExternalStorageDirectory()).getAbsolutePath()); 364 365 try { 366 Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); 367 if (legacyApp) { 368 // For legacy apps we do not crash - just make the operation do nothing 369 assertEquals(MediaStore.Images.Media.EXTERNAL_CONTENT_URI 370 .buildUpon().appendPath("0").build().toString(), uri.toString()); 371 } else { 372 fail("Expected access to be blocked"); 373 } 374 } catch (Exception expected) { 375 } 376 } 377 assertMediaReadWriteAccess(ContentResolver resolver)378 public static void assertMediaReadWriteAccess(ContentResolver resolver) throws Exception { 379 final ContentValues values = new ContentValues(); 380 values.put(Images.Media.MIME_TYPE, "image/jpeg"); 381 values.put(Images.Media.DATA, 382 buildProbeFile(Environment.getExternalStorageDirectory()).getAbsolutePath()); 383 384 final Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); 385 try { 386 resolver.openFileDescriptor(uri, "rw").close(); 387 resolver.openFileDescriptor(uri, "w").close(); 388 resolver.openFileDescriptor(uri, "r").close(); 389 } finally { 390 resolver.delete(uri, null, null); 391 } 392 } 393 isWhiteList(File file)394 private static boolean isWhiteList(File file) { 395 final String[] whiteLists = { 396 "autorun.inf", ".android_secure", "android_secure" 397 }; 398 if (file.getParentFile().getAbsolutePath().equals( 399 Environment.getExternalStorageDirectory().getAbsolutePath())) { 400 for (String whiteList : whiteLists) { 401 if (file.getName().equalsIgnoreCase(whiteList)) { 402 return true; 403 } 404 } 405 } 406 return false; 407 } 408 removeWhiteList(File[] files)409 private static File[] removeWhiteList(File[] files) { 410 List<File> fileList = new ArrayList<File>(); 411 if (files == null) { 412 return null; 413 } 414 415 for (File file : files) { 416 if (!isWhiteList(file)) { 417 fileList.add(file); 418 } 419 } 420 return fileList.toArray(new File[fileList.size()]); 421 } 422 deleteContents(File dir)423 public static void deleteContents(File dir) throws IOException { 424 File[] files = dir.listFiles(); 425 files = removeWhiteList(files); 426 if (files != null) { 427 for (File file : files) { 428 if (file.isDirectory()) { 429 deleteContents(file); 430 } 431 assertTrue(file.delete()); 432 } 433 434 File[] dirs = removeWhiteList(dir.listFiles()); 435 if (dirs.length != 0) { 436 fail("Expected wiped storage but found: " + Arrays.toString(dirs)); 437 } 438 } 439 } 440 writeInt(File file, int value)441 public static void writeInt(File file, int value) throws IOException { 442 final DataOutputStream os = new DataOutputStream(new FileOutputStream(file)); 443 try { 444 os.writeInt(value); 445 } finally { 446 os.close(); 447 } 448 } 449 readInt(File file)450 public static int readInt(File file) throws IOException { 451 final DataInputStream is = new DataInputStream(new FileInputStream(file)); 452 try { 453 return is.readInt(); 454 } finally { 455 is.close(); 456 } 457 } 458 logCommand(String... cmd)459 public static void logCommand(String... cmd) throws Exception { 460 final Process proc = new ProcessBuilder(cmd).redirectErrorStream(true).start(); 461 462 final ByteArrayOutputStream buf = new ByteArrayOutputStream(); 463 copy(proc.getInputStream(), buf); 464 final int res = proc.waitFor(); 465 466 Log.d(TAG, Arrays.toString(cmd) + " result " + res + ":"); 467 Log.d(TAG, buf.toString()); 468 } 469 470 /** Shamelessly lifted from libcore.io.Streams */ copy(InputStream in, OutputStream out)471 public static int copy(InputStream in, OutputStream out) throws IOException { 472 int total = 0; 473 byte[] buffer = new byte[8192]; 474 int c; 475 while ((c = in.read(buffer)) != -1) { 476 total += c; 477 out.write(buffer, 0, c); 478 } 479 return total; 480 } 481 } 482