| /* |
| * Copyright (C) 2012 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.media.cts; |
| |
| import com.android.cts.media.R; |
| |
| import android.content.ComponentName; |
| import android.content.ContentResolver; |
| import android.content.ContentUris; |
| import android.content.ContentValues; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.res.AssetFileDescriptor; |
| import android.cts.util.FileCopyHelper; |
| import android.cts.util.PollingCheck; |
| import android.database.Cursor; |
| import android.media.MediaMetadataRetriever; |
| import android.media.MediaScannerConnection; |
| import android.media.MediaScannerConnection.MediaScannerConnectionClient; |
| import android.mtp.MtpConstants; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.os.Environment; |
| import android.os.IBinder; |
| import android.os.SystemClock; |
| import android.provider.MediaStore; |
| import android.test.AndroidTestCase; |
| import android.util.Log; |
| |
| import java.io.File; |
| import java.io.IOException; |
| |
| public class MediaScannerTest extends AndroidTestCase { |
| |
| private static final String MEDIA_TYPE = "audio/mpeg"; |
| private File mMediaFile; |
| private static final int TIME_OUT = 10000; |
| private MockMediaScannerConnection mMediaScannerConnection; |
| private MockMediaScannerConnectionClient mMediaScannerConnectionClient; |
| private String mFileDir; |
| |
| @Override |
| protected void setUp() throws Exception { |
| super.setUp(); |
| // prepare the media file. |
| |
| mFileDir = Environment.getExternalStorageDirectory() + "/" + getClass().getCanonicalName(); |
| cleanup(); |
| String fileName = mFileDir + "/test" + System.currentTimeMillis() + ".mp3"; |
| writeFile(R.raw.testmp3, fileName); |
| |
| mMediaFile = new File(fileName); |
| assertTrue(mMediaFile.exists()); |
| } |
| |
| private void writeFile(int resid, String path) throws IOException { |
| File out = new File(path); |
| File dir = out.getParentFile(); |
| dir.mkdirs(); |
| FileCopyHelper copier = new FileCopyHelper(mContext); |
| copier.copyToExternalStorage(resid, out); |
| } |
| |
| @Override |
| protected void tearDown() throws Exception { |
| cleanup(); |
| super.tearDown(); |
| } |
| |
| private void cleanup() { |
| if (mMediaFile != null) { |
| mMediaFile.delete(); |
| } |
| if (mFileDir != null) { |
| String files[] = new File(mFileDir).list(); |
| if (files != null) { |
| for (String f: files) { |
| new File(mFileDir + "/" + f).delete(); |
| } |
| } |
| new File(mFileDir).delete(); |
| } |
| |
| if (mMediaScannerConnection != null) { |
| mMediaScannerConnection.disconnect(); |
| mMediaScannerConnection = null; |
| } |
| |
| mContext.getContentResolver().delete(MediaStore.Audio.Media.getContentUri("external"), |
| "_data like ?", new String[] { mFileDir + "%"}); |
| } |
| |
| public void testMediaScanner() throws InterruptedException, IOException { |
| mMediaScannerConnectionClient = new MockMediaScannerConnectionClient(); |
| mMediaScannerConnection = new MockMediaScannerConnection(getContext(), |
| mMediaScannerConnectionClient); |
| |
| assertFalse(mMediaScannerConnection.isConnected()); |
| |
| // start connection and wait until connected |
| mMediaScannerConnection.connect(); |
| checkConnectionState(true); |
| |
| // start and wait for scan |
| mMediaScannerConnection.scanFile(mMediaFile.getAbsolutePath(), MEDIA_TYPE); |
| checkMediaScannerConnection(); |
| |
| Uri insertUri = mMediaScannerConnectionClient.mediaUri; |
| long id = Long.valueOf(insertUri.getLastPathSegment()); |
| ContentResolver res = mContext.getContentResolver(); |
| |
| // check that the file ended up in the audio view |
| Uri audiouri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id); |
| Cursor c = res.query(audiouri, null, null, null, null); |
| assertEquals(1, c.getCount()); |
| c.close(); |
| |
| // add nomedia file and insert into database, file should no longer be in audio view |
| File nomedia = new File(mMediaFile.getParent() + "/.nomedia"); |
| nomedia.createNewFile(); |
| ContentValues values = new ContentValues(); |
| values.put(MediaStore.MediaColumns.DATA, nomedia.getAbsolutePath()); |
| values.put(MediaStore.Files.FileColumns.FORMAT, MtpConstants.FORMAT_UNDEFINED); |
| Uri nomediauri = res.insert(MediaStore.Files.getContentUri("external"), values); |
| // clean up nomedia file |
| nomedia.delete(); |
| |
| // entry should not be in audio view anymore |
| c = res.query(audiouri, null, null, null, null); |
| assertEquals(0, c.getCount()); |
| c.close(); |
| |
| // with nomedia file removed, do media scan and check that entry is in audio table again |
| startMediaScanAndWait(); |
| |
| // Give the 2nd stage scan that makes the unhidden files visible again |
| // a little more time |
| SystemClock.sleep(10000); |
| // entry should be in audio view again |
| c = res.query(audiouri, null, null, null, null); |
| assertEquals(1, c.getCount()); |
| c.close(); |
| |
| // ensure that we don't currently have playlists named ctsmediascanplaylist* |
| res.delete(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, |
| MediaStore.Audio.PlaylistsColumns.NAME + "=?", |
| new String[] { "ctsmediascanplaylist1"}); |
| res.delete(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, |
| MediaStore.Audio.PlaylistsColumns.NAME + "=?", |
| new String[] { "ctsmediascanplaylist2"}); |
| // delete the playlist file entries, if they exist |
| res.delete(MediaStore.Files.getContentUri("external"), |
| MediaStore.Files.FileColumns.DATA + "=?", |
| new String[] { mFileDir + "/ctsmediascanplaylist1.pls"}); |
| res.delete(MediaStore.Files.getContentUri("external"), |
| MediaStore.Files.FileColumns.DATA + "=?", |
| new String[] { mFileDir + "/ctsmediascanplaylist2.m3u"}); |
| |
| // write some more files |
| writeFile(R.raw.testmp3, mFileDir + "/testmp3.mp3"); |
| writeFile(R.raw.testmp3_2, mFileDir + "/testmp3_2.mp3"); |
| writeFile(R.raw.playlist1, mFileDir + "/ctsmediascanplaylist1.pls"); |
| writeFile(R.raw.playlist2, mFileDir + "/ctsmediascanplaylist2.m3u"); |
| |
| startMediaScanAndWait(); |
| |
| // verify that the two playlists were created correctly; |
| c = res.query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, null, |
| MediaStore.Audio.PlaylistsColumns.NAME + "=?", |
| new String[] { "ctsmediascanplaylist1"}, null); |
| assertEquals(1, c.getCount()); |
| c.moveToFirst(); |
| long playlistid = c.getLong(c.getColumnIndex(MediaStore.MediaColumns._ID)); |
| c.close(); |
| |
| c = res.query(MediaStore.Audio.Playlists.Members.getContentUri("external", playlistid), |
| null, null, null, MediaStore.Audio.Playlists.Members.PLAY_ORDER); |
| assertEquals(2, c.getCount()); |
| c.moveToNext(); |
| long song1a = c.getLong(c.getColumnIndex(MediaStore.Audio.Playlists.Members.AUDIO_ID)); |
| c.moveToNext(); |
| long song1b = c.getLong(c.getColumnIndex(MediaStore.Audio.Playlists.Members.AUDIO_ID)); |
| c.close(); |
| assertTrue("song id should not be 0", song1a != 0); |
| assertTrue("song id should not be 0", song1b != 0); |
| assertTrue("song ids should not be same", song1a != song1b); |
| |
| // 2nd playlist should have the same songs, in reverse order |
| c = res.query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, null, |
| MediaStore.Audio.PlaylistsColumns.NAME + "=?", |
| new String[] { "ctsmediascanplaylist2"}, null); |
| assertEquals(1, c.getCount()); |
| c.moveToFirst(); |
| playlistid = c.getLong(c.getColumnIndex(MediaStore.MediaColumns._ID)); |
| c.close(); |
| |
| c = res.query(MediaStore.Audio.Playlists.Members.getContentUri("external", playlistid), |
| null, null, null, MediaStore.Audio.Playlists.Members.PLAY_ORDER); |
| assertEquals(2, c.getCount()); |
| c.moveToNext(); |
| long song2a = c.getLong(c.getColumnIndex(MediaStore.Audio.Playlists.Members.AUDIO_ID)); |
| c.moveToNext(); |
| long song2b = c.getLong(c.getColumnIndex(MediaStore.Audio.Playlists.Members.AUDIO_ID)); |
| c.close(); |
| assertEquals("mismatched song ids", song1a, song2b); |
| assertEquals("mismatched song ids", song2a, song1b); |
| |
| mMediaScannerConnection.disconnect(); |
| |
| checkConnectionState(false); |
| } |
| |
| public void testWildcardPaths() throws InterruptedException, IOException { |
| mMediaScannerConnectionClient = new MockMediaScannerConnectionClient(); |
| mMediaScannerConnection = new MockMediaScannerConnection(getContext(), |
| mMediaScannerConnectionClient); |
| |
| assertFalse(mMediaScannerConnection.isConnected()); |
| |
| // start connection and wait until connected |
| mMediaScannerConnection.connect(); |
| checkConnectionState(true); |
| |
| long now = System.currentTimeMillis(); |
| String dir1 = mFileDir + "/test-" + now; |
| String file1 = dir1 + "/test.mp3"; |
| String dir2 = mFileDir + "/test_" + now; |
| String file2 = dir2 + "/test.mp3"; |
| assertTrue(new File(dir1).mkdir()); |
| writeFile(R.raw.testmp3, file1); |
| mMediaScannerConnection.scanFile(file1, MEDIA_TYPE); |
| checkMediaScannerConnection(); |
| Uri file1Uri = mMediaScannerConnectionClient.mediaUri; |
| |
| assertTrue(new File(dir2).mkdir()); |
| writeFile(R.raw.testmp3, file2); |
| mMediaScannerConnectionClient.reset(); |
| mMediaScannerConnection.scanFile(file2, MEDIA_TYPE); |
| checkMediaScannerConnection(); |
| Uri file2Uri = mMediaScannerConnectionClient.mediaUri; |
| |
| // if the URIs are the same, then the media scanner likely treated the _ character |
| // in the second path as a wildcard, and matched it with the first path |
| assertFalse(file1Uri.equals(file2Uri)); |
| |
| // rewrite Uris to use the file scheme |
| long file1id = Long.valueOf(file1Uri.getLastPathSegment()); |
| long file2id = Long.valueOf(file2Uri.getLastPathSegment()); |
| file1Uri = MediaStore.Files.getContentUri("external", file1id); |
| file2Uri = MediaStore.Files.getContentUri("external", file2id); |
| |
| ContentResolver res = mContext.getContentResolver(); |
| Cursor c = res.query(file1Uri, new String[] { "parent" }, null, null, null); |
| c.moveToFirst(); |
| long parent1id = c.getLong(0); |
| c.close(); |
| c = res.query(file2Uri, new String[] { "parent" }, null, null, null); |
| c.moveToFirst(); |
| long parent2id = c.getLong(0); |
| c.close(); |
| // if the parent ids are the same, then the media provider likely |
| // treated the _ character in the second path as a wildcard |
| assertTrue("same parent", parent1id != parent2id); |
| |
| // check the parent paths are correct |
| c = res.query(MediaStore.Files.getContentUri("external", parent1id), |
| new String[] { "_data" }, null, null, null); |
| c.moveToFirst(); |
| assertEquals(dir1, c.getString(0)); |
| c.close(); |
| |
| c = res.query(MediaStore.Files.getContentUri("external", parent2id), |
| new String[] { "_data" }, null, null, null); |
| c.moveToFirst(); |
| assertEquals(dir2, c.getString(0)); |
| c.close(); |
| |
| // clean up |
| new File(file1).delete(); |
| new File(dir1).delete(); |
| new File(file2).delete(); |
| new File(dir2).delete(); |
| res.delete(file1Uri, null, null); |
| res.delete(file2Uri, null, null); |
| res.delete(MediaStore.Files.getContentUri("external", parent1id), null, null); |
| res.delete(MediaStore.Files.getContentUri("external", parent2id), null, null); |
| |
| mMediaScannerConnection.disconnect(); |
| |
| checkConnectionState(false); |
| } |
| |
| public void testCanonicalize() throws Exception { |
| mMediaScannerConnectionClient = new MockMediaScannerConnectionClient(); |
| mMediaScannerConnection = new MockMediaScannerConnection(getContext(), |
| mMediaScannerConnectionClient); |
| |
| assertFalse(mMediaScannerConnection.isConnected()); |
| |
| // start connection and wait until connected |
| mMediaScannerConnection.connect(); |
| checkConnectionState(true); |
| |
| // write file and scan to insert into database |
| String fileDir = Environment.getExternalStorageDirectory() + "/" |
| + getClass().getCanonicalName() + "/canonicaltest-" + System.currentTimeMillis(); |
| String fileName = fileDir + "/test.mp3"; |
| writeFile(R.raw.testmp3, fileName); |
| mMediaScannerConnection.scanFile(fileName, MEDIA_TYPE); |
| checkMediaScannerConnection(); |
| |
| // check path and uri |
| Uri uri = mMediaScannerConnectionClient.mediaUri; |
| String path = mMediaScannerConnectionClient.mediaPath; |
| assertEquals(fileName, path); |
| assertNotNull(uri); |
| |
| // check canonicalization |
| ContentResolver res = mContext.getContentResolver(); |
| Uri canonicalUri = res.canonicalize(uri); |
| assertNotNull(canonicalUri); |
| assertFalse(uri.equals(canonicalUri)); |
| Uri uncanonicalizedUri = res.uncanonicalize(canonicalUri); |
| assertEquals(uri, uncanonicalizedUri); |
| |
| // remove the entry from the database |
| assertEquals(1, res.delete(uri, null, null)); |
| assertTrue(new File(path).delete()); |
| |
| // write same file again and scan to insert into database |
| mMediaScannerConnectionClient.reset(); |
| String fileName2 = fileDir + "/test2.mp3"; |
| writeFile(R.raw.testmp3, fileName2); |
| mMediaScannerConnection.scanFile(fileName2, MEDIA_TYPE); |
| checkMediaScannerConnection(); |
| |
| // check path and uri |
| Uri uri2 = mMediaScannerConnectionClient.mediaUri; |
| String path2 = mMediaScannerConnectionClient.mediaPath; |
| assertEquals(fileName2, path2); |
| assertNotNull(uri2); |
| |
| // this should be a different entry in the database and not re-use the same database id |
| assertFalse(uri.equals(uri2)); |
| |
| Uri canonicalUri2 = res.canonicalize(uri2); |
| assertNotNull(canonicalUri2); |
| assertFalse(uri2.equals(canonicalUri2)); |
| Uri uncanonicalizedUri2 = res.uncanonicalize(canonicalUri2); |
| assertEquals(uri2, uncanonicalizedUri2); |
| |
| // uncanonicalize the original canonicalized uri, it should resolve to the new uri |
| Uri uncanonicalizedUri3 = res.uncanonicalize(canonicalUri); |
| assertEquals(uri2, uncanonicalizedUri3); |
| |
| assertEquals(1, res.delete(uri2, null, null)); |
| assertTrue(new File(path2).delete()); |
| } |
| |
| static class MediaScanEntry { |
| MediaScanEntry(int r, String[] t) { |
| this.res = r; |
| this.tags = t; |
| } |
| int res; |
| String[] tags; |
| } |
| |
| MediaScanEntry encodingtestfiles[] = { |
| new MediaScanEntry(R.raw.gb18030_1, |
| new String[] {"罗志祥", "2009年11月新歌", "罗志祥", "爱不单行(TV Version)", null} ), |
| new MediaScanEntry(R.raw.gb18030_2, |
| new String[] {"张杰", "明天过后", null, "明天过后", null} ), |
| new MediaScanEntry(R.raw.gb18030_3, |
| new String[] {"电视原声带", "格斗天王(限量精装版)(预购版)", null, "11.Open Arms.( cn808.net )", null} ), |
| new MediaScanEntry(R.raw.gb18030_4, |
| new String[] {"莫扎特", "黄金古典", "柏林爱乐乐团", "第25号交响曲", "莫扎特"} ), |
| new MediaScanEntry(R.raw.gb18030_5, |
| new String[] {"光良", "童话", "光良", "02.童话", "鍏夎壇"} ), |
| new MediaScanEntry(R.raw.gb18030_6, |
| new String[] {"张韶涵", "潘朵拉", "張韶涵", "隐形的翅膀", "王雅君"} ), |
| new MediaScanEntry(R.raw.gb18030_7, // this is actually utf-8 |
| new String[] {"五月天", "后青春期的诗", null, "突然好想你", null} ), |
| new MediaScanEntry(R.raw.gb18030_8, |
| new String[] {"周杰伦", "Jay", null, "反方向的钟", null} ), |
| new MediaScanEntry(R.raw.big5_1, |
| new String[] {"蘇永康", "So I Sing 08 Live", "蘇永康", "囍帖街", null} ), |
| new MediaScanEntry(R.raw.big5_2, |
| new String[] {"蘇永康", "So I Sing 08 Live", "蘇永康", "從不喜歡孤單一個 - 蘇永康/吳雨霏", null} ), |
| new MediaScanEntry(R.raw.cp1251_v1, |
| new String[] {"Екатерина Железнова", "Корабль игрушек", null, "Раз, два, три", null} ), |
| new MediaScanEntry(R.raw.cp1251_v1v2, |
| new String[] {"Мельница", "Перевал", null, "Королевна", null} ), |
| new MediaScanEntry(R.raw.cp1251_3, |
| new String[] {"Тату (tATu)", "200 По Встречной [Limited edi", null, "Я Сошла С Ума", null} ), |
| // The following 3 use cp1251 encoding, expanded to 16 bits and stored as utf16 |
| new MediaScanEntry(R.raw.cp1251_4, |
| new String[] {"Александр Розенбаум", "Философия любви", null, "Разговор в гостинице (Как жить без веры)", "А.Розенбаум"} ), |
| new MediaScanEntry(R.raw.cp1251_5, |
| new String[] {"Александр Розенбаум", "Философия любви", null, "Четвертиночка", "А.Розенбаум"} ), |
| new MediaScanEntry(R.raw.cp1251_6, |
| new String[] {"Александр Розенбаум", "Философия ремесла", null, "Ну, вот...", "А.Розенбаум"} ), |
| new MediaScanEntry(R.raw.cp1251_7, |
| new String[] {"Вопли Видоплясова", "Хвилі Амура", null, "Або або", null} ), |
| new MediaScanEntry(R.raw.cp1251_8, |
| new String[] {"Вопли Видоплясова", "Хвилі Амура", null, "Таємнi сфери", null} ), |
| new MediaScanEntry(R.raw.shiftjis1, |
| new String[] {"", "", null, "中島敦「山月記」(第1回)", null} ), |
| new MediaScanEntry(R.raw.shiftjis2, |
| new String[] {"音人", "SoundEffects", null, "ファンファーレ", null} ), |
| new MediaScanEntry(R.raw.shiftjis3, |
| new String[] {"音人", "SoundEffects", null, "シンキングタイム", null} ), |
| new MediaScanEntry(R.raw.shiftjis4, |
| new String[] {"音人", "SoundEffects", null, "出題", null} ), |
| new MediaScanEntry(R.raw.shiftjis5, |
| new String[] {"音人", "SoundEffects", null, "時報", null} ), |
| new MediaScanEntry(R.raw.shiftjis6, |
| new String[] {"音人", "SoundEffects", null, "正解", null} ), |
| new MediaScanEntry(R.raw.shiftjis7, |
| new String[] {"音人", "SoundEffects", null, "残念", null} ), |
| new MediaScanEntry(R.raw.shiftjis8, |
| new String[] {"音人", "SoundEffects", null, "間違い", null} ), |
| new MediaScanEntry(R.raw.iso88591_1, |
| new String[] {"Mozart", "Best of Mozart", null, "Overtüre (Die Hochzeit des Figaro)", null} ), |
| new MediaScanEntry(R.raw.iso88591_2, // actually UTF16, but only uses iso8859-1 chars |
| new String[] {"Björk", "Telegram", "Björk", "Possibly Maybe (Lucy Mix)", null} ), |
| new MediaScanEntry(R.raw.hebrew, |
| new String[] {"אריק סיני", "", null, "לי ולך", null } ), |
| new MediaScanEntry(R.raw.hebrew2, |
| new String[] {"הפרוייקט של עידן רייכל", "Untitled - 11-11-02 (9)", null, "בואי", null } ), |
| new MediaScanEntry(R.raw.iso88591_3, |
| new String[] {"Mobilé", "Kartographie", null, "Zu Wenig", null }), |
| new MediaScanEntry(R.raw.iso88591_4, |
| new String[] {"Mobilé", "Kartographie", null, "Rotebeetesalat (Igel Stehlen)", null }), |
| new MediaScanEntry(R.raw.iso88591_5, |
| new String[] {"The Creatures", "Hai! [UK Bonus DVD] Disc 1", "The Creatures", "Imagoró", null }), |
| new MediaScanEntry(R.raw.iso88591_6, |
| new String[] {"¡Forward, Russia!", "Give Me a Wall", "Forward Russia", "Fifteen, Pt. 1", "Canning/Nicholls/Sarah Nicolls/Woodhead"}), |
| new MediaScanEntry(R.raw.iso88591_7, |
| new String[] {"Björk", "Homogenic", "Björk", "Jòga", "Björk/Sjòn"}), |
| // this one has a genre of "Indé" which confused the detector |
| new MediaScanEntry(R.raw.iso88591_8, |
| new String[] {"The Black Heart Procession", "3", null, "A Heart Like Mine", null}), |
| new MediaScanEntry(R.raw.iso88591_9, |
| new String[] {"DJ Tiësto", "Just Be", "DJ Tiësto", "Adagio For Strings", "Samuel Barber"}), |
| new MediaScanEntry(R.raw.iso88591_10, |
| new String[] {"Ratatat", "LP3", null, "Bruleé", null}), |
| new MediaScanEntry(R.raw.iso88591_11, |
| new String[] {"Sempé", "Le Petit Nicolas vol. 1", null, "Les Cow-Boys", null}), |
| new MediaScanEntry(R.raw.iso88591_12, |
| new String[] {"UUVVWWZ", "UUVVWWZ", null, "Neolaño", null}), |
| new MediaScanEntry(R.raw.iso88591_13, |
| new String[] {"Michael Bublé", "Crazy Love", "Michael Bublé", "Haven't Met You Yet", null}), |
| new MediaScanEntry(R.raw.utf16_1, |
| new String[] {"Shakira", "Latin Mix USA", "Shakira", "Estoy Aquí", null}) |
| }; |
| |
| public void testEncodingDetection() throws Exception { |
| for (int i = 0; i< encodingtestfiles.length; i++) { |
| MediaScanEntry entry = encodingtestfiles[i]; |
| String name = mContext.getResources().getResourceEntryName(entry.res); |
| String path = mFileDir + "/" + name + ".mp3"; |
| writeFile(entry.res, path); |
| } |
| |
| startMediaScanAndWait(); |
| |
| String columns[] = { |
| MediaStore.Audio.Media.ARTIST, |
| MediaStore.Audio.Media.ALBUM, |
| MediaStore.Audio.Media.ALBUM_ARTIST, |
| MediaStore.Audio.Media.TITLE, |
| MediaStore.Audio.Media.COMPOSER |
| }; |
| ContentResolver res = mContext.getContentResolver(); |
| for (int i = 0; i< encodingtestfiles.length; i++) { |
| MediaScanEntry entry = encodingtestfiles[i]; |
| String name = mContext.getResources().getResourceEntryName(entry.res); |
| String path = mFileDir + "/" + name + ".mp3"; |
| Cursor c = res.query(MediaStore.Audio.Media.getContentUri("external"), columns, |
| MediaStore.Audio.Media.DATA + "=?", new String[] {path}, null); |
| assertNotNull("null cursor", c); |
| assertEquals("wrong number or results", 1, c.getCount()); |
| assertTrue("failed to move cursor", c.moveToFirst()); |
| |
| for (int j =0; j < 5; j++) { |
| String expected = entry.tags[j]; |
| if ("".equals(expected)) { |
| // empty entry in the table means an unset id3 tag that is filled in by |
| // the media scanner, e.g. by using "<unknown>". Since this may be localized, |
| // don't check it for any particular value. |
| assertNotNull("unexpected null entry " + i + " field " + j + "(" + path + ")", |
| c.getString(j)); |
| } else { |
| assertEquals("mismatch on entry " + i + " field " + j + "(" + path + ")", |
| expected, c.getString(j)); |
| } |
| } |
| // clean up |
| new File(path).delete(); |
| res.delete(MediaStore.Audio.Media.getContentUri("external"), |
| MediaStore.Audio.Media.DATA + "=?", new String[] {path}); |
| |
| c.close(); |
| |
| // also test with the MediaMetadataRetriever API |
| MediaMetadataRetriever woodly = new MediaMetadataRetriever(); |
| AssetFileDescriptor afd = mContext.getResources().openRawResourceFd(entry.res); |
| woodly.setDataSource(afd.getFileDescriptor(), |
| afd.getStartOffset(), afd.getDeclaredLength()); |
| |
| String[] actual = new String[5]; |
| actual[0] = woodly.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST); |
| actual[1] = woodly.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM); |
| actual[2] = woodly.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST); |
| actual[3] = woodly.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE); |
| actual[4] = woodly.extractMetadata(MediaMetadataRetriever.METADATA_KEY_COMPOSER); |
| |
| for (int j = 0; j < 5; j++) { |
| if ("".equals(entry.tags[j])) { |
| // retriever doesn't insert "unknown artist" and such, it just returns null |
| assertNull("retriever: unexpected non-null for entry " + i + " field " + j, |
| actual[j]); |
| } else { |
| Log.i("@@@", "tags: @@" + entry.tags[j] + "@@" + actual[j] + "@@"); |
| assertEquals("retriever: mismatch on entry " + i + " field " + j, |
| entry.tags[j], actual[j]); |
| } |
| } |
| } |
| } |
| |
| private void startMediaScanAndWait() throws InterruptedException { |
| ScannerNotificationReceiver finishedReceiver = new ScannerNotificationReceiver( |
| Intent.ACTION_MEDIA_SCANNER_FINISHED); |
| IntentFilter finishedIntentFilter = new IntentFilter(Intent.ACTION_MEDIA_SCANNER_FINISHED); |
| finishedIntentFilter.addDataScheme("file"); |
| mContext.registerReceiver(finishedReceiver, finishedIntentFilter); |
| |
| Bundle args = new Bundle(); |
| args.putString("volume", "external"); |
| Intent i = new Intent("android.media.IMediaScannerService").putExtras(args); |
| i.setClassName("com.android.providers.media", |
| "com.android.providers.media.MediaScannerService"); |
| mContext.startService(i); |
| |
| finishedReceiver.waitForBroadcast(); |
| mContext.unregisterReceiver(finishedReceiver); |
| } |
| |
| private void checkMediaScannerConnection() { |
| new PollingCheck(TIME_OUT) { |
| protected boolean check() { |
| return mMediaScannerConnectionClient.isOnMediaScannerConnectedCalled; |
| } |
| }.run(); |
| new PollingCheck(TIME_OUT) { |
| protected boolean check() { |
| return mMediaScannerConnectionClient.mediaPath != null; |
| } |
| }.run(); |
| } |
| |
| private void checkConnectionState(final boolean expected) { |
| new PollingCheck(TIME_OUT) { |
| protected boolean check() { |
| return mMediaScannerConnection.isConnected() == expected; |
| } |
| }.run(); |
| } |
| |
| class MockMediaScannerConnection extends MediaScannerConnection { |
| |
| public boolean mIsOnServiceConnectedCalled; |
| public boolean mIsOnServiceDisconnectedCalled; |
| public MockMediaScannerConnection(Context context, MediaScannerConnectionClient client) { |
| super(context, client); |
| } |
| |
| @Override |
| public void onServiceConnected(ComponentName className, IBinder service) { |
| super.onServiceConnected(className, service); |
| mIsOnServiceConnectedCalled = true; |
| } |
| |
| @Override |
| public void onServiceDisconnected(ComponentName className) { |
| super.onServiceDisconnected(className); |
| mIsOnServiceDisconnectedCalled = true; |
| // this is not called. |
| } |
| } |
| |
| class MockMediaScannerConnectionClient implements MediaScannerConnectionClient { |
| |
| public boolean isOnMediaScannerConnectedCalled; |
| public String mediaPath; |
| public Uri mediaUri; |
| public void onMediaScannerConnected() { |
| isOnMediaScannerConnectedCalled = true; |
| } |
| |
| public void onScanCompleted(String path, Uri uri) { |
| mediaPath = path; |
| if (uri != null) { |
| mediaUri = uri; |
| } |
| } |
| |
| public void reset() { |
| mediaPath = null; |
| mediaUri = null; |
| } |
| } |
| |
| } |