• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.provider.cts.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         path.delete();
282 
283         File sdfile = null;
284         if (Environment.isExternalStorageEmulated()) {
285             // create file on sdcard and check access via real path
286             String fileDir = Environment.getExternalStorageDirectory() +
287                     "/" + getClass().getCanonicalName() + "/test.mp3";
288             sdfile = new File(fileDir);
289             writeFile(R.raw.testmp3, sdfile.getCanonicalPath());
290             assertTrue(sdfile.exists());
291             values = new ContentValues();
292             values.put("_data", sdfile.getCanonicalPath());
293             mResolver.update(uri, values, null, null);
294             try {
295                 pfd = mResolver.openFileDescriptor(uri, "r");
296 
297                 // get the real path from the file descriptor (this relies on the media provider
298                 // having opened the path via the real path instead of the emulated path).
299                 String realPath = realPathFor(pfd);
300                 pfd.close();
301                 if (realPath.equals(sdfile.getCanonicalPath())) {
302                     // provider did not use real/translated path
303                     sdfile = null;
304                 } else {
305                     values = new ContentValues();
306                     values.put("_data", realPath);
307                     mResolver.update(uri, values, null, null);
308 
309                     // we shouldn't be able to access this
310                     try {
311                         pfd = mResolver.openFileDescriptor(uri, "r");
312                         fail("shouldn't have fd for " + realPath);
313                     } catch (FileNotFoundException e) {
314                         // expected
315                     } finally {
316                         pfd.close();
317                     }
318                 }
319             } catch (FileNotFoundException e) {
320                 fail("couldn't open file");
321             }
322         }
323 
324         // clean up
325         assertEquals(1, mResolver.delete(uri, null, null));
326         if (sdfile != null) {
327             assertEquals("couldn't delete " + sdfile.getCanonicalPath(), true, sdfile.delete());
328         }
329 
330         // test secondary storage if present
331         List<File> allpaths = getSecondaryPackageSpecificPaths(mContext);
332         List<String> trimmedPaths = new ArrayList<String>();
333 
334         for (File extpath: allpaths) {
335             assertNotNull("Valid media must be inserted during CTS", extpath);
336             assertEquals("Valid media must be inserted for " + extpath
337                     + " during CTS", Environment.MEDIA_MOUNTED,
338                     Environment.getStorageState(extpath));
339 
340             File child = extpath;
341             while (true) {
342                 File parent = child.getParentFile();
343                 if (parent == null) {
344                     fail("didn't expect to be here");
345                 }
346                 if (!Environment.MEDIA_MOUNTED.equals(Environment.getStorageState(parent))) {
347                     // we went past the root
348                     String abspath = child.getAbsolutePath();
349                     if (!trimmedPaths.contains(abspath)) {
350                         trimmedPaths.add(abspath);
351                     }
352                     break;
353                 }
354                 child = parent;
355             }
356         }
357 
358         String fileDir = Environment.getExternalStorageDirectory() +
359                 "/" + getClass().getCanonicalName() + "-" + SystemClock.elapsedRealtime();
360         String fileName = fileDir + "/TestSecondary.Mp3";
361         writeFile(R.raw.testmp3_2, fileName); // file without album art
362 
363 
364         // insert temp file
365         values = new ContentValues();
366         values.put(MediaStore.Audio.Media.DATA, fileName);
367         values.put(MediaStore.Audio.Media.ARTIST, "Artist-" + SystemClock.elapsedRealtime());
368         values.put(MediaStore.Audio.Media.ALBUM, "Album-" + SystemClock.elapsedRealtime());
369         values.put(MediaStore.Audio.Media.MIME_TYPE, "audio/mp3");
370         Uri fileUri = mResolver.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values);
371         // give media provider some time to realize there's no album art
372         SystemClock.sleep(1000);
373         // get its album id
374         Cursor c = mResolver.query(fileUri, new String[] { MediaStore.Audio.Media.ALBUM_ID},
375                 null, null, null);
376         assertTrue(c.moveToFirst());
377         int albumid = c.getInt(0);
378         Uri albumArtUriBase = Uri.parse("content://media/external/audio/albumart");
379         Uri albumArtUri = ContentUris.withAppendedId(albumArtUriBase, albumid);
380         try {
381             pfd = mResolver.openFileDescriptor(albumArtUri, "r");
382             fail("no album art, shouldn't be here. Got: " + realPathFor(pfd));
383         } catch (Exception e) {
384             // expected
385         }
386 
387         // replace file with one that has album art
388         writeFile(R.raw.testmp3, fileName); // file with album art
389 
390         for (String s: trimmedPaths) {
391             File dir = new File(s + "/foobardir-" + SystemClock.elapsedRealtime());
392             assertFalse("please remove " + dir.getAbsolutePath()
393                     + " before running", dir.exists());
394             File file = new File(dir, "foobar");
395             values = new ContentValues();
396             values.put(MediaStore.Audio.Media.ALBUM_ID, albumid);
397             values.put(MediaStore.Audio.Media.DATA, file.getAbsolutePath());
398             mResolver.insert(albumArtUriBase, values);
399             try {
400                 pfd = mResolver.openFileDescriptor(albumArtUri, "r");
401                 fail("shouldn't have fd for album " + albumid + ", got " + realPathFor(pfd));
402             } catch (Exception e) {
403                 // expected
404             } finally {
405                 pfd.close();
406             }
407             assertFalse(dir.getAbsolutePath() + " was created", dir.exists());
408         }
409         mResolver.delete(fileUri, null, null);
410         new File(fileName).delete();
411 
412         // try creating files in root
413         for (String s: trimmedPaths) {
414             File dir = new File(s);
415             File file = new File(dir, "foobar.jpg");
416 
417             values = new ContentValues();
418             values.put(MediaStore.Files.FileColumns.DATA, file.getAbsolutePath());
419             fileUri = mResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
420             assertNotNull(fileUri);
421 
422             // check that adding the file doesn't cause it to be created
423             assertFalse(file.exists());
424 
425             // check if opening the file for write works
426             try {
427                 mResolver.openOutputStream(fileUri).close();
428                 fail("shouldn't have been able to create output stream");
429             } catch (SecurityException e) {
430                 // expected
431             }
432             // check that deleting the file doesn't cause it to be created
433             mResolver.delete(fileUri, null, null);
434             assertFalse(file.exists());
435         }
436 
437         // try creating files in new subdir
438         for (String s: trimmedPaths) {
439             File dir = new File(s + "/foobardir");
440             File file = new File(dir, "foobar.jpg");
441 
442             values = new ContentValues();
443             values.put(MediaStore.Files.FileColumns.DATA, dir.getAbsolutePath());
444 
445             Uri dirUri = mResolver.insert(MediaStore.Files.getContentUri("external"), values);
446             assertNotNull(dirUri);
447 
448             values = new ContentValues();
449             values.put(MediaStore.Files.FileColumns.DATA, file.getAbsolutePath());
450             fileUri = mResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
451             assertNotNull(fileUri);
452 
453             // check that adding the file or its folder didn't cause either one to be created
454             assertFalse(dir.exists());
455             assertFalse(file.exists());
456 
457             // check if opening the file for write works
458             try {
459                 mResolver.openOutputStream(fileUri).close();
460                 fail("shouldn't have been able to create output stream");
461             } catch (SecurityException e) {
462                 // expected
463             }
464             // check that deleting the file or its folder doesn't cause either one to be created
465             mResolver.delete(fileUri, null, null);
466             assertFalse(dir.exists());
467             assertFalse(file.exists());
468             mResolver.delete(dirUri, null, null);
469             assertFalse(dir.exists());
470             assertFalse(file.exists());
471         }
472     }
473 
getSecondaryPackageSpecificPaths(Context context)474     public static List<File> getSecondaryPackageSpecificPaths(Context context) {
475         final List<File> paths = new ArrayList<File>();
476         Collections.addAll(paths, dropFirst(context.getExternalCacheDirs()));
477         Collections.addAll(paths, dropFirst(context.getExternalFilesDirs(null)));
478         Collections.addAll(
479                 paths, dropFirst(context.getExternalFilesDirs(Environment.DIRECTORY_PICTURES)));
480         Collections.addAll(paths, dropFirst(context.getObbDirs()));
481         return paths;
482     }
483 
dropFirst(File[] before)484     private static File[] dropFirst(File[] before) {
485         final File[] after = new File[before.length - 1];
486         System.arraycopy(before, 1, after, 0, after.length);
487         return after;
488     }
489 
writeFile(int resid, String path)490     private void writeFile(int resid, String path) throws IOException {
491         File out = new File(path);
492         File dir = out.getParentFile();
493         dir.mkdirs();
494         FileCopyHelper copier = new FileCopyHelper(mContext);
495         copier.copyToExternalStorage(resid, out);
496     }
497 
getFileCount(Uri uri)498     private int getFileCount(Uri uri) {
499         Cursor cursor = mResolver.query(uri, null, null, null, null);
500         try {
501             return cursor.getCount();
502         } finally {
503             cursor.close();
504         }
505     }
506 
assertStringColumn(Uri fileUri, String columnName, String expectedValue)507     private void assertStringColumn(Uri fileUri, String columnName, String expectedValue) {
508         Cursor cursor = mResolver.query(fileUri, null, null, null, null);
509         try {
510             assertTrue(cursor.moveToNext());
511             int index = cursor.getColumnIndexOrThrow(columnName);
512             assertEquals(expectedValue, cursor.getString(index));
513         } finally {
514             cursor.close();
515         }
516     }
517 }
518