FileDocCategorySizeDatePackage
MediaScanner.javaAPI DocAndroid 1.5 API53449Wed May 06 22:42:00 BST 2009android.media

MediaScanner

public class MediaScanner extends Object
Internal service that no-one should use directly. {@hide}

Fields Summary
private static final String
TAG
private static final String[]
AUDIO_PROJECTION
private static final int
ID_AUDIO_COLUMN_INDEX
private static final int
PATH_AUDIO_COLUMN_INDEX
private static final int
DATE_MODIFIED_AUDIO_COLUMN_INDEX
private static final String[]
VIDEO_PROJECTION
private static final int
ID_VIDEO_COLUMN_INDEX
private static final int
PATH_VIDEO_COLUMN_INDEX
private static final int
DATE_MODIFIED_VIDEO_COLUMN_INDEX
private static final String[]
IMAGES_PROJECTION
private static final int
ID_IMAGES_COLUMN_INDEX
private static final int
PATH_IMAGES_COLUMN_INDEX
private static final int
DATE_MODIFIED_IMAGES_COLUMN_INDEX
private static final String[]
PLAYLISTS_PROJECTION
private static final String[]
PLAYLIST_MEMBERS_PROJECTION
private static final int
ID_PLAYLISTS_COLUMN_INDEX
private static final int
PATH_PLAYLISTS_COLUMN_INDEX
private static final int
DATE_MODIFIED_PLAYLISTS_COLUMN_INDEX
private static final String[]
GENRE_LOOKUP_PROJECTION
private static final String
RINGTONES_DIR
private static final String
NOTIFICATIONS_DIR
private static final String
ALARMS_DIR
private static final String
MUSIC_DIR
private static final String
PODCAST_DIR
private static final String[]
ID3_GENRES
private int
mNativeContext
private android.content.Context
mContext
private android.content.IContentProvider
mMediaProvider
private android.net.Uri
mAudioUri
private android.net.Uri
mVideoUri
private android.net.Uri
mImagesUri
private android.net.Uri
mThumbsUri
private android.net.Uri
mGenresUri
private android.net.Uri
mPlaylistsUri
private boolean
mProcessPlaylists
private boolean
mProcessGenres
private int
mOriginalCount
private boolean
mDefaultRingtoneSet
Whether the scanner has set a default sound for the ringer ringtone.
private boolean
mDefaultNotificationSet
Whether the scanner has set a default sound for the notification ringtone.
private String
mDefaultRingtoneFilename
The filename for the default sound for the ringer ringtone.
private String
mDefaultNotificationFilename
The filename for the default sound for the notification ringtone.
private static final String
DEFAULT_RINGTONE_PROPERTY_PREFIX
The prefix for system properties that define the default sound for ringtones. Concatenate the name of the setting from Settings to get the full system property.
private boolean
mCaseInsensitivePaths
private BitmapFactory.Options
mBitmapOptions
private HashMap
mFileCache
private ArrayList
mPlayLists
private HashMap
mGenreCache
private MyMediaScannerClient
mClient
Constructors Summary
public MediaScanner(android.content.Context c)

        native_setup();
        mContext = c;
        mBitmapOptions.inSampleSize = 1;
        mBitmapOptions.inJustDecodeBounds = true;
        
        setDefaultRingtoneFileNames();
    
