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