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