summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Dipankar Bhardwaj <dipankarb@google.com> 2025-03-13 12:03:31 -0700
committer Android (Google) Code Review <android-gerrit@google.com> 2025-03-13 12:03:31 -0700
commit69c71c18f7143fffc0559bd46491876d34245bd7 (patch)
treebb112688f5d18def0530caa68ab85df7c545fa02
parentb7f549960eff6450f38680215e453cf7368278dc (diff)
parent9a831d5492fb663b95b49693de14287988ccad30 (diff)
Merge "Add API to bulk update oem_metadata" into main
-rw-r--r--AndroidManifest.xml7
-rw-r--r--apex/framework/api/system-current.txt2
-rw-r--r--apex/framework/java/android/provider/MediaStore.java35
-rw-r--r--mediaprovider_flags.aconfig9
-rw-r--r--src/com/android/providers/media/LocalCallingIdentity.java12
-rw-r--r--src/com/android/providers/media/MediaProvider.java93
-rw-r--r--src/com/android/providers/media/scan/LegacyMediaScanner.java6
-rw-r--r--src/com/android/providers/media/scan/MediaScanner.java6
-rw-r--r--src/com/android/providers/media/scan/ModernMediaScanner.java3
-rw-r--r--src/com/android/providers/media/util/PermissionUtils.java12
-rw-r--r--tests/src/com/android/providers/media/IsolatedContext.java5
-rw-r--r--tests/src/com/android/providers/media/oemmetadataservices/OemMetadataServiceTest.java150
-rw-r--r--tests/src/com/android/providers/media/oemmetadataservices/TestOemMetadataService.java25
13 files changed, 350 insertions, 15 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index e4249ce1c..a19e718b5 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -54,6 +54,10 @@
<uses-permission
android:name="com.android.providers.media.permission.ACCESS_OEM_METADATA" />
+ <!-- Permission required to update OEM metadata. Declared by us -->
+ <uses-permission
+ android:name="com.android.providers.media.permission.UPDATE_OEM_METADATA" />
+
<!-- Permission required to bind to OemMetadataService -->
<uses-permission android:name="com.android.providers.media.permission.BIND_OEM_METADATA_SERVICE" />
@@ -74,6 +78,9 @@
<permission android:name="com.android.providers.media.permission.ACCESS_OEM_METADATA"
android:protectionLevel="signature|privileged" />
+ <permission android:name="com.android.providers.media.permission.UPDATE_OEM_METADATA"
+ android:protectionLevel="signature|privileged" />
+
<permission android:name="com.android.providers.media.permission.BIND_OEM_METADATA_SERVICE"
android:protectionLevel="signature"/>
diff --git a/apex/framework/api/system-current.txt b/apex/framework/api/system-current.txt
index 1d11267f4..f002605a1 100644
--- a/apex/framework/api/system-current.txt
+++ b/apex/framework/api/system-current.txt
@@ -64,6 +64,7 @@ package android.provider {
}
public final class MediaStore {
+ method @FlaggedApi("com.android.providers.media.flags.enable_oem_metadata_update") public static void bulkUpdateOemMetadataInNextScan(@NonNull android.content.Context);
method @NonNull public static android.net.Uri rewriteToLegacy(@NonNull android.net.Uri);
method @NonNull @WorkerThread public static android.net.Uri scanFile(@NonNull android.content.ContentResolver, @NonNull java.io.File);
method @WorkerThread public static void scanVolume(@NonNull android.content.ContentResolver, @NonNull String);
@@ -72,6 +73,7 @@ package android.provider {
field public static final String AUTHORITY_LEGACY = "media_legacy";
field @NonNull public static final android.net.Uri AUTHORITY_LEGACY_URI;
field public static final String QUERY_ARG_DEFER_SCAN = "android:query-arg-defer-scan";
+ field @FlaggedApi("com.android.providers.media.flags.enable_oem_metadata_update") public static final String UPDATE_OEM_METADATA_PERMISSION = "com.android.providers.media.permission.UPDATE_OEM_METADATA";
}
@FlaggedApi("com.android.providers.media.flags.enable_oem_metadata") public abstract class OemMetadataService extends android.app.Service {
diff --git a/apex/framework/java/android/provider/MediaStore.java b/apex/framework/java/android/provider/MediaStore.java
index c9ee81196..1e64cbd4e 100644
--- a/apex/framework/java/android/provider/MediaStore.java
+++ b/apex/framework/java/android/provider/MediaStore.java
@@ -304,6 +304,9 @@ public final class MediaStore {
"revoke_all_media_grants_for_package";
/** @hide */
+ public static final String BULK_UPDATE_OEM_METADATA_CALL = "bulk_update_oem_metadata";
+
+ /** @hide */
public static final String OPEN_FILE_CALL =
"open_file_call";
@@ -1342,6 +1345,16 @@ public final class MediaStore {
public static final String ACCESS_OEM_METADATA_PERMISSION =
"com.android.providers.media.permission.ACCESS_OEM_METADATA";
+ /**
+ * Permission that grants ability to trigger update of {@link MediaColumns#OEM_METADATA}.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_OEM_METADATA_UPDATE)
+ @SystemApi
+ public static final String UPDATE_OEM_METADATA_PERMISSION =
+ "com.android.providers.media.permission.UPDATE_OEM_METADATA";
+
/** @hide */
@IntDef(flag = true, prefix = { "MATCH_" }, value = {
MATCH_DEFAULT,
@@ -5734,4 +5747,26 @@ public final class MediaStore {
throw e.rethrowAsRuntimeException();
}
}
+
+ /**
+ * Allows bulk update of {@link MediaColumns#OEM_METADATA} column in next scan.
+ * Requires calling package to hold {@link UPDATE_OEM_METADATA_PERMISSION} permission. Updates
+ * {@link MediaColumns#OEM_METADATA} to NULL for OEM supported media files and re-fetch
+ * the latest values in the next scan.
+ * Caller can enforce file/volume scan after this to update MediaStore with the latest OEM
+ * metadata. If not done, next scan by MediaStore will fetch and update the latest data.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_ENABLE_OEM_METADATA_UPDATE)
+ @SystemApi
+ public static void bulkUpdateOemMetadataInNextScan(@NonNull Context context) {
+ final ContentResolver resolver = context.getContentResolver();
+ try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) {
+ final Bundle extras = new Bundle();
+ client.call(BULK_UPDATE_OEM_METADATA_CALL, /* arg= */ null, /* extras= */ extras);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
}
diff --git a/mediaprovider_flags.aconfig b/mediaprovider_flags.aconfig
index 7a6c28299..4b7871002 100644
--- a/mediaprovider_flags.aconfig
+++ b/mediaprovider_flags.aconfig
@@ -282,3 +282,12 @@ flag {
is_fixed_read_only: true
bug: "376910932"
}
+
+flag {
+ name: "enable_oem_metadata_update"
+ is_exported: true
+ namespace: "mediaprovider"
+ description: "Controls ability to update oem_metadata column"
+ bug: "352528913"
+ is_fixed_read_only: true
+}
diff --git a/src/com/android/providers/media/LocalCallingIdentity.java b/src/com/android/providers/media/LocalCallingIdentity.java
index b85cdc881..8074656bd 100644
--- a/src/com/android/providers/media/LocalCallingIdentity.java
+++ b/src/com/android/providers/media/LocalCallingIdentity.java
@@ -35,6 +35,7 @@ import static com.android.providers.media.util.PermissionUtils.checkPermissionRe
import static com.android.providers.media.util.PermissionUtils.checkPermissionReadVisualUserSelected;
import static com.android.providers.media.util.PermissionUtils.checkPermissionSelf;
import static com.android.providers.media.util.PermissionUtils.checkPermissionShell;
+import static com.android.providers.media.util.PermissionUtils.checkPermissionUpdateOemMetadata;
import static com.android.providers.media.util.PermissionUtils.checkPermissionWriteAudio;
import static com.android.providers.media.util.PermissionUtils.checkPermissionWriteImages;
import static com.android.providers.media.util.PermissionUtils.checkPermissionWriteStorage;
@@ -353,6 +354,7 @@ public class LocalCallingIdentity {
public static final int PERMISSION_QUERY_ALL_PACKAGES = 1 << 28;
public static final int PERMISSION_ACCESS_MEDIA_OWNER_PACKAGE_NAME = 1 << 29;
public static final int PERMISSION_ACCESS_OEM_METADATA = 1 << 30;
+ public static final int PERMISSION_UPDATE_OEM_METADATA = 1 << 31;
private volatile int hasPermission;
private volatile int hasPermissionResolved;
@@ -452,6 +454,9 @@ public class LocalCallingIdentity {
case PERMISSION_ACCESS_OEM_METADATA:
return checkPermissionAccessOemMetadata(context, pid, uid, getPackageName(),
attributionTag);
+ case PERMISSION_UPDATE_OEM_METADATA:
+ return checkPermissionUpdateOemMetadata(context, pid, uid, getPackageName(),
+ attributionTag);
default:
return false;
}
@@ -748,6 +753,13 @@ public class LocalCallingIdentity {
}
/**
+ * Returns {@code true} if this package has permission to update oem_metadata of any media.
+ */
+ public boolean checkCallingPermissionToUpdateOemMetadata() {
+ return hasPermission(PERMISSION_UPDATE_OEM_METADATA);
+ }
+
+ /**
* Returns {@code true} if this package is a legacy app and has read permission
*/
public boolean isCallingPackageLegacyRead() {
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index 51efa9210..5336b089f 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -34,6 +34,7 @@ import static android.provider.CloudMediaProviderContract.METHOD_GET_ASYNC_CONTE
import static android.provider.MediaStore.EXTRA_IS_STABLE_URIS_ENABLED;
import static android.provider.MediaStore.EXTRA_OPEN_ASSET_FILE_REQUEST;
import static android.provider.MediaStore.EXTRA_OPEN_FILE_REQUEST;
+import static android.provider.MediaStore.EXTRA_URI_LIST;
import static android.provider.MediaStore.Files.FileColumns.MEDIA_TYPE;
import static android.provider.MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE;
import static android.provider.MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO;
@@ -5849,6 +5850,12 @@ public class MediaProvider extends ContentProvider {
}
}
+
+ // Enforce oem_metadata permission if caller is not MediaProvider
+ if (Flags.enableOemMetadataUpdate() && initialValues.containsKey(OEM_METADATA)) {
+ enforcePermissionCheckForOemMetadataUpdate();
+ }
+
long rowId = -1;
Uri newUri = null;
@@ -7306,11 +7313,80 @@ public class MediaProvider extends ContentProvider {
removeRecoveryData();
return new Bundle();
}
+ case MediaStore.BULK_UPDATE_OEM_METADATA_CALL: {
+ callForBulkUpdateOemMetadataColumn();
+ return new Bundle();
+ }
default:
throw new UnsupportedOperationException("Unsupported call: " + method);
}
}
+ private void callForBulkUpdateOemMetadataColumn() {
+ if (!Flags.enableOemMetadataUpdate()) {
+ return;
+ }
+
+ enforcePermissionCheckForOemMetadataUpdate();
+ Set<String> oemSupportedMimeTypes = mMediaScanner.getOemSupportedMimeTypes();
+ if (oemSupportedMimeTypes == null || oemSupportedMimeTypes.isEmpty()) {
+ // Nothing to update
+ return;
+ }
+
+ // Get media types to update rows based on media type
+ Set<Integer> mediaTypesToBeUpdated = new HashSet<>();
+ for (String mimeType : oemSupportedMimeTypes) {
+ // Convert to media type to avoid using like clause on mime types to protect against
+ // SQL injection
+ mediaTypesToBeUpdated.add(MimeUtils.resolveMediaType(mimeType));
+ }
+
+ if (mediaTypesToBeUpdated.isEmpty()) {
+ // For invalid mime types, do not bother
+ return;
+ }
+
+ final LocalCallingIdentity token = clearLocalCallingIdentity();
+ try {
+ ContentValues values = new ContentValues();
+ values.putNull(OEM_METADATA);
+ // Mark _modifier as _MODIFIER_CR to allow metadata update on next scan. This
+ // is explicitly required when calling update with MediaProvider identity
+ values.put(FileColumns._MODIFIER, FileColumns._MODIFIER_CR);
+ Bundle extras = new Bundle();
+ extras.putString(ContentResolver.QUERY_ARG_SQL_SELECTION,
+ appendMediaTypeClause(mediaTypesToBeUpdated));
+ Log.v(TAG, "Trigger bulk update of OEM metadata");
+ update(MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL), values, extras);
+ } finally {
+ restoreLocalCallingIdentity(token);
+ }
+ }
+
+ private String appendMediaTypeClause(Set<Integer> mediaTypesToBeUpdated) {
+ List<String> whereMediaTypesCondition = new ArrayList<String>();
+ for (Integer mediaType : mediaTypesToBeUpdated) {
+ whereMediaTypesCondition.add(
+ String.format(Locale.ROOT, "%s=%d", MEDIA_TYPE, mediaType));
+ }
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("(");
+ sb.append(TextUtils.join(" OR ", whereMediaTypesCondition));
+ sb.append(")");
+ return sb.toString();
+ }
+
+ @VisibleForTesting
+ protected void enforcePermissionCheckForOemMetadataUpdate() {
+ if (!isCallingPackageSelf()
+ && !mCallingIdentity.get().checkCallingPermissionToUpdateOemMetadata()) {
+ throw new SecurityException(
+ "Calling package does not have permission to update OEM metadata");
+ }
+ }
+
@Nullable
private Bundle getResultForRevokeReadGrantForPackage(Bundle extras) {
final int caller = Binder.getCallingUid();
@@ -7353,7 +7429,7 @@ public class MediaProvider extends ContentProvider {
userId = uidToUserId(packageUid);
// Uris are not a requirement for revoke all call
if (!isCallForRevokeAll) {
- uris = extras.getParcelableArrayList(MediaStore.EXTRA_URI_LIST);
+ uris = extras.getParcelableArrayList(EXTRA_URI_LIST);
}
} else {
// All other callers are unauthorized.
@@ -7630,14 +7706,14 @@ public class MediaProvider extends ContentProvider {
@NotNull
private Bundle getResultForGetRedactedMediaUriList(Bundle extras) {
- final List<Uri> uris = extras.getParcelableArrayList(MediaStore.EXTRA_URI_LIST);
+ final List<Uri> uris = extras.getParcelableArrayList(EXTRA_URI_LIST);
// NOTE: It is ok to update the DB and return a redacted URI for the cases when
// the user code only has read access, hence we don't check for write permission.
enforceCallingPermission(uris, false);
final LocalCallingIdentity token = clearLocalCallingIdentity();
try {
final Bundle res = new Bundle();
- res.putParcelableArrayList(MediaStore.EXTRA_URI_LIST,
+ res.putParcelableArrayList(EXTRA_URI_LIST,
(ArrayList<? extends Parcelable>) getRedactedUri(uris));
return res;
} finally {
@@ -7668,12 +7744,12 @@ public class MediaProvider extends ContentProvider {
} else if (checkPermissionSelf(caller) || isCallerPhotoPicker()) {
// If the caller is MediaProvider the accepted parameters are EXTRA_URI_LIST
// and EXTRA_UID.
- if (!extras.containsKey(MediaStore.EXTRA_URI_LIST)
+ if (!extras.containsKey(EXTRA_URI_LIST)
&& !extras.containsKey(Intent.EXTRA_UID)) {
throw new IllegalArgumentException(
"Missing required extras arguments: EXTRA_URI_LIST or" + " EXTRA_UID");
}
- uris = extras.getParcelableArrayList(MediaStore.EXTRA_URI_LIST);
+ uris = extras.getParcelableArrayList(EXTRA_URI_LIST);
final PackageManager pm = getContext().getPackageManager();
final int packageUid = extras.getInt(Intent.EXTRA_UID);
final String[] packages = pm.getPackagesForUid(packageUid);
@@ -8753,6 +8829,11 @@ public class MediaProvider extends ContentProvider {
initialValues.remove(FileColumns.GENERATION_MODIFIED);
}
+ // Enforce oem_metadata permission if caller is not MediaProvider
+ if (Flags.enableOemMetadataUpdate() && initialValues.containsKey(OEM_METADATA)) {
+ enforcePermissionCheckForOemMetadataUpdate();
+ }
+
if (!isCallingPackageSelf()) {
Trace.beginSection("MP.filter");
@@ -12058,6 +12139,8 @@ public class MediaProvider extends ContentProvider {
sMutableColumns.add(MediaStore.Files.FileColumns.MIME_TYPE);
sMutableColumns.add(MediaStore.Files.FileColumns.MEDIA_TYPE);
+
+ sMutableColumns.add(MediaStore.Files.FileColumns.OEM_METADATA);
}
/**
diff --git a/src/com/android/providers/media/scan/LegacyMediaScanner.java b/src/com/android/providers/media/scan/LegacyMediaScanner.java
index d73dda584..39bffe2f5 100644
--- a/src/com/android/providers/media/scan/LegacyMediaScanner.java
+++ b/src/com/android/providers/media/scan/LegacyMediaScanner.java
@@ -22,6 +22,7 @@ import android.net.Uri;
import com.android.providers.media.MediaVolume;
import java.io.File;
+import java.util.Set;
@Deprecated
public class LegacyMediaScanner implements MediaScanner {
@@ -60,4 +61,9 @@ public class LegacyMediaScanner implements MediaScanner {
public void onDirectoryDirty(File file) {
throw new UnsupportedOperationException();
}
+
+ @Override
+ public Set<String> getOemSupportedMimeTypes() {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/src/com/android/providers/media/scan/MediaScanner.java b/src/com/android/providers/media/scan/MediaScanner.java
index 8999542bf..2ce537409 100644
--- a/src/com/android/providers/media/scan/MediaScanner.java
+++ b/src/com/android/providers/media/scan/MediaScanner.java
@@ -33,6 +33,7 @@ import com.android.providers.media.MediaVolume;
import java.io.File;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Set;
public interface MediaScanner {
int REASON_UNKNOWN = MEDIA_PROVIDER_SCAN_OCCURRED__REASON__UNKNOWN;
@@ -62,4 +63,9 @@ public interface MediaScanner {
void onIdleScanStopped();
void onDirectoryDirty(@NonNull File file);
+
+ /**
+ * Returns OEM supported mime types for OEM metadata.
+ */
+ Set<String> getOemSupportedMimeTypes();
}
diff --git a/src/com/android/providers/media/scan/ModernMediaScanner.java b/src/com/android/providers/media/scan/ModernMediaScanner.java
index 3b9f3c4bb..da6b30b5f 100644
--- a/src/com/android/providers/media/scan/ModernMediaScanner.java
+++ b/src/com/android/providers/media/scan/ModernMediaScanner.java
@@ -287,7 +287,8 @@ public class ModernMediaScanner implements MediaScanner {
}
}
- private Set<String> getOemSupportedMimeTypes() {
+ @Override
+ public Set<String> getOemSupportedMimeTypes() {
try {
// Return if no package implements OemMetadataService
if (!mDefaultOemMetadataServicePackage.isPresent()) {
diff --git a/src/com/android/providers/media/util/PermissionUtils.java b/src/com/android/providers/media/util/PermissionUtils.java
index 97f14aebb..2458785e2 100644
--- a/src/com/android/providers/media/util/PermissionUtils.java
+++ b/src/com/android/providers/media/util/PermissionUtils.java
@@ -360,7 +360,7 @@ public class PermissionUtils {
/**
* Check if the given package has been granted the
- * android.provider.MediaStore#ACCESS_OEM_METADATA_PERMISSION permission.
+ * {@link android.provider.MediaStore#ACCESS_OEM_METADATA_PERMISSION} permission.
*/
public static boolean checkPermissionAccessOemMetadata(@NonNull Context context,
int pid, int uid, @NonNull String packageName, @Nullable String attributionTag) {
@@ -368,6 +368,16 @@ public class PermissionUtils {
pid, uid, packageName, attributionTag, null);
}
+ /**
+ * Check if the given package has been granted the
+ * {@link android.provider.MediaStore#UPDATE_OEM_METADATA_PERMISSION} permission.
+ */
+ public static boolean checkPermissionUpdateOemMetadata(@NonNull Context context,
+ int pid, int uid, @NonNull String packageName, @Nullable String attributionTag) {
+ return checkPermissionForPreflight(context, MediaStore.UPDATE_OEM_METADATA_PERMISSION,
+ pid, uid, packageName);
+ }
+
public static boolean checkPermissionInstallPackages(@NonNull Context context, int pid, int uid,
@NonNull String packageName, @Nullable String attributionTag) {
return checkPermissionForDataDelivery(context, INSTALL_PACKAGES, pid,
diff --git a/tests/src/com/android/providers/media/IsolatedContext.java b/tests/src/com/android/providers/media/IsolatedContext.java
index fa9a103ba..1b0f1f31e 100644
--- a/tests/src/com/android/providers/media/IsolatedContext.java
+++ b/tests/src/com/android/providers/media/IsolatedContext.java
@@ -152,6 +152,11 @@ public class IsolatedContext extends ContextWrapper {
protected boolean shouldCheckForMaliciousActivity() {
return Flags.enableMaliciousAppDetector();
}
+
+ @Override
+ protected void enforcePermissionCheckForOemMetadataUpdate(){
+
+ }
};
}
diff --git a/tests/src/com/android/providers/media/oemmetadataservices/OemMetadataServiceTest.java b/tests/src/com/android/providers/media/oemmetadataservices/OemMetadataServiceTest.java
index 774635be4..d8001c6c4 100644
--- a/tests/src/com/android/providers/media/oemmetadataservices/OemMetadataServiceTest.java
+++ b/tests/src/com/android/providers/media/oemmetadataservices/OemMetadataServiceTest.java
@@ -26,6 +26,7 @@ import android.Manifest;
import android.app.Instrumentation;
import android.content.ComponentName;
import android.content.ContentUris;
+import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
@@ -63,6 +64,7 @@ import org.junit.runner.RunWith;
import java.io.File;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
@@ -71,7 +73,6 @@ import java.util.concurrent.TimeoutException;
import java.util.function.Supplier;
@RunWith(AndroidJUnit4.class)
-@EnableFlags(com.android.providers.media.flags.Flags.FLAG_ENABLE_OEM_METADATA)
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
public class OemMetadataServiceTest {
@@ -105,6 +106,7 @@ public class OemMetadataServiceTest {
}
@Test
+ @EnableFlags(com.android.providers.media.flags.Flags.FLAG_ENABLE_OEM_METADATA)
public void testGetSupportedMimeTypes() throws Exception {
bindService();
assertNotNull(mOemMetadataServiceWrapper);
@@ -116,6 +118,7 @@ public class OemMetadataServiceTest {
}
@Test
+ @EnableFlags(com.android.providers.media.flags.Flags.FLAG_ENABLE_OEM_METADATA)
public void testGetOemCustomData() throws Exception {
bindService();
assertNotNull(mOemMetadataServiceWrapper);
@@ -141,6 +144,7 @@ public class OemMetadataServiceTest {
}
@Test
+ @EnableFlags(com.android.providers.media.flags.Flags.FLAG_ENABLE_OEM_METADATA)
public void testScanOfOemMetadataAndFilterOnReadWithoutPermission() throws Exception {
IsolatedContext isolatedContext = new IsolatedContext(mContext, "modern",
/* asFuseThread */ false);
@@ -181,6 +185,130 @@ public class OemMetadataServiceTest {
}
@Test
+ @EnableFlags({com.android.providers.media.flags.Flags.FLAG_ENABLE_OEM_METADATA,
+ com.android.providers.media.flags.Flags.FLAG_ENABLE_OEM_METADATA_UPDATE})
+ public void testTriggerOemMetadataUpdateWithPermission() throws Exception {
+ IsolatedContext isolatedContext = new IsolatedContext(mContext, "modern",
+ /* asFuseThread */ false);
+ ModernMediaScanner modernMediaScanner = new ModernMediaScanner(isolatedContext,
+ new TestConfigStore());
+ final File downloads = new File(Environment.getExternalStorageDirectory(),
+ Environment.DIRECTORY_DOWNLOADS);
+ final File audioFile = new File(downloads, "audio.mp3");
+ try {
+ stage(R.raw.test_audio, audioFile);
+ Uri uri = modernMediaScanner.scanFile(audioFile, MediaScanner.REASON_UNKNOWN);
+ DatabaseHelper databaseHelper = isolatedContext.getExternalDatabase();
+ // Direct query on DB returns stored value of oem_metadata
+ try (Cursor c = databaseHelper.runWithoutTransaction(db -> db.query(
+ "files", new String[]{FileColumns.OEM_METADATA, FileColumns._MODIFIER},
+ "_id=?", new String[]{String.valueOf(ContentUris.parseId(uri))},
+ null, null, null))) {
+ assertThat(c.getCount()).isEqualTo(1);
+ c.moveToNext();
+ byte[] oemData = c.getBlob(0);
+ int modifier = c.getInt(1);
+ assertThat(oemData).isNotNull();
+ Map<String, String> map = convertStringToOemMetadataMap(new String(oemData));
+ assertThat(map.keySet()).containsExactly("a", "b", "c", "d", "e");
+ assertThat(modifier).isEqualTo(FileColumns._MODIFIER_MEDIA_SCAN);
+ }
+
+ ContentValues contentValues = new ContentValues();
+ Map<String, String> updatedData = Map.of("a1", "b1", "a2", "b2");
+ contentValues.put(FileColumns.OEM_METADATA, updatedData.toString());
+ isolatedContext.getContentResolver().update(uri, contentValues, null);
+
+ try (Cursor c = databaseHelper.runWithoutTransaction(db -> db.query(
+ "files", new String[]{FileColumns.OEM_METADATA, FileColumns._MODIFIER},
+ "_id=?", new String[]{String.valueOf(ContentUris.parseId(uri))},
+ null, null, null))) {
+ assertThat(c.getCount()).isEqualTo(1);
+ c.moveToNext();
+ byte[] oemData = c.getBlob(0);
+ int modifier = c.getInt(1);
+ assertThat(modifier).isEqualTo(FileColumns._MODIFIER_MEDIA_SCAN);
+ assertThat(oemData).isNotNull();
+ Map<String, String> map = convertStringToOemMetadataMap(new String(oemData));
+ assertThat(map.keySet()).containsExactly("a1", "a2");
+ }
+ } finally {
+ audioFile.delete();
+ }
+ }
+
+ @Test
+ @EnableFlags({com.android.providers.media.flags.Flags.FLAG_ENABLE_OEM_METADATA,
+ com.android.providers.media.flags.Flags.FLAG_ENABLE_OEM_METADATA_UPDATE})
+ public void testTriggerBulkUpdateOemMetadataInNextScan() throws Exception {
+ IsolatedContext isolatedContext = new IsolatedContext(mContext, "modern",
+ /* asFuseThread */ false);
+ ModernMediaScanner modernMediaScanner = new ModernMediaScanner(isolatedContext,
+ new TestConfigStore());
+ final File downloads = new File(Environment.getExternalStorageDirectory(),
+ Environment.DIRECTORY_DOWNLOADS);
+ final File audioFile = new File(downloads, "audio.mp3");
+ try {
+ stage(R.raw.test_audio, audioFile);
+
+ Uri uri = modernMediaScanner.scanFile(audioFile, MediaScanner.REASON_UNKNOWN);
+
+ DatabaseHelper databaseHelper = isolatedContext.getExternalDatabase();
+ // Direct query on DB returns stored value of oem_metadata
+ try (Cursor c = databaseHelper.runWithoutTransaction(db -> db.query(
+ "files", new String[]{FileColumns.OEM_METADATA, FileColumns._MODIFIER},
+ "_id=?", new String[]{String.valueOf(ContentUris.parseId(uri))},
+ null, null, null))) {
+ assertThat(c.getCount()).isEqualTo(1);
+ c.moveToNext();
+ byte[] oemData = c.getBlob(0);
+ int modifier = c.getInt(1);
+ assertThat(oemData).isNotNull();
+ Map<String, String> map = convertStringToOemMetadataMap(new String(oemData));
+ assertThat(map.keySet()).containsExactly("a", "b", "c", "d", "e");
+ assertThat(modifier).isEqualTo(FileColumns._MODIFIER_MEDIA_SCAN);
+ }
+
+ // Change service behavior to verify updated results. Add new key "f".
+ TestOemMetadataService.updateOemMetadataServiceData();
+ // OEM metadata should be allowed to update to null and modifier
+ // should now be set to _MODIFIER_CR as scan has not happened yet
+ MediaStore.bulkUpdateOemMetadataInNextScan(isolatedContext);
+ try (Cursor c = databaseHelper.runWithoutTransaction(db -> db.query(
+ "files", new String[]{FileColumns.OEM_METADATA, FileColumns._MODIFIER},
+ "_id=?", new String[]{String.valueOf(ContentUris.parseId(uri))},
+ null, null, null))) {
+ assertThat(c.getCount()).isEqualTo(1);
+ c.moveToNext();
+ byte[] oemData = c.getBlob(0);
+ int modifier = c.getInt(1);
+ assertThat(oemData).isNull();
+ assertThat(modifier).isEqualTo(FileColumns._MODIFIER_CR);
+ }
+
+ // Trigger scan to allow OEM metadata update
+ MediaStore.scanFile(isolatedContext.getContentResolver(), audioFile);
+
+ try (Cursor c = databaseHelper.runWithoutTransaction(db -> db.query(
+ "files", new String[]{FileColumns.OEM_METADATA, FileColumns._MODIFIER},
+ "_id=?", new String[]{String.valueOf(ContentUris.parseId(uri))},
+ null, null, null))) {
+ assertThat(c.getCount()).isEqualTo(1);
+ c.moveToNext();
+ byte[] oemData = c.getBlob(0);
+ int modifier = c.getInt(1);
+ assertThat(modifier).isEqualTo(FileColumns._MODIFIER_MEDIA_SCAN);
+ assertThat(oemData).isNotNull();
+ Map<String, String> map = convertStringToOemMetadataMap(new String(oemData));
+ assertThat(map.keySet()).containsExactly("a", "b", "c", "d", "e", "f");
+ }
+ } finally {
+ audioFile.delete();
+ TestOemMetadataService.resetOemMetadataServiceData();
+ }
+ }
+
+ @Test
public void testNoServiceBindingWithoutPermission() throws Exception {
updateStateOfServiceWithPermission(PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
IsolatedContext isolatedContext = new IsolatedContext(mContext, "modern",
@@ -272,4 +400,24 @@ public class OemMetadataServiceTest {
mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
mServiceLatch.await(3, TimeUnit.SECONDS);
}
+
+ public static Map<String, String> convertStringToOemMetadataMap(String stringMapping) {
+ Map<String, String> map = new HashMap<>();
+ if (stringMapping == null || stringMapping.isEmpty()) {
+ return map;
+ }
+ stringMapping = stringMapping.substring(1, stringMapping.length() - 1);
+ // Split into key-value pairs
+ String[] pairs = stringMapping.split(", ");
+
+ for (String pair : pairs) {
+ String[] keyValue = pair.split("=");
+ String key = keyValue[0];
+ String value = keyValue[1];
+ if (key != null) {
+ map.put(key, value);
+ }
+ }
+ return map;
+ }
}
diff --git a/tests/src/com/android/providers/media/oemmetadataservices/TestOemMetadataService.java b/tests/src/com/android/providers/media/oemmetadataservices/TestOemMetadataService.java
index cdc4f71ce..ae1dcf8a2 100644
--- a/tests/src/com/android/providers/media/oemmetadataservices/TestOemMetadataService.java
+++ b/tests/src/com/android/providers/media/oemmetadataservices/TestOemMetadataService.java
@@ -27,6 +27,15 @@ import java.util.Set;
public class TestOemMetadataService extends OemMetadataService {
+ static Map<String, String> sOemMetadata = new HashMap<>();
+
+ static {
+ sOemMetadata.put("a", "1");
+ sOemMetadata.put("b", "2");
+ sOemMetadata.put("c", "3");
+ sOemMetadata.put("d", "4");
+ sOemMetadata.put("e", "5");
+ }
@Override
public Set<String> onGetSupportedMimeTypes() {
@@ -35,12 +44,14 @@ public class TestOemMetadataService extends OemMetadataService {
@Override
public Map<String, String> onGetOemCustomData(@NonNull ParcelFileDescriptor pfd) {
- Map<String, String> oemMetadata = new HashMap<>();
- oemMetadata.put("a", "1");
- oemMetadata.put("b", "2");
- oemMetadata.put("c", "3");
- oemMetadata.put("d", "4");
- oemMetadata.put("e", "5");
- return oemMetadata;
+ return sOemMetadata;
+ }
+
+ public static void updateOemMetadataServiceData() {
+ sOemMetadata.put("f", "6");
+ }
+
+ public static void resetOemMetadataServiceData() {
+ sOemMetadata.remove("f");
}
}