FileDocCategorySizeDatePackage
MediaScanner.javaAPI DocAndroid 5.1 API76205Thu Mar 12 22:22:30 GMT 2015android.media

MediaScanner

public class MediaScanner extends Object
Internal service helper that no-one should use directly. The way the scan currently works is: - The Java MediaScannerService creates a MediaScanner (this class), and calls MediaScanner.scanDirectories on it. - scanDirectories() calls the native processDirectory() for each of the specified directories. - the processDirectory() JNI method wraps the provided mediascanner client in a native 'MyMediaScannerClient' class, then calls processDirectory() on the native MediaScanner object (which got created when the Java MediaScanner was created). - native MediaScanner.processDirectory() calls doProcessDirectory(), which recurses over the folder, and calls native MyMediaScannerClient.scanFile() for every file whose extension matches. - native MyMediaScannerClient.scanFile() calls back on Java MediaScannerClient.scanFile, which calls doScanFile, which after some setup calls back down to native code, calling MediaScanner.processFile(). - MediaScanner.processFile() calls one of several methods, depending on the type of the file: parseMP3, parseMP4, parseMidi, parseOgg or parseWMA. - each of these methods gets metadata key/value pairs from the file, and repeatedly calls native MyMediaScannerClient.handleStringTag, which calls back up to its Java counterparts in this file. - Java handleStringTag() gathers the key/value pairs that it's interested in. - once processFile returns and we're back in Java code in doScanFile(), it calls Java MyMediaScannerClient.endFile(), which takes all the data that's been gathered and inserts an entry in to the database. In summary: Java MediaScannerService calls Java MediaScanner scanDirectories, which calls Java MediaScanner processDirectory (native method), which calls native MediaScanner processDirectory, which calls native MyMediaScannerClient scanFile, which calls Java MyMediaScannerClient scanFile, which calls Java MediaScannerClient doScanFile, which calls Java MediaScanner processFile (native method), which calls native MediaScanner processFile, which calls native parseMP3, parseMP4, parseMidi, parseOgg or parseWMA, which calls native MyMediaScanner handleStringTag, which calls Java MyMediaScanner handleStringTag. Once MediaScanner processFile returns, an entry is inserted in to the database. The MediaScanner class is not thread-safe, so it should only be used in a single threaded manner. {@hide}

Fields Summary
private static final String
TAG
private static final String[]
FILES_PRESCAN_PROJECTION
private static final String[]
ID_PROJECTION
private static final int
FILES_PRESCAN_ID_COLUMN_INDEX
private static final int
FILES_PRESCAN_PATH_COLUMN_INDEX
private static final int
FILES_PRESCAN_FORMAT_COLUMN_INDEX
private static final int
FILES_PRESCAN_DATE_MODIFIED_COLUMN_INDEX
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
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 long
mNativeContext
private android.content.Context
mContext
private String
mPackageName
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
mPlaylistsUri
private android.net.Uri
mFilesUri
private android.net.Uri
mFilesUriNoNotify
private boolean
mProcessPlaylists
private boolean
mProcessGenres
private int
mMtpObjectHandle
private final String
mExternalStoragePath
private final boolean
mExternalIsEmulated
private static final boolean
ENABLE_BULK_INSERTS
whether to use bulk inserts or individual inserts for each item
private int
mOriginalCount
private boolean
mWasEmptyPriorToScan
Whether the database had any entries in it before the scan started
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 boolean
mDefaultAlarmSet
Whether the scanner has set a default sound for the alarm 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 String
mDefaultAlarmAlertFilename
The filename for the default sound for the alarm 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 final BitmapFactory.Options
mBitmapOptions
private ArrayList
mPlaylistEntries
private MediaInserter
mMediaInserter
private ArrayList
mPlayLists
private android.drm.DrmManagerClient
mDrmManagerClient
private final MyMediaScannerClient
mClient
private static HashMap
mNoMediaPaths
private static HashMap
mMediaPaths
Constructors Summary
public MediaScanner(android.content.Context c)


       
        native_setup();
        mContext = c;
        mPackageName = c.getPackageName();
        mBitmapOptions.inSampleSize = 1;
        mBitmapOptions.inJustDecodeBounds = true;

        setDefaultRingtoneFileNames();

        mExternalStoragePath = Environment.getExternalStorageDirectory().getAbsolutePath();
        mExternalIsEmulated = Environment.isExternalStorageEmulated();
        //mClient.testGenreNameConverter();
    
