How can I refresh MediaStore on Android?
Asked Answered
A

4

55

This started out as a general user question on Android forums. However it's become, by necessity, a programming question. Here's my problem.

Android has a service - MediaScanner - which runs in the background any time (I believe) the SD card is un-mounted and re-mounted. This service collects data on all the media files on the card, and provides a SQLite DB which can be queried by music applications. Most music applications use this service as it saves on battery-drain associated with scanning the SD card.

Since I started using android, I've consistently had a problem whereby M3U playlists synchronised to the device remain in this SQLite DB even after being deleted from the SD Card. It's gotten to the point where I now have a collection of about 40 playlists showing up in any music app I use, despite there only being around 10 m3u files on the card. The remaining playlists do not play, and are empty. I can remove them manually by deleting them from the music app, but I'm sick of doing this. There has to be a better way to remove these ghost playlists.

There are two apps on the Android Market - SDRescan and Music Scanner, which supposedly do exactly this but neither of them work.

I set about writing my own app to refresh or delete the MediaStore database and start from scratch, but I'm not getting very far. I've got an android app which runs the following code :

sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, 
        Uri.parse("file://" + Environment.getExternalStorageDirectory()))); 

I've found a few examples of this code online as a way to scan the SD Card but I'm not having any luck with it whatsoever. Any tips?

FULL CODE:

package com.roryok.MediaRescan;

import android.app.Activity;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;

public class MediaRescan extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, 
                Uri.parse("file://" + Environment.getExternalStorageDirectory()))); 
        setContentView(R.layout.main);
    }

    //Rescan the sdcard after copy the file
    private void rescanSdcard() throws Exception{     
      Intent scanIntent = new Intent(Intent.ACTION_MEDIA_MOUNTED, 
                Uri.parse("file://" + Environment.getExternalStorageDirectory()));   
      IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MEDIA_SCANNER_STARTED);
      intentFilter.addDataScheme("file");     
      sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, 
                Uri.parse("file://" + Environment.getExternalStorageDirectory())));    
    }
}
Airport answered 21/7, 2010 at 14:11 Comment(2)
Hi, Do you know if sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + Environment.getExternalStorageDirectory()))); will work for sdcard which are extra slot cards ? in other words getExternalStorageDirectory() returns a path to sdcard which is internal but there may be manufacturer provided external card ? how can be invoke media scanner for such cards ?Wanwand
there doesn't seem to be a way to do this at the moment. see this thread: #5454208Airport
A
20

Ok, I've done it.

Rather than rescan the card, the app iterates through all the playlists in mediastore and checks the length of the _data field. I discovered that for all the lists with no associated M3U file, this field was always empty. Then it was just a case of finding the source code for the original android music app, finding the delete method and using that to delete any playlists with a length of 0. I've renamed the app PlaylistPurge (since it doesn't 'rescan' anymore) and am posting the code below.

I'll probably also publish this somewhere, either on the Market or on my own site, http://roryok.com

package com.roryok.PlaylistPurge;

import java.util.ArrayList;
import java.util.List;

import android.app.ListActivity;
import android.content.ContentUris;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.widget.ArrayAdapter;
import android.widget.ListAdapter;

public class PlaylistPurge extends ListActivity {

    private List<String> list = new ArrayList<String>();
    private final String [] STAR= {"*"};

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ListAdapter adapter = createAdapter();
        setListAdapter(adapter);
    }

    /**
     * Creates and returns a list adapter for the current list activity
     * @return
     */
    protected ListAdapter createAdapter()
    {
        // return play-lists
        Uri playlist_uri= MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI;    
        Cursor cursor= managedQuery(playlist_uri, STAR, null,null,null);
        cursor.moveToFirst();
        for(int r= 0; r<cursor.getCount(); r++, cursor.moveToNext()){
            int i = cursor.getInt(0);
            int l = cursor.getString(1).length();
            if(l>0){
                // keep any playlists with a valid data field, and let me know
                list.add("Keeping : " + cursor.getString(2) + " : id(" + i + ")");
            }else{
                // delete any play-lists with a data length of '0'
                Uri uri = ContentUris.withAppendedId(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, i);
                getContentResolver().delete(uri, null, null);
                list.add("Deleted : " + cursor.getString(2) + " : id(" + i + ")");
            }
        }       
        cursor.close();
        // publish list of retained / deleted playlists
        ListAdapter adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, list);

        return adapter;
    }
}

UPDATE:

Here's a link to a post on my blog about the app http://roryok.com/blog/index.php/2010/07/23/clearing-out-deleted-playlists-in-android/

UPDATE 2: Tuesday, April 9, 2013

I've gotten a lot of traffic to my blog from this post, and a huge number of emails from people thanking me for it. Glad it helped out! Any potential users should know that my crappy app currently crashes as soon as you run it, but actually does what it's supposed to do! I've always intended to go back and fix this crashing behaviour and put it on the market, hopefully 2013 will be the year I do that.

Airport answered 22/7, 2010 at 14:28 Comment(4)
will this delete the playlists which have not been deleted but have no songs too?Stubstad
Hi vinay. No, it will only remove entries for playlists which have been deleted. Playlists with 0 entries will not be affectedAirport
Thanks! Good SolutionAcademicism
how this will add new files ?Academicism
P
59

Here is an easy to use 'single file based' solution:

Adding a file:

Whenever you add a file, inform MediaStore's Content Provider using:

sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(newMediaFile)));

Deleting a file:

Similarly, when you delete a file, inform MediaStore's Content Provider using:

getContentResolver().delete(uri, null, null)  // (Credit goes to [DDSports][1])
Prot answered 13/2, 2013 at 8:52 Comment(5)
presumably that's meant as a fix to the issue of orphaned files cropping up in the first place. My app was to clean up the mess left by other apps that do NOT do thisAirport
I gave this an upvote because while it doesn't solve the exact problem mentioned, it is a very valuable piece of information that I think is relevant.Witkin
This actually only works when adding a file. It doesn't work when a file is to be deleted. You need the getContentResolver().delete(uri, null, null); method for that.Homespun
A already observed some empty file 're-creation' when notifying the MediaStore after file deletion: so, YES, getContentResolver().delete(uri, null, null) is useful. ThanksProt
ACTION_MEDIA_SCANNER_SCAN_FILE is deprecated. Instead you may refer to this answer: https://mcmap.net/q/131392/-39-action_media_scanner_scan_file-string-39-is-deprecatedUnexceptionable
A
20

Ok, I've done it.

Rather than rescan the card, the app iterates through all the playlists in mediastore and checks the length of the _data field. I discovered that for all the lists with no associated M3U file, this field was always empty. Then it was just a case of finding the source code for the original android music app, finding the delete method and using that to delete any playlists with a length of 0. I've renamed the app PlaylistPurge (since it doesn't 'rescan' anymore) and am posting the code below.

I'll probably also publish this somewhere, either on the Market or on my own site, http://roryok.com

package com.roryok.PlaylistPurge;

import java.util.ArrayList;
import java.util.List;

import android.app.ListActivity;
import android.content.ContentUris;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.widget.ArrayAdapter;
import android.widget.ListAdapter;

public class PlaylistPurge extends ListActivity {

    private List<String> list = new ArrayList<String>();
    private final String [] STAR= {"*"};

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ListAdapter adapter = createAdapter();
        setListAdapter(adapter);
    }

    /**
     * Creates and returns a list adapter for the current list activity
     * @return
     */
    protected ListAdapter createAdapter()
    {
        // return play-lists
        Uri playlist_uri= MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI;    
        Cursor cursor= managedQuery(playlist_uri, STAR, null,null,null);
        cursor.moveToFirst();
        for(int r= 0; r<cursor.getCount(); r++, cursor.moveToNext()){
            int i = cursor.getInt(0);
            int l = cursor.getString(1).length();
            if(l>0){
                // keep any playlists with a valid data field, and let me know
                list.add("Keeping : " + cursor.getString(2) + " : id(" + i + ")");
            }else{
                // delete any play-lists with a data length of '0'
                Uri uri = ContentUris.withAppendedId(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, i);
                getContentResolver().delete(uri, null, null);
                list.add("Deleted : " + cursor.getString(2) + " : id(" + i + ")");
            }
        }       
        cursor.close();
        // publish list of retained / deleted playlists
        ListAdapter adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, list);

        return adapter;
    }
}

UPDATE:

Here's a link to a post on my blog about the app http://roryok.com/blog/index.php/2010/07/23/clearing-out-deleted-playlists-in-android/

UPDATE 2: Tuesday, April 9, 2013

I've gotten a lot of traffic to my blog from this post, and a huge number of emails from people thanking me for it. Glad it helped out! Any potential users should know that my crappy app currently crashes as soon as you run it, but actually does what it's supposed to do! I've always intended to go back and fix this crashing behaviour and put it on the market, hopefully 2013 will be the year I do that.

Airport answered 22/7, 2010 at 14:28 Comment(4)
will this delete the playlists which have not been deleted but have no songs too?Stubstad
Hi vinay. No, it will only remove entries for playlists which have been deleted. Playlists with 0 entries will not be affectedAirport
Thanks! Good SolutionAcademicism
how this will add new files ?Academicism
N
14

You can request a rescan of specific files using the following code.

NOTE: The MIME TYPE passed is important. I noticed changes made to MP3 ID3 tags did not refresh properly in the SQLite if I used "*/*", however using "audio/mp3" worked

MediaScannerConnection.scanFile(
    context, 
    new String[]{ pathToFile1, pathToFile2 }, 
    new String[]{ "audio/mp3", "*/*" }, 
    new MediaScannerConnectionClient()
    {
        public void onMediaScannerConnected()
        {
        }
        public void onScanCompleted(String path, Uri uri)
        {
        }
    });
Nonferrous answered 19/12, 2012 at 22:57 Comment(3)
You really don't have to pass the mime type at all. If the file is properly named (*.mp3, *.m4a, etc.) then the scanner will determine the mime type from the extension. So, instead of new String[] { mime types }, just pass null.Verduzco
This worked for me to detect the removal of deleted and moved files. For my case I'm communicating over MTP (Media Transfer Protocol) and without scanning the files again they still appear to be there until reboot, etc.Unalloyed
as far as I know this will still not affect playlists which reference deleted media, only the library referencesAirport
F
1

just add this in your activity :

Uri uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;

 MediaScannerConnection.scanFile(
            this, new String[]{"/storage/emulated/0"}, null,
            new MediaScannerConnection.OnScanCompletedListener() {
                public void onScanCompleted(String path, Uri uri) {
                    Log.i("231","onScanCompleted");
                }
            });
Fem answered 2/2, 2023 at 13:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.