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