From f8e15d9019a1454c174c42030d4aae877bcc682a Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Fri, 11 Oct 2019 15:44:27 -0600 Subject: [PATCH] Finish deprecating legacy MediaScanner. We switched completely over to using ModernMediaScanner in Android Q, and this legacy code is no longer being used. Clean it up, but leave enough remnants behind to begrudgingly keep reflecting apps working. Bug: 128243527 Test: atest --test-mapping packages/providers/MediaProvider Change-Id: Ib2bc341fafcd4c2e049c864a35fbe651d013f6fd --- media/java/android/media/MediaScanner.java | 1962 +---------------- media/java/android/media/RingtoneManager.java | 2 +- media/jni/Android.bp | 1 - media/jni/android_media_MediaPlayer.cpp | 6 - media/jni/android_media_MediaScanner.cpp | 468 ---- 5 files changed, 89 insertions(+), 2350 deletions(-) delete mode 100644 media/jni/android_media_MediaScanner.cpp diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java index 771628cf7b1e8..ca96c9ab56700 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 mPlaylistEntries = new ArrayList<>(); - private final ArrayList 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"); + throw new UnsupportedOperationException(); } - 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); - } - - @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 - 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 String mMimeType; + @Deprecated + @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; + throw new UnsupportedOperationException(); } - 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); - } - - /** @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; - } - - 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; + 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 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(); - } + throw new UnsupportedOperationException(); } - static class MediaBulkDeleter { - StringBuilder whereClause = new StringBuilder(); - ArrayList whereArgs = new ArrayList(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(); - } - } - } - - @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(); + throw new UnsupportedOperationException(); } - 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(); - } - } - - // 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(); - } + throw new UnsupportedOperationException(); } - 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 mNoMediaPaths = new HashMap(); - private static HashMap mMediaPaths = new HashMap(); - - /* 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(); - } - } - } - - @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); + throw new UnsupportedOperationException(); } - 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(); - } - } - - @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; + @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 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(); - } - - 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 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 435d8d7661496..ff40442204285 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 45ee210c80c95..bd9ea13af0469 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 d24edc7552aef..94299bc8431a9 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 58044c0307eb8..0000000000000 --- 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 -#include -#include -#include -#include - -#include "jni.h" -#include -#include "android_runtime/AndroidRuntime.h" -#include "android_runtime/Log.h" -#include // 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(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)); -}