Merge "Skip scanning non-changed audio/mp4 files." into mainline-prod
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index fff9037..51f9e23 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -5062,11 +5062,14 @@
* @return true if the given Files uri has media_type=MEDIA_TYPE_SUBTITLE
*/
private boolean isSubtitleFile(Uri uri) {
+ final LocalCallingIdentity tokenInner = clearLocalCallingIdentity();
try (Cursor cursor = queryForSingleItem(uri, new String[]{FileColumns.MEDIA_TYPE}, null,
null, null)) {
return cursor.getInt(0) == FileColumns.MEDIA_TYPE_SUBTITLE;
} catch (FileNotFoundException e) {
Log.e(TAG, "Couldn't find database row for requested uri " + uri, e);
+ } finally {
+ restoreLocalCallingIdentity(tokenInner);
}
return false;
}
@@ -6405,6 +6408,23 @@
}
/**
+ * Query the given {@link Uri} as MediaProvider, expecting only a single item to be found.
+ *
+ * @throws FileNotFoundException if no items were found, or multiple items
+ * were found, or there was trouble reading the data.
+ */
+ Cursor queryForSingleItemAsMediaProvider(Uri uri, String[] projection, String selection,
+ String[] selectionArgs, CancellationSignal signal)
+ throws FileNotFoundException {
+ final LocalCallingIdentity tokenInner = clearLocalCallingIdentity();
+ try {
+ return queryForSingleItem(uri, projection, selection, selectionArgs, signal);
+ } finally {
+ restoreLocalCallingIdentity(tokenInner);
+ }
+ }
+
+ /**
* Query the given {@link Uri}, expecting only a single item to be found.
*
* @throws FileNotFoundException if no items were found, or multiple items
@@ -7070,33 +7090,49 @@
final String[] projection = new String[]{
MediaColumns._ID,
MediaColumns.OWNER_PACKAGE_NAME,
- MediaColumns.IS_PENDING};
+ MediaColumns.IS_PENDING,
+ FileColumns.MEDIA_TYPE};
final String selection = MediaColumns.DATA + "=?";
- final String[] selectionArgs = new String[] { path };
- final Uri fileUri;
+ final String[] selectionArgs = new String[]{ path };
+ final long id;
+ final int mediaType;
final boolean isPending;
String ownerPackageName = null;
- try (final Cursor c = queryForSingleItem(contentUri, projection, selection,
+ try (final Cursor c = queryForSingleItemAsMediaProvider(contentUri, projection,
+ selection,
selectionArgs, null)) {
- fileUri = ContentUris.withAppendedId(contentUri, c.getInt(0));
+ id = c.getLong(0);
ownerPackageName = c.getString(1);
isPending = c.getInt(2) != 0;
+ mediaType = c.getInt(3);
}
-
final File file = new File(path);
- checkAccess(fileUri, Bundle.EMPTY, file, forWrite);
-
+ final Uri fileUri = MediaStore.Files.getContentUri(extractVolumeName(path), id);
// We don't check ownership for files with IS_PENDING set by FUSE
if (isPending && !isPendingFromFuse(new File(path))) {
requireOwnershipForItem(ownerPackageName, fileUri);
}
+
+ // Check that path looks consistent before uri checks
+ if (!FileUtils.contains(Environment.getStorageDirectory(), file)) {
+ checkWorldReadAccess(file.getAbsolutePath());
+ }
+
+ try {
+ // checkAccess throws FileNotFoundException only from checkWorldReadAccess(),
+ // which we already check above. Hence, handling only SecurityException.
+ checkAccess(fileUri, Bundle.EMPTY, file, forWrite);
+ } catch (SecurityException e) {
+ // Check for other Uri formats only when the single uri check flow fails.
+ // Throw the previous exception if the multi-uri checks failed.
+ if (!(isFilePathSupportForMediaUris() &&
+ hasOtherUriGrants(path, mediaType, id, forWrite))) {
+ throw e;
+ }
+ }
return 0;
} catch (FileNotFoundException e) {
- // We are here because
- // * App doesn't have read permission to the requested path, hence queryForSingleItem
- // couldn't return a valid db row, or,
- // * There is no db row corresponding to the requested path, which is more unlikely.
- // In both of these cases, it means that app doesn't have access permission to the file.
+ // We are here because there is no db row corresponding to the requested path.
Log.e(TAG, "Couldn't find file: " + path);
return OsConstants.EACCES;
} catch (IllegalStateException | SecurityException e) {
@@ -7107,6 +7143,43 @@
}
}
+ private boolean hasOtherUriGrants(String path, int mediaType, long id, boolean forWrite) {
+ Set<Uri> otherUris = new ArraySet<Uri>();
+ final Uri mediaUri = getMediaUriForFuse(extractVolumeName(path), mediaType, id);
+ otherUris.add(mediaUri);
+ final Uri externalMediaUri = getMediaUriForFuse(MediaStore.VOLUME_EXTERNAL, mediaType, id);
+ otherUris.add(externalMediaUri);
+ return bulkCheckUriPermissions(otherUris, forWrite);
+ }
+
+ private @NonNull Uri getMediaUriForFuse(@NonNull String volumeName, int mediaType, long id) {
+ switch (mediaType) {
+ case FileColumns.MEDIA_TYPE_IMAGE:
+ return MediaStore.Images.Media.getContentUri(volumeName, id);
+ case FileColumns.MEDIA_TYPE_VIDEO:
+ return MediaStore.Video.Media.getContentUri(volumeName, id);
+ case FileColumns.MEDIA_TYPE_AUDIO:
+ return MediaStore.Audio.Media.getContentUri(volumeName, id);
+ case FileColumns.MEDIA_TYPE_PLAYLIST:
+ return ContentUris.withAppendedId(
+ MediaStore.Audio.Playlists.getContentUri(volumeName), id);
+ default:
+ // return files URIs
+ return MediaStore.Files.getContentUri(volumeName, id);
+ }
+ }
+
+ /**
+ * Feature flag to support File APIs for different formats of media-store URI grants like:
+ * * content://media/external_primary/images/media/123
+ * * content://media/external/images/media/123
+ *
+ * Default value: false
+ */
+ private boolean isFilePathSupportForMediaUris() {
+ return SystemProperties.getBoolean("sys.filepathsupport.mediauri", false);
+ }
+
/**
* Returns {@code true} if {@link #mCallingIdentity#getSharedPackages(String)} contains the
* given package name, {@code false} otherwise.
@@ -7670,14 +7743,24 @@
}
// Outstanding grant means they get access
- if (getContext().checkUriPermission(uri, mCallingIdentity.get().pid,
+ return isUriPermissionGranted(uri, forWrite);
+ }
+
+ private boolean bulkCheckUriPermissions(Set<Uri> uris, boolean forWrite) {
+ for (Uri uri : uris) {
+ if (isUriPermissionGranted(uri, forWrite)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isUriPermissionGranted(Uri uri, boolean forWrite) {
+ int uriPermission = getContext().checkUriPermission(uri, mCallingIdentity.get().pid,
mCallingIdentity.get().uid, forWrite
? Intent.FLAG_GRANT_WRITE_URI_PERMISSION
- : Intent.FLAG_GRANT_READ_URI_PERMISSION) == PERMISSION_GRANTED) {
- return true;
- }
-
- return false;
+ : Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ return uriPermission == PERMISSION_GRANTED;
}
@VisibleForTesting