Methods Summary
private booleanaddPlayListEntry(java.lang.String entry, java.lang.String playListDirectory, android.net.Uri uri, android.content.ContentValues values, int index)

        
        // watch for trailing whitespace
        int entryLength = entry.length();
        while (entryLength > 0 && Character.isWhitespace(entry.charAt(entryLength - 1))) entryLength--;
        // path should be longer than 3 characters.
        // avoid index out of bounds errors below by returning here.
        if (entryLength < 3) return false;
        if (entryLength < entry.length()) entry = entry.substring(0, entryLength);

        // does entry appear to be an absolute path?
        // look for Unix or DOS absolute paths
        char ch1 = entry.charAt(0);
        boolean fullPath = (ch1 == '/" ||
                (Character.isLetter(ch1) && entry.charAt(1) == ':" && entry.charAt(2) == '\\"));
        // if we have a relative path, combine entry with playListDirectory
        if (!fullPath)
            entry = playListDirectory + entry;
            
        //FIXME - should we look for "../" within the path?
        
        // best matching MediaFile for the play list entry
        FileCacheEntry bestMatch = null;
        
        // number of rightmost file/directory names for bestMatch
        int bestMatchLength = 0;    
                
        Iterator<FileCacheEntry> iterator = mFileCache.values().iterator();
        while (iterator.hasNext()) {
            FileCacheEntry cacheEntry = iterator.next();
            String path = cacheEntry.mPath;
        
            if (path.equalsIgnoreCase(entry)) {
                bestMatch = cacheEntry;
                break;    // don't bother continuing search
            }
            
            int matchLength = matchPaths(path, entry);
            if (matchLength > bestMatchLength) {
                bestMatch = cacheEntry;
                bestMatchLength = matchLength;
            }
        }
        
        if (bestMatch == null) {
            return false;
        }
        
        try {
        // OK, now we need to add this to the database
            values.clear();
            values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, Integer.valueOf(index));
            values.put(MediaStore.Audio.Playlists.Members.AUDIO_ID, Long.valueOf(bestMatch.mRowId));
            mMediaProvider.insert(uri, values);
        } catch (RemoteException e) {
            Log.e(TAG, "RemoteException in MediaScanner.addPlayListEntry()", e);
            return false;
        }

        return true;
    
public native byte[]extractAlbumArt(java.io.FileDescriptor fd)

protected voidfinalize()

 
        mContext.getContentResolver().releaseProvider(mMediaProvider);
        native_finalize(); 
    
private booleaninScanDirectory(java.lang.String path, java.lang.String[] directories)

        for (int i = 0; i < directories.length; i++) {
            if (path.startsWith(directories[i])) {
                return true;
            }
        }
        return false;
    
private voidinitialize(java.lang.String volumeName)

        mMediaProvider = mContext.getContentResolver().acquireProvider("media");
        
        mAudioUri = Audio.Media.getContentUri(volumeName);
        mVideoUri = Video.Media.getContentUri(volumeName);
        mImagesUri = Images.Media.getContentUri(volumeName);
        mThumbsUri = Images.Thumbnails.getContentUri(volumeName);

        if (!volumeName.equals("internal")) {
            // we only support playlists on external media
            mProcessPlaylists = true;
            mProcessGenres = true;
            mGenreCache = new HashMap<String, Uri>();
            mGenresUri = Genres.getContentUri(volumeName);
            mPlaylistsUri = Playlists.getContentUri(volumeName);
            // assuming external storage is FAT (case insensitive), except on the simulator.
            if ( Process.supportsProcesses()) {
                mCaseInsensitivePaths = true;
            }
        }          
    
private intmatchPaths(java.lang.String path1, java.lang.String path2)

        int result = 0;
        int end1 = path1.length();
        int end2 = path2.length();
        
        while (end1 > 0 && end2 > 0) {
            int slash1 = path1.lastIndexOf('/", end1 - 1);
            int slash2 = path2.lastIndexOf('/", end2 - 1);
            int backSlash1 = path1.lastIndexOf('\\", end1 - 1);
            int backSlash2 = path2.lastIndexOf('\\", end2 - 1);
            int start1 = (slash1 > backSlash1 ? slash1 : backSlash1);
            int start2 = (slash2 > backSlash2 ? slash2 : backSlash2);
            if (start1 < 0) start1 = 0; else start1++;
            if (start2 < 0) start2 = 0; else start2++;
            int length = end1 - start1;
            if (end2 - start2 != length) break;
            if (path1.regionMatches(true, start1, path2, start2, length)) {
                result++;
                end1 = start1 - 1;
                end2 = start2 - 1;
            } else break;
        }
               
        return result;
    
private final native voidnative_finalize()

private final native voidnative_setup()

private voidpostscan(java.lang.String[] directories)

        Iterator<FileCacheEntry> iterator = mFileCache.values().iterator();

        while (iterator.hasNext()) {
            FileCacheEntry entry = iterator.next();
            String path = entry.mPath;
            
            // remove database entries for files that no longer exist.
            boolean fileMissing = false;
            
            if (!entry.mSeenInFileSystem) {
                if (inScanDirectory(path, directories)) {
                    // we didn't see this file in the scan directory.
                    fileMissing = true;
                } else {
                    // the file is outside of our scan directory,
                    // so we need to check for file existence here.
                    File testFile = new File(path);
                    if (!testFile.exists()) {
                        fileMissing = true;
                    }
                }
            }
            
            if (fileMissing) {
                // do not delete missing playlists, since they may have been modified by the user.
                // the user can delete them in the media player instead.
                // instead, clear the path and lastModified fields in the row
                MediaFile.MediaFileType mediaFileType = MediaFile.getFileType(path);
                int fileType = (mediaFileType == null ? 0 : mediaFileType.fileType);

                if (MediaFile.isPlayListFileType(fileType)) {
                    ContentValues values = new ContentValues();
                    values.put(MediaStore.Audio.Playlists.DATA, "");
                    values.put(MediaStore.Audio.Playlists.DATE_MODIFIED, 0);
                    mMediaProvider.update(ContentUris.withAppendedId(mPlaylistsUri, entry.mRowId), values, null, null);
                } else {
                    mMediaProvider.delete(ContentUris.withAppendedId(entry.mTableUri, entry.mRowId), null, null);
                    iterator.remove();
                }
            }
        }
        
        // handle playlists last, after we know what media files are on the storage.
        if (mProcessPlaylists) {
            processPlayLists();
        }
        
        if (mOriginalCount == 0 && mImagesUri.equals(Images.Media.getContentUri("external")))
            pruneDeadThumbnailFiles();
        
        // allow GC to clean up
        mGenreCache = null;
        mPlayLists = null;
        mFileCache = null;
        mMediaProvider = null;
    
private voidprescan(java.lang.String filePath)

        Cursor c = null;
        String where = null;
        String[] selectionArgs = null;
         
        if (mFileCache == null) {
            mFileCache = new HashMap<String, FileCacheEntry>();
        } else {
            mFileCache.clear();
        }
        if (mPlayLists == null) {
            mPlayLists = new ArrayList<FileCacheEntry>();
        } else {
            mPlayLists.clear();
        }
  
        // Build the list of files from the content provider
        try {
            // Read existing files from the audio table
            if (filePath != null) {
                where = MediaStore.Audio.Media.DATA + "=?";
                selectionArgs = new String[] { filePath };
            }
            c = mMediaProvider.query(mAudioUri, AUDIO_PROJECTION, where, selectionArgs, null);
 
            if (c != null) {
                try {
                    while (c.moveToNext()) {
                        long rowId = c.getLong(ID_AUDIO_COLUMN_INDEX);
                        String path = c.getString(PATH_AUDIO_COLUMN_INDEX);
                        long lastModified = c.getLong(DATE_MODIFIED_AUDIO_COLUMN_INDEX);
                        
                        String key = path;
                        if (mCaseInsensitivePaths) {
                            key = path.toLowerCase();
                        }
                        mFileCache.put(key, new FileCacheEntry(mAudioUri, rowId, path,
                                lastModified));
                    }
                } finally {
                    c.close();
                    c = null;
                }
            }

            // Read existing files from the video table
            if (filePath != null) {
                where = MediaStore.Video.Media.DATA + "=?";
            } else {
                where = null;
            }
            c = mMediaProvider.query(mVideoUri, VIDEO_PROJECTION, where, selectionArgs, null);
 
            if (c != null) {
                try {
                    while (c.moveToNext()) {
                        long rowId = c.getLong(ID_VIDEO_COLUMN_INDEX);
                        String path = c.getString(PATH_VIDEO_COLUMN_INDEX);
                        long lastModified = c.getLong(DATE_MODIFIED_VIDEO_COLUMN_INDEX);
                        
                        String key = path;
                        if (mCaseInsensitivePaths) {
                            key = path.toLowerCase();
                        }
                        mFileCache.put(key, new FileCacheEntry(mVideoUri, rowId, path,
                                lastModified));
                    }
                } finally {
                    c.close();
                    c = null;
                }
            }

            // Read existing files from the images table
            if (filePath != null) {
                where = MediaStore.Images.Media.DATA + "=?";
            } else {
                where = null;
            }
            mOriginalCount = 0;
            c = mMediaProvider.query(mImagesUri, IMAGES_PROJECTION, where, selectionArgs, null);
 
            if (c != null) {
                try {
                    mOriginalCount = c.getCount();
                    while (c.moveToNext()) {
                        long rowId = c.getLong(ID_IMAGES_COLUMN_INDEX);
                        String path = c.getString(PATH_IMAGES_COLUMN_INDEX);
                       long lastModified = c.getLong(DATE_MODIFIED_IMAGES_COLUMN_INDEX);
    
                        String key = path;
                        if (mCaseInsensitivePaths) {
                            key = path.toLowerCase();
                        }
                        mFileCache.put(key, new FileCacheEntry(mImagesUri, rowId, path,
                                lastModified));
                    }
                } finally {
                    c.close();
                    c = null;
                }
            }
            
            if (mProcessPlaylists) {
                // Read existing files from the playlists table
                if (filePath != null) {
                    where = MediaStore.Audio.Playlists.DATA + "=?";
                } else {
                    where = null;
                }
                c = mMediaProvider.query(mPlaylistsUri, PLAYLISTS_PROJECTION, where, selectionArgs, null);
     
                if (c != null) {
                    try {
                        while (c.moveToNext()) {
                            String path = c.getString(PATH_IMAGES_COLUMN_INDEX);
            
                            if (path != null && path.length() > 0) {
                                long rowId = c.getLong(ID_PLAYLISTS_COLUMN_INDEX);
                                long lastModified = c.getLong(DATE_MODIFIED_PLAYLISTS_COLUMN_INDEX);
    
                                String key = path;
                                if (mCaseInsensitivePaths) {
                                    key = path.toLowerCase();
                                }
                                mFileCache.put(key, new FileCacheEntry(mPlaylistsUri, rowId, path,
                                        lastModified));
                            }
                        }
                    } finally {
                        c.close();
                        c = null;
                    }
                }
            }
        }
        finally {
            if (c != null) {
                c.close();
            }
        }
    
private native voidprocessDirectory(java.lang.String path, java.lang.String extensions, MediaScannerClient client)

private native voidprocessFile(java.lang.String path, java.lang.String mimeType, MediaScannerClient client)

private voidprocessM3uPlayList(java.lang.String path, java.lang.String playListDirectory, android.net.Uri uri, android.content.ContentValues values)

        BufferedReader reader = null;
        try {
            File f = new File(path);
            if (f.exists()) {
                reader = new BufferedReader(
                        new InputStreamReader(new FileInputStream(f)), 8192);
                String line = reader.readLine();
                int index = 0;
                while (line != null) {
                    // ignore comment lines, which begin with '#'
                    if (line.length() > 0 && line.charAt(0) != '#") {
                        values.clear();
                        if (addPlayListEntry(line, playListDirectory, uri, values, index))
                            index++;
                    }
                    line = reader.readLine();
                }
            }
        } catch (IOException e) {
            Log.e(TAG, "IOException in MediaScanner.processM3uPlayList()", e);
        } finally {
            try {
                if (reader != null)
                    reader.close();
            } catch (IOException e) {
                Log.e(TAG, "IOException in MediaScanner.processM3uPlayList()", e);
            }
        }
    
private voidprocessPlayLists()

        Iterator<FileCacheEntry> iterator = mPlayLists.iterator();
        while (iterator.hasNext()) {
            FileCacheEntry entry = iterator.next();
            String path = entry.mPath;  

            // only process playlist files if they are new or have been modified since the last scan
            if (entry.mLastModifiedChanged) {
                ContentValues values = new ContentValues();
                int lastSlash = path.lastIndexOf('/");
                if (lastSlash < 0) throw new IllegalArgumentException("bad path " + path);
                Uri uri, membersUri;
                long rowId = entry.mRowId;
                if (rowId == 0) {
                    // Create a new playlist
        
                    int lastDot = path.lastIndexOf('.");
                    String name = (lastDot < 0 ? path.substring(lastSlash + 1) : path.substring(lastSlash + 1, lastDot));
                    values.put(MediaStore.Audio.Playlists.NAME, name);
                    values.put(MediaStore.Audio.Playlists.DATA, path);
                    values.put(MediaStore.Audio.Playlists.DATE_MODIFIED, entry.mLastModified);
                    uri = mMediaProvider.insert(mPlaylistsUri, values);
                    rowId = ContentUris.parseId(uri);
                    membersUri = Uri.withAppendedPath(uri, Playlists.Members.CONTENT_DIRECTORY);
                } else {
                    uri = ContentUris.withAppendedId(mPlaylistsUri, rowId);
                    
                    // update lastModified value of existing playlist
                    values.put(MediaStore.Audio.Playlists.DATE_MODIFIED, entry.mLastModified);
                    mMediaProvider.update(uri, values, null, null);

                    // delete members of existing playlist
                    membersUri = Uri.withAppendedPath(uri, Playlists.Members.CONTENT_DIRECTORY);
                    mMediaProvider.delete(membersUri, null, null);
                }
               
                String playListDirectory = path.substring(0, lastSlash + 1);
                MediaFile.MediaFileType mediaFileType = MediaFile.getFileType(path);
                int fileType = (mediaFileType == null ? 0 : mediaFileType.fileType);

                if (fileType == MediaFile.FILE_TYPE_M3U)
                    processM3uPlayList(path, playListDirectory, membersUri, values);
                else if (fileType == MediaFile.FILE_TYPE_PLS)
                    processPlsPlayList(path, playListDirectory, membersUri, values);
                else if (fileType == MediaFile.FILE_TYPE_WPL)
                    processWplPlayList(path, playListDirectory, membersUri);
                    
                Cursor cursor = mMediaProvider.query(membersUri, PLAYLIST_MEMBERS_PROJECTION, null,
                        null, null);
                try {
                    if (cursor == null || cursor.getCount() == 0) {
                        Log.d(TAG, "playlist is empty - deleting");
                        mMediaProvider.delete(uri, null, null);
                    }
                } finally {
                    if (cursor != null) cursor.close();
                }
            }
        }
    
private voidprocessPlsPlayList(java.lang.String path, java.lang.String playListDirectory, android.net.Uri uri, android.content.ContentValues values)

        BufferedReader reader = null;
        try {
            File f = new File(path);
            if (f.exists()) {
                reader = new BufferedReader(
                        new InputStreamReader(new FileInputStream(f)), 8192);
                String line = reader.readLine();
                int index = 0;
                while (line != null) {
                    // ignore comment lines, which begin with '#'
                    if (line.startsWith("File")) {
                        int equals = line.indexOf('=");
                        if (equals > 0) {
                            values.clear();
                            if (addPlayListEntry(line.substring(equals + 1), playListDirectory, uri, values, index))
                                index++;
                        }
                    }
                    line = reader.readLine();
                }
            }
        } catch (IOException e) {
            Log.e(TAG, "IOException in MediaScanner.processPlsPlayList()", e);
        } finally {
            try {
                if (reader != null)
                    reader.close();
            } catch (IOException e) {
                Log.e(TAG, "IOException in MediaScanner.processPlsPlayList()", e);
            }
        }
    
private voidprocessWplPlayList(java.lang.String path, java.lang.String playListDirectory, android.net.Uri uri)

        FileInputStream fis = null;
        try {
            File f = new File(path);
            if (f.exists()) {
                fis = new FileInputStream(f);

                Xml.parse(fis, Xml.findEncodingByName("UTF-8"), new WplHandler(playListDirectory, uri).getContentHandler());
            }
        } catch (SAXException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fis != null)
                    fis.close();
            } catch (IOException e) {
                Log.e(TAG, "IOException in MediaScanner.processWplPlayList()", e);
            }
        }
    
private voidpruneDeadThumbnailFiles()

        HashSet<String> existingFiles = new HashSet<String>();
        String directory = "/sdcard/DCIM/.thumbnails";
        String [] files = (new File(directory)).list();
        if (files == null)
            files = new String[0];
        
        for (int i = 0; i < files.length; i++) {
            String fullPathString = directory + "/" + files[i];
            existingFiles.add(fullPathString);
        }
        
        try {
            Cursor c = mMediaProvider.query(
                    mThumbsUri, 
                    new String [] { "_data" }, 
                    null, 
                    null, 
                    null);
            Log.v(TAG, "pruneDeadThumbnailFiles... " + c);
            if (c != null && c.moveToFirst()) {
                do {
                    String fullPathString = c.getString(0);
                    existingFiles.remove(fullPathString);
                } while (c.moveToNext());
            }
            
            for (String fileToDelete : existingFiles) {
                if (Config.LOGV)
                    Log.v(TAG, "fileToDelete is " + fileToDelete);
                try {
                    (new File(fileToDelete)).delete();
                } catch (SecurityException ex) {
                }
            }
            
            Log.v(TAG, "/pruneDeadThumbnailFiles... " + c);
            if (c != null) {
                c.close();
            }
        } catch (RemoteException e) {
            // We will soon be killed...
        }
    
public voidscanDirectories(java.lang.String[] directories, java.lang.String volumeName)

        try {
            long start = System.currentTimeMillis();
            initialize(volumeName);    
            prescan(null);
            long prescan = System.currentTimeMillis();
            
            for (int i = 0; i < directories.length; i++) {
                processDirectory(directories[i], MediaFile.sFileExtensions, mClient);
            }
            long scan = System.currentTimeMillis();
            postscan(directories);
            long end = System.currentTimeMillis();
            
            if (Config.LOGD) {
                Log.d(TAG, " prescan time: " + (prescan - start) + "ms\n");
                Log.d(TAG, "    scan time: " + (scan - prescan) + "ms\n");
                Log.d(TAG, "postscan time: " + (end - scan) + "ms\n");
                Log.d(TAG, "   total time: " + (end - start) + "ms\n");
            }
        } catch (SQLException e) {
            // this might happen if the SD card is removed while the media scanner is running
            Log.e(TAG, "SQLException in MediaScanner.scan()", e);
        } catch (UnsupportedOperationException e) {
            // this might happen if the SD card is removed while the media scanner is running
            Log.e(TAG, "UnsupportedOperationException in MediaScanner.scan()", e);
        } catch (RemoteException e) {
            Log.e(TAG, "RemoteException in MediaScanner.scan()", e);
        }
    
public android.net.UriscanSingleFile(java.lang.String path, java.lang.String volumeName, java.lang.String mimeType)

        try {
            initialize(volumeName);        
            prescan(path);
    
            File file = new File(path);
            // always scan the file, so we can return the content://media Uri for existing files
            return mClient.doScanFile(path, mimeType, file.lastModified(), file.length(), true);
        } catch (RemoteException e) {
            Log.e(TAG, "RemoteException in MediaScanner.scanFile()", e);
            return null;
        }
    
private voidsetDefaultRingtoneFileNames()

        mDefaultRingtoneFilename = SystemProperties.get(DEFAULT_RINGTONE_PROPERTY_PREFIX
                + Settings.System.RINGTONE);
        mDefaultNotificationFilename = SystemProperties.get(DEFAULT_RINGTONE_PROPERTY_PREFIX
                + Settings.System.NOTIFICATION_SOUND);
    
public native voidsetLocale(java.lang.String locale)