EDIT: The external folder I chose was the documents folder FYI
So I did finally get it working. Here is the code to rename a video (it may not be the best but it does the trick!)
private static void tryAddVideosToMediaStore(Activity context) {
List<File> files = MediaUtils.getVideoFilesFromDirectory();
for (File file : files) {
try {
Uri fromUri = FileProvider.getUriForFile(context, context.getPackageName() + ".provider", file);
if (getRealPathFromURI(context, fromUri) == null) {
String nameWoExtension = MediaUtils.getNameWithoutStatus(file.getAbsolutePath());
ContentValues values = new ContentValues(3);
values.put(MediaStore.Video.Media.TITLE, nameWoExtension);
values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4");
values.put(MediaStore.Video.Media.DATA, file.getAbsolutePath());
context.getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static String getRealPathFromURI(Context context, Uri contentUri) {
Cursor cursor = null;
try {
String[] proj = {MediaStore.Images.Media.DATA};
cursor = context.getContentResolver().query(contentUri, proj, null, null, null);
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA);
cursor.moveToFirst();
return cursor.getString(column_index);
} catch(Exception e) {
return null;
}finally {
if (cursor != null) {
cursor.close();
}
}
}
And then the calling methods
public static String getVideoNameFromPath(String path) {
return path.substring(path.lastIndexOf("/") + 1, path.indexOf(".mp4"));
}
public static boolean renameVideoFile(MainActivityViewModel viewModel, SharedPreferenceHelper sharedPreferenceHelper, Activity c, File from, File to) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
tryAddVideosToMediaStore(c);
Uri fromUri = MediaUtils.getVideoUriFromFS(c, from);
try {
ContentResolver contentResolver = c.getContentResolver();
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.Files.FileColumns.IS_PENDING, 1);
contentResolver.update(fromUri, contentValues, null, null);
contentValues.clear();
contentValues.put(MediaStore.Files.FileColumns.DISPLAY_NAME, to.getName());
contentValues.put(MediaStore.Files.FileColumns.IS_PENDING, 0);
contentResolver.update(fromUri, contentValues, null, null);
return true;
} catch (Exception securityException) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
sharedPreferenceHelper.get().edit().putString("from", from.getAbsolutePath()).putString("to", to.getAbsolutePath()).apply();
RecoverableSecurityException recoverableSecurityException;
viewModel.setContentUri(fromUri);
if (securityException instanceof RecoverableSecurityException) {
recoverableSecurityException =
(RecoverableSecurityException) securityException;
} else {
requestVideoWritePermissions(c, Uri.parse(MediaStore.Video.Media.EXTERNAL_CONTENT_URI + "/" + MediaUtils.getVideoId(c, from)));
return false;
}
IntentSender intentSender = recoverableSecurityException.getUserAction()
.getActionIntent().getIntentSender();
try {
c.startIntentSenderForResult(intentSender, 55,
null, 0, 0, 0);
} catch (Exception e) {
e.printStackTrace();
return false;
}
} else {
throw new RuntimeException(
securityException.getMessage(), securityException);
}
}
return false;
} else {
if (from.renameTo(to)) {
removeMedia(c, from);
addMedia(c, to);
return true;
} else {
return false;
}
}
}
public static Uri getVideoUriFromFS(Context c, File file) {
long id = getFilePathToMediaID(file, c);
Uri fromUri = ContentUris.withAppendedId( MediaStore.Video.Media.EXTERNAL_CONTENT_URI,id);
return fromUri;
}
public static long getFilePathToMediaID(File videoPath, Context context)
{
Uri mainUri;
Cursor cursor1 = context.getContentResolver().query(
MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
new String[]{MediaStore.Video.Media._ID},
MediaStore.Video.Media.DATA + "=? ",
new String[]{videoPath.getAbsolutePath()}, null);
long id = 0;
if (cursor1 != null && cursor1.moveToFirst()) {
id = cursor1.getLong(cursor1.getColumnIndex(MediaStore.MediaColumns._ID));
cursor1.close();
}
return id;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 55) { //rename video request code
if (resultCode == RESULT_OK) {
//update UI
String from = presenter.getFromFilePath();
String to = presenter.getToFilePath();
if (from != null && to != null) {
Uri fromUri = MediaUtils.getVideoUriFromFS(this, new File(from));
ContentResolver contentResolver = getContentResolver();
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.Files.FileColumns.IS_PENDING, 1);
contentResolver.update(fromUri, contentValues, null, null);
contentValues.clear();
contentValues.put(MediaStore.Files.FileColumns.DISPLAY_NAME, new File(to).getName());
contentValues.put(MediaStore.Files.FileColumns.IS_PENDING, 0);
contentResolver.update(fromUri, contentValues, null, null);
//update UI
}
}
}
}
If I forgot something please let me know and I will post it here. It took literally hours of searching to find this solution. I'm quite upset at the simplicity vs the complexity that google has introduced.
EDIT: I Think I forgot this method which was very important
public static boolean requestVideoWritePermissions(Activity activity, Uri fromUri) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
boolean hasPermission = true;
if (activity.checkUriPermission(fromUri, Binder.getCallingPid(), Binder.getCallingUid(),
Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != PackageManager.PERMISSION_GRANTED) {
hasPermission = false;
}
List<Uri> uriList = new ArrayList<>();
uriList.add(fromUri);
if (!hasPermission) {
PendingIntent pi = MediaStore.createWriteRequest(activity.getContentResolver(), uriList);
try {
activity.startIntentSenderForResult(pi.getIntentSender(), 55, null, 0, 0, 0);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
return false;
}
return true;
}
return true;
}
I should also mention each video is a prompt this way. The user chooses whether or not to allow you to overwrite each video which was less than optimal. I wish I could just do a whole folder of external access but I'm guessing that's not going to happen with scoped storage changes.