Merge change 6208 into donut

* changes:
  Move ExifInterface to android.media package so we can reference it from MediaScanner. Also hide public constructor and wrap common use cases as atomic operation to avoid race condition in jhead native codes.
This commit is contained in:
Android (Google) Code Review
2009-07-08 23:04:34 -07:00
2 changed files with 522 additions and 116 deletions

View File

@@ -0,0 +1,398 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.media;
import android.util.Log;
import java.util.HashMap;
import java.util.Map;
/**
* Wrapper for native Exif library
* {@hide}
*/
public class ExifInterface {
private static final String TAG = "ExifInterface";
private String mFilename;
// Constants used for the Orientation Exif tag.
public static final int ORIENTATION_UNDEFINED = 0;
public static final int ORIENTATION_NORMAL = 1;
// Constants used for white balance
public static final int WHITEBALANCE_AUTO = 0;
public static final int WHITEBALANCE_MANUAL = 1;
// left right reversed mirror
public static final int ORIENTATION_FLIP_HORIZONTAL = 2;
public static final int ORIENTATION_ROTATE_180 = 3;
// upside down mirror
public static final int ORIENTATION_FLIP_VERTICAL = 4;
// flipped about top-left <--> bottom-right axis
public static final int ORIENTATION_TRANSPOSE = 5;
// rotate 90 cw to right it
public static final int ORIENTATION_ROTATE_90 = 6;
// flipped about top-right <--> bottom-left axis
public static final int ORIENTATION_TRANSVERSE = 7;
// rotate 270 to right it
public static final int ORIENTATION_ROTATE_270 = 8;
// The Exif tag names
public static final String TAG_ORIENTATION = "Orientation";
public static final String TAG_DATE_TIME_ORIGINAL = "DateTimeOriginal";
public static final String TAG_MAKE = "Make";
public static final String TAG_MODEL = "Model";
public static final String TAG_FLASH = "Flash";
public static final String TAG_IMAGE_WIDTH = "ImageWidth";
public static final String TAG_IMAGE_LENGTH = "ImageLength";
public static final String TAG_GPS_LATITUDE = "GPSLatitude";
public static final String TAG_GPS_LONGITUDE = "GPSLongitude";
public static final String TAG_GPS_LATITUDE_REF = "GPSLatitudeRef";
public static final String TAG_GPS_LONGITUDE_REF = "GPSLongitudeRef";
public static final String TAG_WHITE_BALANCE = "WhiteBalance";
private boolean mSavedAttributes = false;
private boolean mHasThumbnail = false;
private HashMap<String, String> mCachedAttributes = null;
static {
System.loadLibrary("exif");
}
private static ExifInterface sExifObj = null;
/**
* Since the underlying jhead native code is not thread-safe,
* ExifInterface should use singleton interface instead of public
* constructor.
*/
private static synchronized ExifInterface instance() {
if (sExifObj == null) {
sExifObj = new ExifInterface();
}
return sExifObj;
}
/**
* The following 3 static methods are handy routines for atomic operation
* of underlying jhead library. It retrieves EXIF data and then release
* ExifInterface immediately.
*/
public static synchronized HashMap<String, String> loadExifData(String filename) {
ExifInterface exif = instance();
HashMap<String, String> exifData = null;
if (exif != null) {
exif.setFilename(filename);
exifData = exif.getAttributes();
}
return exifData;
}
public static synchronized void saveExifData(String filename, HashMap<String, String> exifData) {
ExifInterface exif = instance();
if (exif != null) {
exif.setFilename(filename);
exif.saveAttributes(exifData);
}
}
public static synchronized byte[] getExifThumbnail(String filename) {
ExifInterface exif = instance();
if (exif != null) {
exif.setFilename(filename);
return exif.getThumbnail();
}
return null;
}
public void setFilename(String filename) {
mFilename = filename;
}
/**
* Given a HashMap of Exif tags and associated values, an Exif section in
* the JPG file is created and loaded with the tag data. saveAttributes()
* is expensive because it involves copying all the JPG data from one file
* to another and deleting the old file and renaming the other. It's best
* to collect all the attributes to write and make a single call rather
* than multiple calls for each attribute. You must call "commitChanges()"
* at some point to commit the changes.
*/
public void saveAttributes(HashMap<String, String> attributes) {
// format of string passed to native C code:
// "attrCnt attr1=valueLen value1attr2=value2Len value2..."
// example:
// "4 attrPtr ImageLength=4 1024Model=6 FooImageWidth=4 1280Make=3 FOO"
StringBuilder sb = new StringBuilder();
int size = attributes.size();
if (attributes.containsKey("hasThumbnail")) {
--size;
}
sb.append(size + " ");
for (Map.Entry<String, String> iter : attributes.entrySet()) {
String key = iter.getKey();
if (key.equals("hasThumbnail")) {
// this is a fake attribute not saved as an exif tag
continue;
}
String val = iter.getValue();
sb.append(key + "=");
sb.append(val.length() + " ");
sb.append(val);
}
String s = sb.toString();
saveAttributesNative(mFilename, s);
commitChangesNative(mFilename);
mSavedAttributes = true;
}
/**
* Returns a HashMap loaded with the Exif attributes of the file. The key
* is the standard tag name and the value is the tag's value: e.g.
* Model -> Nikon. Numeric values are returned as strings.
*/
public HashMap<String, String> getAttributes() {
if (mCachedAttributes != null) {
return mCachedAttributes;
}
// format of string passed from native C code:
// "attrCnt attr1=valueLen value1attr2=value2Len value2..."
// example:
// "4 attrPtr ImageLength=4 1024Model=6 FooImageWidth=4 1280Make=3 FOO"
mCachedAttributes = new HashMap<String, String>();
String attrStr = getAttributesNative(mFilename);
// get count
int ptr = attrStr.indexOf(' ');
int count = Integer.parseInt(attrStr.substring(0, ptr));
// skip past the space between item count and the rest of the attributes
++ptr;
for (int i = 0; i < count; i++) {
// extract the attribute name
int equalPos = attrStr.indexOf('=', ptr);
String attrName = attrStr.substring(ptr, equalPos);
ptr = equalPos + 1; // skip past =
// extract the attribute value length
int lenPos = attrStr.indexOf(' ', ptr);
int attrLen = Integer.parseInt(attrStr.substring(ptr, lenPos));
ptr = lenPos + 1; // skip pas the space
// extract the attribute value
String attrValue = attrStr.substring(ptr, ptr + attrLen);
ptr += attrLen;
if (attrName.equals("hasThumbnail")) {
mHasThumbnail = attrValue.equalsIgnoreCase("true");
} else {
mCachedAttributes.put(attrName, attrValue);
}
}
return mCachedAttributes;
}
/**
* Given a numerical white balance value, return a
* human-readable string describing it.
*/
public static String whiteBalanceToString(int whitebalance) {
switch (whitebalance) {
case WHITEBALANCE_AUTO:
return "Auto";
case WHITEBALANCE_MANUAL:
return "Manual";
default:
return "";
}
}
/**
* Given a numerical orientation, return a human-readable string describing
* the orientation.
*/
public static String orientationToString(int orientation) {
// TODO: this function needs to be localized and use string resource ids
// rather than strings
String orientationString;
switch (orientation) {
case ORIENTATION_NORMAL:
orientationString = "Normal";
break;
case ORIENTATION_FLIP_HORIZONTAL:
orientationString = "Flipped horizontal";
break;
case ORIENTATION_ROTATE_180:
orientationString = "Rotated 180 degrees";
break;
case ORIENTATION_FLIP_VERTICAL:
orientationString = "Upside down mirror";
break;
case ORIENTATION_TRANSPOSE:
orientationString = "Transposed";
break;
case ORIENTATION_ROTATE_90:
orientationString = "Rotated 90 degrees";
break;
case ORIENTATION_TRANSVERSE:
orientationString = "Transversed";
break;
case ORIENTATION_ROTATE_270:
orientationString = "Rotated 270 degrees";
break;
default:
orientationString = "Undefined";
break;
}
return orientationString;
}
/**
* Copies the thumbnail data out of the filename and puts it in the Exif
* data associated with the file used to create this object. You must call
* "commitChanges()" at some point to commit the changes.
*/
public boolean appendThumbnail(String thumbnailFileName) {
if (!mSavedAttributes) {
throw new RuntimeException("Must call saveAttributes "
+ "before calling appendThumbnail");
}
mHasThumbnail = appendThumbnailNative(mFilename, thumbnailFileName);
return mHasThumbnail;
}
public boolean hasThumbnail() {
if (!mSavedAttributes) {
getAttributes();
}
return mHasThumbnail;
}
public byte[] getThumbnail() {
return getThumbnailNative(mFilename);
}
public static float[] getLatLng(HashMap<String, String> exifData) {
if (exifData == null) {
return null;
}
String latValue = exifData.get(ExifInterface.TAG_GPS_LATITUDE);
String latRef = exifData.get(ExifInterface.TAG_GPS_LATITUDE_REF);
String lngValue = exifData.get(ExifInterface.TAG_GPS_LONGITUDE);
String lngRef = exifData.get(ExifInterface.TAG_GPS_LONGITUDE_REF);
float[] latlng = null;
if (latValue != null && latRef != null
&& lngValue != null && lngRef != null) {
latlng = new float[2];
latlng[0] = ExifInterface.convertRationalLatLonToFloat(
latValue, latRef);
latlng[1] = ExifInterface.convertRationalLatLonToFloat(
lngValue, lngRef);
}
return latlng;
}
public static float convertRationalLatLonToFloat(
String rationalString, String ref) {
try {
String [] parts = rationalString.split(",");
String [] pair;
pair = parts[0].split("/");
int degrees = (int) (Float.parseFloat(pair[0].trim())
/ Float.parseFloat(pair[1].trim()));
pair = parts[1].split("/");
int minutes = (int) ((Float.parseFloat(pair[0].trim())
/ Float.parseFloat(pair[1].trim())));
pair = parts[2].split("/");
float seconds = Float.parseFloat(pair[0].trim())
/ Float.parseFloat(pair[1].trim());
float result = degrees + (minutes / 60F) + (seconds / (60F * 60F));
if ((ref.equals("S") || ref.equals("W"))) {
return -result;
}
return result;
} catch (RuntimeException ex) {
// if for whatever reason we can't parse the lat long then return
// null
return 0f;
}
}
public static String convertRationalLatLonToDecimalString(
String rationalString, String ref, boolean usePositiveNegative) {
float result = convertRationalLatLonToFloat(rationalString, ref);
String preliminaryResult = String.valueOf(result);
if (usePositiveNegative) {
String neg = (ref.equals("S") || ref.equals("E")) ? "-" : "";
return neg + preliminaryResult;
} else {
return preliminaryResult + String.valueOf((char) 186) + " "
+ ref;
}
}
public static String makeLatLongString(double d) {
d = Math.abs(d);
int degrees = (int) d;
double remainder = d - degrees;
int minutes = (int) (remainder * 60D);
// really seconds * 1000
int seconds = (int) (((remainder * 60D) - minutes) * 60D * 1000D);
String retVal = degrees + "/1," + minutes + "/1," + seconds + "/1000";
return retVal;
}
public static String makeLatStringRef(double lat) {
return lat >= 0D ? "N" : "S";
}
public static String makeLonStringRef(double lon) {
return lon >= 0D ? "W" : "E";
}
private native boolean appendThumbnailNative(String fileName,
String thumbnailFileName);
private native void saveAttributesNative(String fileName,
String compressedAttributes);
private native String getAttributesNative(String fileName);
private native void commitChangesNative(String fileName);
private native byte[] getThumbnailNative(String fileName);
}

