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.media.cts; 18 19 import com.android.cts.media.R; 20 21 import android.content.ComponentName; 22 import android.content.ContentResolver; 23 import android.content.ContentUris; 24 import android.content.ContentValues; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.IntentFilter; 28 import android.content.res.AssetFileDescriptor; 29 import android.cts.util.FileCopyHelper; 30 import android.cts.util.PollingCheck; 31 import android.database.Cursor; 32 import android.media.MediaMetadataRetriever; 33 import android.media.MediaScannerConnection; 34 import android.media.MediaScannerConnection.MediaScannerConnectionClient; 35 import android.mtp.MtpConstants; 36 import android.net.Uri; 37 import android.os.Bundle; 38 import android.os.Environment; 39 import android.os.IBinder; 40 import android.os.SystemClock; 41 import android.provider.MediaStore; 42 import android.test.AndroidTestCase; 43 import android.util.Log; 44 45 import java.io.File; 46 import java.io.IOException; 47 48 public class MediaScannerTest extends AndroidTestCase { 49 50 private static final String MEDIA_TYPE = "audio/mpeg"; 51 private File mMediaFile; 52 private static final int TIME_OUT = 10000; 53 private MockMediaScannerConnection mMediaScannerConnection; 54 private MockMediaScannerConnectionClient mMediaScannerConnectionClient; 55 private String mFileDir; 56 57 @Override 58 protected void setUp() throws Exception { 59 super.setUp(); 60 // prepare the media file. 61 62 mFileDir = Environment.getExternalStorageDirectory() + "/" + getClass().getCanonicalName(); 63 cleanup(); 64 String fileName = mFileDir + "/test" + System.currentTimeMillis() + ".mp3"; 65 writeFile(R.raw.testmp3, fileName); 66 67 mMediaFile = new File(fileName); 68 assertTrue(mMediaFile.exists()); 69 } 70 71 private void writeFile(int resid, String path) throws IOException { 72 File out = new File(path); 73 File dir = out.getParentFile(); 74 dir.mkdirs(); 75 FileCopyHelper copier = new FileCopyHelper(mContext); 76 copier.copyToExternalStorage(resid, out); 77 } 78 79 @Override 80 protected void tearDown() throws Exception { 81 cleanup(); 82 super.tearDown(); 83 } 84 85 private void cleanup() { 86 if (mMediaFile != null) { 87 mMediaFile.delete(); 88 } 89 if (mFileDir != null) { 90 String files[] = new File(mFileDir).list(); 91 if (files != null) { 92 for (String f: files) { 93 new File(mFileDir + "/" + f).delete(); 94 } 95 } 96 new File(mFileDir).delete(); 97 } 98 99 if (mMediaScannerConnection != null) { 100 mMediaScannerConnection.disconnect(); 101 mMediaScannerConnection = null; 102 } 103 104 mContext.getContentResolver().delete(MediaStore.Audio.Media.getContentUri("external"), 105 "_data like ?", new String[] { mFileDir + "%"}); 106 } 107 108 public void testMediaScanner() throws InterruptedException, IOException { 109 mMediaScannerConnectionClient = new MockMediaScannerConnectionClient(); 110 mMediaScannerConnection = new MockMediaScannerConnection(getContext(), 111 mMediaScannerConnectionClient); 112 113 assertFalse(mMediaScannerConnection.isConnected()); 114 115 // start connection and wait until connected 116 mMediaScannerConnection.connect(); 117 checkConnectionState(true); 118 119 // start and wait for scan 120 mMediaScannerConnection.scanFile(mMediaFile.getAbsolutePath(), MEDIA_TYPE); 121 checkMediaScannerConnection(); 122 123 Uri insertUri = mMediaScannerConnectionClient.mediaUri; 124 long id = Long.valueOf(insertUri.getLastPathSegment()); 125 ContentResolver res = mContext.getContentResolver(); 126 127 // check that the file ended up in the audio view 128 Uri audiouri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id); 129 Cursor c = res.query(audiouri, null, null, null, null); 130 assertEquals(1, c.getCount()); 131 c.close(); 132 133 // add nomedia file and insert into database, file should no longer be in audio view 134 File nomedia = new File(mMediaFile.getParent() + "/.nomedia"); 135 nomedia.createNewFile(); 136 ContentValues values = new ContentValues(); 137 values.put(MediaStore.MediaColumns.DATA, nomedia.getAbsolutePath()); 138 values.put(MediaStore.Files.FileColumns.FORMAT, MtpConstants.FORMAT_UNDEFINED); 139 Uri nomediauri = res.insert(MediaStore.Files.getContentUri("external"), values); 140 // clean up nomedia file 141 nomedia.delete(); 142 143 // entry should not be in audio view anymore 144 c = res.query(audiouri, null, null, null, null); 145 assertEquals(0, c.getCount()); 146 c.close(); 147 148 // with nomedia file removed, do media scan and check that entry is in audio table again 149 startMediaScanAndWait(); 150 151 // Give the 2nd stage scan that makes the unhidden files visible again 152 // a little more time 153 SystemClock.sleep(10000); 154 // entry should be in audio view again 155 c = res.query(audiouri, null, null, null, null); 156 assertEquals(1, c.getCount()); 157 c.close(); 158 159 // ensure that we don't currently have playlists named ctsmediascanplaylist* 160 res.delete(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, 161 MediaStore.Audio.PlaylistsColumns.NAME + "=?", 162 new String[] { "ctsmediascanplaylist1"}); 163 res.delete(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, 164 MediaStore.Audio.PlaylistsColumns.NAME + "=?", 165 new String[] { "ctsmediascanplaylist2"}); 166 // delete the playlist file entries, if they exist 167 res.delete(MediaStore.Files.getContentUri("external"), 168 MediaStore.Files.FileColumns.DATA + "=?", 169 new String[] { mFileDir + "/ctsmediascanplaylist1.pls"}); 170 res.delete(MediaStore.Files.getContentUri("external"), 171 MediaStore.Files.FileColumns.DATA + "=?", 172 new String[] { mFileDir + "/ctsmediascanplaylist2.m3u"}); 173 174 // write some more files 175 writeFile(R.raw.testmp3, mFileDir + "/testmp3.mp3"); 176 writeFile(R.raw.testmp3_2, mFileDir + "/testmp3_2.mp3"); 177 writeFile(R.raw.playlist1, mFileDir + "/ctsmediascanplaylist1.pls"); 178 writeFile(R.raw.playlist2, mFileDir + "/ctsmediascanplaylist2.m3u"); 179 180 startMediaScanAndWait(); 181 182 // verify that the two playlists were created correctly; 183 c = res.query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, null, 184 MediaStore.Audio.PlaylistsColumns.NAME + "=?", 185 new String[] { "ctsmediascanplaylist1"}, null); 186 assertEquals(1, c.getCount()); 187 c.moveToFirst(); 188 long playlistid = c.getLong(c.getColumnIndex(MediaStore.MediaColumns._ID)); 189 c.close(); 190 191 c = res.query(MediaStore.Audio.Playlists.Members.getContentUri("external", playlistid), 192 null, null, null, MediaStore.Audio.Playlists.Members.PLAY_ORDER); 193 assertEquals(2, c.getCount()); 194 c.moveToNext(); 195 long song1a = c.getLong(c.getColumnIndex(MediaStore.Audio.Playlists.Members.AUDIO_ID)); 196 c.moveToNext(); 197 long song1b = c.getLong(c.getColumnIndex(MediaStore.Audio.Playlists.Members.AUDIO_ID)); 198 c.close(); 199 assertTrue("song id should not be 0", song1a != 0); 200 assertTrue("song id should not be 0", song1b != 0); 201 assertTrue("song ids should not be same", song1a != song1b); 202 203 // 2nd playlist should have the same songs, in reverse order 204 c = res.query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, null, 205 MediaStore.Audio.PlaylistsColumns.NAME + "=?", 206 new String[] { "ctsmediascanplaylist2"}, null); 207 assertEquals(1, c.getCount()); 208 c.moveToFirst(); 209 playlistid = c.getLong(c.getColumnIndex(MediaStore.MediaColumns._ID)); 210 c.close(); 211 212 c = res.query(MediaStore.Audio.Playlists.Members.getContentUri("external", playlistid), 213 null, null, null, MediaStore.Audio.Playlists.Members.PLAY_ORDER); 214 assertEquals(2, c.getCount()); 215 c.moveToNext(); 216 long song2a = c.getLong(c.getColumnIndex(MediaStore.Audio.Playlists.Members.AUDIO_ID)); 217 c.moveToNext(); 218 long song2b = c.getLong(c.getColumnIndex(MediaStore.Audio.Playlists.Members.AUDIO_ID)); 219 c.close(); 220 assertEquals("mismatched song ids", song1a, song2b); 221 assertEquals("mismatched song ids", song2a, song1b); 222 223 mMediaScannerConnection.disconnect(); 224 225 checkConnectionState(false); 226 } 227 228 public void testWildcardPaths() throws InterruptedException, IOException { 229 mMediaScannerConnectionClient = new MockMediaScannerConnectionClient(); 230 mMediaScannerConnection = new MockMediaScannerConnection(getContext(), 231 mMediaScannerConnectionClient); 232 233 assertFalse(mMediaScannerConnection.isConnected()); 234 235 // start connection and wait until connected 236 mMediaScannerConnection.connect(); 237 checkConnectionState(true); 238 239 long now = System.currentTimeMillis(); 240 String dir1 = mFileDir + "/test-" + now; 241 String file1 = dir1 + "/test.mp3"; 242 String dir2 = mFileDir + "/test_" + now; 243 String file2 = dir2 + "/test.mp3"; 244 assertTrue(new File(dir1).mkdir()); 245 writeFile(R.raw.testmp3, file1); 246 mMediaScannerConnection.scanFile(file1, MEDIA_TYPE); 247 checkMediaScannerConnection(); 248 Uri file1Uri = mMediaScannerConnectionClient.mediaUri; 249 250 assertTrue(new File(dir2).mkdir()); 251 writeFile(R.raw.testmp3, file2); 252 mMediaScannerConnectionClient.reset(); 253 mMediaScannerConnection.scanFile(file2, MEDIA_TYPE); 254 checkMediaScannerConnection(); 255 Uri file2Uri = mMediaScannerConnectionClient.mediaUri; 256 257 // if the URIs are the same, then the media scanner likely treated the _ character 258 // in the second path as a wildcard, and matched it with the first path 259 assertFalse(file1Uri.equals(file2Uri)); 260 261 // rewrite Uris to use the file scheme 262 long file1id = Long.valueOf(file1Uri.getLastPathSegment()); 263 long file2id = Long.valueOf(file2Uri.getLastPathSegment()); 264 file1Uri = MediaStore.Files.getContentUri("external", file1id); 265 file2Uri = MediaStore.Files.getContentUri("external", file2id); 266 267 ContentResolver res = mContext.getContentResolver(); 268 Cursor c = res.query(file1Uri, new String[] { "parent" }, null, null, null); 269 c.moveToFirst(); 270 long parent1id = c.getLong(0); 271 c.close(); 272 c = res.query(file2Uri, new String[] { "parent" }, null, null, null); 273 c.moveToFirst(); 274 long parent2id = c.getLong(0); 275 c.close(); 276 // if the parent ids are the same, then the media provider likely 277 // treated the _ character in the second path as a wildcard 278 assertTrue("same parent", parent1id != parent2id); 279 280 // check the parent paths are correct 281 c = res.query(MediaStore.Files.getContentUri("external", parent1id), 282 new String[] { "_data" }, null, null, null); 283 c.moveToFirst(); 284 assertEquals(dir1, c.getString(0)); 285 c.close(); 286 287 c = res.query(MediaStore.Files.getContentUri("external", parent2id), 288 new String[] { "_data" }, null, null, null); 289 c.moveToFirst(); 290 assertEquals(dir2, c.getString(0)); 291 c.close(); 292 293 // clean up 294 new File(file1).delete(); 295 new File(dir1).delete(); 296 new File(file2).delete(); 297 new File(dir2).delete(); 298 res.delete(file1Uri, null, null); 299 res.delete(file2Uri, null, null); 300 res.delete(MediaStore.Files.getContentUri("external", parent1id), null, null); 301 res.delete(MediaStore.Files.getContentUri("external", parent2id), null, null); 302 303 mMediaScannerConnection.disconnect(); 304 305 checkConnectionState(false); 306 } 307 308 public void testCanonicalize() throws Exception { 309 mMediaScannerConnectionClient = new MockMediaScannerConnectionClient(); 310 mMediaScannerConnection = new MockMediaScannerConnection(getContext(), 311 mMediaScannerConnectionClient); 312 313 assertFalse(mMediaScannerConnection.isConnected()); 314 315 // start connection and wait until connected 316 mMediaScannerConnection.connect(); 317 checkConnectionState(true); 318 319 // write file and scan to insert into database 320 String fileDir = Environment.getExternalStorageDirectory() + "/" 321 + getClass().getCanonicalName() + "/canonicaltest-" + System.currentTimeMillis(); 322 String fileName = fileDir + "/test.mp3"; 323 writeFile(R.raw.testmp3, fileName); 324 mMediaScannerConnection.scanFile(fileName, MEDIA_TYPE); 325 checkMediaScannerConnection(); 326 327 // check path and uri 328 Uri uri = mMediaScannerConnectionClient.mediaUri; 329 String path = mMediaScannerConnectionClient.mediaPath; 330 assertEquals(fileName, path); 331 assertNotNull(uri); 332 333 // check canonicalization 334 ContentResolver res = mContext.getContentResolver(); 335 Uri canonicalUri = res.canonicalize(uri); 336 assertNotNull(canonicalUri); 337 assertFalse(uri.equals(canonicalUri)); 338 Uri uncanonicalizedUri = res.uncanonicalize(canonicalUri); 339 assertEquals(uri, uncanonicalizedUri); 340 341 // remove the entry from the database 342 assertEquals(1, res.delete(uri, null, null)); 343 assertTrue(new File(path).delete()); 344 345 // write same file again and scan to insert into database 346 mMediaScannerConnectionClient.reset(); 347 String fileName2 = fileDir + "/test2.mp3"; 348 writeFile(R.raw.testmp3, fileName2); 349 mMediaScannerConnection.scanFile(fileName2, MEDIA_TYPE); 350 checkMediaScannerConnection(); 351 352 // check path and uri 353 Uri uri2 = mMediaScannerConnectionClient.mediaUri; 354 String path2 = mMediaScannerConnectionClient.mediaPath; 355 assertEquals(fileName2, path2); 356 assertNotNull(uri2); 357 358 // this should be a different entry in the database and not re-use the same database id 359 assertFalse(uri.equals(uri2)); 360 361 Uri canonicalUri2 = res.canonicalize(uri2); 362 assertNotNull(canonicalUri2); 363 assertFalse(uri2.equals(canonicalUri2)); 364 Uri uncanonicalizedUri2 = res.uncanonicalize(canonicalUri2); 365 assertEquals(uri2, uncanonicalizedUri2); 366 367 // uncanonicalize the original canonicalized uri, it should resolve to the new uri 368 Uri uncanonicalizedUri3 = res.uncanonicalize(canonicalUri); 369 assertEquals(uri2, uncanonicalizedUri3); 370 371 assertEquals(1, res.delete(uri2, null, null)); 372 assertTrue(new File(path2).delete()); 373 } 374 375 static class MediaScanEntry { 376 MediaScanEntry(int r, String[] t) { 377 this.res = r; 378 this.tags = t; 379 } 380 int res; 381 String[] tags; 382 } 383 384 MediaScanEntry encodingtestfiles[] = { 385 new MediaScanEntry(R.raw.gb18030_1, 386 new String[] {"", "200911", "", "(TV Version)", null} ), 387 new MediaScanEntry(R.raw.gb18030_2, 388 new String[] {"", "", null, "", null} ), 389 new MediaScanEntry(R.raw.gb18030_3, 390 new String[] {"", "()()", null, "11.Open Arms.( cn808.net )", null} ), 391 new MediaScanEntry(R.raw.gb18030_4, 392 new String[] {"", "", "", "25", ""} ), 393 new MediaScanEntry(R.raw.gb18030_5, 394 new String[] {"", "", "", "02.", ""} ), 395 new MediaScanEntry(R.raw.gb18030_6, 396 new String[] {"", "", "", "", ""} ), 397 new MediaScanEntry(R.raw.gb18030_7, // this is actually utf-8 398 new String[] {"", "", null, "", null} ), 399 new MediaScanEntry(R.raw.gb18030_8, 400 new String[] {"", "Jay", null, "", null} ), 401 new MediaScanEntry(R.raw.big5_1, 402 new String[] {"", "So I Sing 08 Live", "", "", null} ), 403 new MediaScanEntry(R.raw.big5_2, 404 new String[] {"", "So I Sing 08 Live", "", " - ", null} ), 405 new MediaScanEntry(R.raw.cp1251_v1, 406 new String[] {" ", " ", null, ", , ", null} ), 407 new MediaScanEntry(R.raw.cp1251_v1v2, 408 new String[] {"", "", null, "", null} ), 409 new MediaScanEntry(R.raw.cp1251_3, 410 new String[] {" (tATu)", "200 [Limited edi", null, " ", null} ), 411 // The following 3 use cp1251 encoding, expanded to 16 bits and stored as utf16 412 new MediaScanEntry(R.raw.cp1251_4, 413 new String[] {" ", " ", null, " ( )", "."} ), 414 new MediaScanEntry(R.raw.cp1251_5, 415 new String[] {" ", " ", null, "", "."} ), 416 new MediaScanEntry(R.raw.cp1251_6, 417 new String[] {" ", " ", null, ", ...", "."} ), 418 new MediaScanEntry(R.raw.cp1251_7, 419 new String[] {" ", " ", null, " ", null} ), 420 new MediaScanEntry(R.raw.cp1251_8, 421 new String[] {" ", " ", null, "i ", null} ), 422 new MediaScanEntry(R.raw.shiftjis1, 423 new String[] {"", "", null, "", null} ), 424 new MediaScanEntry(R.raw.shiftjis2, 425 new String[] {"", "SoundEffects", null, "", null} ), 426 new MediaScanEntry(R.raw.shiftjis3, 427 new String[] {"", "SoundEffects", null, "", null} ), 428 new MediaScanEntry(R.raw.shiftjis4, 429 new String[] {"", "SoundEffects", null, "", null} ), 430 new MediaScanEntry(R.raw.shiftjis5, 431 new String[] {"", "SoundEffects", null, "", null} ), 432 new MediaScanEntry(R.raw.shiftjis6, 433 new String[] {"", "SoundEffects", null, "", null} ), 434 new MediaScanEntry(R.raw.shiftjis7, 435 new String[] {"", "SoundEffects", null, "", null} ), 436 new MediaScanEntry(R.raw.shiftjis8, 437 new String[] {"", "SoundEffects", null, "", null} ), 438 new MediaScanEntry(R.raw.iso88591_1, 439 new String[] {"Mozart", "Best of Mozart", null, "Overtre (Die Hochzeit des Figaro)", null} ), 440 new MediaScanEntry(R.raw.iso88591_2, // actually UTF16, but only uses iso8859-1 chars 441 new String[] {"Bjrk", "Telegram", "Bjrk", "Possibly Maybe (Lucy Mix)", null} ), 442 new MediaScanEntry(R.raw.hebrew, 443 new String[] {" ", "", null, " ", null } ), 444 new MediaScanEntry(R.raw.hebrew2, 445 new String[] {" ", "Untitled - 11-11-02 (9)", null, "", null } ), 446 new MediaScanEntry(R.raw.iso88591_3, 447 new String[] {"Mobil", "Kartographie", null, "Zu Wenig", null }), 448 new MediaScanEntry(R.raw.iso88591_4, 449 new String[] {"Mobil", "Kartographie", null, "Rotebeetesalat (Igel Stehlen)", null }), 450 new MediaScanEntry(R.raw.iso88591_5, 451 new String[] {"The Creatures", "Hai! [UK Bonus DVD] Disc 1", "The Creatures", "Imagor", null }), 452 new MediaScanEntry(R.raw.iso88591_6, 453 new String[] {"Forward, Russia!", "Give Me a Wall", "Forward Russia", "Fifteen, Pt. 1", "Canning/Nicholls/Sarah Nicolls/Woodhead"}), 454 new MediaScanEntry(R.raw.iso88591_7, 455 new String[] {"Bjrk", "Homogenic", "Bjrk", "Jga", "Bjrk/Sjn"}), 456 // this one has a genre of "Ind" which confused the detector 457 new MediaScanEntry(R.raw.iso88591_8, 458 new String[] {"The Black Heart Procession", "3", null, "A Heart Like Mine", null}), 459 new MediaScanEntry(R.raw.iso88591_9, 460 new String[] {"DJ Tisto", "Just Be", "DJ Tisto", "Adagio For Strings", "Samuel Barber"}), 461 new MediaScanEntry(R.raw.iso88591_10, 462 new String[] {"Ratatat", "LP3", null, "Brule", null}), 463 new MediaScanEntry(R.raw.iso88591_11, 464 new String[] {"Semp", "Le Petit Nicolas vol. 1", null, "Les Cow-Boys", null}), 465 new MediaScanEntry(R.raw.iso88591_12, 466 new String[] {"UUVVWWZ", "UUVVWWZ", null, "Neolao", null}), 467 new MediaScanEntry(R.raw.iso88591_13, 468 new String[] {"Michael Bubl", "Crazy Love", "Michael Bubl", "Haven't Met You Yet", null}), 469 new MediaScanEntry(R.raw.utf16_1, 470 new String[] {"Shakira", "Latin Mix USA", "Shakira", "Estoy Aqu", null}) 471 }; 472 473 public void testEncodingDetection() throws Exception { 474 for (int i = 0; i< encodingtestfiles.length; i++) { 475 MediaScanEntry entry = encodingtestfiles[i]; 476 String name = mContext.getResources().getResourceEntryName(entry.res); 477 String path = mFileDir + "/" + name + ".mp3"; 478 writeFile(entry.res, path); 479 } 480 481 startMediaScanAndWait(); 482 483 String columns[] = { 484 MediaStore.Audio.Media.ARTIST, 485 MediaStore.Audio.Media.ALBUM, 486 MediaStore.Audio.Media.ALBUM_ARTIST, 487 MediaStore.Audio.Media.TITLE, 488 MediaStore.Audio.Media.COMPOSER 489 }; 490 ContentResolver res = mContext.getContentResolver(); 491 for (int i = 0; i< encodingtestfiles.length; i++) { 492 MediaScanEntry entry = encodingtestfiles[i]; 493 String name = mContext.getResources().getResourceEntryName(entry.res); 494 String path = mFileDir + "/" + name + ".mp3"; 495 Cursor c = res.query(MediaStore.Audio.Media.getContentUri("external"), columns, 496 MediaStore.Audio.Media.DATA + "=?", new String[] {path}, null); 497 assertNotNull("null cursor", c); 498 assertEquals("wrong number or results", 1, c.getCount()); 499 assertTrue("failed to move cursor", c.moveToFirst()); 500 501 for (int j =0; j < 5; j++) { 502 String expected = entry.tags[j]; 503 if ("".equals(expected)) { 504 // empty entry in the table means an unset id3 tag that is filled in by 505 // the media scanner, e.g. by using "<unknown>". Since this may be localized, 506 // don't check it for any particular value. 507 assertNotNull("unexpected null entry " + i + " field " + j + "(" + path + ")", 508 c.getString(j)); 509 } else { 510 assertEquals("mismatch on entry " + i + " field " + j + "(" + path + ")", 511 expected, c.getString(j)); 512 } 513 } 514 // clean up 515 new File(path).delete(); 516 res.delete(MediaStore.Audio.Media.getContentUri("external"), 517 MediaStore.Audio.Media.DATA + "=?", new String[] {path}); 518 519 c.close(); 520 521 // also test with the MediaMetadataRetriever API 522 MediaMetadataRetriever woodly = new MediaMetadataRetriever(); 523 AssetFileDescriptor afd = mContext.getResources().openRawResourceFd(entry.res); 524 woodly.setDataSource(afd.getFileDescriptor(), 525 afd.getStartOffset(), afd.getDeclaredLength()); 526 527 String[] actual = new String[5]; 528 actual[0] = woodly.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST); 529 actual[1] = woodly.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM); 530 actual[2] = woodly.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST); 531 actual[3] = woodly.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE); 532 actual[4] = woodly.extractMetadata(MediaMetadataRetriever.METADATA_KEY_COMPOSER); 533 534 for (int j = 0; j < 5; j++) { 535 if ("".equals(entry.tags[j])) { 536 // retriever doesn't insert "unknown artist" and such, it just returns null 537 assertNull("retriever: unexpected non-null for entry " + i + " field " + j, 538 actual[j]); 539 } else { 540 Log.i("@@@", "tags: @@" + entry.tags[j] + "@@" + actual[j] + "@@"); 541 assertEquals("retriever: mismatch on entry " + i + " field " + j, 542 entry.tags[j], actual[j]); 543 } 544 } 545 } 546 } 547 548 private void startMediaScanAndWait() throws InterruptedException { 549 ScannerNotificationReceiver finishedReceiver = new ScannerNotificationReceiver( 550 Intent.ACTION_MEDIA_SCANNER_FINISHED); 551 IntentFilter finishedIntentFilter = new IntentFilter(Intent.ACTION_MEDIA_SCANNER_FINISHED); 552 finishedIntentFilter.addDataScheme("file"); 553 mContext.registerReceiver(finishedReceiver, finishedIntentFilter); 554 555 Bundle args = new Bundle(); 556 args.putString("volume", "external"); 557 Intent i = new Intent("android.media.IMediaScannerService").putExtras(args); 558 i.setClassName("com.android.providers.media", 559 "com.android.providers.media.MediaScannerService"); 560 mContext.startService(i); 561 562 finishedReceiver.waitForBroadcast(); 563 mContext.unregisterReceiver(finishedReceiver); 564 } 565 566 private void checkMediaScannerConnection() { 567 new PollingCheck(TIME_OUT) { 568 protected boolean check() { 569 return mMediaScannerConnectionClient.isOnMediaScannerConnectedCalled; 570 } 571 }.run(); 572 new PollingCheck(TIME_OUT) { 573 protected boolean check() { 574 return mMediaScannerConnectionClient.mediaPath != null; 575 } 576 }.run(); 577 } 578 579 private void checkConnectionState(final boolean expected) { 580 new PollingCheck(TIME_OUT) { 581 protected boolean check() { 582 return mMediaScannerConnection.isConnected() == expected; 583 } 584 }.run(); 585 } 586 587 class MockMediaScannerConnection extends MediaScannerConnection { 588 589 public boolean mIsOnServiceConnectedCalled; 590 public boolean mIsOnServiceDisconnectedCalled; 591 public MockMediaScannerConnection(Context context, MediaScannerConnectionClient client) { 592 super(context, client); 593 } 594 595 @Override 596 public void onServiceConnected(ComponentName className, IBinder service) { 597 super.onServiceConnected(className, service); 598 mIsOnServiceConnectedCalled = true; 599 } 600 601 @Override 602 public void onServiceDisconnected(ComponentName className) { 603 super.onServiceDisconnected(className); 604 mIsOnServiceDisconnectedCalled = true; 605 // this is not called. 606 } 607 } 608 609 class MockMediaScannerConnectionClient implements MediaScannerConnectionClient { 610 611 public boolean isOnMediaScannerConnectedCalled; 612 public String mediaPath; 613 public Uri mediaUri; 614 public void onMediaScannerConnected() { 615 isOnMediaScannerConnectedCalled = true; 616 } 617 618 public void onScanCompleted(String path, Uri uri) { 619 mediaPath = path; 620 if (uri != null) { 621 mediaUri = uri; 622 } 623 } 624 625 public void reset() { 626 mediaPath = null; 627 mediaUri = null; 628 } 629 } 630 631 } 632