Merge changes I72f39d3e,Id86d7f77,I3a1b3f5e
* changes:
Support lazy transcode via ContentResolver
Improve FUSE file open JNI check
Add fields to FileLookupResult and improve node invalidation logic
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index 406bb2a..2390047 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -248,19 +248,21 @@
* These directory names aren't declared in Environment as final variables, and so we need to
* have the same values in separate final variables in order to have them considered constant
* expressions.
+ * These directory names are intentionally in lower case to ease the case insensitive path
+ * comparison.
*/
- private static final String DIRECTORY_MUSIC = "Music";
- private static final String DIRECTORY_PODCASTS = "Podcasts";
- private static final String DIRECTORY_RINGTONES = "Ringtones";
- private static final String DIRECTORY_ALARMS = "Alarms";
- private static final String DIRECTORY_NOTIFICATIONS = "Notifications";
- private static final String DIRECTORY_PICTURES = "Pictures";
- private static final String DIRECTORY_MOVIES = "Movies";
- private static final String DIRECTORY_DOWNLOADS = "Download";
- private static final String DIRECTORY_DCIM = "DCIM";
- private static final String DIRECTORY_DOCUMENTS = "Documents";
- private static final String DIRECTORY_AUDIOBOOKS = "Audiobooks";
- private static final String DIRECTORY_ANDROID = "Android";
+ private static final String DIRECTORY_MUSIC_LOWER_CASE = "music";
+ private static final String DIRECTORY_PODCASTS_LOWER_CASE = "podcasts";
+ private static final String DIRECTORY_RINGTONES_LOWER_CASE = "ringtones";
+ private static final String DIRECTORY_ALARMS_LOWER_CASE = "alarms";
+ private static final String DIRECTORY_NOTIFICATIONS_LOWER_CASE = "notifications";
+ private static final String DIRECTORY_PICTURES_LOWER_CASE = "pictures";
+ private static final String DIRECTORY_MOVIES_LOWER_CASE = "movies";
+ private static final String DIRECTORY_DOWNLOADS_LOWER_CASE = "download";
+ private static final String DIRECTORY_DCIM_LOWER_CASE = "dcim";
+ private static final String DIRECTORY_DOCUMENTS_LOWER_CASE = "documents";
+ private static final String DIRECTORY_AUDIOBOOKS_LOWER_CASE = "audiobooks";
+ private static final String DIRECTORY_ANDROID_LOWER_CASE = "android";
private static final String DIRECTORY_MEDIA = "media";
private static final String DIRECTORY_THUMBNAILS = ".thumbnails";
@@ -2034,12 +2036,12 @@
private ArrayList<String> getIncludedDefaultDirectories() {
final ArrayList<String> includedDefaultDirs = new ArrayList<>();
if (checkCallingPermissionVideo(/*forWrite*/ true, null)) {
- includedDefaultDirs.add(DIRECTORY_DCIM);
- includedDefaultDirs.add(DIRECTORY_PICTURES);
- includedDefaultDirs.add(DIRECTORY_MOVIES);
+ includedDefaultDirs.add(Environment.DIRECTORY_DCIM);
+ includedDefaultDirs.add(Environment.DIRECTORY_PICTURES);
+ includedDefaultDirs.add(Environment.DIRECTORY_MOVIES);
} else if (checkCallingPermissionImages(/*forWrite*/ true, null)) {
- includedDefaultDirs.add(DIRECTORY_DCIM);
- includedDefaultDirs.add(DIRECTORY_PICTURES);
+ includedDefaultDirs.add(Environment.DIRECTORY_DCIM);
+ includedDefaultDirs.add(Environment.DIRECTORY_PICTURES);
}
return includedDefaultDirs;
}
@@ -2417,10 +2419,11 @@
return OsConstants.EPERM;
}
+ // TODO(b/177049768): We shouldn't use getExternalStorageDirectory for these checks.
final File directoryAndroid = new File(Environment.getExternalStorageDirectory(),
- DIRECTORY_ANDROID);
+ DIRECTORY_ANDROID_LOWER_CASE);
final File directoryAndroidMedia = new File(directoryAndroid, DIRECTORY_MEDIA);
- if (directoryAndroidMedia.getAbsolutePath().equals(oldPath)) {
+ if (directoryAndroidMedia.getAbsolutePath().equalsIgnoreCase(oldPath)) {
// Don't allow renaming 'Android/media' directory.
// Android/[data|obb] are bind mounted and these paths don't go through FUSE.
Log.e(TAG, errorMessage + oldPath + " is a default folder in app external "
@@ -2999,7 +3002,7 @@
final String[] relativePath = values.getAsString(MediaColumns.RELATIVE_PATH).split("/");
final String primary = (relativePath.length > 0) ? relativePath[0] : null;
if (!validPath) {
- validPath = allowedPrimary.contains(primary);
+ validPath = containsIgnoreCase(allowedPrimary, primary);
}
// Next, consider allowing paths when referencing a related item
@@ -3138,7 +3141,7 @@
final String secondary =
(relativePathSegments.length > 1) ? relativePathSegments[1] : "";
- if (DIRECTORY_ANDROID.equalsIgnoreCase(primary)
+ if (DIRECTORY_ANDROID_LOWER_CASE.equalsIgnoreCase(primary)
&& PRIVATE_SUBDIRECTORIES_ANDROID.contains(
secondary.toLowerCase(Locale.ROOT))) {
throw new IllegalArgumentException(
@@ -5448,9 +5451,10 @@
private List<File> getThumbnailDirectories(String volumeName) throws FileNotFoundException {
final File volumePath = getVolumePath(volumeName);
return Arrays.asList(
- FileUtils.buildPath(volumePath, DIRECTORY_MUSIC, DIRECTORY_THUMBNAILS),
- FileUtils.buildPath(volumePath, DIRECTORY_MOVIES, DIRECTORY_THUMBNAILS),
- FileUtils.buildPath(volumePath, DIRECTORY_PICTURES, DIRECTORY_THUMBNAILS));
+ FileUtils.buildPath(volumePath, Environment.DIRECTORY_MUSIC, DIRECTORY_THUMBNAILS),
+ FileUtils.buildPath(volumePath, Environment.DIRECTORY_MOVIES, DIRECTORY_THUMBNAILS),
+ FileUtils.buildPath(volumePath, Environment.DIRECTORY_PICTURES,
+ DIRECTORY_THUMBNAILS));
}
private void invalidateThumbnails(Uri uri) {
@@ -7350,21 +7354,23 @@
throw new IllegalStateException("Couldn't get volume name for " + filePath);
}
Uri uri = Files.getContentUri(volName);
- final String topLevelDir = extractTopLevelDir(filePath);
+ String topLevelDir = extractTopLevelDir(filePath);
if (topLevelDir == null) {
// If the file path doesn't match the external storage directory, we use the files URI
// as default and let #insert enforce the restrictions
return uri;
}
+ topLevelDir = topLevelDir.toLowerCase(Locale.ROOT);
+
switch (topLevelDir) {
- case DIRECTORY_PODCASTS:
- case DIRECTORY_RINGTONES:
- case DIRECTORY_ALARMS:
- case DIRECTORY_NOTIFICATIONS:
- case DIRECTORY_AUDIOBOOKS:
+ case DIRECTORY_PODCASTS_LOWER_CASE:
+ case DIRECTORY_RINGTONES_LOWER_CASE:
+ case DIRECTORY_ALARMS_LOWER_CASE:
+ case DIRECTORY_NOTIFICATIONS_LOWER_CASE:
+ case DIRECTORY_AUDIOBOOKS_LOWER_CASE:
uri = Audio.Media.getContentUri(volName);
break;
- case DIRECTORY_MUSIC:
+ case DIRECTORY_MUSIC_LOWER_CASE:
if (MimeUtils.isPlaylistMimeType(mimeType)) {
uri = Audio.Playlists.getContentUri(volName);
} else if (!MimeUtils.isSubtitleMimeType(mimeType)) {
@@ -7372,7 +7378,7 @@
uri = Audio.Media.getContentUri(volName);
}
break;
- case DIRECTORY_MOVIES:
+ case DIRECTORY_MOVIES_LOWER_CASE:
if (MimeUtils.isPlaylistMimeType(mimeType)) {
uri = Audio.Playlists.getContentUri(volName);
} else if (!MimeUtils.isSubtitleMimeType(mimeType)) {
@@ -7380,16 +7386,16 @@
uri = Video.Media.getContentUri(volName);
}
break;
- case DIRECTORY_DCIM:
- case DIRECTORY_PICTURES:
+ case DIRECTORY_DCIM_LOWER_CASE:
+ case DIRECTORY_PICTURES_LOWER_CASE:
if (MimeUtils.isImageMimeType(mimeType)) {
uri = Images.Media.getContentUri(volName);
} else {
uri = Video.Media.getContentUri(volName);
}
break;
- case DIRECTORY_DOWNLOADS:
- case DIRECTORY_DOCUMENTS:
+ case DIRECTORY_DOWNLOADS_LOWER_CASE:
+ case DIRECTORY_DOCUMENTS_LOWER_CASE:
break;
default:
Log.w(TAG, "Forgot to handle a top level directory in getContentUriForFile?");
@@ -7397,6 +7403,15 @@
return uri;
}
+ private boolean containsIgnoreCase(@Nullable List<String> stringsList, @Nullable String item) {
+ if (item == null || stringsList == null) return false;
+
+ for (String current : stringsList) {
+ if (item.equalsIgnoreCase(current)) return true;
+ }
+ return false;
+ }
+
private boolean fileExists(@NonNull String absolutePath) {
// We don't care about specific columns in the match,
// we just want to check IF there's a match
diff --git a/src/com/android/providers/media/TranscodeHelper.java b/src/com/android/providers/media/TranscodeHelper.java
index e8d050e..95d6ba0 100644
--- a/src/com/android/providers/media/TranscodeHelper.java
+++ b/src/com/android/providers/media/TranscodeHelper.java
@@ -39,7 +39,6 @@
import android.content.ContentValues;
import android.content.Context;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManager.Property;
import android.content.res.XmlResourceParser;
import android.database.Cursor;
@@ -85,6 +84,7 @@
import java.io.RandomAccessFile;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Locale;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -487,8 +487,9 @@
final String cameraRelativePath =
String.format("%s/%s/", Environment.DIRECTORY_DCIM, DIRECTORY_CAMERA);
- return !isTranscodeFile(path) && name.endsWith(".mp4") &&
- cameraRelativePath.equalsIgnoreCase(FileUtils.extractRelativePath(path));
+ return !isTranscodeFile(path) && name.toLowerCase(Locale.ROOT).endsWith(".mp4")
+ && path.startsWith("/storage/emulated/")
+ && cameraRelativePath.equalsIgnoreCase(FileUtils.extractRelativePath(path));
}
/**
@@ -517,13 +518,16 @@
identity.setApplicationMediaCapabilitiesFlags(capabilitiesToFlags(capability));
return capability.isVideoMimeTypeSupported(MediaFormat.MIMETYPE_VIDEO_HEVC);
- } catch (NameNotFoundException | UnsupportedOperationException e) {
+ } catch (PackageManager.NameNotFoundException
+ | ApplicationMediaCapabilities.FormatNotFoundException
+ | UnsupportedOperationException e) {
return false;
}
}
@ApplicationMediaCapabilitiesFlags
- private int capabilitiesToFlags(ApplicationMediaCapabilities capability) {
+ private int capabilitiesToFlags(ApplicationMediaCapabilities capability)
+ throws ApplicationMediaCapabilities.FormatNotFoundException {
int flags = 0;
if (capability.isVideoMimeTypeSupported(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
flags |= FLAG_HEVC;
@@ -638,7 +642,8 @@
FileColumns._VIDEO_CODEC_TYPE,
MediaStore.MediaColumns.WIDTH,
MediaStore.MediaColumns.HEIGHT,
- MediaStore.MediaColumns.BITRATE
+ MediaStore.MediaColumns.BITRATE,
+ MediaStore.MediaColumns.CAPTURE_FRAMERATE
};
try (Cursor c = queryFileForTranscode(path, resolverInfoProjection)) {
if (c != null && c.moveToNext()) {
@@ -646,19 +651,22 @@
int width = c.getInt(1);
int height = c.getInt(2);
int bitRate = c.getInt(3);
+ float framerate = c.getFloat(4);
// TODO(b/169849854): Get this info from Manifest, for now if app got here it
// definitely doesn't support hevc
ApplicationMediaCapabilities capability =
new ApplicationMediaCapabilities.Builder().build();
+ MediaFormat sourceFormat = MediaFormat.createVideoFormat(
+ codecType, width, height);
+ sourceFormat.setFloat(MediaFormat.KEY_FRAME_RATE, framerate);
MediaFormatResolver resolver = new MediaFormatResolver()
- .setSourceVideoFormatHint(MediaFormat.createVideoFormat(
- codecType, width, height))
+ .setSourceVideoFormatHint(sourceFormat)
.setClientCapabilities(capability);
- MediaFormat format = resolver.resolveVideoFormat();
- format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
+ MediaFormat resolvedFormat = resolver.resolveVideoFormat();
+ resolvedFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
- return format;
+ return resolvedFormat;
}
}
throw new IllegalStateException("Couldn't get video format info from database for " + path);
diff --git a/tests/client/src/com/android/providers/media/client/LegacyProviderMigrationTest.java b/tests/client/src/com/android/providers/media/client/LegacyProviderMigrationTest.java
index 9d012f6..8014b35 100644
--- a/tests/client/src/com/android/providers/media/client/LegacyProviderMigrationTest.java
+++ b/tests/client/src/com/android/providers/media/client/LegacyProviderMigrationTest.java
@@ -53,6 +53,7 @@
import android.webkit.MimeTypeMap;
import androidx.annotation.NonNull;
+import androidx.test.filters.FlakyTest;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
@@ -82,6 +83,7 @@
* {@link MediaColumns#IS_FAVORITE} should be retained.
*/
@RunWith(AndroidJUnit4.class)
+@FlakyTest(bugId = 176977253)
public class LegacyProviderMigrationTest {
private static final String TAG = "LegacyTest";
diff --git a/tests/client/src/com/android/providers/media/client/PerformanceTest.java b/tests/client/src/com/android/providers/media/client/PerformanceTest.java
index 5f685cc..30c815d 100644
--- a/tests/client/src/com/android/providers/media/client/PerformanceTest.java
+++ b/tests/client/src/com/android/providers/media/client/PerformanceTest.java
@@ -88,15 +88,15 @@
// Verify that core actions finished within 30ms deadline
final long actionDeadline = 30;
- assertTrue(timers.actionInsert.getAverageDurationMillis() < actionDeadline);
- assertTrue(timers.actionUpdate.getAverageDurationMillis() < actionDeadline);
- assertTrue(timers.actionDelete.getAverageDurationMillis() < actionDeadline);
+ assertThat(timers.actionInsert.getAverageDurationMillis()).isLessThan(actionDeadline);
+ assertThat(timers.actionUpdate.getAverageDurationMillis()).isLessThan(actionDeadline);
+ assertThat(timers.actionDelete.getAverageDurationMillis()).isLessThan(actionDeadline);
// Verify that external notifications finished within 30ms deadline
final long notifyDeadline = 30;
- assertTrue(timers.notifyInsert.getAverageDurationMillis() < notifyDeadline);
- assertTrue(timers.notifyUpdate.getAverageDurationMillis() < notifyDeadline);
- assertTrue(timers.notifyDelete.getAverageDurationMillis() < notifyDeadline);
+ assertThat(timers.notifyInsert.getAverageDurationMillis()).isLessThan(notifyDeadline);
+ assertThat(timers.notifyUpdate.getAverageDurationMillis()).isLessThan(notifyDeadline);
+ assertThat(timers.notifyDelete.getAverageDurationMillis()).isLessThan(notifyDeadline);
}
private void doSingle(Uri collection, Timers timers) throws Exception {
@@ -167,15 +167,15 @@
// Verify that core actions finished within 30ms deadline
final long actionDeadline = 30 * COUNT_BULK;
- assertTrue(timers.actionInsert.getAverageDurationMillis() < actionDeadline);
- assertTrue(timers.actionUpdate.getAverageDurationMillis() < actionDeadline);
- assertTrue(timers.actionDelete.getAverageDurationMillis() < actionDeadline);
+ assertThat(timers.actionInsert.getAverageDurationMillis()).isLessThan(actionDeadline);
+ assertThat(timers.actionUpdate.getAverageDurationMillis()).isLessThan(actionDeadline);
+ assertThat(timers.actionDelete.getAverageDurationMillis()).isLessThan(actionDeadline);
// Verify that external notifications finished within 100ms deadline
final long notifyDeadline = 100;
- assertTrue(timers.notifyInsert.getAverageDurationMillis() < notifyDeadline);
- assertTrue(timers.notifyUpdate.getAverageDurationMillis() < notifyDeadline);
- assertTrue(timers.notifyDelete.getAverageDurationMillis() < notifyDeadline);
+ assertThat(timers.notifyInsert.getAverageDurationMillis()).isLessThan(notifyDeadline);
+ assertThat(timers.notifyUpdate.getAverageDurationMillis()).isLessThan(notifyDeadline);
+ assertThat(timers.notifyDelete.getAverageDurationMillis()).isLessThan(notifyDeadline);
}
private void doBulk(Uri collection, Timers timers) throws Exception {