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