View File

@@ -54,7 +54,7 @@ import java.util.Iterator;
/** /**
* Internal service helper that no-one should use directly. * Internal service helper that no-one should use directly.
* *
* The way the scan currently works is: * The way the scan currently works is:
* - The Java MediaScannerService creates a MediaScanner (this class), and calls * - The Java MediaScannerService creates a MediaScanner (this class), and calls
* MediaScanner.scanDirectories on it. * MediaScanner.scanDirectories on it.
@@ -96,7 +96,7 @@ import java.util.Iterator;
* {@hide} * {@hide}
*/ */
public class MediaScanner public class MediaScanner
{ {
static { static {
System.loadLibrary("media_jni"); System.loadLibrary("media_jni");
} }
@@ -108,17 +108,17 @@ public class MediaScanner
Audio.Media.DATA, // 1 Audio.Media.DATA, // 1
Audio.Media.DATE_MODIFIED, // 2 Audio.Media.DATE_MODIFIED, // 2
}; };
private static final int ID_AUDIO_COLUMN_INDEX = 0; private static final int ID_AUDIO_COLUMN_INDEX = 0;
private static final int PATH_AUDIO_COLUMN_INDEX = 1; private static final int PATH_AUDIO_COLUMN_INDEX = 1;
private static final int DATE_MODIFIED_AUDIO_COLUMN_INDEX = 2; private static final int DATE_MODIFIED_AUDIO_COLUMN_INDEX = 2;
private static final String[] VIDEO_PROJECTION = new String[] { private static final String[] VIDEO_PROJECTION = new String[] {
Video.Media._ID, // 0 Video.Media._ID, // 0
Video.Media.DATA, // 1 Video.Media.DATA, // 1
Video.Media.DATE_MODIFIED, // 2 Video.Media.DATE_MODIFIED, // 2
}; };
private static final int ID_VIDEO_COLUMN_INDEX = 0; private static final int ID_VIDEO_COLUMN_INDEX = 0;
private static final int PATH_VIDEO_COLUMN_INDEX = 1; private static final int PATH_VIDEO_COLUMN_INDEX = 1;
private static final int DATE_MODIFIED_VIDEO_COLUMN_INDEX = 2; private static final int DATE_MODIFIED_VIDEO_COLUMN_INDEX = 2;
@@ -128,11 +128,11 @@ public class MediaScanner
Images.Media.DATA, // 1 Images.Media.DATA, // 1
Images.Media.DATE_MODIFIED, // 2 Images.Media.DATE_MODIFIED, // 2
}; };
private static final int ID_IMAGES_COLUMN_INDEX = 0; private static final int ID_IMAGES_COLUMN_INDEX = 0;
private static final int PATH_IMAGES_COLUMN_INDEX = 1; private static final int PATH_IMAGES_COLUMN_INDEX = 1;
private static final int DATE_MODIFIED_IMAGES_COLUMN_INDEX = 2; private static final int DATE_MODIFIED_IMAGES_COLUMN_INDEX = 2;
private static final String[] PLAYLISTS_PROJECTION = new String[] { private static final String[] PLAYLISTS_PROJECTION = new String[] {
Audio.Playlists._ID, // 0 Audio.Playlists._ID, // 0
Audio.Playlists.DATA, // 1 Audio.Playlists.DATA, // 1
@@ -157,7 +157,7 @@ public class MediaScanner
private static final String ALARMS_DIR = "/alarms/"; private static final String ALARMS_DIR = "/alarms/";
private static final String MUSIC_DIR = "/music/"; private static final String MUSIC_DIR = "/music/";
private static final String PODCAST_DIR = "/podcasts/"; private static final String PODCAST_DIR = "/podcasts/";
private static final String[] ID3_GENRES = { private static final String[] ID3_GENRES = {
// ID3v1 Genres // ID3v1 Genres
"Blues", "Blues",
@@ -317,11 +317,11 @@ public class MediaScanner
* to get the full system property. * to get the full system property.
*/ */
private static final String DEFAULT_RINGTONE_PROPERTY_PREFIX = "ro.config."; private static final String DEFAULT_RINGTONE_PROPERTY_PREFIX = "ro.config.";
// set to true if file path comparisons should be case insensitive. // set to true if file path comparisons should be case insensitive.
// this should be set when scanning files on a case insensitive file system. // this should be set when scanning files on a case insensitive file system.
private boolean mCaseInsensitivePaths; private boolean mCaseInsensitivePaths;
private BitmapFactory.Options mBitmapOptions = new BitmapFactory.Options(); private BitmapFactory.Options mBitmapOptions = new BitmapFactory.Options();
private static class FileCacheEntry { private static class FileCacheEntry {
@@ -331,7 +331,7 @@ public class MediaScanner
long mLastModified; long mLastModified;
boolean mSeenInFileSystem; boolean mSeenInFileSystem;
boolean mLastModifiedChanged; boolean mLastModifiedChanged;
FileCacheEntry(Uri tableUri, long rowId, String path, long lastModified) { FileCacheEntry(Uri tableUri, long rowId, String path, long lastModified) {
mTableUri = tableUri; mTableUri = tableUri;
mRowId = rowId; mRowId = rowId;
@@ -346,10 +346,10 @@ public class MediaScanner
return mPath; return mPath;
} }
} }
// hashes file path to FileCacheEntry. // hashes file path to FileCacheEntry.
// path should be lower case if mCaseInsensitivePaths is true // path should be lower case if mCaseInsensitivePaths is true
private HashMap<String, FileCacheEntry> mFileCache; private HashMap<String, FileCacheEntry> mFileCache;
private ArrayList<FileCacheEntry> mPlayLists; private ArrayList<FileCacheEntry> mPlayLists;
private HashMap<String, Uri> mGenreCache; private HashMap<String, Uri> mGenreCache;
@@ -360,7 +360,7 @@ public class MediaScanner
mContext = c; mContext = c;
mBitmapOptions.inSampleSize = 1; mBitmapOptions.inSampleSize = 1;
mBitmapOptions.inJustDecodeBounds = true; mBitmapOptions.inJustDecodeBounds = true;
setDefaultRingtoneFileNames(); setDefaultRingtoneFileNames();
} }
@@ -370,11 +370,11 @@ public class MediaScanner
mDefaultNotificationFilename = SystemProperties.get(DEFAULT_RINGTONE_PROPERTY_PREFIX mDefaultNotificationFilename = SystemProperties.get(DEFAULT_RINGTONE_PROPERTY_PREFIX
+ Settings.System.NOTIFICATION_SOUND); + Settings.System.NOTIFICATION_SOUND);
} }
private MyMediaScannerClient mClient = new MyMediaScannerClient(); private MyMediaScannerClient mClient = new MyMediaScannerClient();
private class MyMediaScannerClient implements MediaScannerClient { private class MyMediaScannerClient implements MediaScannerClient {
private String mArtist; private String mArtist;
private String mAlbumArtist; // use this if mArtist is missing private String mAlbumArtist; // use this if mArtist is missing
private String mAlbum; private String mAlbum;
@@ -389,11 +389,11 @@ public class MediaScanner
private String mPath; private String mPath;
private long mLastModified; private long mLastModified;
private long mFileSize; private long mFileSize;
public FileCacheEntry beginFile(String path, String mimeType, long lastModified, long fileSize) { public FileCacheEntry beginFile(String path, String mimeType, long lastModified, long fileSize) {
// special case certain file names // special case certain file names
// I use regionMatches() instead of substring() below // I use regionMatches() instead of substring() below
// to avoid memory allocation // to avoid memory allocation
int lastSlash = path.lastIndexOf('/'); int lastSlash = path.lastIndexOf('/');
if (lastSlash >= 0 && lastSlash + 2 < path.length()) { if (lastSlash >= 0 && lastSlash + 2 < path.length()) {
@@ -401,7 +401,7 @@ public class MediaScanner
if (path.regionMatches(lastSlash + 1, "._", 0, 2)) { if (path.regionMatches(lastSlash + 1, "._", 0, 2)) {
return null; return null;
} }
// ignore album art files created by Windows Media Player: // ignore album art files created by Windows Media Player:
// Folder.jpg, AlbumArtSmall.jpg, AlbumArt_{...}_Large.jpg and AlbumArt_{...}_Small.jpg // 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, path.length() - 4, ".jpg", 0, 4)) {
@@ -416,7 +416,7 @@ public class MediaScanner
} }
} }
} }
mMimeType = null; mMimeType = null;
// try mimeType first, if it is specified // try mimeType first, if it is specified
if (mimeType != null) { if (mimeType != null) {
@@ -435,7 +435,7 @@ public class MediaScanner
mMimeType = mediaFileType.mimeType; mMimeType = mediaFileType.mimeType;
} }
} }
String key = path; String key = path;
if (mCaseInsensitivePaths) { if (mCaseInsensitivePaths) {
key = path.toLowerCase(); key = path.toLowerCase();
@@ -446,20 +446,20 @@ public class MediaScanner
mFileCache.put(key, entry); mFileCache.put(key, entry);
} }
entry.mSeenInFileSystem = true; entry.mSeenInFileSystem = true;
// add some slack to avoid a rounding error // add some slack to avoid a rounding error
long delta = lastModified - entry.mLastModified; long delta = lastModified - entry.mLastModified;
if (delta > 1 || delta < -1) { if (delta > 1 || delta < -1) {
entry.mLastModified = lastModified; entry.mLastModified = lastModified;
entry.mLastModifiedChanged = true; entry.mLastModifiedChanged = true;
} }
if (mProcessPlaylists && MediaFile.isPlayListFileType(mFileType)) { if (mProcessPlaylists && MediaFile.isPlayListFileType(mFileType)) {
mPlayLists.add(entry); mPlayLists.add(entry);
// we don't process playlists in the main scan, so return null // we don't process playlists in the main scan, so return null
return null; return null;
} }
// clear all the metadata // clear all the metadata
mArtist = null; mArtist = null;
mAlbumArtist = null; mAlbumArtist = null;
@@ -472,10 +472,10 @@ public class MediaScanner
mDuration = 0; mDuration = 0;
mPath = path; mPath = path;
mLastModified = lastModified; mLastModified = lastModified;
return entry; return entry;
} }
public void scanFile(String path, long lastModified, long fileSize) { public void scanFile(String path, long lastModified, long fileSize) {
doScanFile(path, null, lastModified, fileSize, false); doScanFile(path, null, lastModified, fileSize, false);
} }
@@ -513,7 +513,7 @@ public class MediaScanner
} else if (MediaFile.isImageFileType(mFileType)) { } else if (MediaFile.isImageFileType(mFileType)) {
// we used to compute the width and height but it's not worth it // we used to compute the width and height but it's not worth it
} }
result = endFile(entry, ringtones, notifications, alarms, music, podcasts); result = endFile(entry, ringtones, notifications, alarms, music, podcasts);
} }
} catch (RemoteException e) { } catch (RemoteException e) {
@@ -531,17 +531,17 @@ public class MediaScanner
char ch = s.charAt(start++); char ch = s.charAt(start++);
// return defaultValue if we have no integer at all // return defaultValue if we have no integer at all
if (ch < '0' || ch > '9') return defaultValue; if (ch < '0' || ch > '9') return defaultValue;
int result = ch - '0'; int result = ch - '0';
while (start < length) { while (start < length) {
ch = s.charAt(start++); ch = s.charAt(start++);
if (ch < '0' || ch > '9') return result; if (ch < '0' || ch > '9') return result;
result = result * 10 + (ch - '0'); result = result * 10 + (ch - '0');
} }
return result; return result;
} }
public void handleStringTag(String name, String value) { public void handleStringTag(String name, String value) {
if (name.equalsIgnoreCase("title") || name.startsWith("title;")) { if (name.equalsIgnoreCase("title") || name.startsWith("title;")) {
// Don't trim() here, to preserve the special \001 character // Don't trim() here, to preserve the special \001 character
@@ -577,7 +577,7 @@ public class MediaScanner
// track number might be of the form "2/12" // track number might be of the form "2/12"
// we just read the number before the slash // we just read the number before the slash
int num = parseSubstring(value, 0, 0); int num = parseSubstring(value, 0, 0);
mTrack = (mTrack / 1000) * 1000 + num; mTrack = (mTrack / 1000) * 1000 + num;
} else if (name.equalsIgnoreCase("discnumber") || } else if (name.equalsIgnoreCase("discnumber") ||
name.equals("set") || name.startsWith("set;")) { name.equals("set") || name.startsWith("set;")) {
// set number might be of the form "1/3" // set number might be of the form "1/3"
@@ -588,16 +588,16 @@ public class MediaScanner
mDuration = parseSubstring(value, 0, 0); mDuration = parseSubstring(value, 0, 0);
} }
} }
public void setMimeType(String mimeType) { public void setMimeType(String mimeType) {
mMimeType = mimeType; mMimeType = mimeType;
mFileType = MediaFile.getFileTypeForMimeType(mimeType); mFileType = MediaFile.getFileTypeForMimeType(mimeType);
} }
/** /**
* Formats the data into a values array suitable for use with the Media * Formats the data into a values array suitable for use with the Media
* Content Provider. * Content Provider.
* *
* @return a map of values * @return a map of values
*/ */
private ContentValues toValues() { private ContentValues toValues() {
@@ -608,7 +608,7 @@ public class MediaScanner
map.put(MediaStore.MediaColumns.DATE_MODIFIED, mLastModified); map.put(MediaStore.MediaColumns.DATE_MODIFIED, mLastModified);
map.put(MediaStore.MediaColumns.SIZE, mFileSize); map.put(MediaStore.MediaColumns.SIZE, mFileSize);
map.put(MediaStore.MediaColumns.MIME_TYPE, mMimeType); map.put(MediaStore.MediaColumns.MIME_TYPE, mMimeType);
if (MediaFile.isVideoFileType(mFileType)) { if (MediaFile.isVideoFileType(mFileType)) {
map.put(Video.Media.ARTIST, (mArtist != null && mArtist.length() > 0 ? mArtist : MediaFile.UNKNOWN_STRING)); map.put(Video.Media.ARTIST, (mArtist != null && mArtist.length() > 0 ? mArtist : MediaFile.UNKNOWN_STRING));
map.put(Video.Media.ALBUM, (mAlbum != null && mAlbum.length() > 0 ? mAlbum : MediaFile.UNKNOWN_STRING)); map.put(Video.Media.ALBUM, (mAlbum != null && mAlbum.length() > 0 ? mAlbum : MediaFile.UNKNOWN_STRING));
@@ -629,9 +629,9 @@ public class MediaScanner
} }
return map; return map;
} }
private Uri endFile(FileCacheEntry entry, boolean ringtones, boolean notifications, private Uri endFile(FileCacheEntry entry, boolean ringtones, boolean notifications,
boolean alarms, boolean music, boolean podcasts) boolean alarms, boolean music, boolean podcasts)
throws RemoteException { throws RemoteException {
// update database // update database
Uri tableUri; Uri tableUri;
@@ -649,7 +649,7 @@ public class MediaScanner
return null; return null;
} }
entry.mTableUri = tableUri; entry.mTableUri = tableUri;
// use album artist if artist is missing // use album artist if artist is missing
if (mArtist == null || mArtist.length() == 0) { if (mArtist == null || mArtist.length() == 0) {
mArtist = mAlbumArtist; mArtist = mAlbumArtist;
@@ -680,10 +680,18 @@ public class MediaScanner
values.put(Audio.Media.IS_ALARM, alarms); values.put(Audio.Media.IS_ALARM, alarms);
values.put(Audio.Media.IS_MUSIC, music); values.put(Audio.Media.IS_MUSIC, music);
values.put(Audio.Media.IS_PODCAST, podcasts); values.put(Audio.Media.IS_PODCAST, podcasts);
} else if (isImage) { } else if (mFileType == MediaFile.FILE_TYPE_JPEG) {
// nothing right now HashMap<String, String> exifData =
ExifInterface.loadExifData(entry.mPath);
if (exifData != null) {
float[] latlng = ExifInterface.getLatLng(exifData);
if (latlng != null) {
values.put(Images.Media.LATITUDE, latlng[0]);
values.put(Images.Media.LONGITUDE, latlng[1]);
}
}
} }
Uri result = null; Uri result = null;
long rowId = entry.mRowId; long rowId = entry.mRowId;
if (rowId == 0) { if (rowId == 0) {
@@ -730,15 +738,15 @@ public class MediaScanner
} }
} }
} }
if (uri != null) { if (uri != null) {
// add entry to audio_genre_map // add entry to audio_genre_map
values.clear(); values.clear();
values.put(MediaStore.Audio.Genres.Members.AUDIO_ID, Long.valueOf(rowId)); values.put(MediaStore.Audio.Genres.Members.AUDIO_ID, Long.valueOf(rowId));
mMediaProvider.insert(uri, values); mMediaProvider.insert(uri, values);
} }
} }
if (notifications && !mDefaultNotificationSet) { if (notifications && !mDefaultNotificationSet) {
if (TextUtils.isEmpty(mDefaultNotificationFilename) || if (TextUtils.isEmpty(mDefaultNotificationFilename) ||
doesPathHaveFilename(entry.mPath, mDefaultNotificationFilename)) { doesPathHaveFilename(entry.mPath, mDefaultNotificationFilename)) {
@@ -752,36 +760,36 @@ public class MediaScanner
mDefaultRingtoneSet = true; mDefaultRingtoneSet = true;
} }
} }
return result; return result;
} }
private boolean doesPathHaveFilename(String path, String filename) { private boolean doesPathHaveFilename(String path, String filename) {
int pathFilenameStart = path.lastIndexOf(File.separatorChar) + 1; int pathFilenameStart = path.lastIndexOf(File.separatorChar) + 1;
int filenameLength = filename.length(); int filenameLength = filename.length();
return path.regionMatches(pathFilenameStart, filename, 0, filenameLength) && return path.regionMatches(pathFilenameStart, filename, 0, filenameLength) &&
pathFilenameStart + filenameLength == path.length(); pathFilenameStart + filenameLength == path.length();
} }
private void setSettingIfNotSet(String settingName, Uri uri, long rowId) { private void setSettingIfNotSet(String settingName, Uri uri, long rowId) {
String existingSettingValue = Settings.System.getString(mContext.getContentResolver(), String existingSettingValue = Settings.System.getString(mContext.getContentResolver(),
settingName); settingName);
if (TextUtils.isEmpty(existingSettingValue)) { if (TextUtils.isEmpty(existingSettingValue)) {
// Set the setting to the given URI // Set the setting to the given URI
Settings.System.putString(mContext.getContentResolver(), settingName, Settings.System.putString(mContext.getContentResolver(), settingName,
ContentUris.withAppendedId(uri, rowId).toString()); ContentUris.withAppendedId(uri, rowId).toString());
} }
} }
}; // end of anonymous MediaScannerClient instance }; // end of anonymous MediaScannerClient instance
private void prescan(String filePath) throws RemoteException { private void prescan(String filePath) throws RemoteException {
Cursor c = null; Cursor c = null;
String where = null; String where = null;
String[] selectionArgs = null; String[] selectionArgs = null;
if (mFileCache == null) { if (mFileCache == null) {
mFileCache = new HashMap<String, FileCacheEntry>(); mFileCache = new HashMap<String, FileCacheEntry>();
} else { } else {
@@ -792,7 +800,7 @@ public class MediaScanner
} else { } else {
mPlayLists.clear(); mPlayLists.clear();
} }
// Build the list of files from the content provider // Build the list of files from the content provider
try { try {
// Read existing files from the audio table // Read existing files from the audio table
@@ -801,14 +809,14 @@ public class MediaScanner
selectionArgs = new String[] { filePath }; selectionArgs = new String[] { filePath };
} }
c = mMediaProvider.query(mAudioUri, AUDIO_PROJECTION, where, selectionArgs, null); c = mMediaProvider.query(mAudioUri, AUDIO_PROJECTION, where, selectionArgs, null);
if (c != null) { if (c != null) {
try { try {
while (c.moveToNext()) { while (c.moveToNext()) {
long rowId = c.getLong(ID_AUDIO_COLUMN_INDEX); long rowId = c.getLong(ID_AUDIO_COLUMN_INDEX);
String path = c.getString(PATH_AUDIO_COLUMN_INDEX); String path = c.getString(PATH_AUDIO_COLUMN_INDEX);
long lastModified = c.getLong(DATE_MODIFIED_AUDIO_COLUMN_INDEX); long lastModified = c.getLong(DATE_MODIFIED_AUDIO_COLUMN_INDEX);
String key = path; String key = path;
if (mCaseInsensitivePaths) { if (mCaseInsensitivePaths) {
key = path.toLowerCase(); key = path.toLowerCase();
@@ -829,14 +837,14 @@ public class MediaScanner
where = null; where = null;
} }
c = mMediaProvider.query(mVideoUri, VIDEO_PROJECTION, where, selectionArgs, null); c = mMediaProvider.query(mVideoUri, VIDEO_PROJECTION, where, selectionArgs, null);
if (c != null) { if (c != null) {
try { try {
while (c.moveToNext()) { while (c.moveToNext()) {
long rowId = c.getLong(ID_VIDEO_COLUMN_INDEX); long rowId = c.getLong(ID_VIDEO_COLUMN_INDEX);
String path = c.getString(PATH_VIDEO_COLUMN_INDEX); String path = c.getString(PATH_VIDEO_COLUMN_INDEX);
long lastModified = c.getLong(DATE_MODIFIED_VIDEO_COLUMN_INDEX); long lastModified = c.getLong(DATE_MODIFIED_VIDEO_COLUMN_INDEX);
String key = path; String key = path;
if (mCaseInsensitivePaths) { if (mCaseInsensitivePaths) {
key = path.toLowerCase(); key = path.toLowerCase();
@@ -858,7 +866,7 @@ public class MediaScanner
} }
mOriginalCount = 0; mOriginalCount = 0;
c = mMediaProvider.query(mImagesUri, IMAGES_PROJECTION, where, selectionArgs, null); c = mMediaProvider.query(mImagesUri, IMAGES_PROJECTION, where, selectionArgs, null);
if (c != null) { if (c != null) {
try { try {
mOriginalCount = c.getCount(); mOriginalCount = c.getCount();
@@ -866,7 +874,7 @@ public class MediaScanner
long rowId = c.getLong(ID_IMAGES_COLUMN_INDEX); long rowId = c.getLong(ID_IMAGES_COLUMN_INDEX);
String path = c.getString(PATH_IMAGES_COLUMN_INDEX); String path = c.getString(PATH_IMAGES_COLUMN_INDEX);
long lastModified = c.getLong(DATE_MODIFIED_IMAGES_COLUMN_INDEX); long lastModified = c.getLong(DATE_MODIFIED_IMAGES_COLUMN_INDEX);
String key = path; String key = path;
if (mCaseInsensitivePaths) { if (mCaseInsensitivePaths) {
key = path.toLowerCase(); key = path.toLowerCase();
@@ -879,7 +887,7 @@ public class MediaScanner
c = null; c = null;
} }
} }
if (mProcessPlaylists) { if (mProcessPlaylists) {
// Read existing files from the playlists table // Read existing files from the playlists table
if (filePath != null) { if (filePath != null) {
@@ -888,16 +896,16 @@ public class MediaScanner
where = null; where = null;
} }
c = mMediaProvider.query(mPlaylistsUri, PLAYLISTS_PROJECTION, where, selectionArgs, null); c = mMediaProvider.query(mPlaylistsUri, PLAYLISTS_PROJECTION, where, selectionArgs, null);
if (c != null) { if (c != null) {
try { try {
while (c.moveToNext()) { while (c.moveToNext()) {
String path = c.getString(PATH_IMAGES_COLUMN_INDEX); String path = c.getString(PATH_IMAGES_COLUMN_INDEX);
if (path != null && path.length() > 0) { if (path != null && path.length() > 0) {
long rowId = c.getLong(ID_PLAYLISTS_COLUMN_INDEX); long rowId = c.getLong(ID_PLAYLISTS_COLUMN_INDEX);
long lastModified = c.getLong(DATE_MODIFIED_PLAYLISTS_COLUMN_INDEX); long lastModified = c.getLong(DATE_MODIFIED_PLAYLISTS_COLUMN_INDEX);
String key = path; String key = path;
if (mCaseInsensitivePaths) { if (mCaseInsensitivePaths) {
key = path.toLowerCase(); key = path.toLowerCase();
@@ -919,7 +927,7 @@ public class MediaScanner
} }
} }
} }
private boolean inScanDirectory(String path, String[] directories) { private boolean inScanDirectory(String path, String[] directories) {
for (int i = 0; i < directories.length; i++) { for (int i = 0; i < directories.length; i++) {
if (path.startsWith(directories[i])) { if (path.startsWith(directories[i])) {
@@ -928,25 +936,25 @@ public class MediaScanner
} }
return false; return false;
} }
private void pruneDeadThumbnailFiles() { private void pruneDeadThumbnailFiles() {
HashSet<String> existingFiles = new HashSet<String>(); HashSet<String> existingFiles = new HashSet<String>();
String directory = "/sdcard/DCIM/.thumbnails"; String directory = "/sdcard/DCIM/.thumbnails";
String [] files = (new File(directory)).list(); String [] files = (new File(directory)).list();
if (files == null) if (files == null)
files = new String[0]; files = new String[0];
for (int i = 0; i < files.length; i++) { for (int i = 0; i < files.length; i++) {
String fullPathString = directory + "/" + files[i]; String fullPathString = directory + "/" + files[i];
existingFiles.add(fullPathString); existingFiles.add(fullPathString);
} }
try { try {
Cursor c = mMediaProvider.query( Cursor c = mMediaProvider.query(
mThumbsUri, mThumbsUri,
new String [] { "_data" }, new String [] { "_data" },
null, null,
null, null,
null); null);
Log.v(TAG, "pruneDeadThumbnailFiles... " + c); Log.v(TAG, "pruneDeadThumbnailFiles... " + c);
if (c != null && c.moveToFirst()) { if (c != null && c.moveToFirst()) {
@@ -955,7 +963,7 @@ public class MediaScanner
existingFiles.remove(fullPathString); existingFiles.remove(fullPathString);
} while (c.moveToNext()); } while (c.moveToNext());
} }
for (String fileToDelete : existingFiles) { for (String fileToDelete : existingFiles) {
if (Config.LOGV) if (Config.LOGV)
Log.v(TAG, "fileToDelete is " + fileToDelete); Log.v(TAG, "fileToDelete is " + fileToDelete);
@@ -964,7 +972,7 @@ public class MediaScanner
} catch (SecurityException ex) { } catch (SecurityException ex) {
} }
} }
Log.v(TAG, "/pruneDeadThumbnailFiles... " + c); Log.v(TAG, "/pruneDeadThumbnailFiles... " + c);
if (c != null) { if (c != null) {
c.close(); c.close();
@@ -980,10 +988,10 @@ public class MediaScanner
while (iterator.hasNext()) { while (iterator.hasNext()) {
FileCacheEntry entry = iterator.next(); FileCacheEntry entry = iterator.next();
String path = entry.mPath; String path = entry.mPath;
// remove database entries for files that no longer exist. // remove database entries for files that no longer exist.
boolean fileMissing = false; boolean fileMissing = false;
if (!entry.mSeenInFileSystem) { if (!entry.mSeenInFileSystem) {
if (inScanDirectory(path, directories)) { if (inScanDirectory(path, directories)) {
// we didn't see this file in the scan directory. // we didn't see this file in the scan directory.
@@ -997,7 +1005,7 @@ public class MediaScanner
} }
} }
} }
if (fileMissing) { if (fileMissing) {
// do not delete missing playlists, since they may have been modified by the user. // do not delete missing playlists, since they may have been modified by the user.
// the user can delete them in the media player instead. // the user can delete them in the media player instead.
@@ -1016,25 +1024,25 @@ public class MediaScanner
} }
} }
} }
// handle playlists last, after we know what media files are on the storage. // handle playlists last, after we know what media files are on the storage.
if (mProcessPlaylists) { if (mProcessPlaylists) {
processPlayLists(); processPlayLists();
} }
if (mOriginalCount == 0 && mImagesUri.equals(Images.Media.getContentUri("external"))) if (mOriginalCount == 0 && mImagesUri.equals(Images.Media.getContentUri("external")))
pruneDeadThumbnailFiles(); pruneDeadThumbnailFiles();
// allow GC to clean up // allow GC to clean up
mGenreCache = null; mGenreCache = null;
mPlayLists = null; mPlayLists = null;
mFileCache = null; mFileCache = null;
mMediaProvider = null; mMediaProvider = null;
} }
private void initialize(String volumeName) { private void initialize(String volumeName) {
mMediaProvider = mContext.getContentResolver().acquireProvider("media"); mMediaProvider = mContext.getContentResolver().acquireProvider("media");
mAudioUri = Audio.Media.getContentUri(volumeName); mAudioUri = Audio.Media.getContentUri(volumeName);
mVideoUri = Video.Media.getContentUri(volumeName); mVideoUri = Video.Media.getContentUri(volumeName);
mImagesUri = Images.Media.getContentUri(volumeName); mImagesUri = Images.Media.getContentUri(volumeName);
@@ -1051,23 +1059,23 @@ public class MediaScanner
if ( Process.supportsProcesses()) { if ( Process.supportsProcesses()) {
mCaseInsensitivePaths = true; mCaseInsensitivePaths = true;
} }
} }
} }
public void scanDirectories(String[] directories, String volumeName) { public void scanDirectories(String[] directories, String volumeName) {
try { try {
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
initialize(volumeName); initialize(volumeName);
prescan(null); prescan(null);
long prescan = System.currentTimeMillis(); long prescan = System.currentTimeMillis();
for (int i = 0; i < directories.length; i++) { for (int i = 0; i < directories.length; i++) {
processDirectory(directories[i], MediaFile.sFileExtensions, mClient); processDirectory(directories[i], MediaFile.sFileExtensions, mClient);
} }
long scan = System.currentTimeMillis(); long scan = System.currentTimeMillis();
postscan(directories); postscan(directories);
long end = System.currentTimeMillis(); long end = System.currentTimeMillis();
if (Config.LOGD) { if (Config.LOGD) {
Log.d(TAG, " prescan time: " + (prescan - start) + "ms\n"); Log.d(TAG, " prescan time: " + (prescan - start) + "ms\n");
Log.d(TAG, " scan time: " + (scan - prescan) + "ms\n"); Log.d(TAG, " scan time: " + (scan - prescan) + "ms\n");
@@ -1088,9 +1096,9 @@ public class MediaScanner
// this function is used to scan a single file // this function is used to scan a single file
public Uri scanSingleFile(String path, String volumeName, String mimeType) { public Uri scanSingleFile(String path, String volumeName, String mimeType) {
try { try {
initialize(volumeName); initialize(volumeName);
prescan(path); prescan(path);
File file = new File(path); File file = new File(path);
// always scan the file, so we can return the content://media Uri for existing files // 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); return mClient.doScanFile(path, mimeType, file.lastModified(), file.length(), true);
@@ -1105,7 +1113,7 @@ public class MediaScanner
int result = 0; int result = 0;
int end1 = path1.length(); int end1 = path1.length();
int end2 = path2.length(); int end2 = path2.length();
while (end1 > 0 && end2 > 0) { while (end1 > 0 && end2 > 0) {
int slash1 = path1.lastIndexOf('/', end1 - 1); int slash1 = path1.lastIndexOf('/', end1 - 1);
int slash2 = path2.lastIndexOf('/', end2 - 1); int slash2 = path2.lastIndexOf('/', end2 - 1);
@@ -1123,13 +1131,13 @@ public class MediaScanner
end2 = start2 - 1; end2 = start2 - 1;
} else break; } else break;
} }
return result; return result;
} }
private boolean addPlayListEntry(String entry, String playListDirectory, private boolean addPlayListEntry(String entry, String playListDirectory,
Uri uri, ContentValues values, int index) { Uri uri, ContentValues values, int index) {
// watch for trailing whitespace // watch for trailing whitespace
int entryLength = entry.length(); int entryLength = entry.length();
while (entryLength > 0 && Character.isWhitespace(entry.charAt(entryLength - 1))) entryLength--; while (entryLength > 0 && Character.isWhitespace(entry.charAt(entryLength - 1))) entryLength--;
@@ -1146,36 +1154,36 @@ public class MediaScanner
// if we have a relative path, combine entry with playListDirectory // if we have a relative path, combine entry with playListDirectory
if (!fullPath) if (!fullPath)
entry = playListDirectory + entry; entry = playListDirectory + entry;
//FIXME - should we look for "../" within the path? //FIXME - should we look for "../" within the path?
// best matching MediaFile for the play list entry // best matching MediaFile for the play list entry
FileCacheEntry bestMatch = null; FileCacheEntry bestMatch = null;
// number of rightmost file/directory names for bestMatch // number of rightmost file/directory names for bestMatch
int bestMatchLength = 0; int bestMatchLength = 0;
Iterator<FileCacheEntry> iterator = mFileCache.values().iterator(); Iterator<FileCacheEntry> iterator = mFileCache.values().iterator();
while (iterator.hasNext()) { while (iterator.hasNext()) {
FileCacheEntry cacheEntry = iterator.next(); FileCacheEntry cacheEntry = iterator.next();
String path = cacheEntry.mPath; String path = cacheEntry.mPath;
if (path.equalsIgnoreCase(entry)) { if (path.equalsIgnoreCase(entry)) {
bestMatch = cacheEntry; bestMatch = cacheEntry;
break; // don't bother continuing search break; // don't bother continuing search
} }
int matchLength = matchPaths(path, entry); int matchLength = matchPaths(path, entry);
if (matchLength > bestMatchLength) { if (matchLength > bestMatchLength) {
bestMatch = cacheEntry; bestMatch = cacheEntry;
bestMatchLength = matchLength; bestMatchLength = matchLength;
} }
} }
if (bestMatch == null) { if (bestMatch == null) {
return false; return false;
} }
try { try {
// OK, now we need to add this to the database // OK, now we need to add this to the database
values.clear(); values.clear();
@@ -1189,7 +1197,7 @@ public class MediaScanner
return true; return true;
} }
private void processM3uPlayList(String path, String playListDirectory, Uri uri, ContentValues values) { private void processM3uPlayList(String path, String playListDirectory, Uri uri, ContentValues values) {
BufferedReader reader = null; BufferedReader reader = null;
try { try {
@@ -1266,7 +1274,7 @@ public class MediaScanner
public WplHandler(String playListDirectory, Uri uri) { public WplHandler(String playListDirectory, Uri uri) {
this.playListDirectory = playListDirectory; this.playListDirectory = playListDirectory;
this.uri = uri; this.uri = uri;
RootElement root = new RootElement("smil"); RootElement root = new RootElement("smil");
Element body = root.getChild("body"); Element body = root.getChild("body");
Element seq = body.getChild("seq"); Element seq = body.getChild("seq");
@@ -1316,12 +1324,12 @@ public class MediaScanner
} }
} }
} }
private void processPlayLists() throws RemoteException { private void processPlayLists() throws RemoteException {
Iterator<FileCacheEntry> iterator = mPlayLists.iterator(); Iterator<FileCacheEntry> iterator = mPlayLists.iterator();
while (iterator.hasNext()) { while (iterator.hasNext()) {
FileCacheEntry entry = iterator.next(); FileCacheEntry entry = iterator.next();
String path = entry.mPath; String path = entry.mPath;
// only process playlist files if they are new or have been modified since the last scan // only process playlist files if they are new or have been modified since the last scan
if (entry.mLastModifiedChanged) { if (entry.mLastModifiedChanged) {
@@ -1332,7 +1340,7 @@ public class MediaScanner
long rowId = entry.mRowId; long rowId = entry.mRowId;
if (rowId == 0) { if (rowId == 0) {
// Create a new playlist // Create a new playlist
int lastDot = path.lastIndexOf('.'); int lastDot = path.lastIndexOf('.');
String name = (lastDot < 0 ? path.substring(lastSlash + 1) : path.substring(lastSlash + 1, lastDot)); 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.NAME, name);
@@ -1343,7 +1351,7 @@ public class MediaScanner
membersUri = Uri.withAppendedPath(uri, Playlists.Members.CONTENT_DIRECTORY); membersUri = Uri.withAppendedPath(uri, Playlists.Members.CONTENT_DIRECTORY);
} else { } else {
uri = ContentUris.withAppendedId(mPlaylistsUri, rowId); uri = ContentUris.withAppendedId(mPlaylistsUri, rowId);
// update lastModified value of existing playlist // update lastModified value of existing playlist
values.put(MediaStore.Audio.Playlists.DATE_MODIFIED, entry.mLastModified); values.put(MediaStore.Audio.Playlists.DATE_MODIFIED, entry.mLastModified);
mMediaProvider.update(uri, values, null, null); mMediaProvider.update(uri, values, null, null);
@@ -1352,7 +1360,7 @@ public class MediaScanner
membersUri = Uri.withAppendedPath(uri, Playlists.Members.CONTENT_DIRECTORY); membersUri = Uri.withAppendedPath(uri, Playlists.Members.CONTENT_DIRECTORY);
mMediaProvider.delete(membersUri, null, null); mMediaProvider.delete(membersUri, null, null);
} }
String playListDirectory = path.substring(0, lastSlash + 1); String playListDirectory = path.substring(0, lastSlash + 1);
MediaFile.MediaFileType mediaFileType = MediaFile.getFileType(path); MediaFile.MediaFileType mediaFileType = MediaFile.getFileType(path);
int fileType = (mediaFileType == null ? 0 : mediaFileType.fileType); int fileType = (mediaFileType == null ? 0 : mediaFileType.fileType);
@@ -1363,7 +1371,7 @@ public class MediaScanner
processPlsPlayList(path, playListDirectory, membersUri, values); processPlsPlayList(path, playListDirectory, membersUri, values);
else if (fileType == MediaFile.FILE_TYPE_WPL) else if (fileType == MediaFile.FILE_TYPE_WPL)
processWplPlayList(path, playListDirectory, membersUri); processWplPlayList(path, playListDirectory, membersUri);
Cursor cursor = mMediaProvider.query(membersUri, PLAYLIST_MEMBERS_PROJECTION, null, Cursor cursor = mMediaProvider.query(membersUri, PLAYLIST_MEMBERS_PROJECTION, null,
null, null); null, null);
try { try {
@@ -1377,18 +1385,18 @@ public class MediaScanner
} }
} }
} }
private native void processDirectory(String path, String extensions, MediaScannerClient client); private native void processDirectory(String path, String extensions, MediaScannerClient client);
private native void processFile(String path, String mimeType, MediaScannerClient client); private native void processFile(String path, String mimeType, MediaScannerClient client);
public native void setLocale(String locale); public native void setLocale(String locale);
public native byte[] extractAlbumArt(FileDescriptor fd); public native byte[] extractAlbumArt(FileDescriptor fd);
private native final void native_setup(); private native final void native_setup();
private native final void native_finalize(); private native final void native_finalize();
@Override @Override
protected void finalize() { protected void finalize() {
mContext.getContentResolver().releaseProvider(mMediaProvider); mContext.getContentResolver().releaseProvider(mMediaProvider);
native_finalize(); native_finalize();
} }
} }