Rename File of the Mediastore Which Is Created by App in Android 10. Working on Android API 30 But Shows Error in API 29

Rename file of the Mediastore which is created by app in android 10. Working on Android API 30 but shows error in API 29

java.lang.IllegalArgumentException: Movement of content://media/external/file/116 which isn't part of well-defined collection not allowed

So it is for Android Q not allowed if you use the collection;

Uri extUri = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL);

But is is allowed for a 'well-defined collection' like:

Uri extUri = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL);
// Use "Pictures/MyFolder" for RELATIVE_PATH

I leave it to you to find other well-defined collections.

Why this is only for Android Q i dont know.

You can see the message in the java file: https://android.googlesource.com/platform/packages/providers/MediaProvider/+/refs/heads/master/src/com/android/providers/media/MediaProvider.java

Quote:

     // We only support movement under well-defined collections
switch (match) {
case AUDIO_MEDIA_ID:
case VIDEO_MEDIA_ID:
case IMAGES_MEDIA_ID:
case DOWNLOADS_ID:
break;
default:
throw new IllegalArgumentException("Movement of " + uri
+ " which isn't part of well-defined collection not allowed");
}

If the rename fails use SAF (as mentioned before). How to rename a file in Android knowing only its media content Uri

Video renaming on Android 11 / API 30 using MediaStore and ContentValues

First of all, I had to add this permission to manifest in order to be able to access all files on device:

<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>

Then I added this line to application tag:

android:requestLegacyExternalStorage="true"

Now after you change the name of the video, you need to update the media store with the new name:

if (ContextUtils.isAndroidR()) {
Uri mUri = ContentUris.withAppendedId(MediaStore.Video.Media.EXTERNAL_CONTENT_URI , videoId);
try {
ContentValues contentValues = new ContentValues(3);
contentValues.put(MediaStore.Files.FileColumns.IS_PENDING, 1);
mContext.getContentResolver().update(mUri, contentValues, null, null);
contentValues.clear();
contentValues.put(MediaStore.Files.FileColumns.DISPLAY_NAME, newTitle);
contentValues.put(MediaStore.Files.FileColumns.IS_PENDING, 0);
mContext.getContentResolver().update(mUri, contentValues, null, null);
} catch (Exception exception) {
if (ContextUtils.isAndroidQ()) {
RecoverableSecurityException recoverableSecurityException;
if (exception instanceof RecoverableSecurityException) {
recoverableSecurityException = (RecoverableSecurityException) exception;
} else {
ContextUtils.makeShortToast( "Maybe make sure you request permissions first?" );
}
try {
ContentResolver contentResolver = mContext.getContentResolver();
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.Files.FileColumns.IS_PENDING, 1);
contentResolver.update(mUri, contentValues, null, null);
contentValues.clear();
contentValues.put(MediaStore.Files.FileColumns.DISPLAY_NAME, newTitle);
contentValues.put(MediaStore.Files.FileColumns.IS_PENDING, 0);
contentResolver.update(mUri, contentValues, null, null);
} catch (Exception e) {
e.printStackTrace();
ContextUtils.makeShortToast( String.valueOf(e) );
}
} else {
throw new RuntimeException ( exception.getMessage() , exception );
}
}
} else {
ContentValues contentValues = new ContentValues(2);
contentValues.put(MediaStore.Video.Media.DATA, newPath);
contentValues.put(MediaStore.Video.Media.DISPLAY_NAME, newTitle);
Uri extUri = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL);
mContext.getContentResolver().update(extUri , contentValues, MediaStore.Video.Media._ID + "=" + videoId, null);
}

This is how I got renaming a video on Android 11 to work with no issues.

MediaStore: After updating the file which is created by my app it cannot be deleted easily

I found a solution.

When you create a file we need to add put(MediaStore.*.Media.IS_PENDING, 1) to content values:

val contentValues = ContentValues(5).apply {
put(MediaStore.Video.Media.IS_PENDING, 1)
put(MediaStore.Video.Media.DISPLAY_NAME, "INITIAL FILENAME")
put(MediaStore.Video.Media.DATE_ADDED, System.currentTimeMillis() / 1000)
put(MediaStore.Video.Media.MIME_TYPE, "video/mp4")
put(MediaStore.Video.Media.RELATIVE_PATH, relativePath)
}

val uri = resolver.insert(
MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
contentValues
)

And then when you're done with a file (e.g., finished video recording using MediaRecorder to that file) you can update it like this (you have to add put(MediaStore.*.Media.IS_PENDING, 0):

val contentValues = ContentValues(2).apply {
put(MediaStore.Video.Media.IS_PENDING, 0)
put(MediaStore.Video.Media.DISPLAY_NAME, "NEW FILENAME, E.G. APPENDING END RECORDING TIMESTAMP TO PREVIOUS FILE NAME")
}
resolver.update(uri, contentValues, null, null)

