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.getObbDirs());
141         return paths;
142     }
143 
getAllPackageSpecificPathsExceptObb(Context context)144     public static List<File> getAllPackageSpecificPathsExceptObb(Context context) {
145         final List<File> paths = new ArrayList<File>();
146         Collections.addAll(paths, context.getExternalCacheDirs());
147         Collections.addAll(paths, context.getExternalFilesDirs(null));
148         Collections.addAll(paths, context.getExternalFilesDirs(Environment.DIRECTORY_PICTURES));
149         return paths;
150     }
151 
getPrimaryPackageSpecificPaths(Context context)152     public static List<File> getPrimaryPackageSpecificPaths(Context context) {
153         final List<File> paths = new ArrayList<File>();
154         Collections.addAll(paths, context.getExternalCacheDir());
155         Collections.addAll(paths, context.getExternalFilesDir(null));
156         Collections.addAll(paths, context.getExternalFilesDir(Environment.DIRECTORY_PICTURES));
157         Collections.addAll(paths, context.getObbDir());
158         return paths;
159     }
160 
getSecondaryPackageSpecificPaths(Context context)161     public static List<File> getSecondaryPackageSpecificPaths(Context context) {
162         final List<File> paths = new ArrayList<File>();
163         Collections.addAll(paths, dropFirst(context.getExternalCacheDirs()));
164         Collections.addAll(paths, dropFirst(context.getExternalFilesDirs(null)));
165         Collections.addAll(
166                 paths, dropFirst(context.getExternalFilesDirs(Environment.DIRECTORY_PICTURES)));
167         Collections.addAll(paths, dropFirst(context.getObbDirs()));
168         return paths;
169     }
170 
getMountPaths()171     public static List<File> getMountPaths() throws IOException {
172         final List<File> paths = new ArrayList<>();
173         final BufferedReader br = new BufferedReader(new FileReader("/proc/self/mounts"));
174         try {
175             String line;
176             while ((line = br.readLine()) != null) {
177                 final String[] fields = line.split(" ");
178                 paths.add(new File(fields[1]));
179             }
180         } finally {
181             br.close();
182         }
183         return paths;
184     }
185 
dropFirst(File[] before)186     private static File[] dropFirst(File[] before) {
187         final File[] after = new File[before.length - 1];
188         System.arraycopy(before, 1, after, 0, after.length);
189         return after;
190     }
191 
buildGiftForPackage(Context context, String packageName)192     public static File buildGiftForPackage(Context context, String packageName) {
193         final File myCache = context.getExternalCacheDir();
194         return new File(myCache.getAbsolutePath().replace(context.getPackageName(), packageName),
195                 packageName + ".gift");
196     }
197 
buildProbeFile(File dir)198     public static File buildProbeFile(File dir) {
199         return new File(dir, ".probe_" + System.nanoTime());
200     }
201 
assertDirReadOnlyAccess(File path)202     public static void assertDirReadOnlyAccess(File path) {
203         Log.d(TAG, "Asserting read-only access to " + path);
204 
205         assertTrue("exists", path.exists());
206         assertTrue("read", path.canRead());
207         assertTrue("execute", path.canExecute());
208         assertNotNull("list", path.list());
209 
210         try {
211             final File probe = buildProbeFile(path);
212             probe.createNewFile();
213             probe.delete();
214             fail("able to create probe!");
215         } catch (IOException e) {
216             // expected
217         }
218     }
219 
assertDirReadWriteAccess(File path)220     public static void assertDirReadWriteAccess(File path) {
221         Log.d(TAG, "Asserting read/write access to " + path);
222 
223         assertTrue("exists", path.exists());
224         assertTrue("read", path.canRead());
225         assertTrue("execute", path.canExecute());
226         assertNotNull("list", path.list());
227 
228         try {
229             final File probe = buildProbeFile(path);
230             probe.createNewFile();
231             probe.delete();
232         } catch (IOException e) {
233             fail("failed to create probe!");
234         }
235     }
236 
assertDirNoAccess(File path)237     public static void assertDirNoAccess(File path) {
238         Log.d(TAG, "Asserting no access to " + path);
239 
240         assertFalse("read", path.canRead());
241         assertNull("list", path.list());
242 
243         try {
244             final File probe = buildProbeFile(path);
245             probe.createNewFile();
246             probe.delete();
247             fail("able to create probe!");
248         } catch (IOException e) {
249             // expected
250         }
251     }
252 
assertDirNoWriteAccess(File path)253     public static void assertDirNoWriteAccess(File path) {
254         Log.d(TAG, "Asserting no write access to " + path);
255 
256         try {
257             final File probe = buildProbeFile(path);
258             probe.createNewFile();
259             probe.delete();
260             fail("able to create probe!");
261         } catch (IOException e) {
262             // expected
263         }
264     }
265 
assertFileReadOnlyAccess(File path)266     public static void assertFileReadOnlyAccess(File path) {
267         try {
268             new FileInputStream(path).close();
269         } catch (IOException e) {
270             fail("failed to read!");
271         }
272 
273         try {
274             new FileOutputStream(path, true).close();
275             fail("able to write!");
276         } catch (IOException e) {
277             // expected
278         }
279     }
280 
assertFileReadWriteAccess(File path)281     public static void assertFileReadWriteAccess(File path) {
282         try {
283             new FileInputStream(path).close();
284         } catch (IOException e) {
285             fail("failed to read!");
286         }
287 
288         try {
289             new FileOutputStream(path, true).close();
290         } catch (IOException e) {
291             fail("failed to write!");
292         }
293     }
294 
assertFileNoAccess(File path)295     public static void assertFileNoAccess(File path) {
296         try {
297             new FileInputStream(path).close();
298             fail("able to read!");
299         } catch (IOException e) {
300             // expected
301         }
302 
303         try {
304             new FileOutputStream(path, true).close();
305             fail("able to write!");
306         } catch (IOException e) {
307             // expected
308         }
309     }
310 
assertMediaNoAccess(ContentResolver resolver)311     public static void assertMediaNoAccess(ContentResolver resolver) throws Exception {
312         final ContentValues values = new ContentValues();
313         values.put(Images.Media.MIME_TYPE, "image/jpeg");
314         values.put(Images.Media.DATA,
315                 buildProbeFile(Environment.getExternalStorageDirectory()).getAbsolutePath());
316 
317         try {
318             resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
319             fail("Expected access to be blocked");
320         } catch (Exception expected) {
321         }
322     }
323 
assertMediaReadWriteAccess(ContentResolver resolver)324     public static void assertMediaReadWriteAccess(ContentResolver resolver) throws Exception {
325         final ContentValues values = new ContentValues();
326         values.put(Images.Media.MIME_TYPE, "image/jpeg");
327         values.put(Images.Media.DATA,
328                 buildProbeFile(Environment.getExternalStorageDirectory()).getAbsolutePath());
329 
330         final Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
331         try {
332             resolver.openFileDescriptor(uri, "rw").close();
333             resolver.openFileDescriptor(uri, "w").close();
334             resolver.openFileDescriptor(uri, "r").close();
335         } finally {
336             resolver.delete(uri, null, null);
337         }
338     }
339 
isWhiteList(File file)340     private static boolean isWhiteList(File file) {
341         final String[] whiteLists = {
342                 "autorun.inf", ".android_secure", "android_secure"
343         };
344         if (file.getParentFile().getAbsolutePath().equals(
345                 Environment.getExternalStorageDirectory().getAbsolutePath())) {
346             for (String whiteList : whiteLists) {
347                 if (file.getName().equalsIgnoreCase(whiteList)) {
348                     return true;
349                 }
350             }
351         }
352         return false;
353     }
354 
removeWhiteList(File[] files)355     private static File[] removeWhiteList(File[] files) {
356         List<File> fileList = new ArrayList<File>();
357         if (files == null) {
358             return null;
359         }
360 
361         for (File file : files) {
362             if (!isWhiteList(file)) {
363                 fileList.add(file);
364             }
365         }
366         return fileList.toArray(new File[fileList.size()]);
367     }
368 
deleteContents(File dir)369     public static void deleteContents(File dir) throws IOException {
370         File[] files = dir.listFiles();
371         files = removeWhiteList(files);
372         if (files != null) {
373             for (File file : files) {
374                 if (file.isDirectory()) {
375                     deleteContents(file);
376                 }
377                 assertTrue(file.delete());
378             }
379 
380             File[] dirs = removeWhiteList(dir.listFiles());
381             if (dirs.length != 0) {
382                 fail("Expected wiped storage but found: " + Arrays.toString(dirs));
383             }
384         }
385     }
386 
writeInt(File file, int value)387     public static void writeInt(File file, int value) throws IOException {
388         final DataOutputStream os = new DataOutputStream(new FileOutputStream(file));
389         try {
390             os.writeInt(value);
391         } finally {
392             os.close();
393         }
394     }
395 
readInt(File file)396     public static int readInt(File file) throws IOException {
397         final DataInputStream is = new DataInputStream(new FileInputStream(file));
398         try {
399             return is.readInt();
400         } finally {
401             is.close();
402         }
403     }
404 
logCommand(String... cmd)405     public static void logCommand(String... cmd) throws Exception {
406         final Process proc = new ProcessBuilder(cmd).redirectErrorStream(true).start();
407 
408         final ByteArrayOutputStream buf = new ByteArrayOutputStream();
409         copy(proc.getInputStream(), buf);
410         final int res = proc.waitFor();
411 
412         Log.d(TAG, Arrays.toString(cmd) + " result " + res + ":");
413         Log.d(TAG, buf.toString());
414     }
415 
416     /** Shamelessly lifted from libcore.io.Streams */
copy(InputStream in, OutputStream out)417     public static int copy(InputStream in, OutputStream out) throws IOException {
418         int total = 0;
419         byte[] buffer = new byte[8192];
420         int c;
421         while ((c = in.read(buffer)) != -1) {
422             total += c;
423             out.write(buffer, 0, c);
424         }
425         return total;
426     }
427 }
428