From ff200957fd93d2eae2db46714e23fabd89ff1696 Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Sun, 24 Mar 2019 12:50:51 -0600 Subject: [PATCH] Query specific collections for properties. We're now enforcing the public API schema, so clients like MTP need to query specific collections when asking for properties that are specific to those media types. Also refactor MediaScanner calls to go through MediaProvider, so they can be handled by ModernMediaScanner. We no longer directly try inserting items, and instead rely completely on the scanner. Bug: 127625964, 122263824 Test: atest MediaProviderTests Test: atest cts/tests/tests/provider/src/android/provider/cts/MediaStore* Change-Id: Ic86982766606a619afafe725f9c60013a5671840 --- core/java/android/provider/MediaStore.java | 14 ++ .../annotations/VisibleForNative.java | 28 +++ media/java/android/mtp/MtpDatabase.java | 183 ++++++------------ media/java/android/mtp/MtpPropertyGroup.java | 66 +------ 4 files changed, 106 insertions(+), 185 deletions(-) create mode 100644 core/java/com/android/internal/annotations/VisibleForNative.java diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index 20d3c9139bbed..dc5fdc0ba67c2 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -136,6 +136,7 @@ public final class MediaStore { * removing nomedia files * @hide */ + @Deprecated public static final String UNHIDE_CALL = "unhide"; /** @@ -3536,4 +3537,17 @@ public final class MediaStore { throw new IOException("User " + user + " must be unlocked and running"); } } + + /** @hide */ + public static Uri scanFile(Context context, File file) { + final ContentResolver resolver = context.getContentResolver(); + try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) { + final Bundle in = new Bundle(); + in.putParcelable(Intent.EXTRA_STREAM, Uri.fromFile(file)); + final Bundle out = client.call(SCAN_FILE_CALL, null, in); + return out.getParcelable(Intent.EXTRA_STREAM); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } } diff --git a/core/java/com/android/internal/annotations/VisibleForNative.java b/core/java/com/android/internal/annotations/VisibleForNative.java new file mode 100644 index 0000000000000..e6a3fc67b7d39 --- /dev/null +++ b/core/java/com/android/internal/annotations/VisibleForNative.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Denotes that the class, method or field has its visibility relaxed so + * that native code can access it. + */ +@Retention(RetentionPolicy.CLASS) +public @interface VisibleForNative { +} diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java index dc2d17753b1d6..6361fd81d2af4 100755 --- a/media/java/android/mtp/MtpDatabase.java +++ b/media/java/android/mtp/MtpDatabase.java @@ -25,16 +25,13 @@ import android.content.IntentFilter; import android.content.SharedPreferences; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; -import android.media.MediaScanner; import android.net.Uri; import android.os.BatteryManager; import android.os.RemoteException; import android.os.SystemProperties; import android.os.storage.StorageVolume; import android.provider.MediaStore; -import android.provider.MediaStore.Audio; import android.provider.MediaStore.Files; -import android.provider.MediaStore.MediaColumns; import android.system.ErrnoException; import android.system.Os; import android.system.OsConstants; @@ -42,6 +39,8 @@ import android.util.Log; import android.view.Display; import android.view.WindowManager; +import com.android.internal.annotations.VisibleForNative; + import dalvik.system.CloseGuard; import com.google.android.collect.Sets; @@ -72,7 +71,6 @@ public class MtpDatabase implements AutoCloseable { private final ContentProviderClient mMediaProvider; private final String mVolumeName; private final Uri mObjectsUri; - private final MediaScanner mMediaScanner; private final AtomicBoolean mClosed = new AtomicBoolean(); private final CloseGuard mCloseGuard = CloseGuard.get(); @@ -159,7 +157,6 @@ public class MtpDatabase implements AutoCloseable { MtpConstants.PROPERTY_TRACK, MtpConstants.PROPERTY_ORIGINAL_RELEASE_DATE, MtpConstants.PROPERTY_DURATION, - MtpConstants.PROPERTY_GENRE, MtpConstants.PROPERTY_COMPOSER, MtpConstants.PROPERTY_AUDIO_WAVE_CODEC, MtpConstants.PROPERTY_BITRATE_TYPE, @@ -187,6 +184,7 @@ public class MtpDatabase implements AutoCloseable { MtpConstants.DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE, }; + @VisibleForNative private int[] getSupportedObjectProperties(int format) { switch (format) { case MtpConstants.FORMAT_MP3: @@ -214,14 +212,41 @@ public class MtpDatabase implements AutoCloseable { } } + public static Uri getObjectPropertiesUri(int format, String volumeName) { + switch (format) { + case MtpConstants.FORMAT_MP3: + case MtpConstants.FORMAT_WAV: + case MtpConstants.FORMAT_WMA: + case MtpConstants.FORMAT_OGG: + case MtpConstants.FORMAT_AAC: + return MediaStore.Audio.Media.getContentUri(volumeName); + case MtpConstants.FORMAT_MPEG: + case MtpConstants.FORMAT_3GP_CONTAINER: + case MtpConstants.FORMAT_WMV: + return MediaStore.Video.Media.getContentUri(volumeName); + case MtpConstants.FORMAT_EXIF_JPEG: + case MtpConstants.FORMAT_GIF: + case MtpConstants.FORMAT_PNG: + case MtpConstants.FORMAT_BMP: + case MtpConstants.FORMAT_DNG: + case MtpConstants.FORMAT_HEIF: + return MediaStore.Images.Media.getContentUri(volumeName); + default: + return MediaStore.Files.getContentUri(volumeName); + } + } + + @VisibleForNative private int[] getSupportedDeviceProperties() { return DEVICE_PROPERTIES; } + @VisibleForNative private int[] getSupportedPlaybackFormats() { return PLAYBACK_FORMATS; } + @VisibleForNative private int[] getSupportedCaptureFormats() { // no capture formats yet return null; @@ -254,7 +279,6 @@ public class MtpDatabase implements AutoCloseable { .acquireContentProviderClient(MediaStore.AUTHORITY); mVolumeName = volumeName; mObjectsUri = Files.getMtpObjectsUri(volumeName); - mMediaScanner = new MediaScanner(context, mVolumeName); mManager = new MtpStorageManager(new MtpStorageManager.MtpNotifier() { @Override public void sendObjectAdded(int id) { @@ -304,7 +328,6 @@ public class MtpDatabase implements AutoCloseable { mManager.close(); mCloseGuard.close(); if (mClosed.compareAndSet(false, true)) { - mMediaScanner.close(); if (mMediaProvider != null) { mMediaProvider.close(); } @@ -380,6 +403,7 @@ public class MtpDatabase implements AutoCloseable { } } + @VisibleForNative private int beginSendObject(String path, int format, int parent, int storageId) { MtpStorageManager.MtpObject parentObj = parent == 0 ? mManager.getStorageRoot(storageId) : mManager.getObject(parent); @@ -391,6 +415,7 @@ public class MtpDatabase implements AutoCloseable { return mManager.beginSendObject(parentObj, objPath.getFileName().toString(), format); } + @VisibleForNative private void endSendObject(int handle, boolean succeeded) { MtpStorageManager.MtpObject obj = mManager.getObject(handle); if (obj == null || !mManager.endSendObject(obj, succeeded)) { @@ -399,69 +424,16 @@ public class MtpDatabase implements AutoCloseable { } // Add the new file to MediaProvider if (succeeded) { - String path = obj.getPath().toString(); - int format = obj.getFormat(); - // Get parent info from MediaProvider, since the id is different from MTP's - ContentValues values = new ContentValues(); - values.put(Files.FileColumns.DATA, path); - values.put(Files.FileColumns.FORMAT, format); - values.put(Files.FileColumns.SIZE, obj.getSize()); - values.put(Files.FileColumns.DATE_MODIFIED, obj.getModifiedTime()); - try { - if (obj.getParent().isRoot()) { - values.put(Files.FileColumns.PARENT, 0); - } else { - int parentId = findInMedia(obj.getParent().getPath()); - if (parentId != -1) { - values.put(Files.FileColumns.PARENT, parentId); - } else { - // The parent isn't in MediaProvider. Don't add the new file. - return; - } - } - - Uri uri = mMediaProvider.insert(mObjectsUri, values); - if (uri != null) { - rescanFile(path, Integer.parseInt(uri.getPathSegments().get(2)), format); - } - } catch (RemoteException e) { - Log.e(TAG, "RemoteException in beginSendObject", e); - } + MediaStore.scanFile(mContext, obj.getPath().toFile()); } } + @VisibleForNative private void rescanFile(String path, int handle, int format) { - // handle abstract playlists separately - // they do not exist in the file system so don't use the media scanner here - if (format == MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST) { - // extract name from path - String name = path; - int lastSlash = name.lastIndexOf('/'); - if (lastSlash >= 0) { - name = name.substring(lastSlash + 1); - } - // strip trailing ".pla" from the name - if (name.endsWith(".pla")) { - name = name.substring(0, name.length() - 4); - } - - ContentValues values = new ContentValues(1); - values.put(Audio.Playlists.DATA, path); - values.put(Audio.Playlists.NAME, name); - values.put(Files.FileColumns.FORMAT, format); - values.put(Files.FileColumns.DATE_MODIFIED, System.currentTimeMillis() / 1000); - values.put(MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, handle); - try { - mMediaProvider.insert( - Audio.Playlists.EXTERNAL_CONTENT_URI, values); - } catch (RemoteException e) { - Log.e(TAG, "RemoteException in endSendObject", e); - } - } else { - mMediaScanner.scanMtpFile(path, handle, format); - } + MediaStore.scanFile(mContext, new File(path)); } + @VisibleForNative private int[] getObjectList(int storageID, int format, int parent) { List objs = mManager.getObjects(parent, format, storageID); @@ -475,6 +447,7 @@ public class MtpDatabase implements AutoCloseable { return ret; } + @VisibleForNative private int getNumObjects(int storageID, int format, int parent) { List objs = mManager.getObjects(parent, format, storageID); @@ -484,6 +457,7 @@ public class MtpDatabase implements AutoCloseable { return objs.size(); } + @VisibleForNative private MtpPropertyList getObjectPropertyList(int handle, int format, int property, int groupCode, int depth) { // FIXME - implement group support @@ -551,16 +525,16 @@ public class MtpDatabase implements AutoCloseable { // format should be the same between get & put propertyGroup = mPropertyGroupsByFormat.get(format); if (propertyGroup == null) { - int[] propertyList = getSupportedObjectProperties(format); + final int[] propertyList = getSupportedObjectProperties(format); propertyGroup = new MtpPropertyGroup(mMediaProvider, mVolumeName, propertyList); mPropertyGroupsByFormat.put(format, propertyGroup); } } else { // Get this property value - final int[] propertyList = new int[]{property}; propertyGroup = mPropertyGroupsByProperty.get(property); if (propertyGroup == null) { + final int[] propertyList = new int[]{property}; propertyGroup = new MtpPropertyGroup(mMediaProvider, mVolumeName, propertyList); mPropertyGroupsByProperty.put(property, propertyGroup); @@ -616,28 +590,19 @@ public class MtpDatabase implements AutoCloseable { if (obj.isDir()) { // for directories, check if renamed from something hidden to something non-hidden if (oldPath.getFileName().startsWith(".") && !newPath.startsWith(".")) { - // directory was unhidden - try { - mMediaProvider.call(MediaStore.UNHIDE_CALL, newPath.toString(), null); - } catch (RemoteException e) { - Log.e(TAG, "failed to unhide/rescan for " + newPath); - } + MediaStore.scanFile(mContext, newPath.toFile()); } } else { // for files, check if renamed from .nomedia to something else if (oldPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA) && !newPath.getFileName().toString().toLowerCase(Locale.US).equals(NO_MEDIA)) { - try { - mMediaProvider.call(MediaStore.UNHIDE_CALL, - oldPath.getParent().toString(), null); - } catch (RemoteException e) { - Log.e(TAG, "failed to unhide/rescan for " + newPath); - } + MediaStore.scanFile(mContext, newPath.getParent().toFile()); } } return MtpConstants.RESPONSE_OK; } + @VisibleForNative private int beginMoveObject(int handle, int newParent, int newStorage) { MtpStorageManager.MtpObject obj = mManager.getObject(handle); MtpStorageManager.MtpObject parent = newParent == 0 ? @@ -649,6 +614,7 @@ public class MtpDatabase implements AutoCloseable { return allowed ? MtpConstants.RESPONSE_OK : MtpConstants.RESPONSE_GENERAL_ERROR; } + @VisibleForNative private void endMoveObject(int oldParent, int newParent, int oldStorage, int newStorage, int objId, boolean success) { MtpStorageManager.MtpObject oldParentObj = oldParent == 0 ? @@ -698,20 +664,14 @@ public class MtpDatabase implements AutoCloseable { mMediaProvider.update(mObjectsUri, values, PATH_WHERE, whereArgs); } else { // Old parent doesn't exist - add the object - values.put(Files.FileColumns.FORMAT, obj.getFormat()); - values.put(Files.FileColumns.SIZE, obj.getSize()); - values.put(Files.FileColumns.DATE_MODIFIED, obj.getModifiedTime()); - Uri uri = mMediaProvider.insert(mObjectsUri, values); - if (uri != null) { - rescanFile(path.toString(), - Integer.parseInt(uri.getPathSegments().get(2)), obj.getFormat()); - } + MediaStore.scanFile(mContext, path.toFile()); } } catch (RemoteException e) { Log.e(TAG, "RemoteException in mMediaProvider.update", e); } } + @VisibleForNative private int beginCopyObject(int handle, int newParent, int newStorage) { MtpStorageManager.MtpObject obj = mManager.getObject(handle); MtpStorageManager.MtpObject parent = newParent == 0 ? @@ -721,6 +681,7 @@ public class MtpDatabase implements AutoCloseable { return mManager.beginCopyObject(obj, parent); } + @VisibleForNative private void endCopyObject(int handle, boolean success) { MtpStorageManager.MtpObject obj = mManager.getObject(handle); if (obj == null || !mManager.endCopyObject(obj, success)) { @@ -730,39 +691,10 @@ public class MtpDatabase implements AutoCloseable { if (!success) { return; } - String path = obj.getPath().toString(); - int format = obj.getFormat(); - // Get parent info from MediaProvider, since the id is different from MTP's - ContentValues values = new ContentValues(); - values.put(Files.FileColumns.DATA, path); - values.put(Files.FileColumns.FORMAT, format); - values.put(Files.FileColumns.SIZE, obj.getSize()); - values.put(Files.FileColumns.DATE_MODIFIED, obj.getModifiedTime()); - try { - if (obj.getParent().isRoot()) { - values.put(Files.FileColumns.PARENT, 0); - } else { - int parentId = findInMedia(obj.getParent().getPath()); - if (parentId != -1) { - values.put(Files.FileColumns.PARENT, parentId); - } else { - // The parent isn't in MediaProvider. Don't add the new file. - return; - } - } - if (obj.isDir()) { - mMediaScanner.scanDirectories(new String[]{path}); - } else { - Uri uri = mMediaProvider.insert(mObjectsUri, values); - if (uri != null) { - rescanFile(path, Integer.parseInt(uri.getPathSegments().get(2)), format); - } - } - } catch (RemoteException e) { - Log.e(TAG, "RemoteException in beginSendObject", e); - } + MediaStore.scanFile(mContext, obj.getPath().toFile()); } + @VisibleForNative private int setObjectProperty(int handle, int property, long intValue, String stringValue) { switch (property) { @@ -774,6 +706,7 @@ public class MtpDatabase implements AutoCloseable { } } + @VisibleForNative private int getDeviceProperty(int property, long[] outIntValue, char[] outStringValue) { switch (property) { case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER: @@ -809,6 +742,7 @@ public class MtpDatabase implements AutoCloseable { } } + @VisibleForNative private int setDeviceProperty(int property, long intValue, String stringValue) { switch (property) { case MtpConstants.DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER: @@ -823,6 +757,7 @@ public class MtpDatabase implements AutoCloseable { return MtpConstants.RESPONSE_DEVICE_PROP_NOT_SUPPORTED; } + @VisibleForNative private boolean getObjectInfo(int handle, int[] outStorageFormatParent, char[] outName, long[] outCreatedModified) { MtpStorageManager.MtpObject obj = mManager.getObject(handle); @@ -842,6 +777,7 @@ public class MtpDatabase implements AutoCloseable { return true; } + @VisibleForNative private int getObjectFilePath(int handle, char[] outFilePath, long[] outFileLengthFormat) { MtpStorageManager.MtpObject obj = mManager.getObject(handle); if (obj == null) { @@ -866,6 +802,7 @@ public class MtpDatabase implements AutoCloseable { return obj.getFormat(); } + @VisibleForNative private int beginDeleteObject(int handle) { MtpStorageManager.MtpObject obj = mManager.getObject(handle); if (obj == null) { @@ -877,6 +814,7 @@ public class MtpDatabase implements AutoCloseable { return MtpConstants.RESPONSE_OK; } + @VisibleForNative private void endDeleteObject(int handle, boolean success) { MtpStorageManager.MtpObject obj = mManager.getObject(handle); if (obj == null) { @@ -922,12 +860,7 @@ public class MtpDatabase implements AutoCloseable { String[] whereArgs = new String[]{path.toString()}; if (mMediaProvider.delete(mObjectsUri, PATH_WHERE, whereArgs) > 0) { if (!isDir && path.toString().toLowerCase(Locale.US).endsWith(NO_MEDIA)) { - try { - String parentPath = path.getParent().toString(); - mMediaProvider.call(MediaStore.UNHIDE_CALL, parentPath, null); - } catch (RemoteException e) { - Log.e(TAG, "failed to unhide/rescan for " + path); - } + MediaStore.scanFile(mContext, path.getParent().toFile()); } } else { Log.i(TAG, "Mediaprovider didn't delete " + path); @@ -937,6 +870,7 @@ public class MtpDatabase implements AutoCloseable { } } + @VisibleForNative private int[] getObjectReferences(int handle) { MtpStorageManager.MtpObject obj = mManager.getObject(handle); if (obj == null) @@ -972,6 +906,7 @@ public class MtpDatabase implements AutoCloseable { return null; } + @VisibleForNative private int setObjectReferences(int handle, int[] references) { MtpStorageManager.MtpObject obj = mManager.getObject(handle); if (obj == null) @@ -1004,7 +939,7 @@ public class MtpDatabase implements AutoCloseable { return MtpConstants.RESPONSE_GENERAL_ERROR; } - // used by the JNI code + @VisibleForNative private long mNativeContext; private native final void native_setup(); diff --git a/media/java/android/mtp/MtpPropertyGroup.java b/media/java/android/mtp/MtpPropertyGroup.java index 77d0f34f1ad6a..6d5be8ef6985f 100644 --- a/media/java/android/mtp/MtpPropertyGroup.java +++ b/media/java/android/mtp/MtpPropertyGroup.java @@ -48,7 +48,6 @@ class MtpPropertyGroup { private final ContentProviderClient mProvider; private final String mVolumeName; - private final Uri mUri; // list of all properties in this group private final Property[] mProperties; @@ -62,7 +61,6 @@ class MtpPropertyGroup { public MtpPropertyGroup(ContentProviderClient provider, String volumeName, int[] properties) { mProvider = provider; mVolumeName = volumeName; - mUri = Files.getMtpObjectsUri(volumeName); int count = properties.length; ArrayList columns = new ArrayList<>(count); @@ -139,10 +137,6 @@ class MtpPropertyGroup { column = Audio.AudioColumns.ALBUM_ARTIST; type = MtpConstants.TYPE_STR; break; - case MtpConstants.PROPERTY_GENRE: - // genre requires a special query - type = MtpConstants.TYPE_STR; - break; case MtpConstants.PROPERTY_COMPOSER: column = Audio.AudioColumns.COMPOSER; type = MtpConstants.TYPE_STR; @@ -176,46 +170,6 @@ class MtpPropertyGroup { } } - private String queryAudio(String path, String column) { - Cursor c = null; - try { - c = mProvider.query(Audio.Media.getContentUri(mVolumeName), - new String [] { column }, - PATH_WHERE, new String[] {path}, null, null); - if (c != null && c.moveToNext()) { - return c.getString(0); - } else { - return ""; - } - } catch (Exception e) { - return ""; - } finally { - if (c != null) { - c.close(); - } - } - } - - private String queryGenre(String path) { - Cursor c = null; - try { - c = mProvider.query(Audio.Genres.getContentUri(mVolumeName), - new String [] { Audio.GenresColumns.NAME }, - PATH_WHERE, new String[] {path}, null, null); - if (c != null && c.moveToNext()) { - return c.getString(0); - } else { - return ""; - } - } catch (Exception e) { - return ""; - } finally { - if (c != null) { - c.close(); - } - } - } - /** * Gets the values of the properties represented by this property group for the given * object and adds them to the given property list. @@ -229,12 +183,16 @@ class MtpPropertyGroup { if (property.column != -1 && c == null) { try { // Look up the entry in MediaProvider only if one of those properties is needed. - c = mProvider.query(mUri, mColumns, + final Uri uri = MtpDatabase.getObjectPropertiesUri(object.getFormat(), + mVolumeName); + c = mProvider.query(uri, mColumns, PATH_WHERE, new String[] {path}, null, null); if (c != null && !c.moveToNext()) { c.close(); c = null; } + } catch (IllegalArgumentException e) { + return MtpConstants.RESPONSE_INVALID_OBJECT_PROP_CODE; } catch (RemoteException e) { Log.e(TAG, "Mediaprovider lookup failed"); } @@ -290,20 +248,6 @@ class MtpPropertyGroup { list.append(id, property.code, MtpConstants.TYPE_UINT16, track % 1000); break; - case MtpConstants.PROPERTY_ARTIST: - list.append(id, property.code, - queryAudio(path, Audio.AudioColumns.ARTIST)); - break; - case MtpConstants.PROPERTY_ALBUM_NAME: - list.append(id, property.code, - queryAudio(path, Audio.AudioColumns.ALBUM)); - break; - case MtpConstants.PROPERTY_GENRE: - String genre = queryGenre(path); - if (genre != null) { - list.append(id, property.code, genre); - } - break; case MtpConstants.PROPERTY_AUDIO_WAVE_CODEC: case MtpConstants.PROPERTY_AUDIO_BITRATE: case MtpConstants.PROPERTY_SAMPLE_RATE: