Home | History | Annotate | Download | only in cts
      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