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