Methods Summary
private voidcachePlaylistEntry(java.lang.String line, java.lang.String playListDirectory)

        PlaylistEntry entry = new PlaylistEntry();
        // watch for trailing whitespace
        int entryLength = line.length();
        while (entryLength > 0 && Character.isWhitespace(line.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;
        if (entryLength < line.length()) line = line.substring(0, entryLength);

        // does entry appear to be an absolute path?
        // look for Unix or DOS absolute paths
        char ch1 = line.charAt(0);
        boolean fullPath = (ch1 == '/" ||
                (Character.isLetter(ch1) && line.charAt(1) == ':" && line.charAt(2) == '\\"));
        // if we have a relative path, combine entry with playListDirectory
        if (!fullPath)
            line = playListDirectory + line;
        entry.path = line;
        //FIXME - should we look for "../" within the path?

        mPlaylistEntries.add(entry);
    
public static voidclearMediaPathCache(boolean clearMediaPaths, boolean clearNoMediaPaths)


    /* MediaProvider calls this when a .nomedia file is added or removed */
           
        synchronized (MediaScanner.class) {
            if (clearMediaPaths) {
                mMediaPaths.clear();
            }
            if (clearNoMediaPaths) {
                mNoMediaPaths.clear();
            }
        }
    
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++) {
            String directory = directories[i];
            if (path.startsWith(directory)) {
                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);
        mFilesUri = Files.getContentUri(volumeName);
        mFilesUriNoNotify = mFilesUri.buildUpon().appendQueryParameter("nonotify", "1").build();

        if (!volumeName.equals("internal")) {
            // we only support playlists on external media
            mProcessPlaylists = true;
            mProcessGenres = true;
            mPlaylistsUri = Playlists.getContentUri(volumeName);

            mCaseInsensitivePaths = true;
        }
    
private booleanisDrmEnabled()


       
        String prop = SystemProperties.get("drm.service.enabled");
        return prop != null && prop.equals("true");
    
private static booleanisNoMediaFile(java.lang.String path)

        File file = new File(path);
        if (file.isDirectory()) return false;

        // special case certain file names
        // I use regionMatches() instead of substring() below
        // to avoid memory allocation
        int lastSlash = path.lastIndexOf('/");
        if (lastSlash >= 0 && lastSlash + 2 < path.length()) {
            // ignore those ._* files created by MacOS
            if (path.regionMatches(lastSlash + 1, "._", 0, 2)) {
                return true;
            }

            // ignore album art files created by Windows Media Player:
            // Folder.jpg, AlbumArtSmall.jpg, AlbumArt_{...}_Large.jpg
            // and AlbumArt_{...}_Small.jpg
            if (path.regionMatches(true, path.length() - 4, ".jpg", 0, 4)) {
                if (path.regionMatches(true, lastSlash + 1, "AlbumArt_{", 0, 10) ||
                        path.regionMatches(true, lastSlash + 1, "AlbumArt.", 0, 9)) {
                    return true;
                }
                int length = path.length() - lastSlash - 1;
                if ((length == 17 && path.regionMatches(
                        true, lastSlash + 1, "AlbumArtSmall", 0, 13)) ||
                        (length == 10
                         && path.regionMatches(true, lastSlash + 1, "Folder", 0, 6))) {
                    return true;
                }
            }
        }
        return false;
    
public static booleanisNoMediaPath(java.lang.String path)

        if (path == null) {
            return false;
        }
        // return true if file or any parent directory has name starting with a dot
        if (path.indexOf("/.") >= 0) {
            return true;
        }

        int firstSlash = path.lastIndexOf('/");
        if (firstSlash <= 0) {
            return false;
        }
        String parent = path.substring(0,  firstSlash);

        synchronized (MediaScanner.class) {
            if (mNoMediaPaths.containsKey(parent)) {
                return true;
            } else if (!mMediaPaths.containsKey(parent)) {
                // check to see if any parent directories have a ".nomedia" file
                // start from 1 so we don't bother checking in the root directory
                int offset = 1;
                while (offset >= 0) {
                    int slashIndex = path.indexOf('/", offset);
                    if (slashIndex > offset) {
                        slashIndex++; // move past slash
                        File file = new File(path.substring(0, slashIndex) + ".nomedia");
                        if (file.exists()) {
                            // we have a .nomedia in one of the parent directories
                            mNoMediaPaths.put(parent, "");
                            return true;
                        }
                    }
                    offset = slashIndex;
                }
                mMediaPaths.put(parent, "");
            }
        }

        return isNoMediaFile(path);
    
android.media.MediaScanner$FileEntrymakeEntryFor(java.lang.String path)

        String where;
        String[] selectionArgs;

        Cursor c = null;
        try {
            where = Files.FileColumns.DATA + "=?";
            selectionArgs = new String[] { path };
            c = mMediaProvider.query(mPackageName, mFilesUriNoNotify, FILES_PRESCAN_PROJECTION,
                    where, selectionArgs, null, null);
            if (c.moveToFirst()) {
                long rowId = c.getLong(FILES_PRESCAN_ID_COLUMN_INDEX);
                int format = c.getInt(FILES_PRESCAN_FORMAT_COLUMN_INDEX);
                long lastModified = c.getLong(FILES_PRESCAN_DATE_MODIFIED_COLUMN_INDEX);
                return new FileEntry(rowId, path, lastModified, format);
            }
        } catch (RemoteException e) {
        } finally {
            if (c != null) {
                c.close();
            }
        }
        return null;
    
private booleanmatchEntries(long rowId, java.lang.String data)


        int len = mPlaylistEntries.size();
        boolean done = true;
        for (int i = 0; i < len; i++) {
            PlaylistEntry entry = mPlaylistEntries.get(i);
            if (entry.bestmatchlevel == Integer.MAX_VALUE) {
                continue; // this entry has been matched already
            }
            done = false;
            if (data.equalsIgnoreCase(entry.path)) {
                entry.bestmatchid = rowId;
                entry.bestmatchlevel = Integer.MAX_VALUE;
                continue; // no need for path matching
            }

            int matchLength = matchPaths(data, entry.path);
            if (matchLength > entry.bestmatchlevel) {
                entry.bestmatchid = rowId;
                entry.bestmatchlevel = matchLength;
            }
        }
        return done;
    
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 static final native voidnative_init()

private final native voidnative_setup()

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


        // 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
        mPlayLists = null;
        mMediaProvider = null;
    
private voidprescan(java.lang.String filePath, boolean prescanFiles)

        Cursor c = null;
        String where = null;
        String[] selectionArgs = null;

        if (mPlayLists == null) {
            mPlayLists = new ArrayList<FileEntry>();
        } else {
            mPlayLists.clear();
        }

        if (filePath != null) {
            // query for only one file
            where = MediaStore.Files.FileColumns._ID + ">?" +
                " AND " + Files.FileColumns.DATA + "=?";
            selectionArgs = new String[] { "", filePath };
        } else {
            where = MediaStore.Files.FileColumns._ID + ">?";
            selectionArgs = new String[] { "" };
        }

        // Tell the provider to not delete the file.
        // If the file is truly gone the delete is unnecessary, and we want to avoid
        // accidentally deleting files that are really there (this may happen if the
        // filesystem is mounted and unmounted while the scanner is running).
        Uri.Builder builder = mFilesUri.buildUpon();
        builder.appendQueryParameter(MediaStore.PARAM_DELETE_DATA, "false");
        MediaBulkDeleter deleter = new MediaBulkDeleter(mMediaProvider, mPackageName,
                builder.build());

        // Build the list of files from the content provider
        try {
            if (prescanFiles) {
                // First read existing files from the files table.
                // Because we'll be deleting entries for missing files as we go,
                // we need to query the database in small batches, to avoid problems
                // with CursorWindow positioning.
                long lastId = Long.MIN_VALUE;
                Uri limitUri = mFilesUri.buildUpon().appendQueryParameter("limit", "1000").build();
                mWasEmptyPriorToScan = true;

                while (true) {
                    selectionArgs[0] = "" + lastId;
                    if (c != null) {
                        c.close();
                        c = null;
                    }
                    c = mMediaProvider.query(mPackageName, limitUri, FILES_PRESCAN_PROJECTION,
                            where, selectionArgs, MediaStore.Files.FileColumns._ID, null);
                    if (c == null) {
                        break;
                    }

                    int num = c.getCount();

                    if (num == 0) {
                        break;
                    }
                    mWasEmptyPriorToScan = false;
                    while (c.moveToNext()) {
                        long rowId = c.getLong(FILES_PRESCAN_ID_COLUMN_INDEX);
                        String path = c.getString(FILES_PRESCAN_PATH_COLUMN_INDEX);
                        int format = c.getInt(FILES_PRESCAN_FORMAT_COLUMN_INDEX);
                        long lastModified = c.getLong(FILES_PRESCAN_DATE_MODIFIED_COLUMN_INDEX);
                        lastId = rowId;

                        // Only consider entries with absolute path names.
                        // This allows storing URIs in the database without the
                        // media scanner removing them.
                        if (path != null && path.startsWith("/")) {
                            boolean exists = false;
                            try {
                                exists = Os.access(path, android.system.OsConstants.F_OK);
                            } catch (ErrnoException e1) {
                            }
                            if (!exists && !MtpConstants.isAbstractObject(format)) {
                                // 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)) {
                                    deleter.delete(rowId);
                                    if (path.toLowerCase(Locale.US).endsWith("/.nomedia")) {
                                        deleter.flush();
                                        String parent = new File(path).getParent();
                                        mMediaProvider.call(mPackageName, MediaStore.UNHIDE_CALL,
                                                parent, null);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        finally {
            if (c != null) {
                c.close();
            }
            deleter.flush();
        }

        // compute original size of images
        mOriginalCount = 0;
        c = mMediaProvider.query(mPackageName, mImagesUri, ID_PROJECTION, null, null, null, null);
        if (c != null) {
            mOriginalCount = c.getCount();
            c.close();
        }
    
private voidprocessCachedPlaylist(android.database.Cursor fileList, android.content.ContentValues values, android.net.Uri playlistUri)

        fileList.moveToPosition(-1);
        while (fileList.moveToNext()) {
            long rowId = fileList.getLong(FILES_PRESCAN_ID_COLUMN_INDEX);
            String data = fileList.getString(FILES_PRESCAN_PATH_COLUMN_INDEX);
            if (matchEntries(rowId, data)) {
                break;
            }
        }

        int len = mPlaylistEntries.size();
        int index = 0;
        for (int i = 0; i < len; i++) {
            PlaylistEntry entry = mPlaylistEntries.get(i);
            if (entry.bestmatchlevel > 0) {
                try {
                    values.clear();
                    values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, Integer.valueOf(index));
                    values.put(MediaStore.Audio.Playlists.Members.AUDIO_ID, Long.valueOf(entry.bestmatchid));
                    mMediaProvider.insert(mPackageName, playlistUri, values);
                    index++;
                } catch (RemoteException e) {
                    Log.e(TAG, "RemoteException in MediaScanner.processCachedPlaylist()", e);
                    return;
                }
            }
        }
        mPlaylistEntries.clear();
    
private native voidprocessDirectory(java.lang.String path, 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, android.database.Cursor fileList)

        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();
                mPlaylistEntries.clear();
                while (line != null) {
                    // ignore comment lines, which begin with '#'
                    if (line.length() > 0 && line.charAt(0) != '#") {
                        cachePlaylistEntry(line, playListDirectory);
                    }
                    line = reader.readLine();
                }

                processCachedPlaylist(fileList, values, uri);
            }
        } 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 voidprocessPlayList(android.media.MediaScanner$FileEntry entry, android.database.Cursor fileList)

        String path = entry.mPath;
        ContentValues values = new ContentValues();
        int lastSlash = path.lastIndexOf('/");
        if (lastSlash < 0) throw new IllegalArgumentException("bad path " + path);
        Uri uri, membersUri;
        long rowId = entry.mRowId;

        // make sure we have a name
        String name = values.getAsString(MediaStore.Audio.Playlists.NAME);
        if (name == null) {
            name = values.getAsString(MediaStore.MediaColumns.TITLE);
            if (name == null) {
                // extract name from file name
                int lastDot = path.lastIndexOf('.");
                name = (lastDot < 0 ? path.substring(lastSlash + 1)
                        : path.substring(lastSlash + 1, lastDot));
            }
        }

        values.put(MediaStore.Audio.Playlists.NAME, name);
        values.put(MediaStore.Audio.Playlists.DATE_MODIFIED, entry.mLastModified);

        if (rowId == 0) {
            values.put(MediaStore.Audio.Playlists.DATA, path);
            uri = mMediaProvider.insert(mPackageName, mPlaylistsUri, values);
            rowId = ContentUris.parseId(uri);
            membersUri = Uri.withAppendedPath(uri, Playlists.Members.CONTENT_DIRECTORY);
        } else {
            uri = ContentUris.withAppendedId(mPlaylistsUri, rowId);
            mMediaProvider.update(mPackageName, uri, values, null, null);

            // delete members of existing playlist
            membersUri = Uri.withAppendedPath(uri, Playlists.Members.CONTENT_DIRECTORY);
            mMediaProvider.delete(mPackageName, 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, fileList);
        } else if (fileType == MediaFile.FILE_TYPE_PLS) {
            processPlsPlayList(path, playListDirectory, membersUri, values, fileList);
        } else if (fileType == MediaFile.FILE_TYPE_WPL) {
            processWplPlayList(path, playListDirectory, membersUri, values, fileList);
        }
    
private voidprocessPlayLists()

        Iterator<FileEntry> iterator = mPlayLists.iterator();
        Cursor fileList = null;
        try {
            // use the files uri and projection because we need the format column,
            // but restrict the query to just audio files
            fileList = mMediaProvider.query(mPackageName, mFilesUri, FILES_PRESCAN_PROJECTION,
                    "media_type=2", null, null, null);
            while (iterator.hasNext()) {
                FileEntry entry = iterator.next();
                // only process playlist files if they are new or have been modified since the last scan
                if (entry.mLastModifiedChanged) {
                    processPlayList(entry, fileList);
                }
            }
        } catch (RemoteException e1) {
        } finally {
            if (fileList != null) {
                fileList.close();
            }
        }
    
private voidprocessPlsPlayList(java.lang.String path, java.lang.String playListDirectory, android.net.Uri uri, android.content.ContentValues values, android.database.Cursor fileList)

        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();
                mPlaylistEntries.clear();
                while (line != null) {
                    // ignore comment lines, which begin with '#'
                    if (line.startsWith("File")) {
                        int equals = line.indexOf('=");
                        if (equals > 0) {
                            cachePlaylistEntry(line.substring(equals + 1), playListDirectory);
                        }
                    }
                    line = reader.readLine();
                }

                processCachedPlaylist(fileList, values, uri);
            }
        } 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, android.content.ContentValues values, android.database.Cursor fileList)

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

                mPlaylistEntries.clear();
                Xml.parse(fis, Xml.findEncodingByName("UTF-8"),
                        new WplHandler(playListDirectory, uri, fileList).getContentHandler());

                processCachedPlaylist(fileList, values, uri);
            }
        } 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();
        Cursor c = null;
        if (files == null)
            files = new String[0];

        for (int i = 0; i < files.length; i++) {
            String fullPathString = directory + "/" + files[i];
            existingFiles.add(fullPathString);
        }

        try {
            c = mMediaProvider.query(
                    mPackageName,
                    mThumbsUri,
                    new String [] { "_data" },
                    null,
                    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 (false)
                    Log.v(TAG, "fileToDelete is " + fileToDelete);
                try {
                    (new File(fileToDelete)).delete();
                } catch (SecurityException ex) {
                }
            }

            Log.v(TAG, "/pruneDeadThumbnailFiles... " + c);
        } catch (RemoteException e) {
            // We will soon be killed...
        } finally {
            if (c != null) {
                c.close();
            }
        }
    
public voidrelease()
Releases resources associated with this MediaScanner object. It is considered good practice to call this method when one is done using the MediaScanner object. After this method is called, the MediaScanner object can no longer be used.

        native_finalize();
    
private voidreleaseResources()

        // release the DrmManagerClient resources
        if (mDrmManagerClient != null) {
            mDrmManagerClient.release();
            mDrmManagerClient = null;
        }
    
public voidscanDirectories(java.lang.String[] directories, java.lang.String volumeName)

        try {
            long start = System.currentTimeMillis();
            initialize(volumeName);
            prescan(null, true);
            long prescan = System.currentTimeMillis();

            if (ENABLE_BULK_INSERTS) {
                // create MediaInserter for bulk inserts
                mMediaInserter = new MediaInserter(mMediaProvider, mPackageName, 500);
            }

            for (int i = 0; i < directories.length; i++) {
                processDirectory(directories[i], mClient);
            }

            if (ENABLE_BULK_INSERTS) {
                // flush remaining inserts
                mMediaInserter.flushAll();
                mMediaInserter = null;
            }

            long scan = System.currentTimeMillis();
            postscan(directories);
            long end = System.currentTimeMillis();

            if (false) {
                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);
        } finally {
            releaseResources();
        }
    
public voidscanMtpFile(java.lang.String path, java.lang.String volumeName, int objectHandle, int format)

        initialize(volumeName);
        MediaFile.MediaFileType mediaFileType = MediaFile.getFileType(path);
        int fileType = (mediaFileType == null ? 0 : mediaFileType.fileType);
        File file = new File(path);
        long lastModifiedSeconds = file.lastModified() / 1000;

        if (!MediaFile.isAudioFileType(fileType) && !MediaFile.isVideoFileType(fileType) &&
            !MediaFile.isImageFileType(fileType) && !MediaFile.isPlayListFileType(fileType) &&
            !MediaFile.isDrmFileType(fileType)) {

            // no need to use the media scanner, but we need to update last modified and file size
            ContentValues values = new ContentValues();
            values.put(Files.FileColumns.SIZE, file.length());
            values.put(Files.FileColumns.DATE_MODIFIED, lastModifiedSeconds);
            try {
                String[] whereArgs = new String[] {  Integer.toString(objectHandle) };
                mMediaProvider.update(mPackageName, Files.getMtpObjectsUri(volumeName), values,
                        "_id=?", whereArgs);
            } catch (RemoteException e) {
                Log.e(TAG, "RemoteException in scanMtpFile", e);
            }
            return;
        }

        mMtpObjectHandle = objectHandle;
        Cursor fileList = null;
        try {
            if (MediaFile.isPlayListFileType(fileType)) {
                // build file cache so we can look up tracks in the playlist
                prescan(null, true);

                FileEntry entry = makeEntryFor(path);
                if (entry != null) {
                    fileList = mMediaProvider.query(mPackageName, mFilesUri,
                            FILES_PRESCAN_PROJECTION, null, null, null, null);
                    processPlayList(entry, fileList);
                }
            } else {
                // MTP will create a file entry for us so we don't want to do it in prescan
                prescan(path, false);

                // always scan the file, so we can return the content://media Uri for existing files
                mClient.doScanFile(path, mediaFileType.mimeType, lastModifiedSeconds, file.length(),
                    (format == MtpConstants.FORMAT_ASSOCIATION), true, isNoMediaPath(path));
            }
        } catch (RemoteException e) {
            Log.e(TAG, "RemoteException in MediaScanner.scanFile()", e);
        } finally {
            mMtpObjectHandle = 0;
            if (fileList != null) {
                fileList.close();
            }
            releaseResources();
        }
    
public android.net.UriscanSingleFile(java.lang.String path, java.lang.String volumeName, java.lang.String mimeType)

        try {
            initialize(volumeName);
            prescan(path, true);

            File file = new File(path);
            if (!file.exists()) {
                return null;
            }

            // lastModified is in milliseconds on Files.
            long lastModifiedSeconds = file.lastModified() / 1000;

            // always scan the file, so we can return the content://media Uri for existing files
            return mClient.doScanFile(path, mimeType, lastModifiedSeconds, file.length(),
                    false, true, MediaScanner.isNoMediaPath(path));
        } catch (RemoteException e) {
            Log.e(TAG, "RemoteException in MediaScanner.scanFile()", e);
            return null;
        } finally {
            releaseResources();
        }
    
private voidsetDefaultRingtoneFileNames()

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