MediaScannerpublic 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 | mDefaultRingtoneSetWhether the scanner has set a default sound for the ringer ringtone. | private boolean | mDefaultNotificationSetWhether the scanner has set a default sound for the notification ringtone. | private String | mDefaultRingtoneFilenameThe filename for the default sound for the ringer ringtone. | private String | mDefaultNotificationFilenameThe filename for the default sound for the notification ringtone. | private static final String | DEFAULT_RINGTONE_PROPERTY_PREFIXThe 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 boolean | addPlayListEntry(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 void | finalize()
mContext.getContentResolver().releaseProvider(mMediaProvider);
native_finalize();
| private boolean | inScanDirectory(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 void | initialize(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 int | matchPaths(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 void | native_finalize()
| private final native void | native_setup()
| private void | postscan(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 void | prescan(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 void | processDirectory(java.lang.String path, java.lang.String extensions, MediaScannerClient client)
| private native void | processFile(java.lang.String path, java.lang.String mimeType, MediaScannerClient client)
| private void | processM3uPlayList(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 void | processPlayLists()
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 void | processPlsPlayList(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 void | processWplPlayList(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 void | pruneDeadThumbnailFiles()
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 void | scanDirectories(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.Uri | scanSingleFile(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 void | setDefaultRingtoneFileNames()
mDefaultRingtoneFilename = SystemProperties.get(DEFAULT_RINGTONE_PROPERTY_PREFIX
+ Settings.System.RINGTONE);
mDefaultNotificationFilename = SystemProperties.get(DEFAULT_RINGTONE_PROPERTY_PREFIX
+ Settings.System.NOTIFICATION_SOUND);
| public native void | setLocale(java.lang.String locale)
|
|