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