diff options
| -rw-r--r-- | media/java/android/media/MediaScanner.java | 1960 | ||||
| -rw-r--r-- | media/java/android/media/RingtoneManager.java | 2 | ||||
| -rw-r--r-- | media/jni/Android.bp | 1 | ||||
| -rw-r--r-- | media/jni/android_media_MediaPlayer.cpp | 6 | ||||
| -rw-r--r-- | media/jni/android_media_MediaScanner.cpp | 468 |
5 files changed, 88 insertions, 2349 deletions
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java index 771628cf7b1e..ca96c9ab5670 100644 --- a/media/java/android/media/MediaScanner.java +++ b/media/java/android/media/MediaScanner.java @@ -17,106 +17,14 @@ package android.media; import android.annotation.UnsupportedAppUsage; -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.ParseException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Locale; -import java.util.TimeZone; -import java.util.concurrent.atomic.AtomicBoolean; /** - * Internal service helper that no-one should use directly. - * - * The way the scan currently works is: - * - The Java MediaScannerService creates a MediaScanner (this class), and calls - * MediaScanner.scanDirectories on it. - * - scanDirectories() calls the native processDirectory() for each of the specified directories. - * - the processDirectory() JNI method wraps the provided mediascanner client in a native - * 'MyMediaScannerClient' class, then calls processDirectory() on the native MediaScanner - * object (which got created when the Java MediaScanner was created). - * - native MediaScanner.processDirectory() calls - * doProcessDirectory(), which recurses over the folder, and calls - * native MyMediaScannerClient.scanFile() for every file whose extension matches. - * - native MyMediaScannerClient.scanFile() calls back on Java MediaScannerClient.scanFile, - * which calls doScanFile, which after some setup calls back down to native code, calling - * MediaScanner.processFile(). - * - MediaScanner.processFile() calls one of several methods, depending on the type of the - * file: parseMP3, parseMP4, parseMidi, parseOgg or parseWMA. - * - each of these methods gets metadata key/value pairs from the file, and repeatedly - * calls native MyMediaScannerClient.handleStringTag, which calls back up to its Java - * counterparts in this file. - * - Java handleStringTag() gathers the key/value pairs that it's interested in. - * - once processFile returns and we're back in Java code in doScanFile(), it calls - * Java MyMediaScannerClient.endFile(), which takes all the data that's been - * gathered and inserts an entry in to the database. - * - * In summary: - * Java MediaScannerService calls - * Java MediaScanner scanDirectories, which calls - * Java MediaScanner processDirectory (native method), which calls - * native MediaScanner processDirectory, which calls - * native MyMediaScannerClient scanFile, which calls - * Java MyMediaScannerClient scanFile, which calls - * Java MediaScannerClient doScanFile, which calls - * Java MediaScanner processFile (native method), which calls - * native MediaScanner processFile, which calls - * native parseMP3, parseMP4, parseMidi, parseOgg or parseWMA, which calls - * native MyMediaScanner handleStringTag, which calls - * Java MyMediaScanner handleStringTag. - * Once MediaScanner processFile returns, an entry is inserted in to the database. - * - * The MediaScanner class is not thread-safe, so it should only be used in a single threaded manner. - * - * {@hide} - * + * @hide * @deprecated this media scanner has served faithfully for many years, but it's * become tedious to test and maintain, mainly due to the way it * weaves obscurely between managed and native code. It's been @@ -125,1876 +33,182 @@ import java.util.concurrent.atomic.AtomicBoolean; */ @Deprecated public class MediaScanner implements AutoCloseable { - static { - System.loadLibrary("media_jni"); - native_init(); - } - - private final static String TAG = "MediaScanner"; - - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}") private static final String[] FILES_PRESCAN_PROJECTION = new String[] { - Files.FileColumns._ID, // 0 - Files.FileColumns.DATA, // 1 - Files.FileColumns.FORMAT, // 2 - Files.FileColumns.DATE_MODIFIED, // 3 - Files.FileColumns.MEDIA_TYPE, // 4 - }; - - 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 int FILES_PRESCAN_MEDIA_TYPE_COLUMN_INDEX = 4; - - private static final String[] PLAYLIST_MEMBERS_PROJECTION = new String[] { - Audio.Playlists.Members.PLAYLIST_ID, // 0 - }; - - 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 PODCASTS_DIR = "/podcasts/"; - private static final String AUDIOBOOKS_DIR = "/audiobooks/"; - - 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 = Environment.getRootDirectory() + "/media/audio"; - private static final String OEM_SOUNDS_DIR = Environment.getOemDirectory() + "/media/audio"; - private static final String PRODUCT_SOUNDS_DIR = Environment.getProductDirectory() + "/media/audio"; - private static String sLastInternalScanFingerprint; - - private static final String[] ID3_GENRES = { - // ID3v1 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", - // The following genres are Winamp extensions - "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", - // The following ones seem to be fairly widely supported as well - "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", - // 148 and up don't seem to have been defined yet. }; - private long mNativeContext; - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}") private final Context mContext; - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}") private final String mPackageName; - private final String mVolumeName; - private final ContentProviderClient mMediaProvider; - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}") private final Uri mAudioUri; - private final Uri mVideoUri; - private final Uri mImagesUri; - private final Uri mPlaylistsUri; - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}") private final Uri mFilesUri; - private final Uri mFilesFullUri; - private final boolean mProcessPlaylists; - private final boolean mProcessGenres; - private int mMtpObjectHandle; - private final AtomicBoolean mClosed = new AtomicBoolean(); - private final CloseGuard mCloseGuard = CloseGuard.get(); - - /** whether to use bulk inserts or individual inserts for each item */ - private static final boolean ENABLE_BULK_INSERTS = true; - - // used when scanning the image database so we know whether we have to prune - // old thumbnail files - private int mOriginalCount; - /** Whether the scanner has set a default sound for the ringer ringtone. */ - private boolean mDefaultRingtoneSet; - /** Whether the scanner has set a default sound for the notification ringtone. */ - private boolean mDefaultNotificationSet; - /** Whether the scanner has set a default sound for the alarm ringtone. */ - private boolean mDefaultAlarmSet; - /** The filename for the default sound for the ringer ringtone. */ - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}") private String mDefaultRingtoneFilename; - /** The filename for the default sound for the notification ringtone. */ - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}") private String mDefaultNotificationFilename; - /** The filename for the default sound for the alarm ringtone. */ - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}") private String mDefaultAlarmAlertFilename; - /** - * The prefix for system properties that define the default sound for - * ringtones. Concatenate the name of the setting from Settings - * to get the full system property. - */ - private static final String DEFAULT_RINGTONE_PROPERTY_PREFIX = "ro.config."; - - private final BitmapFactory.Options mBitmapOptions = new BitmapFactory.Options(); private static class FileEntry { - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}") long mRowId; - String mPath; - long mLastModified; - int mFormat; - int mMediaType; - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}") boolean mLastModifiedChanged; - /** @deprecated kept intact for lame apps using reflection */ @Deprecated - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}") FileEntry(long rowId, String path, long lastModified, int format) { - this(rowId, path, lastModified, format, FileColumns.MEDIA_TYPE_NONE); - } - - FileEntry(long rowId, String path, long lastModified, int format, int mediaType) { - mRowId = rowId; - mPath = path; - mLastModified = lastModified; - mFormat = format; - mMediaType = mediaType; - mLastModifiedChanged = false; - } - - @Override - public String toString() { - return mPath + " mRowId: " + mRowId; + throw new UnsupportedOperationException(); } } - private static class PlaylistEntry { - String path; - long bestmatchid; - int bestmatchlevel; - } - - private final ArrayList<PlaylistEntry> mPlaylistEntries = new ArrayList<>(); - private final ArrayList<FileEntry> mPlayLists = new ArrayList<>(); - - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}") private MediaInserter mMediaInserter; - private DrmManagerClient mDrmManagerClient = null; - - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}") 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); - - Uri filesFullUri = mFilesUri.buildUpon().appendQueryParameter("nonotify", "1").build(); - filesFullUri = MediaStore.setIncludePending(filesFullUri); - filesFullUri = MediaStore.setIncludeTrashed(filesFullUri); - mFilesFullUri = filesFullUri; - - if (!volumeName.equals("internal")) { - // we only support playlists on external media - 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); + throw new UnsupportedOperationException(); } - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}") private final MyMediaScannerClient mClient = new MyMediaScannerClient(); - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}") private boolean isDrmEnabled() { - String prop = SystemProperties.get("drm.service.enabled"); - return prop != null && prop.equals("true"); + throw new UnsupportedOperationException(); } private class MyMediaScannerClient implements MediaScannerClient { - - private final SimpleDateFormat mDateFormatter; - - private String mArtist; - private String mAlbumArtist; // use this if mArtist is missing - private String mAlbum; - private String mTitle; - private String mComposer; - private String mGenre; - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}") private String mMimeType; - /** @deprecated file types no longer exist */ @Deprecated - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}") private int mFileType; - private int mTrack; - private int mYear; - private int mDuration; - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}") private String mPath; - private long mDate; - private long mLastModified; - private long mFileSize; - private String mWriter; - private int mCompilation; - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}") private boolean mIsDrm; - @UnsupportedAppUsage - private boolean mNoMedia; // flag to suppress file from appearing in media tables - private boolean mScanSuccess; - private int mWidth; - private int mHeight; - private int mColorStandard; - private int mColorTransfer; - private int mColorRange; + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}") + private boolean mNoMedia; public MyMediaScannerClient() { - mDateFormatter = new SimpleDateFormat("yyyyMMdd'T'HHmmss"); - mDateFormatter.setTimeZone(TimeZone.getTimeZone("UTC")); + throw new UnsupportedOperationException(); } - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}") public FileEntry beginFile(String path, String mimeType, long lastModified, long fileSize, boolean isDirectory, boolean noMedia) { - mMimeType = mimeType; - mFileSize = fileSize; - mIsDrm = false; - mScanSuccess = true; - - if (!isDirectory) { - if (!noMedia && isNoMediaFile(path)) { - noMedia = true; - } - mNoMedia = noMedia; - - // if mimeType was not specified, compute file type based on file extension. - if (mMimeType == null) { - mMimeType = MediaFile.getMimeTypeForFile(path); - } - - if (isDrmEnabled() && MediaFile.isDrmMimeType(mMimeType)) { - getMimeTypeFromDrm(path); - } - } - - FileEntry entry = makeEntryFor(path); - // add some slack to avoid a rounding error - 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), - FileColumns.MEDIA_TYPE_NONE); - } - entry.mLastModifiedChanged = true; - } - - if (mProcessPlaylists && MediaFile.isPlayListMimeType(mMimeType)) { - mPlayLists.add(entry); - // we don't process playlists in the main scan, so return null - return null; - } - - // clear all the metadata - 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; - mColorStandard = -1; - mColorTransfer = -1; - mColorRange = -1; - - return entry; + throw new UnsupportedOperationException(); } - @Override - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}") public void scanFile(String path, long lastModified, long fileSize, boolean isDirectory, boolean noMedia) { - // This is the callback funtion from native codes. - // Log.v(TAG, "scanFile: "+path); - doScanFile(path, null, lastModified, fileSize, isDirectory, false, noMedia); + throw new UnsupportedOperationException(); } - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}") public Uri doScanFile(String path, String mimeType, long lastModified, long fileSize, boolean isDirectory, boolean scanAlways, boolean noMedia) { - Uri result = null; -// long t1 = System.currentTimeMillis(); - try { - FileEntry entry = beginFile(path, mimeType, lastModified, - fileSize, isDirectory, noMedia); - - if (entry == null) { - return null; - } - - // if this file was just inserted via mtp, set the rowid to zero - // (even though it already exists in the database), to trigger - // the correct code path for updating its entry - 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)) { - // file is located on the system partition where the date cannot be trusted: - // rescan if the build fingerprint has changed since the last scan. - Log.i(TAG, "forcing rescan of " + entry.mPath - + " since build fingerprint changed"); - scanAlways = true; - } - } - - // rescan for metadata if file was modified since last scan - if (entry != null && (entry.mLastModifiedChanged || scanAlways)) { - if (noMedia) { - result = endFile(entry, false, false, false, false, false, false); - } else { - boolean isaudio = MediaFile.isAudioMimeType(mMimeType); - boolean isvideo = MediaFile.isVideoMimeType(mMimeType); - boolean isimage = MediaFile.isImageMimeType(mMimeType); - - if (isaudio || isvideo || isimage) { - path = Environment.maybeTranslateEmulatedPathToInternal(new File(path)) - .getAbsolutePath(); - } - - // we only extract metadata for audio and video files - 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(PODCASTS_DIR) > 0); - boolean audiobooks = mScanSuccess && (lowpath.indexOf(AUDIOBOOKS_DIR) > 0); - boolean music = mScanSuccess && ((lowpath.indexOf(MUSIC_DIR) > 0) || - (!ringtones && !notifications && !alarms && !podcasts && !audiobooks)); - - result = endFile(entry, ringtones, notifications, alarms, podcasts, - audiobooks, music); - } - } - } catch (RemoteException e) { - Log.e(TAG, "RemoteException in MediaScanner.scanFile()", e); - } -// long t2 = System.currentTimeMillis(); -// Log.v(TAG, "scanFile: " + path + " took " + (t2-t1)); - return result; + throw new UnsupportedOperationException(); } - 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++); - // return defaultValue if we have no integer at all - 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; - } - - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}") public void handleStringTag(String name, String value) { - if (name.equalsIgnoreCase("title") || name.startsWith("title;")) { - // Don't trim() here, to preserve the special \001 character - // used to force sorting. The media provider will trim() before - // inserting the title in to the database. - 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;")) { - // track number might be of the form "2/12" - // we just read the number before the slash - int num = parseSubstring(value, 0, 0); - mTrack = (mTrack / 1000) * 1000 + num; - } else if (name.equalsIgnoreCase("discnumber") || - name.equals("set") || name.startsWith("set;")) { - // set number might be of the form "1/3" - // we just read the number before the slash - 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 if (name.equalsIgnoreCase("colorstandard")) { - mColorStandard = parseSubstring(value, 0, -1); - } else if (name.equalsIgnoreCase("colortransfer")) { - mColorTransfer = parseSubstring(value, 0, -1); - } else if (name.equalsIgnoreCase("colorrange")) { - mColorRange = parseSubstring(value, 0, -1); - } else { - //Log.v(TAG, "unknown tag: " + name + " (" + mProcessGenres + ")"); - } + throw new UnsupportedOperationException(); } - 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) { - // genre is valid but unknown, - // if there is a string after the value we take it - if (parenthesized && charAfterNumber == ')') { - i++; - } - String ret = genreTagValue.substring(i).trim(); - if (ret.length() != 0) { - return ret; - } - } else { - // else return the number, without parentheses - 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) { - // ignore; - } - return false; - } - - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}") public void setMimeType(String mimeType) { - if ("audio/mp4".equals(mMimeType) && - mimeType.startsWith("video")) { - // for feature parity with Donut, we force m4a files to keep the - // audio/mp4 mimetype, even if they are really "enhanced podcasts" - // with a video track - return; - } - mMimeType = mimeType; + throw new UnsupportedOperationException(); } - /** - * Formats the data into a values array suitable for use with the Media - * Content Provider. - * - * @return a map of values - */ - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}") 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); - map.putNull(MediaStore.MediaColumns.HASH); - - 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.isVideoMimeType(mMimeType)) { - 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 (mColorStandard >= 0) { - map.put(Video.Media.COLOR_STANDARD, mColorStandard); - } - if (mColorTransfer >= 0) { - map.put(Video.Media.COLOR_TRANSFER, mColorTransfer); - } - if (mColorRange >= 0) { - map.put(Video.Media.COLOR_RANGE, mColorRange); - } - if (mDate > 0) { - map.put(Video.Media.DATE_TAKEN, mDate); - } - } else if (MediaFile.isImageMimeType(mMimeType)) { - // FIXME - add DESCRIPTION - } else if (MediaFile.isAudioMimeType(mMimeType)) { - 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); - } - } - return map; + throw new UnsupportedOperationException(); } - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}") private Uri endFile(FileEntry entry, boolean ringtones, boolean notifications, boolean alarms, boolean podcasts, boolean audiobooks, boolean music) throws RemoteException { - // update database - - // use album artist if artist is missing - 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); - // extract last path segment before file name - 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.isAudioMimeType(mMimeType) && (rowId == 0 || mMtpObjectHandle != 0)) { - // Only set these for new entries. For existing entries, they - // may have been modified later, and we want to keep the current - // values so that custom ringtones still show up in the ringtone - // picker. - 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); - values.put(Audio.Media.IS_AUDIOBOOK, audiobooks); - } else if (MediaFile.isExifMimeType(mMimeType) && !mNoMedia) { - ExifInterface exif = null; - try { - exif = new ExifInterface(entry.mPath); - } catch (Exception ex) { - // exif is null - } - if (exif != null) { - long time = exif.getGpsDateTime(); - if (time != -1) { - values.put(Images.Media.DATE_TAKEN, time); - } else { - // If no time zone information is available, we should consider using - // EXIF local time as taken time if the difference between file time - // and EXIF local time is not less than 1 Day, otherwise MediaProvider - // will use file time as taken time. - 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) { - // We only recognize a subset of orientation tag values. - 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; - int mediaType = FileColumns.MEDIA_TYPE_NONE; - MediaInserter inserter = mMediaInserter; - if (!mNoMedia) { - if (MediaFile.isVideoMimeType(mMimeType)) { - tableUri = mVideoUri; - mediaType = FileColumns.MEDIA_TYPE_VIDEO; - } else if (MediaFile.isImageMimeType(mMimeType)) { - tableUri = mImagesUri; - mediaType = FileColumns.MEDIA_TYPE_IMAGE; - } else if (MediaFile.isAudioMimeType(mMimeType)) { - tableUri = mAudioUri; - mediaType = FileColumns.MEDIA_TYPE_AUDIO; - } else if (MediaFile.isPlayListMimeType(mMimeType)) { - tableUri = mPlaylistsUri; - mediaType = FileColumns.MEDIA_TYPE_PLAYLIST; - } - } - Uri result = null; - boolean needToSetSettings = false; - // Setting a flag in order not to use bulk insert for the file related with - // notifications, ringtones, and alarms, because the rowId of the inserted file is - // needed. - 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); - } - // New file, insert it. - // Directories need to be inserted before the files they contain, so they - // get priority when bulk inserting. - // If the rowId of the inserted file is needed, it gets inserted immediately, - // bypassing the bulk inserter. - 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 { - // updated file - result = ContentUris.withAppendedId(tableUri, rowId); - // path should never change, and we want to avoid replacing mixed cased paths - // with squashed lower case paths - values.remove(MediaStore.MediaColumns.DATA); - - if (!mNoMedia) { - // Changing media type must be done as separate update - if (mediaType != entry.mMediaType) { - final ContentValues mediaTypeValues = new ContentValues(); - mediaTypeValues.put(FileColumns.MEDIA_TYPE, mediaType); - mMediaProvider.update(ContentUris.withAppendedId(mFilesUri, rowId), - mediaTypeValues, null, null); - } - } - - 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(); + throw new UnsupportedOperationException(); } - 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); - } - - /** @deprecated file types no longer exist */ @Deprecated - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}") private int getFileTypeFromDrm(String path) { - return 0; + throw new UnsupportedOperationException(); } - - private void getMimeTypeFromDrm(String path) { - mMimeType = null; - - if (mDrmManagerClient == null) { - mDrmManagerClient = new DrmManagerClient(mContext); - } - - if (mDrmManagerClient.canHandle(path, null)) { - mIsDrm = true; - mMimeType = mDrmManagerClient.getOriginalMimeType(path); - } - - if (mMimeType == null) { - mMimeType = ContentResolver.MIME_TYPE_DEFAULT; - } - } - - }; // end of anonymous MediaScannerClient instance - - 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(OEM_SOUNDS_DIR + ALARMS_DIR) - || path.startsWith(OEM_SOUNDS_DIR + RINGTONES_DIR) - || path.startsWith(OEM_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; - } - } - - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}") private void prescan(String filePath, boolean prescanFiles) throws RemoteException { - Cursor c = null; - String where = null; - String[] selectionArgs = null; - - mPlayLists.clear(); - - if (filePath != null) { - // query for only one file - where = MediaStore.Files.FileColumns._ID + ">?" + - " AND " + Files.FileColumns.DATA + "=?"; - selectionArgs = new String[] { "", filePath }; - } else { - where = MediaStore.Files.FileColumns._ID + ">?"; - selectionArgs = new String[] { "" }; - } - - mDefaultRingtoneSet = wasRingtoneAlreadySet(Settings.System.RINGTONE); - mDefaultNotificationSet = wasRingtoneAlreadySet(Settings.System.NOTIFICATION_SOUND); - mDefaultAlarmSet = wasRingtoneAlreadySet(Settings.System.ALARM_ALERT); - - // Tell the provider to not delete the file. - // If the file is truly gone the delete is unnecessary, and we want to avoid - // accidentally deleting files that are really there (this may happen if the - // filesystem is mounted and unmounted while the scanner is running). - Uri.Builder builder = mFilesUri.buildUpon(); - builder.appendQueryParameter(MediaStore.PARAM_DELETE_DATA, "false"); - MediaBulkDeleter deleter = new MediaBulkDeleter(mMediaProvider, builder.build()); - - // Build the list of files from the content provider - try { - if (prescanFiles) { - // First read existing files from the files table. - // Because we'll be deleting entries for missing files as we go, - // we need to query the database in small batches, to avoid problems - // with CursorWindow positioning. - long lastId = Long.MIN_VALUE; - Uri limitUri = mFilesUri.buildUpon() - .appendQueryParameter(MediaStore.PARAM_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; - - // Only consider entries with absolute path names. - // This allows storing URIs in the database without the - // media scanner removing them. - if (path != null && path.startsWith("/")) { - boolean exists = false; - try { - exists = Os.access(path, android.system.OsConstants.F_OK); - } catch (ErrnoException e1) { - } - if (!exists && !MtpConstants.isAbstractObject(format)) { - // do not delete missing playlists, since they may have been - // modified by the user. - // The user can delete them in the media player instead. - // instead, clear the path and lastModified fields in the row - String mimeType = MediaFile.getMimeTypeForFile(path); - if (!MediaFile.isPlayListMimeType(mimeType)) { - 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(); - } - - // compute original size of images - 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); - //Log.i("@@@@@@@@@", "rows deleted: " + numrows); - whereClause.setLength(0); - whereArgs.clear(); - } - } + throw new UnsupportedOperationException(); } - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}") private void postscan(final String[] directories) throws RemoteException { - - // handle playlists last, after we know what media files are on the storage. - if (mProcessPlaylists) { - processPlayLists(); - } - - // allow GC to clean up - mPlayLists.clear(); - } - - private void releaseResources() { - // release the DrmManagerClient resources - 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) { - // create MediaInserter for bulk inserts - mMediaInserter = new MediaInserter(mMediaProvider, 500); - } - - for (int i = 0; i < directories.length; i++) { - processDirectory(directories[i], mClient); - } - - if (ENABLE_BULK_INSERTS) { - // flush remaining inserts - mMediaInserter.flushAll(); - mMediaInserter = null; - } - - long scan = System.currentTimeMillis(); - postscan(directories); - long end = System.currentTimeMillis(); - - if (false) { - Log.d(TAG, " prescan time: " + (prescan - start) + "ms\n"); - Log.d(TAG, " scan time: " + (scan - prescan) + "ms\n"); - Log.d(TAG, "postscan time: " + (end - scan) + "ms\n"); - Log.d(TAG, " total time: " + (end - start) + "ms\n"); - } - } catch (SQLException e) { - // this might happen if the SD card is removed while the media scanner is running - Log.e(TAG, "SQLException in MediaScanner.scan()", e); - } catch (UnsupportedOperationException e) { - // this might happen if the SD card is removed while the media scanner is running - Log.e(TAG, "UnsupportedOperationException in MediaScanner.scan()", e); - } catch (RemoteException e) { - Log.e(TAG, "RemoteException in MediaScanner.scan()", e); - } finally { - releaseResources(); - } + throw new UnsupportedOperationException(); } - // this function is used to scan a single file - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}") public Uri scanSingleFile(String path, String mimeType) { - try { - prescan(path, true); - - File file = new File(path); - if (!file.exists() || !file.canRead()) { - return null; - } - - // lastModified is in milliseconds on Files. - long lastModifiedSeconds = file.lastModified() / 1000; - - // always scan the file, so we can return the content://media Uri for existing files - return mClient.doScanFile(path, mimeType, lastModifiedSeconds, file.length(), - false, true, MediaScanner.isNoMediaPath(path)); - } catch (RemoteException e) { - Log.e(TAG, "RemoteException in MediaScanner.scanFile()", e); - return null; - } finally { - releaseResources(); - } - } - - private static boolean isNoMediaFile(String path) { - File file = new File(path); - if (file.isDirectory()) return false; - - // special case certain file names - // I use regionMatches() instead of substring() below - // to avoid memory allocation - int lastSlash = path.lastIndexOf('/'); - if (lastSlash >= 0 && lastSlash + 2 < path.length()) { - // ignore those ._* files created by MacOS - if (path.regionMatches(lastSlash + 1, "._", 0, 2)) { - return true; - } - - // ignore album art files created by Windows Media Player: - // Folder.jpg, AlbumArtSmall.jpg, AlbumArt_{...}_Large.jpg - // and AlbumArt_{...}_Small.jpg - if (path.regionMatches(true, path.length() - 4, ".jpg", 0, 4)) { - if (path.regionMatches(true, lastSlash + 1, "AlbumArt_{", 0, 10) || - path.regionMatches(true, lastSlash + 1, "AlbumArt.", 0, 9)) { - return true; - } - int length = path.length() - lastSlash - 1; - if ((length == 17 && path.regionMatches( - true, lastSlash + 1, "AlbumArtSmall", 0, 13)) || - (length == 10 - && path.regionMatches(true, lastSlash + 1, "Folder", 0, 6))) { - return true; - } - } - } - return false; - } - - private static HashMap<String,String> mNoMediaPaths = new HashMap<String,String>(); - private static HashMap<String,String> mMediaPaths = new HashMap<String,String>(); - - /* MediaProvider calls this when a .nomedia file is added or removed */ - public static void clearMediaPathCache(boolean clearMediaPaths, boolean clearNoMediaPaths) { - synchronized (MediaScanner.class) { - if (clearMediaPaths) { - mMediaPaths.clear(); - } - if (clearNoMediaPaths) { - mNoMediaPaths.clear(); - } - } + throw new UnsupportedOperationException(); } - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}") public static boolean isNoMediaPath(String path) { - if (path == null) { - return false; - } - // return true if file or any parent directory has name starting with a dot - if (path.indexOf("/.") >= 0) { - return true; - } - - int firstSlash = path.lastIndexOf('/'); - if (firstSlash <= 0) { - return false; - } - String parent = path.substring(0, firstSlash); - - synchronized (MediaScanner.class) { - if (mNoMediaPaths.containsKey(parent)) { - return true; - } else if (!mMediaPaths.containsKey(parent)) { - // check to see if any parent directories have a ".nomedia" file - // start from 1 so we don't bother checking in the root directory - int offset = 1; - while (offset >= 0) { - int slashIndex = path.indexOf('/', offset); - if (slashIndex > offset) { - slashIndex++; // move past slash - File file = new File(path.substring(0, slashIndex) + ".nomedia"); - if (file.exists()) { - // we have a .nomedia in one of the parent directories - mNoMediaPaths.put(parent, ""); - return true; - } - } - offset = slashIndex; - } - mMediaPaths.put(parent, ""); - } - } - - return isNoMediaFile(path); - } - - public void scanMtpFile(String path, int objectHandle, int format) { - String mimeType = MediaFile.getMimeType(path, format); - File file = new File(path); - long lastModifiedSeconds = file.lastModified() / 1000; - - if (!MediaFile.isAudioMimeType(mimeType) && !MediaFile.isVideoMimeType(mimeType) && - !MediaFile.isImageMimeType(mimeType) && !MediaFile.isPlayListMimeType(mimeType) && - !MediaFile.isDrmMimeType(mimeType)) { - - // no need to use the media scanner, but we need to update last modified and file size - ContentValues values = new ContentValues(); - values.put(Files.FileColumns.SIZE, file.length()); - values.put(Files.FileColumns.DATE_MODIFIED, lastModifiedSeconds); - try { - String[] whereArgs = new String[] { Integer.toString(objectHandle) }; - mMediaProvider.update(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.isPlayListMimeType(mimeType)) { - // build file cache so we can look up tracks in the playlist - prescan(null, true); - - FileEntry entry = makeEntryFor(path); - if (entry != null) { - fileList = mMediaProvider.query(mFilesUri, - FILES_PRESCAN_PROJECTION, null, null, null, null); - processPlayList(entry, fileList); - } - } else { - // MTP will create a file entry for us so we don't want to do it in prescan - prescan(path, false); - - // always scan the file, so we can return the content://media Uri for existing files - mClient.doScanFile(path, 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(); - } + throw new UnsupportedOperationException(); } - @UnsupportedAppUsage + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}") FileEntry makeEntryFor(String path) { - String where; - String[] selectionArgs; - - Cursor c = null; - try { - where = Files.FileColumns.DATA + "=?"; - selectionArgs = new String[] { path }; - c = mMediaProvider.query(mFilesFullUri, FILES_PRESCAN_PROJECTION, - where, selectionArgs, null, null); - if (c != null && c.moveToFirst()) { - long rowId = c.getLong(FILES_PRESCAN_ID_COLUMN_INDEX); - long lastModified = c.getLong(FILES_PRESCAN_DATE_MODIFIED_COLUMN_INDEX); - int format = c.getInt(FILES_PRESCAN_FORMAT_COLUMN_INDEX); - int mediaType = c.getInt(FILES_PRESCAN_MEDIA_TYPE_COLUMN_INDEX); - return new FileEntry(rowId, path, lastModified, format, mediaType); - } - } catch (RemoteException e) { - } finally { - if (c != null) { - c.close(); - } - } - return null; + throw new UnsupportedOperationException(); } - // returns the number of matching file/directory names, starting from the right - 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; // this entry has been matched already - } - done = false; - if (data.equalsIgnoreCase(entry.path)) { - entry.bestmatchid = rowId; - entry.bestmatchlevel = Integer.MAX_VALUE; - continue; // no need for path matching - } - - int matchLength = matchPaths(data, entry.path); - if (matchLength > entry.bestmatchlevel) { - entry.bestmatchid = rowId; - entry.bestmatchlevel = matchLength; - } - } - return done; - } - - private void cachePlaylistEntry(String line, String playListDirectory) { - PlaylistEntry entry = new PlaylistEntry(); - // watch for trailing whitespace - int entryLength = line.length(); - while (entryLength > 0 && Character.isWhitespace(line.charAt(entryLength - 1))) entryLength--; - // path should be longer than 3 characters. - // avoid index out of bounds errors below by returning here. - if (entryLength < 3) return; - if (entryLength < line.length()) line = line.substring(0, entryLength); - - // does entry appear to be an absolute path? - // look for Unix or DOS absolute paths - char ch1 = line.charAt(0); - boolean fullPath = (ch1 == '/' || - (Character.isLetter(ch1) && line.charAt(1) == ':' && line.charAt(2) == '\\')); - // if we have a relative path, combine entry with playListDirectory - if (!fullPath) - line = playListDirectory + line; - entry.path = line; - //FIXME - should we look for "../" within the path? - - mPlaylistEntries.add(entry); - } - - 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(); + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}") + private void setLocale(String locale) { + throw new UnsupportedOperationException(); } - 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) { - // ignore comment lines, which begin with '#' - if (line.length() > 0 && line.charAt(0) != '#') { - cachePlaylistEntry(line, playListDirectory); - } - line = reader.readLine(); - } - - processCachedPlaylist(fileList, values, uri); - } - } catch (IOException e) { - Log.e(TAG, "IOException in MediaScanner.processM3uPlayList()", e); - } finally { - try { - if (reader != null) - reader.close(); - } catch (IOException e) { - Log.e(TAG, "IOException in MediaScanner.processM3uPlayList()", e); - } - } - } - - private 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) { - // ignore comment lines, which begin with '#' - if (line.startsWith("File")) { - int equals = line.indexOf('='); - if (equals > 0) { - cachePlaylistEntry(line.substring(equals + 1), playListDirectory); - } - } - line = reader.readLine(); - } - - processCachedPlaylist(fileList, values, uri); - } - } catch (IOException e) { - Log.e(TAG, "IOException in MediaScanner.processPlsPlayList()", e); - } finally { - try { - if (reader != null) - reader.close(); - } catch (IOException e) { - Log.e(TAG, "IOException in MediaScanner.processPlsPlayList()", e); - } - } - } - - 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; - - // make sure we have a name - String name = values.getAsString(MediaStore.Audio.Playlists.NAME); - if (name == null) { - name = values.getAsString(MediaStore.MediaColumns.TITLE); - if (name == null) { - // extract name from file name - int lastDot = path.lastIndexOf('.'); - name = (lastDot < 0 ? path.substring(lastSlash + 1) - : path.substring(lastSlash + 1, lastDot)); - } - } - - values.put(MediaStore.Audio.Playlists.NAME, name); - values.put(MediaStore.Audio.Playlists.DATE_MODIFIED, entry.mLastModified); - - if (rowId == 0) { - values.put(MediaStore.Audio.Playlists.DATA, path); - uri = mMediaProvider.insert(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); - - // delete members of existing playlist - membersUri = Uri.withAppendedPath(uri, Playlists.Members.CONTENT_DIRECTORY); - mMediaProvider.delete(membersUri, null, null); - } - - String playListDirectory = path.substring(0, lastSlash + 1); - String mimeType = MediaFile.getMimeTypeForFile(path); - switch (mimeType) { - case "application/vnd.ms-wpl": - processWplPlayList(path, playListDirectory, membersUri, values, fileList); - break; - case "audio/x-mpegurl": - processM3uPlayList(path, playListDirectory, membersUri, values, fileList); - break; - case "audio/x-scpls": - processPlsPlayList(path, playListDirectory, membersUri, values, fileList); - break; - } - } - - private void processPlayLists() throws RemoteException { - Iterator<FileEntry> iterator = mPlayLists.iterator(); - Cursor fileList = null; - try { - // use the files uri and projection because we need the format column, - // but restrict the query to just audio files - fileList = mMediaProvider.query(mFilesUri, FILES_PRESCAN_PROJECTION, - "media_type=2", null, null, null); - while (iterator.hasNext()) { - FileEntry entry = iterator.next(); - // only process playlist files if they are new or have been modified since the last scan - if (entry.mLastModifiedChanged) { - processPlayList(entry, fileList); - } - } - } catch (RemoteException e1) { - } finally { - if (fileList != null) { - fileList.close(); - } - } - } - - private native void processDirectory(String path, MediaScannerClient client); - private native boolean processFile(String path, String mimeType, MediaScannerClient client); - @UnsupportedAppUsage - 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(); - } + throw new UnsupportedOperationException(); } } diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java index 435d8d766149..ff4044220428 100644 --- a/media/java/android/media/RingtoneManager.java +++ b/media/java/android/media/RingtoneManager.java @@ -846,7 +846,7 @@ public class RingtoneManager { * Adds an audio file to the list of ringtones. * * After making sure the given file is an audio file, copies the file to the ringtone storage, - * and asks the {@link android.media.MediaScanner} to scan that file. This call will block until + * and asks the system to scan that file. This call will block until * the scan is completed. * * The directory where the copied file is stored is the directory that matches the ringtone's diff --git a/media/jni/Android.bp b/media/jni/Android.bp index 45ee210c80c9..bd9ea13af046 100644 --- a/media/jni/Android.bp +++ b/media/jni/Android.bp @@ -17,7 +17,6 @@ cc_library_shared { "android_media_MediaPlayer.cpp", "android_media_MediaProfiles.cpp", "android_media_MediaRecorder.cpp", - "android_media_MediaScanner.cpp", "android_media_MediaSync.cpp", "android_media_ResampleInputStream.cpp", "android_media_Streams.cpp", diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp index d24edc7552ae..94299bc8431a 100644 --- a/media/jni/android_media_MediaPlayer.cpp +++ b/media/jni/android_media_MediaPlayer.cpp @@ -1446,7 +1446,6 @@ extern int register_android_media_MediaHTTPConnection(JNIEnv *env); extern int register_android_media_MediaMetadataRetriever(JNIEnv *env); extern int register_android_media_MediaMuxer(JNIEnv *env); extern int register_android_media_MediaRecorder(JNIEnv *env); -extern int register_android_media_MediaScanner(JNIEnv *env); extern int register_android_media_MediaSync(JNIEnv *env); extern int register_android_media_ResampleInputStream(JNIEnv *env); extern int register_android_media_MediaProfiles(JNIEnv *env); @@ -1485,11 +1484,6 @@ jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) goto bail; } - if (register_android_media_MediaScanner(env) < 0) { - ALOGE("ERROR: MediaScanner native registration failed\n"); - goto bail; - } - if (register_android_media_MediaMetadataRetriever(env) < 0) { ALOGE("ERROR: MediaMetadataRetriever native registration failed\n"); goto bail; diff --git a/media/jni/android_media_MediaScanner.cpp b/media/jni/android_media_MediaScanner.cpp deleted file mode 100644 index 58044c0307eb..000000000000 --- a/media/jni/android_media_MediaScanner.cpp +++ /dev/null @@ -1,468 +0,0 @@ -/* -** -** Copyright 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. -*/ - -//#define LOG_NDEBUG 0 -#define LOG_TAG "MediaScannerJNI" -#include <utils/Log.h> -#include <utils/threads.h> -#include <media/mediascanner.h> -#include <media/stagefright/StagefrightMediaScanner.h> -#include <private/media/VideoFrame.h> - -#include "jni.h" -#include <nativehelper/JNIHelp.h> -#include "android_runtime/AndroidRuntime.h" -#include "android_runtime/Log.h" -#include <android-base/macros.h> // for FALLTHROUGH_INTENDED - -using namespace android; - - -static const char* const kClassMediaScannerClient = - "android/media/MediaScannerClient"; - -static const char* const kClassMediaScanner = - "android/media/MediaScanner"; - -static const char* const kRunTimeException = - "java/lang/RuntimeException"; - -static const char* const kIllegalArgumentException = - "java/lang/IllegalArgumentException"; - -struct fields_t { - jfieldID context; -}; -static fields_t fields; - -static status_t checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) { - if (env->ExceptionCheck()) { - ALOGE("An exception was thrown by callback '%s'.", methodName); - LOGE_EX(env); - env->ExceptionClear(); - return UNKNOWN_ERROR; - } - return OK; -} - -// stolen from dalvik/vm/checkJni.cpp -static bool isValidUtf8(const char* bytes) { - while (*bytes != '\0') { - unsigned char utf8 = *(bytes++); - // Switch on the high four bits. - switch (utf8 >> 4) { - case 0x00: - case 0x01: - case 0x02: - case 0x03: - case 0x04: - case 0x05: - case 0x06: - case 0x07: - // Bit pattern 0xxx. No need for any extra bytes. - break; - case 0x08: - case 0x09: - case 0x0a: - case 0x0b: - case 0x0f: - /* - * Bit pattern 10xx or 1111, which are illegal start bytes. - * Note: 1111 is valid for normal UTF-8, but not the - * modified UTF-8 used here. - */ - return false; - case 0x0e: - // Bit pattern 1110, so there are two additional bytes. - utf8 = *(bytes++); - if ((utf8 & 0xc0) != 0x80) { - return false; - } - // Fall through to take care of the final byte. - FALLTHROUGH_INTENDED; - case 0x0c: - case 0x0d: - // Bit pattern 110x, so there is one additional byte. - utf8 = *(bytes++); - if ((utf8 & 0xc0) != 0x80) { - return false; - } - break; - } - } - return true; -} - -class MyMediaScannerClient : public MediaScannerClient -{ -public: - MyMediaScannerClient(JNIEnv *env, jobject client) - : mEnv(env), - mClient(env->NewGlobalRef(client)), - mScanFileMethodID(0), - mHandleStringTagMethodID(0), - mSetMimeTypeMethodID(0) - { - ALOGV("MyMediaScannerClient constructor"); - jclass mediaScannerClientInterface = - env->FindClass(kClassMediaScannerClient); - - if (mediaScannerClientInterface == NULL) { - ALOGE("Class %s not found", kClassMediaScannerClient); - } else { - mScanFileMethodID = env->GetMethodID( - mediaScannerClientInterface, - "scanFile", - "(Ljava/lang/String;JJZZ)V"); - - mHandleStringTagMethodID = env->GetMethodID( - mediaScannerClientInterface, - "handleStringTag", - "(Ljava/lang/String;Ljava/lang/String;)V"); - - mSetMimeTypeMethodID = env->GetMethodID( - mediaScannerClientInterface, - "setMimeType", - "(Ljava/lang/String;)V"); - } - } - - virtual ~MyMediaScannerClient() - { - ALOGV("MyMediaScannerClient destructor"); - mEnv->DeleteGlobalRef(mClient); - } - - virtual status_t scanFile(const char* path, long long lastModified, - long long fileSize, bool isDirectory, bool noMedia) - { - ALOGV("scanFile: path(%s), time(%lld), size(%lld) and isDir(%d)", - path, lastModified, fileSize, isDirectory); - - jstring pathStr; - if ((pathStr = mEnv->NewStringUTF(path)) == NULL) { - mEnv->ExceptionClear(); - return NO_MEMORY; - } - - mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, - fileSize, isDirectory, noMedia); - - mEnv->DeleteLocalRef(pathStr); - return checkAndClearExceptionFromCallback(mEnv, "scanFile"); - } - - virtual status_t handleStringTag(const char* name, const char* value) - { - ALOGV("handleStringTag: name(%s) and value(%s)", name, value); - jstring nameStr, valueStr; - if ((nameStr = mEnv->NewStringUTF(name)) == NULL) { - mEnv->ExceptionClear(); - return NO_MEMORY; - } - char *cleaned = NULL; - if (!isValidUtf8(value)) { - cleaned = strdup(value); - char *chp = cleaned; - char ch; - while ((ch = *chp)) { - if (ch & 0x80) { - *chp = '?'; - } - chp++; - } - value = cleaned; - } - valueStr = mEnv->NewStringUTF(value); - free(cleaned); - if (valueStr == NULL) { - mEnv->DeleteLocalRef(nameStr); - mEnv->ExceptionClear(); - return NO_MEMORY; - } - - mEnv->CallVoidMethod( - mClient, mHandleStringTagMethodID, nameStr, valueStr); - - mEnv->DeleteLocalRef(nameStr); - mEnv->DeleteLocalRef(valueStr); - return checkAndClearExceptionFromCallback(mEnv, "handleStringTag"); - } - - virtual status_t setMimeType(const char* mimeType) - { - ALOGV("setMimeType: %s", mimeType); - jstring mimeTypeStr; - if ((mimeTypeStr = mEnv->NewStringUTF(mimeType)) == NULL) { - mEnv->ExceptionClear(); - return NO_MEMORY; - } - - mEnv->CallVoidMethod(mClient, mSetMimeTypeMethodID, mimeTypeStr); - - mEnv->DeleteLocalRef(mimeTypeStr); - return checkAndClearExceptionFromCallback(mEnv, "setMimeType"); - } - -private: - JNIEnv *mEnv; - jobject mClient; - jmethodID mScanFileMethodID; - jmethodID mHandleStringTagMethodID; - jmethodID mSetMimeTypeMethodID; -}; - - -static MediaScanner *getNativeScanner_l(JNIEnv* env, jobject thiz) -{ - return (MediaScanner *) env->GetLongField(thiz, fields.context); -} - -static void setNativeScanner_l(JNIEnv* env, jobject thiz, MediaScanner *s) -{ - env->SetLongField(thiz, fields.context, (jlong)s); -} - -static void -android_media_MediaScanner_processDirectory( - JNIEnv *env, jobject thiz, jstring path, jobject client) -{ - ALOGV("processDirectory"); - MediaScanner *mp = getNativeScanner_l(env, thiz); - if (mp == NULL) { - jniThrowException(env, kRunTimeException, "No scanner available"); - return; - } - - if (path == NULL) { - jniThrowException(env, kIllegalArgumentException, NULL); - return; - } - - const char *pathStr = env->GetStringUTFChars(path, NULL); - if (pathStr == NULL) { // Out of memory - return; - } - - MyMediaScannerClient myClient(env, client); - MediaScanResult result = mp->processDirectory(pathStr, myClient); - if (result == MEDIA_SCAN_RESULT_ERROR) { - ALOGE("An error occurred while scanning directory '%s'.", pathStr); - } - env->ReleaseStringUTFChars(path, pathStr); -} - -static jboolean -android_media_MediaScanner_processFile( - JNIEnv *env, jobject thiz, jstring path, - jstring mimeType, jobject client) -{ - ALOGV("processFile"); - - // Lock already hold by processDirectory - MediaScanner *mp = getNativeScanner_l(env, thiz); - if (mp == NULL) { - jniThrowException(env, kRunTimeException, "No scanner available"); - return false; - } - - if (path == NULL) { - jniThrowException(env, kIllegalArgumentException, NULL); - return false; - } - - const char *pathStr = env->GetStringUTFChars(path, NULL); - if (pathStr == NULL) { // Out of memory - return false; - } - - const char *mimeTypeStr = - (mimeType ? env->GetStringUTFChars(mimeType, NULL) : NULL); - if (mimeType && mimeTypeStr == NULL) { // Out of memory - // ReleaseStringUTFChars can be called with an exception pending. - env->ReleaseStringUTFChars(path, pathStr); - return false; - } - - MyMediaScannerClient myClient(env, client); - MediaScanResult result = mp->processFile(pathStr, mimeTypeStr, myClient); - if (result == MEDIA_SCAN_RESULT_ERROR) { - ALOGE("An error occurred while scanning file '%s'.", pathStr); - } - env->ReleaseStringUTFChars(path, pathStr); - if (mimeType) { - env->ReleaseStringUTFChars(mimeType, mimeTypeStr); - } - return result != MEDIA_SCAN_RESULT_ERROR; -} - -static void -android_media_MediaScanner_setLocale( - JNIEnv *env, jobject thiz, jstring locale) -{ - ALOGV("setLocale"); - MediaScanner *mp = getNativeScanner_l(env, thiz); - if (mp == NULL) { - jniThrowException(env, kRunTimeException, "No scanner available"); - return; - } - - if (locale == NULL) { - jniThrowException(env, kIllegalArgumentException, NULL); - return; - } - const char *localeStr = env->GetStringUTFChars(locale, NULL); - if (localeStr == NULL) { // Out of memory - return; - } - mp->setLocale(localeStr); - - env->ReleaseStringUTFChars(locale, localeStr); -} - -static jbyteArray -android_media_MediaScanner_extractAlbumArt( - JNIEnv *env, jobject thiz, jobject fileDescriptor) -{ - ALOGV("extractAlbumArt"); - MediaScanner *mp = getNativeScanner_l(env, thiz); - if (mp == NULL) { - jniThrowException(env, kRunTimeException, "No scanner available"); - return NULL; - } - - if (fileDescriptor == NULL) { - jniThrowException(env, kIllegalArgumentException, NULL); - return NULL; - } - - int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); - MediaAlbumArt* mediaAlbumArt = mp->extractAlbumArt(fd); - if (mediaAlbumArt == NULL) { - return NULL; - } - - jbyteArray array = env->NewByteArray(mediaAlbumArt->size()); - if (array != NULL) { - const jbyte* data = - reinterpret_cast<const jbyte*>(mediaAlbumArt->data()); - env->SetByteArrayRegion(array, 0, mediaAlbumArt->size(), data); - } - - free(mediaAlbumArt); - // if NewByteArray() returned NULL, an out-of-memory - // exception will have been raised. I just want to - // return null in that case. - env->ExceptionClear(); - return array; -} - -// This function gets a field ID, which in turn causes class initialization. -// It is called from a static block in MediaScanner, which won't run until the -// first time an instance of this class is used. -static void -android_media_MediaScanner_native_init(JNIEnv *env) -{ - ALOGV("native_init"); - jclass clazz = env->FindClass(kClassMediaScanner); - if (clazz == NULL) { - return; - } - - fields.context = env->GetFieldID(clazz, "mNativeContext", "J"); - if (fields.context == NULL) { - return; - } -} - -static void -android_media_MediaScanner_native_setup(JNIEnv *env, jobject thiz) -{ - ALOGV("native_setup"); - MediaScanner *mp = new StagefrightMediaScanner; - - if (mp == NULL) { - jniThrowException(env, kRunTimeException, "Out of memory"); - return; - } - - env->SetLongField(thiz, fields.context, (jlong)mp); -} - -static void -android_media_MediaScanner_native_finalize(JNIEnv *env, jobject thiz) -{ - ALOGV("native_finalize"); - MediaScanner *mp = getNativeScanner_l(env, thiz); - if (mp == 0) { - return; - } - delete mp; - setNativeScanner_l(env, thiz, 0); -} - -static const JNINativeMethod gMethods[] = { - { - "processDirectory", - "(Ljava/lang/String;Landroid/media/MediaScannerClient;)V", - (void *)android_media_MediaScanner_processDirectory - }, - - { - "processFile", - "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)Z", - (void *)android_media_MediaScanner_processFile - }, - - { - "setLocale", - "(Ljava/lang/String;)V", - (void *)android_media_MediaScanner_setLocale - }, - - { - "extractAlbumArt", - "(Ljava/io/FileDescriptor;)[B", - (void *)android_media_MediaScanner_extractAlbumArt - }, - - { - "native_init", - "()V", - (void *)android_media_MediaScanner_native_init - }, - - { - "native_setup", - "()V", - (void *)android_media_MediaScanner_native_setup - }, - - { - "native_finalize", - "()V", - (void *)android_media_MediaScanner_native_finalize - }, -}; - -// This function only registers the native methods, and is called from -// JNI_OnLoad in android_media_MediaPlayer.cpp -int register_android_media_MediaScanner(JNIEnv *env) -{ - return AndroidRuntime::registerNativeMethods(env, - kClassMediaScanner, gMethods, NELEM(gMethods)); -} |