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