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 android.provider.cts;
18 
19 import com.android.cts.provider.R;
20 
21 import android.content.ContentResolver;
22 import android.content.ContentUris;
23 import android.content.ContentValues;
24 import android.content.Context;
25 import android.cts.util.FileCopyHelper;
26 import android.database.Cursor;
27 import android.net.Uri;
28 import android.os.Environment;
29 import android.os.ParcelFileDescriptor;
30 import android.os.SystemClock;
31 import android.provider.MediaStore;
32 import android.provider.MediaStore.MediaColumns;
33 import android.test.AndroidTestCase;
34 import android.util.Log;
35 
36 import java.io.File;
37 import java.io.FileNotFoundException;
38 import java.io.FileOutputStream;
39 import java.io.FilenameFilter;
40 import java.io.IOException;
41 import java.util.ArrayList;
42 import java.util.Arrays;
43 import java.util.Collections;
44 import java.util.List;
45 
46 public class MediaStore_FilesTest extends AndroidTestCase {
47 
48     private ContentResolver mResolver;
49 
50     @Override
setUp()51     protected void setUp() throws Exception {
52         super.setUp();
53         mResolver = mContext.getContentResolver();
54         cleanup();
55     }
56 
57     @Override
tearDown()58     protected void tearDown() throws Exception {
59         super.tearDown();
60         cleanup();
61     }
62 
cleanup()63     void cleanup() {
64         final String testName = getClass().getCanonicalName();
65         mResolver.delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
66                 "_data LIKE ?1", new String[] {"%" + testName + "%"});
67         File ext = Environment.getExternalStorageDirectory();
68         File[] junk = ext.listFiles(new FilenameFilter() {
69 
70             @Override
71             public boolean accept(File dir, String filename) {
72                 return filename.contains(testName);
73             }
74         });
75         for (File f: junk) {
76             deleteAll(f);
77         }
78     }
79 
deleteAll(File f)80     void deleteAll(File f) {
81         if (f.isDirectory()) {
82             File [] sub = f.listFiles();
83             for (File s: sub) {
84                 deleteAll(s);
85             }
86         }
87         f.delete();
88     }
89 
testGetContentUri()90     public void testGetContentUri() {
91         String volumeName = MediaStoreAudioTestHelper.EXTERNAL_VOLUME_NAME;
92         Uri allFilesUri = MediaStore.Files.getContentUri(volumeName);
93 
94         // Get the current file count. We will check if this increases after
95         // adding a file to the provider.
96         int fileCount = getFileCount(allFilesUri);
97 
98         // Check that inserting empty values causes an exception.
99         ContentValues values = new ContentValues();
100         try {
101             mResolver.insert(allFilesUri, values);
102             fail("Should throw an exception");
103         } catch (IllegalArgumentException e) {
104             // Expecting an exception
105         }
106 
107         // Add a path for a file and check that the returned uri appends a
108         // path properly.
109         String dataPath = "does_not_really_exist.txt";
110         values.put(MediaColumns.DATA, dataPath);
111         Uri fileUri = mResolver.insert(allFilesUri, values);
112         long fileId = ContentUris.parseId(fileUri);
113         assertEquals(fileUri, ContentUris.withAppendedId(allFilesUri, fileId));
114 
115         // Check that getContentUri with the file id produces the same url
116         Uri rowUri = MediaStore.Files.getContentUri(volumeName, fileId);
117         assertEquals(fileUri, rowUri);
118 
119         // Check that the file count has increased.
120         int newFileCount = getFileCount(allFilesUri);
121         assertEquals(fileCount + 1, newFileCount);
122 
123         // Check that the path we inserted was stored properly.
124         assertStringColumn(fileUri, MediaColumns.DATA, dataPath);
125 
126         // Update the path and check that the database changed.
127         String updatedPath = "still_does_not_exist.txt";
128         values.put(MediaColumns.DATA, updatedPath);
129         assertEquals(1, mResolver.update(fileUri, values, null, null));
130         assertStringColumn(fileUri, MediaColumns.DATA, updatedPath);
131 
132         // check that inserting a duplicate entry fails
133         Uri foo = mResolver.insert(allFilesUri, values);
134         assertNull(foo);
135 
136         // Delete the file and observe that the file count decreased.
137         assertEquals(1, mResolver.delete(fileUri, null, null));
138         assertEquals(fileCount, getFileCount(allFilesUri));
139 
140         // Make sure the deleted file is not returned by the cursor.
141         Cursor cursor = mResolver.query(fileUri, null, null, null, null);
142         try {
143             assertFalse(cursor.moveToNext());
144         } finally {
145             cursor.close();
146         }
147 
148         // insert file and check its parent
149         values.clear();
150         try {
151             String b = mContext.getExternalFilesDir(Environment.DIRECTORY_MUSIC).getCanonicalPath();
152             values.put(MediaColumns.DATA, b + "/testing");
153             fileUri = mResolver.insert(allFilesUri, values);
154             cursor = mResolver.query(fileUri, new String[] { MediaStore.Files.FileColumns.PARENT },
155                     null, null, null);
156             assertEquals(1, cursor.getCount());
157             cursor.moveToFirst();
158             long parentid = cursor.getLong(0);
159             assertTrue("got 0 parent for non root file", parentid != 0);
160 
161             cursor.close();
162             cursor = mResolver.query(ContentUris.withAppendedId(allFilesUri, parentid),
163                     new String[] { MediaColumns.DATA }, null, null, null);
164             assertEquals(1, cursor.getCount());
165             cursor.moveToFirst();
166             String parentPath = cursor.getString(0);
167             assertEquals(b, parentPath);
168 
169             mResolver.delete(fileUri, null, null);
170         } catch (IOException e) {
171             fail(e.getMessage());
172         } finally {
173             cursor.close();
174         }
175     }
176 
testCaseSensitivity()177     public void testCaseSensitivity() throws IOException {
178         String fileDir = Environment.getExternalStorageDirectory() +
179                 "/" + getClass().getCanonicalName();
180         String fileName = fileDir + "/Test.Mp3";
181         writeFile(R.raw.testmp3, fileName);
182 
183         String volumeName = MediaStoreAudioTestHelper.EXTERNAL_VOLUME_NAME;
184         Uri allFilesUri = MediaStore.Files.getContentUri(volumeName);
185         ContentValues values = new ContentValues();
186         values.put(MediaColumns.DATA, fileDir + "/test.mp3");
187         Uri fileUri = mResolver.insert(allFilesUri, values);
188         try {
189             ParcelFileDescriptor pfd = mResolver.openFileDescriptor(fileUri, "r");
190             pfd.close();
191         } finally {
192             mResolver.delete(fileUri, null, null);
193             new File(fileName).delete();
194             new File(fileDir).delete();
195         }
196     }
197 
realPathFor(ParcelFileDescriptor pfd)198     String realPathFor(ParcelFileDescriptor pfd) {
199         File real = new File("/proc/self/fd/" + pfd.getFd());
200         try {
201             return real.getCanonicalPath();
202         } catch (IOException e) {
203             return null;
204         }
205     }
206 
testAccess()207     public void testAccess() throws IOException {
208         // clean up from previous run
209         mResolver.delete(MediaStore.Images.Media.INTERNAL_CONTENT_URI,
210                 "_data NOT LIKE ?", new String[] { "/system/%" } );
211 
212         // insert some dummy starter data into the provider
213         ContentValues values = new ContentValues();
214         values.put(MediaStore.Images.Media.DISPLAY_NAME, "My Bitmap");
215         values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
216         values.put(MediaStore.Images.Media.DATA, "/foo/bar/dummy.jpg");
217         Uri uri = mResolver.insert(MediaStore.Images.Media.INTERNAL_CONTENT_URI, values);
218 
219         // point _data at directory and try to get an fd for it
220         values = new ContentValues();
221         values.put("_data", "/data/media");
222         mResolver.update(uri, values, null, null);
223         ParcelFileDescriptor pfd = null;
224         try {
225             pfd = mResolver.openFileDescriptor(uri, "r");
226             pfd.close();
227             fail("shouldn't be here");
228         } catch (FileNotFoundException e) {
229             // expected
230         }
231 
232         // try to create a file in a place we don't have access to
233         values = new ContentValues();
234         values.put("_data", "/data/media/test.dat");
235         mResolver.update(uri, values, null, null);
236         try {
237             pfd = mResolver.openFileDescriptor(uri, "w");
238             pfd.close();
239             fail("shouldn't be here");
240         } catch (FileNotFoundException e) {
241             // expected
242         }
243         // read file back
244         try {
245             pfd = mResolver.openFileDescriptor(uri, "r");
246             pfd.close();
247             fail("shouldn't be here");
248         } catch (FileNotFoundException e) {
249             // expected
250         }
251 
252         // point _data at media database and read it
253         values = new ContentValues();
254         values.put("_data", "/data/data/com.android.providers.media/databases/internal.db");
255         mResolver.update(uri, values, null, null);
256         try {
257             pfd = mResolver.openFileDescriptor(uri, "r");
258             pfd.close();
259             fail("shouldn't be here");
260         } catch (FileNotFoundException e) {
261             // expected
262         }
263 
264         // Insert a private file into the database. Since it's private, the media provider won't
265         // be able to open it
266         FileOutputStream fos = mContext.openFileOutput("dummy.dat", Context.MODE_PRIVATE);
267         fos.write(0);
268         fos.close();
269         File path = mContext.getFileStreamPath("dummy.dat");
270         values = new ContentValues();
271         values.put("_data", path.getAbsolutePath());
272 
273         mResolver.update(uri, values, null, null);
274         try {
275             pfd = mResolver.openFileDescriptor(uri, "r");
276             pfd.close();
277             fail("shouldn't be here");
278         } catch (FileNotFoundException e) {
279             // expected
280         }
281         // now make the file world-readable
282         fos = mContext.openFileOutput("dummy.dat", Context.MODE_WORLD_READABLE);
283         fos.write(0);
284         fos.close();
285         try {
286             pfd = mResolver.openFileDescriptor(uri, "r");
287             pfd.close();
288         } catch (FileNotFoundException e) {
289             fail("failed to open file");
290         }
291         path.delete();
292 
293         File sdfile = null;
294         if (Environment.isExternalStorageEmulated()) {
295             // create file on sdcard and check access via real path
296             String fileDir = Environment.getExternalStorageDirectory() +
297                     "/" + getClass().getCanonicalName() + "/test.mp3";
298             sdfile = new File(fileDir);
299             writeFile(R.raw.testmp3, sdfile.getCanonicalPath());
300             assertTrue(sdfile.exists());
301             values = new ContentValues();
302             values.put("_data", sdfile.getCanonicalPath());
303             mResolver.update(uri, values, null, null);
304             try {
305                 pfd = mResolver.openFileDescriptor(uri, "r");
306 
307                 // get the real path from the file descriptor (this relies on the media provider
308                 // having opened the path via the real path instead of the emulated path).
309                 String realPath = realPathFor(pfd);
310                 pfd.close();
311                 if (realPath.equals(sdfile.getCanonicalPath())) {
312                     // provider did not use real/translated path
313                     sdfile = null;
314                 } else {
315                     values = new ContentValues();
316                     values.put("_data", realPath);
317                     mResolver.update(uri, values, null, null);
318 
319                     // we shouldn't be able to access this
320                     try {
321                         pfd = mResolver.openFileDescriptor(uri, "r");
322                         fail("shouldn't have fd for " + realPath);
323                     } catch (FileNotFoundException e) {
324                         // expected
325                     } finally {
326                         pfd.close();
327                     }
328                 }
329             } catch (FileNotFoundException e) {
330                 fail("couldn't open file");
331             }
332         }
333 
334         // clean up
335         assertEquals(1, mResolver.delete(uri, null, null));
336         if (sdfile != null) {
337             assertEquals("couldn't delete " + sdfile.getCanonicalPath(), true, sdfile.delete());
338         }
339 
340         // test secondary storage if present
341         List<File> allpaths = getSecondaryPackageSpecificPaths(mContext);
342         List<String> trimmedPaths = new ArrayList<String>();
343 
344         for (File extpath: allpaths) {
345             assertNotNull("Valid media must be inserted during CTS", extpath);
346             assertEquals("Valid media must be inserted for " + extpath
347                     + " during CTS", Environment.MEDIA_MOUNTED,
348                     Environment.getStorageState(extpath));
349 
350             File child = extpath;
351             while (true) {
352                 File parent = child.getParentFile();
353                 if (parent == null) {
354                     fail("didn't expect to be here");
355                 }
356                 if (!Environment.MEDIA_MOUNTED.equals(Environment.getStorageState(parent))) {
357                     // we went past the root
358                     String abspath = child.getAbsolutePath();
359                     if (!trimmedPaths.contains(abspath)) {
360                         trimmedPaths.add(abspath);
361                     }
362                     break;
363                 }
364                 child = parent;
365             }
366         }
367 
368         String fileDir = Environment.getExternalStorageDirectory() +
369                 "/" + getClass().getCanonicalName() + "-" + SystemClock.elapsedRealtime();
370         String fileName = fileDir + "/TestSecondary.Mp3";
371         writeFile(R.raw.testmp3_2, fileName); // file without album art
372 
373 
374         // insert temp file
375         values = new ContentValues();
376         values.put(MediaStore.Audio.Media.DATA, fileName);
377         values.put(MediaStore.Audio.Media.ARTIST, "Artist-" + SystemClock.elapsedRealtime());
378         values.put(MediaStore.Audio.Media.ALBUM, "Album-" + SystemClock.elapsedRealtime());
379         values.put(MediaStore.Audio.Media.MIME_TYPE, "audio/mp3");
380         Uri fileUri = mResolver.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values);
381         // give media provider some time to realize there's no album art
382         SystemClock.sleep(1000);
383         // get its album id
384         Cursor c = mResolver.query(fileUri, new String[] { MediaStore.Audio.Media.ALBUM_ID},
385                 null, null, null);
386         assertTrue(c.moveToFirst());
387         int albumid = c.getInt(0);
388         Uri albumArtUriBase = Uri.parse("content://media/external/audio/albumart");
389         Uri albumArtUri = ContentUris.withAppendedId(albumArtUriBase, albumid);
390         try {
391             pfd = mResolver.openFileDescriptor(albumArtUri, "r");
392             fail("no album art, shouldn't be here. Got: " + realPathFor(pfd));
393         } catch (Exception e) {
394             // expected
395         }
396 
397         // replace file with one that has album art
398         writeFile(R.raw.testmp3, fileName); // file with album art
399 
400         for (String s: trimmedPaths) {
401             File dir = new File(s + "/foobardir-" + SystemClock.elapsedRealtime());
402             assertFalse("please remove " + dir.getAbsolutePath()
403                     + " before running", dir.exists());
404             File file = new File(dir, "foobar");
405             values = new ContentValues();
406             values.put(MediaStore.Audio.Media.ALBUM_ID, albumid);
407             values.put(MediaStore.Audio.Media.DATA, file.getAbsolutePath());
408             mResolver.insert(albumArtUriBase, values);
409             try {
410                 pfd = mResolver.openFileDescriptor(albumArtUri, "r");
411                 fail("shouldn't have fd for album " + albumid + ", got " + realPathFor(pfd));
412             } catch (Exception e) {
413                 // expected
414             } finally {
415                 pfd.close();
416             }
417             assertFalse(dir.getAbsolutePath() + " was created", dir.exists());
418         }
419         mResolver.delete(fileUri, null, null);
420         new File(fileName).delete();
421 
422         // try creating files in root
423         for (String s: trimmedPaths) {
424             File dir = new File(s);
425             File file = new File(dir, "foobar.jpg");
426 
427             values = new ContentValues();
428             values.put(MediaStore.Files.FileColumns.DATA, file.getAbsolutePath());
429             fileUri = mResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
430             assertNotNull(fileUri);
431 
432             // check that adding the file doesn't cause it to be created
433             assertFalse(file.exists());
434 
435             // check if opening the file for write works
436             try {
437                 mResolver.openOutputStream(fileUri).close();
438                 fail("shouldn't have been able to create output stream");
439             } catch (SecurityException e) {
440                 // expected
441             }
442             // check that deleting the file doesn't cause it to be created
443             mResolver.delete(fileUri, null, null);
444             assertFalse(file.exists());
445         }
446 
447         // try creating files in new subdir
448         for (String s: trimmedPaths) {
449             File dir = new File(s + "/foobardir");
450             File file = new File(dir, "foobar.jpg");
451 
452             values = new ContentValues();
453             values.put(MediaStore.Files.FileColumns.DATA, dir.getAbsolutePath());
454 
455             Uri dirUri = mResolver.insert(MediaStore.Files.getContentUri("external"), values);
456             assertNotNull(dirUri);
457 
458             values = new ContentValues();
459             values.put(MediaStore.Files.FileColumns.DATA, file.getAbsolutePath());
460             fileUri = mResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
461             assertNotNull(fileUri);
462 
463             // check that adding the file or its folder didn't cause either one to be created
464             assertFalse(dir.exists());
465             assertFalse(file.exists());
466 
467             // check if opening the file for write works
468             try {
469                 mResolver.openOutputStream(fileUri).close();
470                 fail("shouldn't have been able to create output stream");
471             } catch (SecurityException e) {
472                 // expected
473             }
474             // check that deleting the file or its folder doesn't cause either one to be created
475             mResolver.delete(fileUri, null, null);
476             assertFalse(dir.exists());
477             assertFalse(file.exists());
478             mResolver.delete(dirUri, null, null);
479             assertFalse(dir.exists());
480             assertFalse(file.exists());
481         }
482     }
483 
getSecondaryPackageSpecificPaths(Context context)484     public static List<File> getSecondaryPackageSpecificPaths(Context context) {
485         final List<File> paths = new ArrayList<File>();
486         Collections.addAll(paths, dropFirst(context.getExternalCacheDirs()));
487         Collections.addAll(paths, dropFirst(context.getExternalFilesDirs(null)));
488         Collections.addAll(
489                 paths, dropFirst(context.getExternalFilesDirs(Environment.DIRECTORY_PICTURES)));
490         Collections.addAll(paths, dropFirst(context.getObbDirs()));
491         return paths;
492     }
493 
dropFirst(File[] before)494     private static File[] dropFirst(File[] before) {
495         final File[] after = new File[before.length - 1];
496         System.arraycopy(before, 1, after, 0, after.length);
497         return after;
498     }
499 
writeFile(int resid, String path)500     private void writeFile(int resid, String path) throws IOException {
501         File out = new File(path);
502         File dir = out.getParentFile();
503         dir.mkdirs();
504         FileCopyHelper copier = new FileCopyHelper(mContext);
505         copier.copyToExternalStorage(resid, out);
506     }
507 
getFileCount(Uri uri)508     private int getFileCount(Uri uri) {
509         Cursor cursor = mResolver.query(uri, null, null, null, null);
510         try {
511             return cursor.getCount();
512         } finally {
513             cursor.close();
514         }
515     }
516 
assertStringColumn(Uri fileUri, String columnName, String expectedValue)517     private void assertStringColumn(Uri fileUri, String columnName, String expectedValue) {
518         Cursor cursor = mResolver.query(fileUri, null, null, null, null);
519         try {
520             assertTrue(cursor.moveToNext());
521             int index = cursor.getColumnIndexOrThrow(columnName);
522             assertEquals(expectedValue, cursor.getString(index));
523         } finally {
524             cursor.close();
525         }
526     }
527 }
528