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