package android.media;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.SQLException;
import android.drm.DrmManagerClient;
import android.graphics.BitmapFactory;
import android.mtp.MtpConstants;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.provider.MediaStore;
import android.provider.MediaStore.Audio;
import android.provider.MediaStore.Audio.Playlists;
import android.provider.MediaStore.Files;
import android.provider.MediaStore.Files.FileColumns;
import android.provider.MediaStore.Images;
import android.provider.MediaStore.Video;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.sax.Element;
import android.sax.ElementListener;
import android.sax.RootElement;
import android.system.ErrnoException;
import android.system.Os;
import android.text.TextUtils;
import android.util.Log;
import android.util.Xml;
import dalvik.system.CloseGuard;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.SimpleDateFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Locale;
import java.util.TimeZone;
import java.util.concurrent.atomic.AtomicBoolean;
public class MediaScanner implements AutoCloseable {
static {
System.loadLibrary("media_jni");
native_init();
}
private final static String TAG = "MediaScanner";
private static final String[] FILES_PRESCAN_PROJECTION = new String[] {
Files.FileColumns._ID,
Files.FileColumns.DATA,
Files.FileColumns.FORMAT,
Files.FileColumns.DATE_MODIFIED,
};
private static final String[] ID_PROJECTION = new String[] {
Files.FileColumns._ID,
};
private static final int FILES_PRESCAN_ID_COLUMN_INDEX = 0;
private static final int FILES_PRESCAN_PATH_COLUMN_INDEX = 1;
private static final int FILES_PRESCAN_FORMAT_COLUMN_INDEX = 2;
private static final int FILES_PRESCAN_DATE_MODIFIED_COLUMN_INDEX = 3;
private static final String[] PLAYLIST_MEMBERS_PROJECTION = new String[] {
Audio.Playlists.Members.PLAYLIST_ID,
};
private static final int ID_PLAYLISTS_COLUMN_INDEX = 0;
private static final int PATH_PLAYLISTS_COLUMN_INDEX = 1;
private static final int DATE_MODIFIED_PLAYLISTS_COLUMN_INDEX = 2;
private static final String RINGTONES_DIR = "/ringtones/";
private static final String NOTIFICATIONS_DIR = "/notifications/";
private static final String ALARMS_DIR = "/alarms/";
private static final String MUSIC_DIR = "/music/";
private static final String PODCAST_DIR = "/podcasts/";
public static final String SCANNED_BUILD_PREFS_NAME = "MediaScanBuild";
public static final String LAST_INTERNAL_SCAN_FINGERPRINT = "lastScanFingerprint";
private static final String SYSTEM_SOUNDS_DIR = "/system/media/audio";
private static final String PRODUCT_SOUNDS_DIR = "/product/media/audio";
private static String sLastInternalScanFingerprint;
private static final String[] ID3_GENRES = {
"Blues",
"Classic Rock",
"Country",
"Dance",
"Disco",
"Funk",
"Grunge",
"Hip-Hop",
"Jazz",
"Metal",
"New Age",
"Oldies",
"Other",
"Pop",
"R&B",
"Rap",
"Reggae",
"Rock",
"Techno",
"Industrial",
"Alternative",
"Ska",
"Death Metal",
"Pranks",
"Soundtrack",
"Euro-Techno",
"Ambient",
"Trip-Hop",
"Vocal",
"Jazz+Funk",
"Fusion",
"Trance",
"Classical",
"Instrumental",
"Acid",
"House",
"Game",
"Sound Clip",
"Gospel",
"Noise",
"AlternRock",
"Bass",
"Soul",
"Punk",
"Space",
"Meditative",
"Instrumental Pop",
"Instrumental Rock",
"Ethnic",
"Gothic",
"Darkwave",
"Techno-Industrial",
"Electronic",
"Pop-Folk",
"Eurodance",
"Dream",
"Southern Rock",
"Comedy",
"Cult",
"Gangsta",
"Top 40",
"Christian Rap",
"Pop/Funk",
"Jungle",
"Native American",
"Cabaret",
"New Wave",
"Psychadelic",
"Rave",
"Showtunes",
"Trailer",
"Lo-Fi",
"Tribal",
"Acid Punk",
"Acid Jazz",
"Polka",
"Retro",
"Musical",
"Rock & Roll",
"Hard Rock",
"Folk",
"Folk-Rock",
"National Folk",
"Swing",
"Fast Fusion",
"Bebob",
"Latin",
"Revival",
"Celtic",
"Bluegrass",
"Avantgarde",
"Gothic Rock",
"Progressive Rock",
"Psychedelic Rock",
"Symphonic Rock",
"Slow Rock",
"Big Band",
"Chorus",
"Easy Listening",
"Acoustic",
"Humour",
"Speech",
"Chanson",
"Opera",
"Chamber Music",
"Sonata",
"Symphony",
"Booty Bass",
"Primus",
"Porn Groove",
"Satire",
"Slow Jam",
"Club",
"Tango",
"Samba",
"Folklore",
"Ballad",
"Power Ballad",
"Rhythmic Soul",
"Freestyle",
"Duet",
"Punk Rock",
"Drum Solo",
"A capella",
"Euro-House",
"Dance Hall",
"Goa",
"Drum & Bass",
"Club-House",
"Hardcore",
"Terror",
"Indie",
"Britpop",
null,
"Polsk Punk",
"Beat",
"Christian Gangsta",
"Heavy Metal",
"Black Metal",
"Crossover",
"Contemporary Christian",
"Christian Rock",
"Merengue",
"Salsa",
"Thrash Metal",
"Anime",
"JPop",
"Synthpop",
};
private long mNativeContext;
private final Context mContext;
private final String mPackageName;
private final String mVolumeName;
private final ContentProviderClient mMediaProvider;
private final Uri mAudioUri;
private final Uri mVideoUri;
private final Uri mImagesUri;
private final Uri mPlaylistsUri;
private final Uri mFilesUri;
private final Uri mFilesUriNoNotify;
private final boolean mProcessPlaylists;
private final boolean mProcessGenres;
private int mMtpObjectHandle;
private final AtomicBoolean mClosed = new AtomicBoolean();
private final CloseGuard mCloseGuard = CloseGuard.get();
private static final boolean ENABLE_BULK_INSERTS = true;
private int mOriginalCount;
private boolean mDefaultRingtoneSet;
private boolean mDefaultNotificationSet;
private boolean mDefaultAlarmSet;
private String mDefaultRingtoneFilename;
private String mDefaultNotificationFilename;
private String mDefaultAlarmAlertFilename;
private static final String DEFAULT_RINGTONE_PROPERTY_PREFIX = "ro.config.";
private final BitmapFactory.Options mBitmapOptions = new BitmapFactory.Options();
private static class FileEntry {
long mRowId;
String mPath;
long mLastModified;
int mFormat;
boolean mLastModifiedChanged;
FileEntry(long rowId, String path, long lastModified, int format) {
mRowId = rowId;
mPath = path;
mLastModified = lastModified;
mFormat = format;
mLastModifiedChanged = false;
}
@Override
public String toString() {
return mPath + " mRowId: " + mRowId;
}
}
private static class PlaylistEntry {
String path;
long bestmatchid;
int bestmatchlevel;
}
private final ArrayList<PlaylistEntry> mPlaylistEntries = new ArrayList<>();
private final ArrayList<FileEntry> mPlayLists = new ArrayList<>();
private MediaInserter mMediaInserter;
private DrmManagerClient mDrmManagerClient = null;
public MediaScanner(Context c, String volumeName) {
native_setup();
mContext = c;
mPackageName = c.getPackageName();
mVolumeName = volumeName;
mBitmapOptions.inSampleSize = 1;
mBitmapOptions.inJustDecodeBounds = true;
setDefaultRingtoneFileNames();
mMediaProvider = mContext.getContentResolver()
.acquireContentProviderClient(MediaStore.AUTHORITY);
if (sLastInternalScanFingerprint == null) {
final SharedPreferences scanSettings =
mContext.getSharedPreferences(SCANNED_BUILD_PREFS_NAME, Context.MODE_PRIVATE);
sLastInternalScanFingerprint =
scanSettings.getString(LAST_INTERNAL_SCAN_FINGERPRINT, new String());
}
mAudioUri = Audio.Media.getContentUri(volumeName);
mVideoUri = Video.Media.getContentUri(volumeName);
mImagesUri = Images.Media.getContentUri(volumeName);
mFilesUri = Files.getContentUri(volumeName);
mFilesUriNoNotify = mFilesUri.buildUpon().appendQueryParameter("nonotify", "1").build();
if (!volumeName.equals("internal")) {
mProcessPlaylists = true;
mProcessGenres = true;
mPlaylistsUri = Playlists.getContentUri(volumeName);
} else {
mProcessPlaylists = false;
mProcessGenres = false;
mPlaylistsUri = null;
}
final Locale locale = mContext.getResources().getConfiguration().locale;
if (locale != null) {
String language = locale.getLanguage();
String country = locale.getCountry();
if (language != null) {
if (country != null) {
setLocale(language + "_" + country);
} else {
setLocale(language);
}
}
}
mCloseGuard.open("close");
}
private void setDefaultRingtoneFileNames() {
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);
}
private final MyMediaScannerClient mClient = new MyMediaScannerClient();
private boolean isDrmEnabled() {
String prop = SystemProperties.get("drm.service.enabled");
return prop != null && prop.equals("true");
}
private class MyMediaScannerClient implements MediaScannerClient {
private final SimpleDateFormat mDateFormatter;
private String mArtist;
private String mAlbumArtist;
private String mAlbum;
private String mTitle;
private String mComposer;
private String mGenre;
private String mMimeType;
private int mFileType;
private int mTrack;
private int mYear;
private int mDuration;
private String mPath;
private long mDate;
private long mLastModified;
private long mFileSize;
private String mWriter;
private int mCompilation;
private boolean mIsDrm;
private boolean mNoMedia;
private boolean mScanSuccess;
private int mWidth;
private int mHeight;
public MyMediaScannerClient() {
mDateFormatter = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
mDateFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
}
public FileEntry beginFile(String path, String mimeType, long lastModified,
long fileSize, boolean isDirectory, boolean noMedia) {
mMimeType = mimeType;
mFileType = 0;
mFileSize = fileSize;
mIsDrm = false;
mScanSuccess = true;
if (!isDirectory) {
if (!noMedia && isNoMediaFile(path)) {
noMedia = true;
}
mNoMedia = noMedia;
if (mimeType != null) {
mFileType = MediaFile.getFileTypeForMimeType(mimeType);
}
if (mFileType == 0) {
MediaFile.MediaFileType mediaFileType = MediaFile.getFileType(path);
if (mediaFileType != null) {
mFileType = mediaFileType.fileType;
if (mMimeType == null) {
mMimeType = mediaFileType.mimeType;
}
}
}
if (isDrmEnabled() && MediaFile.isDrmFileType(mFileType)) {
mFileType = getFileTypeFromDrm(path);
}
}
FileEntry entry = makeEntryFor(path);
long delta = (entry != null) ? (lastModified - entry.mLastModified) : 0;
boolean wasModified = delta > 1 || delta < -1;
if (entry == null || wasModified) {
if (wasModified) {
entry.mLastModified = lastModified;
} else {
entry = new FileEntry(0, path, lastModified,
(isDirectory ? MtpConstants.FORMAT_ASSOCIATION : 0));
}
entry.mLastModifiedChanged = true;
}
if (mProcessPlaylists && MediaFile.isPlayListFileType(mFileType)) {
mPlayLists.add(entry);
return null;
}
mArtist = null;
mAlbumArtist = null;
mAlbum = null;
mTitle = null;
mComposer = null;
mGenre = null;
mTrack = 0;
mYear = 0;
mDuration = 0;
mPath = path;
mDate = 0;
mLastModified = lastModified;
mWriter = null;
mCompilation = 0;
mWidth = 0;
mHeight = 0;
return entry;
}
@Override
public void scanFile(String path, long lastModified, long fileSize,
boolean isDirectory, boolean noMedia) {
doScanFile(path, null, lastModified, fileSize, isDirectory, false, noMedia);
}
public Uri doScanFile(String path, String mimeType, long lastModified,
long fileSize, boolean isDirectory, boolean scanAlways, boolean noMedia) {
Uri result = null;
try {
FileEntry entry = beginFile(path, mimeType, lastModified,
fileSize, isDirectory, noMedia);
if (entry == null) {
return null;
}
if (mMtpObjectHandle != 0) {
entry.mRowId = 0;
}
if (entry.mPath != null) {
if (((!mDefaultNotificationSet &&
doesPathHaveFilename(entry.mPath, mDefaultNotificationFilename))
|| (!mDefaultRingtoneSet &&
doesPathHaveFilename(entry.mPath, mDefaultRingtoneFilename))
|| (!mDefaultAlarmSet &&
doesPathHaveFilename(entry.mPath, mDefaultAlarmAlertFilename)))) {
Log.w(TAG, "forcing rescan of " + entry.mPath +
"since ringtone setting didn't finish");
scanAlways = true;
} else if (isSystemSoundWithMetadata(entry.mPath)
&& !Build.FINGERPRINT.equals(sLastInternalScanFingerprint)) {
Log.i(TAG, "forcing rescan of " + entry.mPath
+ " since build fingerprint changed");
scanAlways = true;
}
}
if (entry != null && (entry.mLastModifiedChanged || scanAlways)) {
if (noMedia) {
result = endFile(entry, false, false, false, false, false);
} else {
boolean isaudio = MediaFile.isAudioFileType(mFileType);
boolean isvideo = MediaFile.isVideoFileType(mFileType);
boolean isimage = MediaFile.isImageFileType(mFileType);
if (isaudio || isvideo || isimage) {
path = Environment.maybeTranslateEmulatedPathToInternal(new File(path))
.getAbsolutePath();
}
if (isaudio || isvideo) {
mScanSuccess = processFile(path, mimeType, this);
}
if (isimage) {
mScanSuccess = processImageFile(path);
}
String lowpath = path.toLowerCase(Locale.ROOT);
boolean ringtones = mScanSuccess && (lowpath.indexOf(RINGTONES_DIR) > 0);
boolean notifications = mScanSuccess &&
(lowpath.indexOf(NOTIFICATIONS_DIR) > 0);
boolean alarms = mScanSuccess && (lowpath.indexOf(ALARMS_DIR) > 0);
boolean podcasts = mScanSuccess && (lowpath.indexOf(PODCAST_DIR) > 0);
boolean music = mScanSuccess && ((lowpath.indexOf(MUSIC_DIR) > 0) ||
(!ringtones && !notifications && !alarms && !podcasts));
result = endFile(entry, ringtones, notifications, alarms, music, podcasts);
}
}
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in MediaScanner.scanFile()", e);
}
return result;
}
private long parseDate(String date) {
try {
return mDateFormatter.parse(date).getTime();
} catch (ParseException e) {
return 0;
}
}
private int parseSubstring(String s, int start, int defaultValue) {
int length = s.length();
if (start == length) return defaultValue;
char ch = s.charAt(start++);
if (ch < '0' || ch > '9') return defaultValue;
int result = ch - '0';
while (start < length) {
ch = s.charAt(start++);
if (ch < '0' || ch > '9') return result;
result = result * 10 + (ch - '0');
}
return result;
}
public void handleStringTag(String name, String value) {
if (name.equalsIgnoreCase("title") || name.startsWith("title;")) {
mTitle = value;
} else if (name.equalsIgnoreCase("artist") || name.startsWith("artist;")) {
mArtist = value.trim();
} else if (name.equalsIgnoreCase("albumartist") || name.startsWith("albumartist;")
|| name.equalsIgnoreCase("band") || name.startsWith("band;")) {
mAlbumArtist = value.trim();
} else if (name.equalsIgnoreCase("album") || name.startsWith("album;")) {
mAlbum = value.trim();
} else if (name.equalsIgnoreCase("composer") || name.startsWith("composer;")) {
mComposer = value.trim();
} else if (mProcessGenres &&
(name.equalsIgnoreCase("genre") || name.startsWith("genre;"))) {
mGenre = getGenreName(value);
} else if (name.equalsIgnoreCase("year") || name.startsWith("year;")) {
mYear = parseSubstring(value, 0, 0);
} else if (name.equalsIgnoreCase("tracknumber") || name.startsWith("tracknumber;")) {
int num = parseSubstring(value, 0, 0);
mTrack = (mTrack / 1000) * 1000 + num;
} else if (name.equalsIgnoreCase("discnumber") ||
name.equals("set") || name.startsWith("set;")) {
int num = parseSubstring(value, 0, 0);
mTrack = (num * 1000) + (mTrack % 1000);
} else if (name.equalsIgnoreCase("duration")) {
mDuration = parseSubstring(value, 0, 0);
} else if (name.equalsIgnoreCase("writer") || name.startsWith("writer;")) {
mWriter = value.trim();
} else if (name.equalsIgnoreCase("compilation")) {
mCompilation = parseSubstring(value, 0, 0);
} else if (name.equalsIgnoreCase("isdrm")) {
mIsDrm = (parseSubstring(value, 0, 0) == 1);
} else if (name.equalsIgnoreCase("date")) {
mDate = parseDate(value);
} else if (name.equalsIgnoreCase("width")) {
mWidth = parseSubstring(value, 0, 0);
} else if (name.equalsIgnoreCase("height")) {
mHeight = parseSubstring(value, 0, 0);
} else {
}
}
private boolean convertGenreCode(String input, String expected) {
String output = getGenreName(input);
if (output.equals(expected)) {
return true;
} else {
Log.d(TAG, "'" + input + "' -> '" + output + "', expected '" + expected + "'");
return false;
}
}
private void testGenreNameConverter() {
convertGenreCode("2", "Country");
convertGenreCode("(2)", "Country");
convertGenreCode("(2", "(2");
convertGenreCode("2 Foo", "Country");
convertGenreCode("(2) Foo", "Country");
convertGenreCode("(2 Foo", "(2 Foo");
convertGenreCode("2Foo", "2Foo");
convertGenreCode("(2)Foo", "Country");
convertGenreCode("200 Foo", "Foo");
convertGenreCode("(200) Foo", "Foo");
convertGenreCode("200Foo", "200Foo");
convertGenreCode("(200)Foo", "Foo");
convertGenreCode("200)Foo", "200)Foo");
convertGenreCode("200) Foo", "200) Foo");
}
public String getGenreName(String genreTagValue) {
if (genreTagValue == null) {
return null;
}
final int length = genreTagValue.length();
if (length > 0) {
boolean parenthesized = false;
StringBuffer number = new StringBuffer();
int i = 0;
for (; i < length; ++i) {
char c = genreTagValue.charAt(i);
if (i == 0 && c == '(') {
parenthesized = true;
} else if (Character.isDigit(c)) {
number.append(c);
} else {
break;
}
}
char charAfterNumber = i < length ? genreTagValue.charAt(i) : ' ';
if ((parenthesized && charAfterNumber == ')')
|| !parenthesized && Character.isWhitespace(charAfterNumber)) {
try {
short genreIndex = Short.parseShort(number.toString());
if (genreIndex >= 0) {
if (genreIndex < ID3_GENRES.length && ID3_GENRES[genreIndex] != null) {
return ID3_GENRES[genreIndex];
} else if (genreIndex == 0xFF) {
return null;
} else if (genreIndex < 0xFF && (i + 1) < length) {
if (parenthesized && charAfterNumber == ')') {
i++;
}
String ret = genreTagValue.substring(i).trim();
if (ret.length() != 0) {
return ret;
}
} else {
return number.toString();
}
}
} catch (NumberFormatException e) {
}
}
}
return genreTagValue;
}
private boolean processImageFile(String path) {
try {
mBitmapOptions.outWidth = 0;
mBitmapOptions.outHeight = 0;
BitmapFactory.decodeFile(path, mBitmapOptions);
mWidth = mBitmapOptions.outWidth;
mHeight = mBitmapOptions.outHeight;
return mWidth > 0 && mHeight > 0;
} catch (Throwable th) {
}
return false;
}
public void setMimeType(String mimeType) {
if ("audio/mp4".equals(mMimeType) &&
mimeType.startsWith("video")) {
return;
}
mMimeType = mimeType;
mFileType = MediaFile.getFileTypeForMimeType(mimeType);
}
private ContentValues toValues() {
ContentValues map = new ContentValues();
map.put(MediaStore.MediaColumns.DATA, mPath);
map.put(MediaStore.MediaColumns.TITLE, mTitle);
map.put(MediaStore.MediaColumns.DATE_MODIFIED, mLastModified);
map.put(MediaStore.MediaColumns.SIZE, mFileSize);
map.put(MediaStore.MediaColumns.MIME_TYPE, mMimeType);
map.put(MediaStore.MediaColumns.IS_DRM, mIsDrm);
String resolution = null;
if (mWidth > 0 && mHeight > 0) {
map.put(MediaStore.MediaColumns.WIDTH, mWidth);
map.put(MediaStore.MediaColumns.HEIGHT, mHeight);
resolution = mWidth + "x" + mHeight;
}
if (!mNoMedia) {
if (MediaFile.isVideoFileType(mFileType)) {
map.put(Video.Media.ARTIST, (mArtist != null && mArtist.length() > 0
? mArtist : MediaStore.UNKNOWN_STRING));
map.put(Video.Media.ALBUM, (mAlbum != null && mAlbum.length() > 0
? mAlbum : MediaStore.UNKNOWN_STRING));
map.put(Video.Media.DURATION, mDuration);
if (resolution != null) {
map.put(Video.Media.RESOLUTION, resolution);
}
if (mDate > 0) {
map.put(Video.Media.DATE_TAKEN, mDate);
}
} else if (MediaFile.isImageFileType(mFileType)) {
} else if (mScanSuccess && MediaFile.isAudioFileType(mFileType)) {
map.put(Audio.Media.ARTIST, (mArtist != null && mArtist.length() > 0) ?
mArtist : MediaStore.UNKNOWN_STRING);
map.put(Audio.Media.ALBUM_ARTIST, (mAlbumArtist != null &&
mAlbumArtist.length() > 0) ? mAlbumArtist : null);
map.put(Audio.Media.ALBUM, (mAlbum != null && mAlbum.length() > 0) ?
mAlbum : MediaStore.UNKNOWN_STRING);
map.put(Audio.Media.COMPOSER, mComposer);
map.put(Audio.Media.GENRE, mGenre);
if (mYear != 0) {
map.put(Audio.Media.YEAR, mYear);
}
map.put(Audio.Media.TRACK, mTrack);
map.put(Audio.Media.DURATION, mDuration);
map.put(Audio.Media.COMPILATION, mCompilation);
}
if (!mScanSuccess) {
map.put(Files.FileColumns.MEDIA_TYPE, 0);
}
}
return map;
}
private Uri endFile(FileEntry entry, boolean ringtones, boolean notifications,
boolean alarms, boolean music, boolean podcasts)
throws RemoteException {
if (mArtist == null || mArtist.length() == 0) {
mArtist = mAlbumArtist;
}
ContentValues values = toValues();
String title = values.getAsString(MediaStore.MediaColumns.TITLE);
if (title == null || TextUtils.isEmpty(title.trim())) {
title = MediaFile.getFileTitle(values.getAsString(MediaStore.MediaColumns.DATA));
values.put(MediaStore.MediaColumns.TITLE, title);
}
String album = values.getAsString(Audio.Media.ALBUM);
if (MediaStore.UNKNOWN_STRING.equals(album)) {
album = values.getAsString(MediaStore.MediaColumns.DATA);
int lastSlash = album.lastIndexOf('/');
if (lastSlash >= 0) {
int previousSlash = 0;
while (true) {
int idx = album.indexOf('/', previousSlash + 1);
if (idx < 0 || idx >= lastSlash) {
break;
}
previousSlash = idx;
}
if (previousSlash != 0) {
album = album.substring(previousSlash + 1, lastSlash);
values.put(Audio.Media.ALBUM, album);
}
}
}
long rowId = entry.mRowId;
if (MediaFile.isAudioFileType(mFileType) && (rowId == 0 || mMtpObjectHandle != 0)) {
values.put(Audio.Media.IS_RINGTONE, ringtones);
values.put(Audio.Media.IS_NOTIFICATION, notifications);
values.put(Audio.Media.IS_ALARM, alarms);
values.put(Audio.Media.IS_MUSIC, music);
values.put(Audio.Media.IS_PODCAST, podcasts);
} else if ((mFileType == MediaFile.FILE_TYPE_JPEG
|| mFileType == MediaFile.FILE_TYPE_HEIF
|| MediaFile.isRawImageFileType(mFileType)) && !mNoMedia) {
ExifInterface exif = null;
try {
exif = new ExifInterface(entry.mPath);
} catch (IOException ex) {
}
if (exif != null) {
float[] latlng = new float[2];
if (exif.getLatLong(latlng)) {
values.put(Images.Media.LATITUDE, latlng[0]);
values.put(Images.Media.LONGITUDE, latlng[1]);
}
long time = exif.getGpsDateTime();
if (time != -1) {
values.put(Images.Media.DATE_TAKEN, time);
} else {
time = exif.getDateTime();
if (time != -1 && Math.abs(mLastModified * 1000 - time) >= 86400000) {
values.put(Images.Media.DATE_TAKEN, time);
}
}
int orientation = exif.getAttributeInt(
ExifInterface.TAG_ORIENTATION, -1);
if (orientation != -1) {
int degree;
switch(orientation) {
case ExifInterface.ORIENTATION_ROTATE_90:
degree = 90;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
degree = 180;
break;
case ExifInterface.ORIENTATION_ROTATE_270:
degree = 270;
break;
default:
degree = 0;
break;
}
values.put(Images.Media.ORIENTATION, degree);
}
}
}
Uri tableUri = mFilesUri;
MediaInserter inserter = mMediaInserter;
if (mScanSuccess && !mNoMedia) {
if (MediaFile.isVideoFileType(mFileType)) {
tableUri = mVideoUri;
} else if (MediaFile.isImageFileType(mFileType)) {
tableUri = mImagesUri;
} else if (MediaFile.isAudioFileType(mFileType)) {
tableUri = mAudioUri;
}
}
Uri result = null;
boolean needToSetSettings = false;
if (notifications && !mDefaultNotificationSet) {
if (TextUtils.isEmpty(mDefaultNotificationFilename) ||
doesPathHaveFilename(entry.mPath, mDefaultNotificationFilename)) {
needToSetSettings = true;
}
} else if (ringtones && !mDefaultRingtoneSet) {
if (TextUtils.isEmpty(mDefaultRingtoneFilename) ||
doesPathHaveFilename(entry.mPath, mDefaultRingtoneFilename)) {
needToSetSettings = true;
}
} else if (alarms && !mDefaultAlarmSet) {
if (TextUtils.isEmpty(mDefaultAlarmAlertFilename) ||
doesPathHaveFilename(entry.mPath, mDefaultAlarmAlertFilename)) {
needToSetSettings = true;
}
}
if (rowId == 0) {
if (mMtpObjectHandle != 0) {
values.put(MediaStore.MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, mMtpObjectHandle);
}
if (tableUri == mFilesUri) {
int format = entry.mFormat;
if (format == 0) {
format = MediaFile.getFormatCode(entry.mPath, mMimeType);
}
values.put(Files.FileColumns.FORMAT, format);
}
if (inserter == null || needToSetSettings) {
if (inserter != null) {
inserter.flushAll();
}
result = mMediaProvider.insert(tableUri, values);
} else if (entry.mFormat == MtpConstants.FORMAT_ASSOCIATION) {
inserter.insertwithPriority(tableUri, values);
} else {
inserter.insert(tableUri, values);
}
if (result != null) {
rowId = ContentUris.parseId(result);
entry.mRowId = rowId;
}
} else {
result = ContentUris.withAppendedId(tableUri, rowId);
values.remove(MediaStore.MediaColumns.DATA);
int mediaType = 0;
if (mScanSuccess && !MediaScanner.isNoMediaPath(entry.mPath)) {
int fileType = MediaFile.getFileTypeForMimeType(mMimeType);
if (MediaFile.isAudioFileType(fileType)) {
mediaType = FileColumns.MEDIA_TYPE_AUDIO;
} else if (MediaFile.isVideoFileType(fileType)) {
mediaType = FileColumns.MEDIA_TYPE_VIDEO;
} else if (MediaFile.isImageFileType(fileType)) {
mediaType = FileColumns.MEDIA_TYPE_IMAGE;
} else if (MediaFile.isPlayListFileType(fileType)) {
mediaType = FileColumns.MEDIA_TYPE_PLAYLIST;
}
values.put(FileColumns.MEDIA_TYPE, mediaType);
}
mMediaProvider.update(result, values, null, null);
}
if(needToSetSettings) {
if (notifications) {
setRingtoneIfNotSet(Settings.System.NOTIFICATION_SOUND, tableUri, rowId);
mDefaultNotificationSet = true;
} else if (ringtones) {
setRingtoneIfNotSet(Settings.System.RINGTONE, tableUri, rowId);
mDefaultRingtoneSet = true;
} else if (alarms) {
setRingtoneIfNotSet(Settings.System.ALARM_ALERT, tableUri, rowId);
mDefaultAlarmSet = true;
}
}
return result;
}
private boolean doesPathHaveFilename(String path, String filename) {
int pathFilenameStart = path.lastIndexOf(File.separatorChar) + 1;
int filenameLength = filename.length();
return path.regionMatches(pathFilenameStart, filename, 0, filenameLength) &&
pathFilenameStart + filenameLength == path.length();
}
private void setRingtoneIfNotSet(String settingName, Uri uri, long rowId) {
if (wasRingtoneAlreadySet(settingName)) {
return;
}
ContentResolver cr = mContext.getContentResolver();
String existingSettingValue = Settings.System.getString(cr, settingName);
if (TextUtils.isEmpty(existingSettingValue)) {
final Uri settingUri = Settings.System.getUriFor(settingName);
final Uri ringtoneUri = ContentUris.withAppendedId(uri, rowId);
RingtoneManager.setActualDefaultRingtoneUri(mContext,
RingtoneManager.getDefaultType(settingUri), ringtoneUri);
}
Settings.System.putInt(cr, settingSetIndicatorName(settingName), 1);
}
private int getFileTypeFromDrm(String path) {
if (!isDrmEnabled()) {
return 0;
}
int resultFileType = 0;
if (mDrmManagerClient == null) {
mDrmManagerClient = new DrmManagerClient(mContext);
}
if (mDrmManagerClient.canHandle(path, null)) {
mIsDrm = true;
String drmMimetype = mDrmManagerClient.getOriginalMimeType(path);
if (drmMimetype != null) {
mMimeType = drmMimetype;
resultFileType = MediaFile.getFileTypeForMimeType(drmMimetype);
}
}
return resultFileType;
}
};
private static boolean isSystemSoundWithMetadata(String path) {
if (path.startsWith(SYSTEM_SOUNDS_DIR + ALARMS_DIR)
|| path.startsWith(SYSTEM_SOUNDS_DIR + RINGTONES_DIR)
|| path.startsWith(SYSTEM_SOUNDS_DIR + NOTIFICATIONS_DIR)
|| path.startsWith(PRODUCT_SOUNDS_DIR + ALARMS_DIR)
|| path.startsWith(PRODUCT_SOUNDS_DIR + RINGTONES_DIR)
|| path.startsWith(PRODUCT_SOUNDS_DIR + NOTIFICATIONS_DIR)) {
return true;
}
return false;
}
private String settingSetIndicatorName(String base) {
return base + "_set";
}
private boolean wasRingtoneAlreadySet(String name) {
ContentResolver cr = mContext.getContentResolver();
String indicatorName = settingSetIndicatorName(name);
try {
return Settings.System.getInt(cr, indicatorName) != 0;
} catch (SettingNotFoundException e) {
return false;
}
}
private void prescan(String filePath, boolean prescanFiles) throws RemoteException {
Cursor c = null;
String where = null;
String[] selectionArgs = null;
mPlayLists.clear();
if (filePath != null) {
where = MediaStore.Files.FileColumns._ID + ">?" +
" AND " + Files.FileColumns.DATA + "=?";
selectionArgs = new String[] { "", filePath };
} else {
where = MediaStore.Files.FileColumns._ID + ">?";
selectionArgs = new String[] { "" };
}
mDefaultRingtoneSet = wasRingtoneAlreadySet(Settings.System.RINGTONE);
mDefaultNotificationSet = wasRingtoneAlreadySet(Settings.System.NOTIFICATION_SOUND);
mDefaultAlarmSet = wasRingtoneAlreadySet(Settings.System.ALARM_ALERT);
Uri.Builder builder = mFilesUri.buildUpon();
builder.appendQueryParameter(MediaStore.PARAM_DELETE_DATA, "false");
MediaBulkDeleter deleter = new MediaBulkDeleter(mMediaProvider, builder.build());
try {
if (prescanFiles) {
long lastId = Long.MIN_VALUE;
Uri limitUri = mFilesUri.buildUpon().appendQueryParameter("limit", "1000").build();
while (true) {
selectionArgs[0] = "" + lastId;
if (c != null) {
c.close();
c = null;
}
c = mMediaProvider.query(limitUri, FILES_PRESCAN_PROJECTION,
where, selectionArgs, MediaStore.Files.FileColumns._ID, null);
if (c == null) {
break;
}
int num = c.getCount();
if (num == 0) {
break;
}
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;
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)) {
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(MediaStore.UNHIDE_CALL, parent, null);
}
}
}
}
}
}
}
}
finally {
if (c != null) {
c.close();
}
deleter.flush();
}
mOriginalCount = 0;
c = mMediaProvider.query(mImagesUri, ID_PROJECTION, null, null, null, null);
if (c != null) {
mOriginalCount = c.getCount();
c.close();
}
}
static class MediaBulkDeleter {
StringBuilder whereClause = new StringBuilder();
ArrayList<String> whereArgs = new ArrayList<String>(100);
final ContentProviderClient mProvider;
final Uri mBaseUri;
public MediaBulkDeleter(ContentProviderClient provider, Uri baseUri) {
mProvider = provider;
mBaseUri = baseUri;
}
public void delete(long id) throws RemoteException {
if (whereClause.length() != 0) {
whereClause.append(",");
}
whereClause.append("?");
whereArgs.add("" + id);
if (whereArgs.size() > 100) {
flush();
}
}
public void flush() throws RemoteException {
int size = whereArgs.size();
if (size > 0) {
String [] foo = new String [size];
foo = whereArgs.toArray(foo);
int numrows = mProvider.delete(mBaseUri,
MediaStore.MediaColumns._ID + " IN (" +
whereClause.toString() + ")", foo);
whereClause.setLength(0);
whereArgs.clear();
}
}
}
private void postscan(final String[] directories) throws RemoteException {
if (mProcessPlaylists) {
processPlayLists();
}
mPlayLists.clear();
}
private void releaseResources() {
if (mDrmManagerClient != null) {
mDrmManagerClient.close();
mDrmManagerClient = null;
}
}
public void scanDirectories(String[] directories) {
try {
long start = System.currentTimeMillis();
prescan(null, true);
long prescan = System.currentTimeMillis();
if (ENABLE_BULK_INSERTS) {
mMediaInserter = new MediaInserter(mMediaProvider, 500);
}
for (int i = 0; i < directories.length; i++) {
processDirectory(directories[i], mClient);
}
if (ENABLE_BULK_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) {
Log.e(TAG, "SQLException in MediaScanner.scan()", e);
} catch (UnsupportedOperationException e) {
Log.e(TAG, "UnsupportedOperationException in MediaScanner.scan()", e);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in MediaScanner.scan()", e);
} finally {
releaseResources();
}
}
public Uri scanSingleFile(String path, String mimeType) {
try {
prescan(path, true);
File file = new File(path);
if (!file.exists() || !file.canRead()) {
return null;
}
long lastModifiedSeconds = file.lastModified() / 1000;
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 static boolean isNoMediaFile(String path) {
File file = new File(path);
if (file.isDirectory()) return false;
int lastSlash = path.lastIndexOf('/');
if (lastSlash >= 0 && lastSlash + 2 < path.length()) {
if (path.regionMatches(lastSlash + 1, "._", 0, 2)) {
return true;
}
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;
}
private static HashMap<String,String> mNoMediaPaths = new HashMap<String,String>();
private static HashMap<String,String> mMediaPaths = new HashMap<String,String>();
public static void clearMediaPathCache(boolean clearMediaPaths, boolean clearNoMediaPaths) {
synchronized (MediaScanner.class) {
if (clearMediaPaths) {
mMediaPaths.clear();
}
if (clearNoMediaPaths) {
mNoMediaPaths.clear();
}
}
}
public static boolean isNoMediaPath(String path) {
if (path == null) {
return false;
}
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)) {
int offset = 1;
while (offset >= 0) {
int slashIndex = path.indexOf('/', offset);
if (slashIndex > offset) {
slashIndex++;
File file = new File(path.substring(0, slashIndex) + ".nomedia");
if (file.exists()) {
mNoMediaPaths.put(parent, "");
return true;
}
}
offset = slashIndex;
}
mMediaPaths.put(parent, "");
}
}
return isNoMediaFile(path);
}
public void scanMtpFile(String path, int objectHandle, int format) {
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)) {
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(Files.getMtpObjectsUri(mVolumeName), values,
"_id=?", whereArgs);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in scanMtpFile", e);
}
return;
}
mMtpObjectHandle = objectHandle;
Cursor fileList = null;
try {
if (MediaFile.isPlayListFileType(fileType)) {
prescan(null, true);
FileEntry entry = makeEntryFor(path);
if (entry != null) {
fileList = mMediaProvider.query(mFilesUri,
FILES_PRESCAN_PROJECTION, null, null, null, null);
processPlayList(entry, fileList);
}
} else {
prescan(path, false);
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();
}
}
FileEntry makeEntryFor(String path) {
String where;
String[] selectionArgs;
Cursor c = null;
try {
where = Files.FileColumns.DATA + "=?";
selectionArgs = new String[] { path };
c = mMediaProvider.query(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 int matchPaths(String path1, 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 boolean matchEntries(long rowId, 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;
}
done = false;
if (data.equalsIgnoreCase(entry.path)) {
entry.bestmatchid = rowId;
entry.bestmatchlevel = Integer.MAX_VALUE;
continue;
}
int matchLength = matchPaths(data, entry.path);
if (matchLength > entry.bestmatchlevel) {
entry.bestmatchid = rowId;
entry.bestmatchlevel = matchLength;
}
}
return done;
}
private void cachePlaylistEntry(String line, String playListDirectory) {
PlaylistEntry entry = new PlaylistEntry();
int entryLength = line.length();
while (entryLength > 0 && Character.isWhitespace(line.charAt(entryLength - 1))) entryLength--;
if (entryLength < 3) return;
if (entryLength < line.length()) line = line.substring(0, entryLength);
char ch1 = line.charAt(0);
boolean fullPath = (ch1 == '/' ||
(Character.isLetter(ch1) && line.charAt(1) == ':' && line.charAt(2) == '\\'));
if (!fullPath)
line = playListDirectory + line;
entry.path = line;
mPlaylistEntries.add(entry);
}
private void processCachedPlaylist(Cursor fileList, ContentValues values, 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(playlistUri, values);
index++;
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in MediaScanner.processCachedPlaylist()", e);
return;
}
}
}
mPlaylistEntries.clear();
}
private void processM3uPlayList(String path, String playListDirectory, Uri uri,
ContentValues values, 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) {
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 void processPlsPlayList(String path, String playListDirectory, Uri uri,
ContentValues values, 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) {
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);
}
}
}
class WplHandler implements ElementListener {
final ContentHandler handler;
String playListDirectory;
public WplHandler(String playListDirectory, Uri uri, Cursor fileList) {
this.playListDirectory = playListDirectory;
RootElement root = new RootElement("smil");
Element body = root.getChild("body");
Element seq = body.getChild("seq");
Element media = seq.getChild("media");
media.setElementListener(this);
this.handler = root.getContentHandler();
}
@Override
public void start(Attributes attributes) {
String path = attributes.getValue("", "src");
if (path != null) {
cachePlaylistEntry(path, playListDirectory);
}
}
@Override
public void end() {
}
ContentHandler getContentHandler() {
return handler;
}
}
private void processWplPlayList(String path, String playListDirectory, Uri uri,
ContentValues values, 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 void processPlayList(FileEntry entry, Cursor fileList) throws RemoteException {
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;
String name = values.getAsString(MediaStore.Audio.Playlists.NAME);
if (name == null) {
name = values.getAsString(MediaStore.MediaColumns.TITLE);
if (name == null) {
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(mPlaylistsUri, values);
rowId = ContentUris.parseId(uri);
membersUri = Uri.withAppendedPath(uri, Playlists.Members.CONTENT_DIRECTORY);
} else {
uri = ContentUris.withAppendedId(mPlaylistsUri, rowId);
mMediaProvider.update(uri, values, null, null);
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, 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 void processPlayLists() throws RemoteException {
Iterator<FileEntry> iterator = mPlayLists.iterator();
Cursor fileList = null;
try {
fileList = mMediaProvider.query(mFilesUri, FILES_PRESCAN_PROJECTION,
"media_type=2", null, null, null);
while (iterator.hasNext()) {
FileEntry entry = iterator.next();
if (entry.mLastModifiedChanged) {
processPlayList(entry, fileList);
}
}
} catch (RemoteException e1) {
} finally {
if (fileList != null) {
fileList.close();
}
}
}
private native void processDirectory(String path, MediaScannerClient client);
private native boolean processFile(String path, String mimeType, MediaScannerClient client);
private native void setLocale(String locale);
public native byte[] extractAlbumArt(FileDescriptor fd);
private static native final void native_init();
private native final void native_setup();
private native final void native_finalize();
@Override
public void close() {
mCloseGuard.close();
if (mClosed.compareAndSet(false, true)) {
mMediaProvider.close();
native_finalize();
}
}
@Override
protected void finalize() throws Throwable {
try {
if (mCloseGuard != null) {
mCloseGuard.warnIfOpen();
}
close();
} finally {
super.finalize();
}
}
}