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 com.android.cts.media.R;
     20 
     21 import android.content.ComponentName;
     22 import android.content.ContentResolver;
     23 import android.content.ContentUris;
     24 import android.content.ContentValues;
     25 import android.content.Context;
     26 import android.content.Intent;
     27 import android.content.IntentFilter;
     28 import android.cts.util.PollingCheck;
     29 import android.database.Cursor;
     30 import android.media.MediaScannerConnection;
     31 import android.media.MediaScannerConnection.MediaScannerConnectionClient;
     32 import android.mtp.MtpConstants;
     33 import android.net.Uri;
     34 import android.os.Bundle;
     35 import android.os.Environment;
     36 import android.os.IBinder;
     37 import android.os.SystemClock;
     38 import android.provider.MediaStore;
     39 import android.provider.cts.FileCopyHelper;
     40 import android.test.AndroidTestCase;
     41 import android.util.Log;
     42 
     43 import java.io.File;
     44 import java.io.IOException;
     45 
     46 public class MediaScannerTest extends AndroidTestCase {
     47 
     48     private static final String MEDIA_TYPE = "audio/mpeg";
     49     private File mMediaFile;
     50     private static final int TIME_OUT = 10000;
     51     private MockMediaScannerConnection mMediaScannerConnection;
     52     private MockMediaScannerConnectionClient mMediaScannerConnectionClient;
     53     private String mFileDir;
     54 
     55     @Override
     56     protected void setUp() throws Exception {
     57         super.setUp();
     58         // prepare the media file.
     59 
     60         mFileDir = Environment.getExternalStorageDirectory() + "/" + getClass().getCanonicalName();
     61         cleanup();
     62         String fileName = mFileDir + "/test" + System.currentTimeMillis() + ".mp3";
     63         writeFile(R.raw.testmp3, fileName);
     64 
     65         mMediaFile = new File(fileName);
     66         assertTrue(mMediaFile.exists());
     67     }
     68 
     69     private void writeFile(int resid, String path) throws IOException {
     70         File out = new File(path);
     71         File dir = out.getParentFile();
     72         dir.mkdirs();
     73         FileCopyHelper copier = new FileCopyHelper(mContext);
     74         copier.copyToExternalStorage(resid, out);
     75     }
     76 
     77     @Override
     78     protected void tearDown() throws Exception {
     79         super.tearDown();
     80     }
     81 
     82     private void cleanup() {
     83         if (mMediaFile != null) {
     84             mMediaFile.delete();
     85         }
     86         if (mFileDir != null) {
     87             new File(mFileDir + "/testmp3.mp3").delete();
     88             new File(mFileDir + "/testmp3_2.mp3").delete();
     89             new File(mFileDir + "/ctsmediascanplaylist1.pls").delete();
     90             new File(mFileDir + "/ctsmediascanplaylist2.m3u").delete();
     91             new File(mFileDir).delete();
     92         }
     93 
     94         if (mMediaScannerConnection != null) {
     95             mMediaScannerConnection.disconnect();
     96             mMediaScannerConnection = null;
     97         }
     98 
     99         mContext.getContentResolver().delete(MediaStore.Audio.Media.getContentUri("external"),
    100                 "_data like ?", new String[] { mFileDir + "%"});
    101     }
    102 
    103     public void testMediaScanner() throws InterruptedException, IOException {
    104         mMediaScannerConnectionClient = new MockMediaScannerConnectionClient();
    105         mMediaScannerConnection = new MockMediaScannerConnection(getContext(),
    106                                     mMediaScannerConnectionClient);
    107 
    108         assertFalse(mMediaScannerConnection.isConnected());
    109 
    110         // start connection and wait until connected
    111         mMediaScannerConnection.connect();
    112         checkConnectionState(true);
    113 
    114         // start and wait for scan
    115         mMediaScannerConnection.scanFile(mMediaFile.getAbsolutePath(), MEDIA_TYPE);
    116         checkMediaScannerConnection();
    117 
    118         Uri insertUri = mMediaScannerConnectionClient.mediaUri;
    119         long id = Long.valueOf(insertUri.getLastPathSegment());
    120         ContentResolver res = mContext.getContentResolver();
    121 
    122         // check that the file ended up in the audio view
    123         Uri audiouri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);
    124         Cursor c = res.query(audiouri, null, null, null, null);
    125         assertEquals(1, c.getCount());
    126         c.close();
    127 
    128         // add nomedia file and insert into database, file should no longer be in audio view
    129         File nomedia = new File(mMediaFile.getParent() + "/.nomedia");
    130         nomedia.createNewFile();
    131         ContentValues values = new ContentValues();
    132         values.put(MediaStore.MediaColumns.DATA, nomedia.getAbsolutePath());
    133         values.put(MediaStore.Files.FileColumns.FORMAT, MtpConstants.FORMAT_UNDEFINED);
    134         Uri nomediauri = res.insert(MediaStore.Files.getContentUri("external"), values);
    135         // clean up nomedia file
    136         nomedia.delete();
    137 
    138         // entry should not be in audio view anymore
    139         c = res.query(audiouri, null, null, null, null);
    140         assertEquals(0, c.getCount());
    141         c.close();
    142 
    143         // with nomedia file removed, do media scan and check that entry is in audio table again
    144         startMediaScanAndWait();
    145 
    146         // Give the 2nd stage scan that makes the unhidden files visible again
    147         // a little more time
    148         SystemClock.sleep(10000);
    149         // entry should be in audio view again
    150         c = res.query(audiouri, null, null, null, null);
    151         assertEquals(1, c.getCount());
    152         c.close();
    153 
    154         // ensure that we don't currently have playlists named ctsmediascanplaylist*
    155         res.delete(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
    156                 MediaStore.Audio.PlaylistsColumns.NAME + "=?",
    157                 new String[] { "ctsmediascanplaylist1"});
    158         res.delete(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
    159                 MediaStore.Audio.PlaylistsColumns.NAME + "=?",
    160                 new String[] { "ctsmediascanplaylist2"});
    161         // delete the playlist file entries, if they exist
    162         res.delete(MediaStore.Files.getContentUri("external"),
    163                 MediaStore.Files.FileColumns.DATA + "=?",
    164                 new String[] { mFileDir + "/ctsmediascanplaylist1.pls"});
    165         res.delete(MediaStore.Files.getContentUri("external"),
    166                 MediaStore.Files.FileColumns.DATA + "=?",
    167                 new String[] { mFileDir + "/ctsmediascanplaylist2.m3u"});
    168 
    169         // write some more files
    170         writeFile(R.raw.testmp3, mFileDir + "/testmp3.mp3");
    171         writeFile(R.raw.testmp3_2, mFileDir + "/testmp3_2.mp3");
    172         writeFile(R.raw.playlist1, mFileDir + "/ctsmediascanplaylist1.pls");
    173         writeFile(R.raw.playlist2, mFileDir + "/ctsmediascanplaylist2.m3u");
    174 
    175         startMediaScanAndWait();
    176 
    177         // verify that the two playlists were created correctly;
    178         c = res.query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, null,
    179                 MediaStore.Audio.PlaylistsColumns.NAME + "=?",
    180                 new String[] { "ctsmediascanplaylist1"}, null);
    181         assertEquals(1, c.getCount());
    182         c.moveToFirst();
    183         long playlistid = c.getLong(c.getColumnIndex(MediaStore.MediaColumns._ID));
    184         c.close();
    185 
    186         c = res.query(MediaStore.Audio.Playlists.Members.getContentUri("external", playlistid),
    187                 null, null, null, MediaStore.Audio.Playlists.Members.PLAY_ORDER);
    188         assertEquals(2, c.getCount());
    189         c.moveToNext();
    190         long song1a = c.getLong(c.getColumnIndex(MediaStore.Audio.Playlists.Members.AUDIO_ID));
    191         c.moveToNext();
    192         long song1b = c.getLong(c.getColumnIndex(MediaStore.Audio.Playlists.Members.AUDIO_ID));
    193         c.close();
    194         assertTrue("song id should not be 0", song1a != 0);
    195         assertTrue("song id should not be 0", song1b != 0);
    196         assertTrue("song ids should not be same", song1a != song1b);
    197 
    198         // 2nd playlist should have the same songs, in reverse order
    199         c = res.query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, null,
    200                 MediaStore.Audio.PlaylistsColumns.NAME + "=?",
    201                 new String[] { "ctsmediascanplaylist2"}, null);
    202         assertEquals(1, c.getCount());
    203         c.moveToFirst();
    204         playlistid = c.getLong(c.getColumnIndex(MediaStore.MediaColumns._ID));
    205         c.close();
    206 
    207         c = res.query(MediaStore.Audio.Playlists.Members.getContentUri("external", playlistid),
    208                 null, null, null, MediaStore.Audio.Playlists.Members.PLAY_ORDER);
    209         assertEquals(2, c.getCount());
    210         c.moveToNext();
    211         long song2a = c.getLong(c.getColumnIndex(MediaStore.Audio.Playlists.Members.AUDIO_ID));
    212         c.moveToNext();
    213         long song2b = c.getLong(c.getColumnIndex(MediaStore.Audio.Playlists.Members.AUDIO_ID));
    214         c.close();
    215         assertEquals("mismatched song ids", song1a, song2b);
    216         assertEquals("mismatched song ids", song2a, song1b);
    217 
    218         mMediaScannerConnection.disconnect();
    219 
    220         checkConnectionState(false);
    221     }
    222 
    223     public void testWildcardPaths() throws InterruptedException, IOException {
    224         mMediaScannerConnectionClient = new MockMediaScannerConnectionClient();
    225         mMediaScannerConnection = new MockMediaScannerConnection(getContext(),
    226                                     mMediaScannerConnectionClient);
    227 
    228         assertFalse(mMediaScannerConnection.isConnected());
    229 
    230         // start connection and wait until connected
    231         mMediaScannerConnection.connect();
    232         checkConnectionState(true);
    233 
    234         long now = System.currentTimeMillis();
    235         String dir1 = mFileDir + "/test-" + now;
    236         String file1 = dir1 + "/test.mp3";
    237         String dir2 = mFileDir + "/test_" + now;
    238         String file2 = dir2 + "/test.mp3";
    239         assertTrue(new File(dir1).mkdir());
    240         writeFile(R.raw.testmp3, file1);
    241         mMediaScannerConnection.scanFile(file1, MEDIA_TYPE);
    242         checkMediaScannerConnection();
    243         Uri file1Uri = mMediaScannerConnectionClient.mediaUri;
    244 
    245         assertTrue(new File(dir2).mkdir());
    246         writeFile(R.raw.testmp3, file2);
    247         mMediaScannerConnectionClient.reset();
    248         mMediaScannerConnection.scanFile(file2, MEDIA_TYPE);
    249         checkMediaScannerConnection();
    250         Uri file2Uri = mMediaScannerConnectionClient.mediaUri;
    251 
    252         // if the URIs are the same, then the media scanner likely treated the _ character
    253         // in the second path as a wildcard, and matched it with the first path
    254         assertFalse(file1Uri.equals(file2Uri));
    255 
    256         // rewrite Uris to use the file scheme
    257         long file1id = Long.valueOf(file1Uri.getLastPathSegment());
    258         long file2id = Long.valueOf(file2Uri.getLastPathSegment());
    259         file1Uri = MediaStore.Files.getContentUri("external", file1id);
    260         file2Uri = MediaStore.Files.getContentUri("external", file2id);
    261 
    262         ContentResolver res = mContext.getContentResolver();
    263         Cursor c = res.query(file1Uri, new String[] { "parent" }, null, null, null);
    264         c.moveToFirst();
    265         long parent1id = c.getLong(0);
    266         c.close();
    267         c = res.query(file2Uri, new String[] { "parent" }, null, null, null);
    268         c.moveToFirst();
    269         long parent2id = c.getLong(0);
    270         c.close();
    271         // if the parent ids are the same, then the media provider likely
    272         // treated the _ character in the second path as a wildcard
    273         assertTrue("same parent", parent1id != parent2id);
    274 
    275         // check the parent paths are correct
    276         c = res.query(MediaStore.Files.getContentUri("external", parent1id),
    277                 new String[] { "_data" }, null, null, null);
    278         c.moveToFirst();
    279         assertEquals(dir1, c.getString(0));
    280         c.close();
    281 
    282         c = res.query(MediaStore.Files.getContentUri("external", parent2id),
    283                 new String[] { "_data" }, null, null, null);
    284         c.moveToFirst();
    285         assertEquals(dir2, c.getString(0));
    286         c.close();
    287 
    288         // clean up
    289         new File(file1).delete();
    290         new File(dir1).delete();
    291         new File(file2).delete();
    292         new File(dir2).delete();
    293         res.delete(file1Uri, null, null);
    294         res.delete(file2Uri, null, null);
    295         res.delete(MediaStore.Files.getContentUri("external", parent1id), null, null);
    296         res.delete(MediaStore.Files.getContentUri("external", parent2id), null, null);
    297 
    298         mMediaScannerConnection.disconnect();
    299 
    300         checkConnectionState(false);
    301     }
    302 
    303     public void testCanonicalize() throws Exception {
    304         mMediaScannerConnectionClient = new MockMediaScannerConnectionClient();
    305         mMediaScannerConnection = new MockMediaScannerConnection(getContext(),
    306                                     mMediaScannerConnectionClient);
    307 
    308         assertFalse(mMediaScannerConnection.isConnected());
    309 
    310         // start connection and wait until connected
    311         mMediaScannerConnection.connect();
    312         checkConnectionState(true);
    313 
    314         // write file and scan to insert into database
    315         String fileDir = Environment.getExternalStorageDirectory() + "/"
    316                 + getClass().getCanonicalName() + "/canonicaltest-" + System.currentTimeMillis();
    317         String fileName = fileDir + "/test.mp3";
    318         writeFile(R.raw.testmp3, fileName);
    319         mMediaScannerConnection.scanFile(fileName, MEDIA_TYPE);
    320         checkMediaScannerConnection();
    321 
    322         // check path and uri
    323         Uri uri = mMediaScannerConnectionClient.mediaUri;
    324         String path = mMediaScannerConnectionClient.mediaPath;
    325         assertEquals(fileName, path);
    326         assertNotNull(uri);
    327 
    328         // check canonicalization
    329         ContentResolver res = mContext.getContentResolver();
    330         Uri canonicalUri = res.canonicalize(uri);
    331         assertNotNull(canonicalUri);
    332         assertFalse(uri.equals(canonicalUri));
    333         Uri uncanonicalizedUri = res.uncanonicalize(canonicalUri);
    334         assertEquals(uri, uncanonicalizedUri);
    335 
    336         // remove the entry from the database
    337         assertEquals(1, res.delete(uri, null, null));
    338         assertTrue(new File(path).delete());
    339 
    340         // write same file again and scan to insert into database
    341         mMediaScannerConnectionClient.reset();
    342         String fileName2 = fileDir + "/test2.mp3";
    343         writeFile(R.raw.testmp3, fileName2);
    344         mMediaScannerConnection.scanFile(fileName2, MEDIA_TYPE);
    345         checkMediaScannerConnection();
    346 
    347         // check path and uri
    348         Uri uri2 = mMediaScannerConnectionClient.mediaUri;
    349         String path2 = mMediaScannerConnectionClient.mediaPath;
    350         assertEquals(fileName2, path2);
    351         assertNotNull(uri2);
    352 
    353         // this should be a different entry in the database and not re-use the same database id
    354         assertFalse(uri.equals(uri2));
    355 
    356         Uri canonicalUri2 = res.canonicalize(uri2);
    357         assertNotNull(canonicalUri2);
    358         assertFalse(uri2.equals(canonicalUri2));
    359         Uri uncanonicalizedUri2 = res.uncanonicalize(canonicalUri2);
    360         assertEquals(uri2, uncanonicalizedUri2);
    361 
    362         // uncanonicalize the original canonicalized uri, it should resolve to the new uri
    363         Uri uncanonicalizedUri3 = res.uncanonicalize(canonicalUri);
    364         assertEquals(uri2, uncanonicalizedUri3);
    365 
    366         assertEquals(1, res.delete(uri2, null, null));
    367         assertTrue(new File(path2).delete());
    368     }
    369 
    370     private void startMediaScanAndWait() throws InterruptedException {
    371         ScannerNotificationReceiver finishedReceiver = new ScannerNotificationReceiver(
    372                 Intent.ACTION_MEDIA_SCANNER_FINISHED);
    373         IntentFilter finishedIntentFilter = new IntentFilter(Intent.ACTION_MEDIA_SCANNER_FINISHED);
    374         finishedIntentFilter.addDataScheme("file");
    375         mContext.registerReceiver(finishedReceiver, finishedIntentFilter);
    376 
    377         Bundle args = new Bundle();
    378         args.putString("volume", "external");
    379         Intent i = new Intent("android.media.IMediaScannerService").putExtras(args);
    380         i.setClassName("com.android.providers.media",
    381                 "com.android.providers.media.MediaScannerService");
    382         mContext.startService(i);
    383 
    384         finishedReceiver.waitForBroadcast();
    385         mContext.unregisterReceiver(finishedReceiver);
    386     }
    387 
    388     private void checkMediaScannerConnection() {
    389         new PollingCheck(TIME_OUT) {
    390             protected boolean check() {
    391                 return mMediaScannerConnectionClient.isOnMediaScannerConnectedCalled;
    392             }
    393         }.run();
    394         new PollingCheck(TIME_OUT) {
    395             protected boolean check() {
    396                 return mMediaScannerConnectionClient.mediaPath != null;
    397             }
    398         }.run();
    399     }
    400 
    401     private void checkConnectionState(final boolean expected) {
    402         new PollingCheck(TIME_OUT) {
    403             protected boolean check() {
    404                 return mMediaScannerConnection.isConnected() == expected;
    405             }
    406         }.run();
    407     }
    408 
    409     class MockMediaScannerConnection extends MediaScannerConnection {
    410 
    411         public boolean mIsOnServiceConnectedCalled;
    412         public boolean mIsOnServiceDisconnectedCalled;
    413         public MockMediaScannerConnection(Context context, MediaScannerConnectionClient client) {
    414             super(context, client);
    415         }
    416 
    417         @Override
    418         public void onServiceConnected(ComponentName className, IBinder service) {
    419             super.onServiceConnected(className, service);
    420             mIsOnServiceConnectedCalled = true;
    421         }
    422 
    423         @Override
    424         public void onServiceDisconnected(ComponentName className) {
    425             super.onServiceDisconnected(className);
    426             mIsOnServiceDisconnectedCalled = true;
    427             // this is not called.
    428         }
    429     }
    430 
    431     class MockMediaScannerConnectionClient implements MediaScannerConnectionClient {
    432 
    433         public boolean isOnMediaScannerConnectedCalled;
    434         public String mediaPath;
    435         public Uri mediaUri;
    436         public void onMediaScannerConnected() {
    437             isOnMediaScannerConnectedCalled = true;
    438         }
    439 
    440         public void onScanCompleted(String path, Uri uri) {
    441             mediaPath = path;
    442             if (uri != null) {
    443                 mediaUri = uri;
    444             }
    445         }
    446 
    447         public void reset() {
    448             mediaPath = null;
    449             mediaUri = null;
    450         }
    451     }
    452 
    453 }
    454