And then when calling ContentResolver.delete(...) function, requesting permission is not needed anymore (RecoverableSecurityException isn't thrown)

try {
resolver.delete(uri, null, null)
} catch (securityException: RecoverableSecurityException) {
val intentSender =
securityException.userAction.actionIntent.intentSender
intentSender?.let {
activity.startIntentSenderForRecsult(
intentSender,
REQUEST_CODE,
null,
0,
0,
0,
null
)
}

Users aren't annoyed :)

Though if a user reinstall your app, permission requesting will be required for all the files your app previously created, yeah beginning with Android Q it's not easy... :)

requestLegacyExternalStorage is not working in Android 11 - API 30

But now when I targetSdkVersion 30, this no longer seems to work

That is correct. Android 11 (API 30+) requestLegacyExternalStorage=true does nothing and you can no longer "opt-out". It was available in Android 10 to give developers a transition/grace period to be able to migrate to the scoped storage model.

Option 1: Migrate data in your app whilst still targeting API 29, then once you're migrated data is compatible with scoped storage you should be able to release an update targetting API 30 - https://developer.android.com/training/data-storage/use-cases

This can come with its own problems if users skip this version and updates directly from a previous version to the latest and you're stuck with un-migrated data you can't access.

Option 2: It seems that Google sees this obvious caveat and has included a preserveLegacyExternalStorage=true option when targetting API 30 to allow you to migrate data. https://developer.android.com/reference/android/R.attr#preserveLegacyExternalStorage

Going forward you can reference this table for deciding what storage "framework" to use based on the use-case: https://developer.android.com/training/data-storage

There is a potential that some apps simply won't be able to successfully migrate, based on how they interacted with the File API as Google's solution will not encompass all current use-cases and there might not be a migration path.

For instance, I released an app a couple of years ago that allowed users to update album artwork using MediaStore and ContentResolver to update the data for the album artwork image - this was stored in shared storage. Having looked at the Android 10+ AOSP MediaProvider source code it seems that apps that used to use MediaStore to update album artwork to point to a data file no longer works, simply because the MediaProvider internally creates its own artwork in a hidden .thumbnails folder looking directly at the mp3's and using a MediaExtractor, and never references the ContentValues that were inserted to reference the artwork. So even though you can update the artwork yourself, query the MediaStore for it and see it, other apps have to use ContentResolver#loadThumbnail in API 29+ that does not reference your updated values and either creates an artwork lazily, or picks the already generated file in the .thumbnails folder. Obviously, none of this is documented, and I got a massive backlash to my app with negative reviews, yet these changes were breaking changes and completely out of my control and took me looking through AOSP source code to find that Android had fundamentally changed behaviour.

(This wasn't a rant, but an example of how these changes offered no migration path because of fundamental undocumented behaviour to AOSP).

WRITE_EXTERNAL_STORAGE when targeting Android 10

A workaround is to actually ignore the warning, as it is just informational and therefore harmless. By setting maxSdkVersion to 28 no need to worry anymore.

<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28"
tools:ignore="ScopedStorage" />

Note that using the android:requestLegacyExternalStorage flag as stated in other answers is not a solution, is just a temporary patch which will no longer work at all in Android 11 (API 30), and future versions

UPDATE, to clarify the doubts and confusions shown by some developers in the comments:

  • If using the requestLegacyExternalStorage flag in Android 10 (API 29) then request the WRITE_EXTERNAL_STORAGE permission as usual.

  • The flag requestLegacyExternalStorage does nothing in Android 11 (API 30), it is completely ignored, and there is not workaround for it.

  • WRITE_EXTERNAL_STORAGE does not give any privileges in Android 11 (API 30), it does nothing at all, therefore in API 11 you need to set the maxSdkVersion to 29.

  • If in Android 10 (API 29) you are also not using requestLegacyExternalStorage then set maxSdkVersion to 28 instead of 29.

  • Starting in Android 11 (API 30), the older File API can again be used but "only" when accessing the public "shared storage" folders (DCIM, Music, etc.), or your app "private" directory. For other locations the DocumentFile API is required.

  • Consider that the File API is now much slower in Android 11 (API 30), because has been refactored becoming essentially a wrapper. This is to enforce its usage just to the allowed locations. So, is no longer a fast system file API, is just a wrapper that internally delegates the work to the MediaStore. When using the File API in Android 11 or above you should consider the performance penalty hit, as according to the Android team it will be 2 to 3 times slower than if accessing directly the MediaStore.

Android 10 MediaStore API changes

Below android Q, for MediaStore.Audio.Artists.Albums.getContentUri("external", artistId) uri, album id was available in column BaseColumns._ID, and in android Q this column is not available, but still returns some random number which is not album id.

We have to use MediaStore.Audio.Artists.Albums.ALBUM_ID to get the album id in Android Q. But this doesn't work for below android Q, as this column is not available in that. And hence we have to use two different versions to refer the album id.

if (Build.VERSION.SDK_INT >= 29) {
return MediaStore.Audio.Artists.Albums.ALBUM_ID;
}else{
return BaseColumns._ID;
}

Edit 1 : Had a raised an issue with google about the same. https://issuetracker.google.com/issues/140508535 . Google has said it is fixed and will be available in next release.



Related Topics



Leave a reply



Submit