Merge "Rename local variables"
diff --git a/apex/framework/java/android/provider/MediaStore.java b/apex/framework/java/android/provider/MediaStore.java
index 53323fb..2dca9fd 100644
--- a/apex/framework/java/android/provider/MediaStore.java
+++ b/apex/framework/java/android/provider/MediaStore.java
@@ -603,24 +603,29 @@
*/
public final static String EXTRA_OUTPUT = "output";
- /**
+ /*
* Specify that the caller wants to receive the original media format without transcoding.
*
- * This is a very dangerous flag to use because apps can suddenly fail to play media after
- * an OS upgrade. Clients should instead specify their supported media capabilities explicitly
- * in their manifest or with the {@link #EXTRA_MEDIA_CAPABILITIES} open flag.
+ * <b>Caution: using this flag can cause app
+ * compatibility issues whenever Android adds support for new media formats.</b>
+ * Clients should instead specify their supported media capabilities explicitly
+ * in their manifest or with the {@link #EXTRA_MEDIA_CAPABILITIES} {@code open} flag.
+ *
+ * This option is useful for apps that don't attempt to parse the actual byte contents of media
+ * files, such as playback using {@link MediaPlayer} or for off-device backup. Note that the
+ * {@link android.Manifest.permission#ACCESS_MEDIA_LOCATION} permission will still be required
+ * to avoid sensitive metadata redaction, similar to {@link #setRequireOriginal(Uri)}.
+ * </ul>
*
* Note that this flag overrides any explicitly declared {@code media_capabilities.xml} or
* {@link ApplicationMediaCapabilities} extras specified in the same {@code open} request.
*
- * This is only useful for apps that are bundled with the system hence are guaranteed to
- * always support any media capabilities released on the OS in the future.
- *
* <p>This option can be added to the {@code opts} {@link Bundle} in various
* {@link ContentResolver} {@code open} methods.
*
* @see ContentResolver#openTypedAssetFileDescriptor(Uri, String, Bundle)
* @see ContentResolver#openTypedAssetFile(Uri, String, Bundle, CancellationSignal)
+ * @see #setRequireOriginal(Uri)
*/
public final static String EXTRA_ACCEPT_ORIGINAL_MEDIA_FORMAT =
"android.provider.extra.ACCEPT_ORIGINAL_MEDIA_FORMAT";
diff --git a/jni/node-inl.h b/jni/node-inl.h
index aa067be..1fa62b0 100644
--- a/jni/node-inl.h
+++ b/jni/node-inl.h
@@ -179,8 +179,10 @@
// associated with its descendants.
std::string BuildSafePath() const;
- // Looks up a direct descendant of this node by name. If |acquire| is true,
+ // Looks up a direct descendant of this node by case-insensitive |name|. If |acquire| is true,
// also Acquire the node before returning a reference to it.
+ // |transforms| is an opaque flag that is used to distinguish multiple nodes sharing the same
+ // |name| but requiring different IO transformations as determined by the MediaProvider.
node* LookupChildByName(const std::string& name, bool acquire, const int transforms = 0) const {
return ForChild(name, [acquire, transforms](node* child) {
if (child->transforms_ == transforms) {
diff --git a/src/com/android/providers/media/PermissionActivity.java b/src/com/android/providers/media/PermissionActivity.java
index 95b5561..0183690 100644
--- a/src/com/android/providers/media/PermissionActivity.java
+++ b/src/com/android/providers/media/PermissionActivity.java
@@ -180,13 +180,26 @@
}
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setTitle(resolveTitleText());
+ // We set the title in message so that the text doesn't get truncated
+ builder.setMessage(resolveTitleText());
builder.setPositiveButton(R.string.allow, this::onPositiveAction);
builder.setNegativeButton(R.string.deny, this::onNegativeAction);
builder.setCancelable(false);
builder.setView(bodyView);
actionDialog = builder.show();
+
+ // The title is being set as a message above.
+ // We need to style it like the default AlertDialog title
+ TextView dialogMessage = (TextView) actionDialog.findViewById(
+ android.R.id.message);
+ if (dialogMessage != null) {
+ dialogMessage.setTextAppearance(
+ android.R.style.TextAppearance_DeviceDefault_DialogWindowTitle);
+ } else {
+ Log.w(TAG, "Couldn't find message element");
+ }
+
final WindowManager.LayoutParams params = actionDialog.getWindow().getAttributes();
params.width = getResources().getDimensionPixelSize(R.dimen.permission_dialog_width);
actionDialog.getWindow().setAttributes(params);
diff --git a/src/com/android/providers/media/scan/ModernMediaScanner.java b/src/com/android/providers/media/scan/ModernMediaScanner.java
index 46c3516..173eb26 100644
--- a/src/com/android/providers/media/scan/ModernMediaScanner.java
+++ b/src/com/android/providers/media/scan/ModernMediaScanner.java
@@ -690,11 +690,8 @@
actualMimeType = mDrmClient.getOriginalMimeType(realFile.getPath());
}
- int actualMediaType = FileColumns.MEDIA_TYPE_NONE;
- if (actualMimeType != null) {
- actualMediaType = resolveMediaTypeFromFilePath(realFile, actualMimeType,
- /*isHidden*/ mHiddenDirCount > 0);
- }
+ int actualMediaType = mediaTypeFromMimeType(
+ realFile, actualMimeType, FileColumns.MEDIA_TYPE_NONE);
Trace.beginSection("checkChanged");
@@ -718,13 +715,9 @@
try (Cursor c = mResolver.query(mFilesUri, projection, queryArgs, mSignal)) {
if (c.moveToFirst()) {
existingId = c.getLong(0);
- final long dateModified = c.getLong(1);
- final long size = c.getLong(2);
final String mimeType = c.getString(3);
final int mediaType = c.getInt(4);
isPendingFromFuse &= c.getInt(5) != 0;
- final boolean isScanned =
- c.getInt(6) == FileColumns._MODIFIER_MEDIA_SCAN;
// Remember visiting this existing item, even if we skipped
// due to it being unchanged; this is needed so we don't
@@ -736,18 +729,36 @@
mFirstId = existingId;
}
- final boolean sameTime = (lastModifiedTime(realFile, attrs) == dateModified);
- final boolean sameSize = (attrs.size() == size);
- final boolean sameMimeType = mimeType == null ? actualMimeType == null :
- mimeType.equalsIgnoreCase(actualMimeType);
- final boolean sameMediaType = (actualMediaType == mediaType);
- final boolean isSame = sameTime && sameSize && sameMediaType && sameMimeType
- && !isPendingFromFuse && isScanned;
- if (attrs.isDirectory() || isSame) {
+ if (attrs.isDirectory()) {
+ if (LOGV) Log.v(TAG, "Skipping directory " + file);
+ return FileVisitResult.CONTINUE;
+ }
+
+ final boolean sameMetadata =
+ hasSameMetadata(attrs, realFile, isPendingFromFuse, c);
+ if (isSame(
+ sameMetadata, actualMimeType, actualMediaType, mimeType, mediaType)) {
if (LOGV) Log.v(TAG, "Skipping unchanged " + file);
return FileVisitResult.CONTINUE;
}
+
+ // For this special case we may have changed mime type from the file's metadata.
+ // This is safe because mime_type cannot be changed outside of scanning.
+ if (sameMetadata
+ && "video/mp4".equalsIgnoreCase(actualMimeType)
+ && "audio/mp4".equalsIgnoreCase(mimeType)) {
+ if (LOGV) Log.v(TAG, "Skipping unchanged video/audio " + file);
+ return FileVisitResult.CONTINUE;
+ }
}
+
+ // Since we allow top-level mime type to be customised, we need to do this early
+ // on, so the file is later scanned as the appropriate type (otherwise, this
+ // audio filed would be scanned as video and it would be missing the correct
+ // metadata).
+ actualMimeType = updateM4aMimeType(realFile, actualMimeType);
+ actualMediaType =
+ mediaTypeFromMimeType(realFile, actualMimeType, actualMediaType);
} finally {
Trace.endSection();
}
@@ -777,6 +788,65 @@
return FileVisitResult.CONTINUE;
}
+ private boolean isSame(
+ boolean hasSameMetadata,
+ String actualMimeType,
+ int actualMediaType,
+ String mimeType,
+ int mediaType) {
+ boolean sameMimeType =
+ mimeType == null
+ ? actualMimeType == null
+ : mimeType.equalsIgnoreCase(actualMimeType);
+ boolean sameMediaType = (actualMediaType == mediaType);
+ return hasSameMetadata && sameMediaType && sameMimeType;
+ }
+
+ private int mediaTypeFromMimeType(
+ File file, String mimeType, int defaultMediaType) {
+ if (mimeType != null) {
+ return resolveMediaTypeFromFilePath(
+ file, mimeType, /*isHidden*/ mHiddenDirCount > 0);
+ }
+ return defaultMediaType;
+ }
+
+ private boolean hasSameMetadata(
+ BasicFileAttributes attrs, File realFile, boolean isPendingFromFuse, Cursor c) {
+ final long dateModified = c.getLong(1);
+ final boolean sameTime = (lastModifiedTime(realFile, attrs) == dateModified);
+
+ final long size = c.getLong(2);
+ final boolean sameSize = (attrs.size() == size);
+
+ final boolean isScanned =
+ c.getInt(6) == FileColumns._MODIFIER_MEDIA_SCAN;
+
+ return sameTime && sameSize && !isPendingFromFuse && isScanned;
+ }
+
+ /**
+ * For this one very narrow case, we allow mime types to be customised when the top levels
+ * differ. This opens the given file, so avoid calling unless really necessary. This
+ * returns the defaultMimeType for non-m4a files or if opening the file throws an exception.
+ */
+ private String updateM4aMimeType(File file, String defaultMimeType) {
+ if ("video/mp4".equalsIgnoreCase(defaultMimeType)) {
+ try (
+ FileInputStream is = new FileInputStream(file);
+ MediaMetadataRetriever mmr = new MediaMetadataRetriever()) {
+ mmr.setDataSource(is.getFD());
+ String refinedMimeType = mmr.extractMetadata(METADATA_KEY_MIMETYPE);
+ if ("audio/mp4".equalsIgnoreCase(refinedMimeType)) {
+ return refinedMimeType;
+ }
+ } catch (Exception e) {
+ return defaultMimeType;
+ }
+ }
+ return defaultMimeType;
+ }
+
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc)
throws IOException {
@@ -1537,12 +1607,6 @@
if (fileMimeType.regionMatches(true, 0, refinedMimeType, 0, refinedSplit + 1)) {
return Optional.of(refinedMimeType);
- } else if ("video/mp4".equalsIgnoreCase(fileMimeType)
- && "audio/mp4".equalsIgnoreCase(refinedMimeType)) {
- // We normally only allow MIME types to be customized when the
- // top-level type agrees, but this one very narrow case is added to
- // support a music service that was writing "m4a" files as "mp4".
- return Optional.of(refinedMimeType);
} else {
return Optional.empty();
}
diff --git a/tests/src/com/android/providers/media/scan/ModernMediaScannerTest.java b/tests/src/com/android/providers/media/scan/ModernMediaScannerTest.java
index 6102464..753547b 100644
--- a/tests/src/com/android/providers/media/scan/ModernMediaScannerTest.java
+++ b/tests/src/com/android/providers/media/scan/ModernMediaScannerTest.java
@@ -146,21 +146,10 @@
assertTrue(parseOptionalMimeType("image/png", "image/x-shiny").isPresent());
assertEquals("image/x-shiny",
parseOptionalMimeType("image/png", "image/x-shiny").get());
- }
- @Test
- public void testOverrideMimeType_148316354() throws Exception {
// Radical file type shifting isn't allowed
assertEquals(Optional.empty(),
parseOptionalMimeType("video/mp4", "audio/mpeg"));
-
- // One specific narrow type of shift (mp4 -> m4a) is allowed
- assertEquals(Optional.of("audio/mp4"),
- parseOptionalMimeType("video/mp4", "audio/mp4"));
-
- // The other direction isn't allowed
- assertEquals(Optional.empty(),
- parseOptionalMimeType("audio/mp4", "video/mp4"));
}
@Test
@@ -900,8 +889,11 @@
.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null, null)) {
assertEquals(1, cursor.getCount());
cursor.moveToFirst();
- assertEquals("audio/mp4",
- cursor.getString(cursor.getColumnIndex(MediaColumns.MIME_TYPE)));
+ assertThat(cursor.getString(cursor.getColumnIndex(MediaColumns.MIME_TYPE)))
+ .isEqualTo("audio/mp4");
+ assertThat(cursor.getString(cursor.getColumnIndex(AudioColumns.IS_MUSIC)))
+ .isEqualTo("1");
+
}
}