summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
author Dipankar Bhardwaj <dipankarb@google.com> 2024-09-29 19:50:56 +0000
committer Dipankar Bhardwaj <dipankarb@google.com> 2025-03-13 16:57:13 +0000
commit9a831d5492fb663b95b49693de14287988ccad30 (patch)
tree7114671cf9b763034b7f092c67b1ad6c8b9e3e65 /src
parent8361c9359191021054311dd73ff9fb05752e06df (diff)
Add API to bulk update oem_metadata
Created new API to bulk update oem_metadata. This can be done only with UPDATE_OEM_METADATA_PERMISSION which is signature and privileged. Triggering bulk update of oem_metadata column will mark it as null for rows with oem supported mime type till the next idle maintenance run re-fetches the values from OEMMetadataService. Also added support for OEM_METADATA update for a URI using update API. Test: atest OemMetadataServiceTest Bug: 352528913 Change-Id: If2ee9050304ab1f60ca402bf4205a34b58277e76 Flag: com.android.providers.media.flags.enable_oem_metadata_update
Diffstat (limited to 'src')
-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
6 files changed, 125 insertions, 7 deletions
diff --git a/src/com/android/providers/media/LocalCallingIdentity.java b/src/com/android/providers/media/LocalCallingIdentity.java
index ef98544ec..269ae622e 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;
}
@@ -749,6 +754,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 d0d5d345d..c338352f6 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);
@@ -8752,6 +8828,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");
@@ -12052,6 +12133,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 3dcdc1150..064095144 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,