diff options
author | 2025-03-13 12:03:31 -0700 | |
---|---|---|
committer | 2025-03-13 12:03:31 -0700 | |
commit | 69c71c18f7143fffc0559bd46491876d34245bd7 (patch) | |
tree | bb112688f5d18def0530caa68ab85df7c545fa02 /src | |
parent | b7f549960eff6450f38680215e453cf7368278dc (diff) | |
parent | 9a831d5492fb663b95b49693de14287988ccad30 (diff) |
Merge "Add API to bulk update oem_metadata" into main
Diffstat (limited to 'src')
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 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, |