From dc0ca635c54ea7bb2f0819ec4b25c3aa143da5bd Mon Sep 17 00:00:00 2001 From: darthpaul Date: Sat, 19 Mar 2022 18:09:54 +0000 Subject: [PATCH] Renaming on SDK 30 - handle renaming files with SAF except files in the Download directory - handle renaming files in the Download directory using MediaStore.createWrite request, then duplicating the file with the new name - handle renaming files in the root of internal storage using MediaStore.createWrite request, then using contentResolver to update the display name - this methods do not work for files in the root of SD card and all files in OTG media --- .../commons/dialogs/FilePickerDialog.kt | 9 ++ .../commons/extensions/Activity-sdk30.kt | 74 ++++++++++ .../commons/extensions/Activity.kt | 95 ++++++++++--- .../extensions/Context-storage-sdk30.kt | 12 ++ .../commons/models/Android30RenameFormat.kt | 7 + .../commons/models/FileDirItem.kt | 2 +- .../commons/views/RenamePatternTab.kt | 108 ++++++++++----- .../commons/views/RenameSimpleTab.kt | 131 ++++++++++++------ 8 files changed, 339 insertions(+), 99 deletions(-) create mode 100644 commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Activity-sdk30.kt create mode 100644 commons/src/main/kotlin/com/simplemobiletools/commons/models/Android30RenameFormat.kt diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/FilePickerDialog.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/FilePickerDialog.kt index bc5ad92c5..451d54a78 100644 --- a/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/FilePickerDialog.kt +++ b/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/FilePickerDialog.kt @@ -207,6 +207,15 @@ class FilePickerDialog( if ((pickFile && fileDocument.isFile) || (!pickFile && fileDocument.isDirectory)) { sendSuccess() } + } else if (activity.isAccessibleWithSAFSdk30(currPath)) { + activity.handleSAFDialogSdk30(currPath) { + if (it) { + val document = activity.getSomeDocumentSdk30(currPath) ?: return@handleSAFDialogSdk30 + if ((pickFile && document.isFile) || (!pickFile && document.isDirectory)) { + sendSuccess() + } + } + } } else { val file = File(currPath) if ((pickFile && file.isFile) || (!pickFile && file.isDirectory)) { diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Activity-sdk30.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Activity-sdk30.kt new file mode 100644 index 000000000..219d807a7 --- /dev/null +++ b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Activity-sdk30.kt @@ -0,0 +1,74 @@ +package com.simplemobiletools.commons.extensions + +import android.content.ContentValues +import android.provider.MediaStore +import com.simplemobiletools.commons.R +import com.simplemobiletools.commons.activities.BaseSimpleActivity +import com.simplemobiletools.commons.models.FileDirItem +import java.io.File +import java.io.InputStream +import java.io.OutputStream + +fun BaseSimpleActivity.copySingleFileSdk30(source: FileDirItem, destination: FileDirItem): Boolean { + val directory = destination.getParentPath() + if (!createDirectorySync(directory)) { + val error = String.format(getString(R.string.could_not_create_folder), directory) + showErrorToast(error) + return false + } + + var inputStream: InputStream? = null + var out: OutputStream? = null + try { + + out = getFileOutputStreamSync(destination.path, source.path.getMimeType()) + inputStream = getFileInputStreamSync(source.path)!! + + var copiedSize = 0L + val buffer = ByteArray(DEFAULT_BUFFER_SIZE) + var bytes = inputStream.read(buffer) + while (bytes >= 0) { + out!!.write(buffer, 0, bytes) + copiedSize += bytes + bytes = inputStream.read(buffer) + } + + out?.flush() + + return if (source.size == copiedSize && getDoesFilePathExist(destination.path)) { + if (baseConfig.keepLastModified) { + copyOldLastModified(source.path, destination.path) + File(destination.path).setLastModified(File(source.path).lastModified()) + } + true + } else { + false + } + } finally { + inputStream?.close() + out?.close() + } +} + +fun BaseSimpleActivity.copyOldLastModified(sourcePath: String, destinationPath: String) { + val projection = arrayOf(MediaStore.Images.Media.DATE_TAKEN, MediaStore.Images.Media.DATE_MODIFIED) + val uri = MediaStore.Files.getContentUri("external") + val selection = "${MediaStore.MediaColumns.DATA} = ?" + var selectionArgs = arrayOf(sourcePath) + val cursor = applicationContext.contentResolver.query(uri, projection, selection, selectionArgs, null) + + cursor?.use { + if (cursor.moveToFirst()) { + val dateTaken = cursor.getLongValue(MediaStore.Images.Media.DATE_TAKEN) + val dateModified = cursor.getIntValue(MediaStore.Images.Media.DATE_MODIFIED) + + val values = ContentValues().apply { + put(MediaStore.Images.Media.DATE_TAKEN, dateTaken) + put(MediaStore.Images.Media.DATE_MODIFIED, dateModified) + } + + selectionArgs = arrayOf(destinationPath) + applicationContext.contentResolver.update(uri, values, selection, selectionArgs) + } + } +} diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Activity.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Activity.kt index 787cf3441..01aa5cc26 100644 --- a/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Activity.kt +++ b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Activity.kt @@ -834,26 +834,49 @@ fun BaseSimpleActivity.renameFile( oldPath: String, newPath: String, isRenamingMultipleFiles: Boolean, - callback: ((success: Boolean, useAndroid30Way: Boolean) -> Unit)? = null + callback: ((success: Boolean, android30RenameFormat: Android30RenameFormat) -> Unit)? = null ) { if (isRestrictedSAFOnlyRoot(oldPath)) { handleAndroidSAFDialog(oldPath) { if (!it) { runOnUiThread { - callback?.invoke(false, false) + callback?.invoke(false, Android30RenameFormat.NONE) } return@handleAndroidSAFDialog } try { - val success = renameAndroidSAFDocument(oldPath, newPath) - runOnUiThread { - callback?.invoke(success, false) + ensureBackgroundThread { + val success = renameAndroidSAFDocument(oldPath, newPath) + runOnUiThread { + callback?.invoke(success, Android30RenameFormat.NONE) + } } } catch (e: Exception) { showErrorToast(e) runOnUiThread { - callback?.invoke(false, false) + callback?.invoke(false, Android30RenameFormat.NONE) + } + } + } + } else if (isAccessibleWithSAFSdk30(oldPath)) { + + handleSAFDialogSdk30(oldPath) { + if (!it) { + return@handleSAFDialogSdk30 + } + + try { + ensureBackgroundThread { + val success = renameDocumentSdk30(oldPath, newPath) + runOnUiThread { + callback?.invoke(success, Android30RenameFormat.NONE) + } + } + } catch (e: Exception) { + showErrorToast(e) + runOnUiThread { + callback?.invoke(false, Android30RenameFormat.NONE) } } } @@ -866,7 +889,7 @@ fun BaseSimpleActivity.renameFile( val document = getSomeDocumentFile(oldPath) if (document == null || (File(oldPath).isDirectory != document.isDirectory)) { runOnUiThread { - callback?.invoke(false, false) + callback?.invoke(false, Android30RenameFormat.NONE) } return@handleSAFDialog } @@ -879,7 +902,7 @@ fun BaseSimpleActivity.renameFile( // FileNotFoundException is thrown in some weird cases, but renaming works just fine } catch (e: Exception) { showErrorToast(e) - callback?.invoke(false, false) + callback?.invoke(false, Android30RenameFormat.NONE) return@ensureBackgroundThread } @@ -890,14 +913,14 @@ fun BaseSimpleActivity.renameFile( } deleteFromMediaStore(oldPath) runOnUiThread { - callback?.invoke(true, false) + callback?.invoke(true, Android30RenameFormat.NONE) } } } } catch (e: Exception) { showErrorToast(e) runOnUiThread { - callback?.invoke(false, false) + callback?.invoke(false, Android30RenameFormat.NONE) } } } @@ -910,7 +933,7 @@ fun BaseSimpleActivity.renameFile( if (isRPlus() && exception is java.nio.file.FileSystemException) { // if we are renaming multiple files at once, we should give the Android 30+ permission dialog all uris together, not one by one if (isRenamingMultipleFiles) { - callback?.invoke(false, true) + callback?.invoke(false, Android30RenameFormat.CONTENT_RESOLVER) } else { val fileUris = getFileUrisFromFileDirItems(arrayListOf(File(oldPath).toFileDirItem(this))).second updateSDK30Uris(fileUris) { success -> @@ -921,19 +944,19 @@ fun BaseSimpleActivity.renameFile( try { contentResolver.update(fileUris.first(), values, null, null) - callback?.invoke(true, false) + callback?.invoke(true, Android30RenameFormat.NONE) } catch (e: Exception) { showErrorToast(e) - callback?.invoke(false, false) + callback?.invoke(false, Android30RenameFormat.NONE) } } else { - callback?.invoke(false, false) + callback?.invoke(false, Android30RenameFormat.NONE) } } } } else { showErrorToast(exception) - callback?.invoke(false, false) + callback?.invoke(false, Android30RenameFormat.NONE) } return } @@ -945,7 +968,7 @@ fun BaseSimpleActivity.renameFile( updateInMediaStore(oldPath, newPath) rescanPath(newPath) { runOnUiThread { - callback?.invoke(true, false) + callback?.invoke(true, Android30RenameFormat.NONE) } deleteFromMediaStore(oldPath) scanPathRecursively(newPath) @@ -958,14 +981,48 @@ fun BaseSimpleActivity.renameFile( scanPathsRecursively(arrayListOf(newPath)) { deleteFromMediaStore(oldPath) runOnUiThread { - callback?.invoke(true, false) + callback?.invoke(true, Android30RenameFormat.NONE) } } } } else { tempFile.delete() - runOnUiThread { - callback?.invoke(false, false) + newFile.delete() + if (isRPlus()) { + // if we are renaming multiple files at once, we should give the Android 30+ permission dialog all uris together, not one by one + if (isRenamingMultipleFiles) { + callback?.invoke(false, Android30RenameFormat.SAF) + } else { + val fileUris = getFileUrisFromFileDirItems(arrayListOf(File(oldPath).toFileDirItem(this))).second + updateSDK30Uris(fileUris) { success -> + if (!success) { + return@updateSDK30Uris + } + try { + val sourceFile = File(oldPath).toFileDirItem(this) + val destinationFile = sourceFile.copy(path = newPath, name = newPath.getFilenameFromPath()) + val copySuccessful = copySingleFileSdk30(sourceFile, destinationFile) + if (copySuccessful) { + if (!baseConfig.keepLastModified) { + newFile.setLastModified(System.currentTimeMillis()) + } + contentResolver.delete(fileUris.first(), null) + updateInMediaStore(oldPath, newPath) + scanPathsRecursively(arrayListOf(newPath)) { + runOnUiThread { + callback?.invoke(true, Android30RenameFormat.NONE) + } + } + } + + } catch (e: Exception) { + showErrorToast(e) + callback?.invoke(false, Android30RenameFormat.NONE) + } + } + } + } else { + callback?.invoke(false, Android30RenameFormat.NONE) } } } diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Context-storage-sdk30.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Context-storage-sdk30.kt index e08307e23..15d960fdd 100644 --- a/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Context-storage-sdk30.kt +++ b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Context-storage-sdk30.kt @@ -153,6 +153,18 @@ fun Context.deleteDocumentWithSAFSdk30(fileDirItem: FileDirItem, allowDeleteFold } } +fun Context.renameDocumentSdk30(oldPath: String, newPath: String): Boolean { + return try { + val treeUri = createFirstParentTreeUri(oldPath) + val documentId = getSAFDocumentId(oldPath) + val parentUri = DocumentsContract.buildDocumentUriUsingTree(treeUri, documentId) + DocumentsContract.renameDocument(contentResolver, parentUri, newPath.getFilenameFromPath()) != null + } catch (e: IllegalStateException) { + showErrorToast(e) + false + } +} + fun Context.hasProperStoredDocumentUriSdk30(path: String): Boolean { val documentUri = buildDocumentUriSdk30(path) return contentResolver.persistedUriPermissions.any { it.uri.toString() == documentUri.toString() } diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/models/Android30RenameFormat.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/models/Android30RenameFormat.kt new file mode 100644 index 000000000..eea02c17b --- /dev/null +++ b/commons/src/main/kotlin/com/simplemobiletools/commons/models/Android30RenameFormat.kt @@ -0,0 +1,7 @@ +package com.simplemobiletools.commons.models + +enum class Android30RenameFormat { + SAF, + CONTENT_RESOLVER, + NONE +} diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/models/FileDirItem.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/models/FileDirItem.kt index 62a4a0cad..95b9e9774 100644 --- a/commons/src/main/kotlin/com/simplemobiletools/commons/models/FileDirItem.kt +++ b/commons/src/main/kotlin/com/simplemobiletools/commons/models/FileDirItem.kt @@ -7,7 +7,7 @@ import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.helpers.* import java.io.File -open class FileDirItem( +data class FileDirItem( val path: String, val name: String = "", var isDirectory: Boolean = false, diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/views/RenamePatternTab.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/views/RenamePatternTab.kt index eba73e471..f2928c5c9 100644 --- a/commons/src/main/kotlin/com/simplemobiletools/commons/views/RenamePatternTab.kt +++ b/commons/src/main/kotlin/com/simplemobiletools/commons/views/RenamePatternTab.kt @@ -12,6 +12,7 @@ import com.simplemobiletools.commons.activities.BaseSimpleActivity import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.helpers.isNougatPlus import com.simplemobiletools.commons.interfaces.RenameTab +import com.simplemobiletools.commons.models.Android30RenameFormat import kotlinx.android.synthetic.main.dialog_rename_items_pattern.view.* import java.io.File import java.text.SimpleDateFormat @@ -50,8 +51,9 @@ class RenamePatternTab(context: Context, attrs: AttributeSet) : RelativeLayout(c } val validPaths = paths.filter { activity?.getDoesFilePathExist(it) == true } - val sdFilePath = validPaths.firstOrNull { activity?.isPathOnSD(it) == true } ?: validPaths.firstOrNull() - if (sdFilePath == null) { + val firstPath = validPaths.firstOrNull() + val sdFilePath = validPaths.firstOrNull { activity?.isPathOnSD(it) == true } ?: firstPath + if (firstPath == null || sdFilePath == null) { activity?.toast(R.string.unknown_error_occurred) return } @@ -62,39 +64,44 @@ class RenamePatternTab(context: Context, attrs: AttributeSet) : RelativeLayout(c return@handleSAFDialog } - ignoreClicks = true - var pathsCnt = validPaths.size - numbersCnt = pathsCnt.toString().length - for (path in validPaths) { - if (stopLooping) { - return@handleSAFDialog + activity?.handleSAFDialogSdk30(firstPath) { + if (!it) { + return@handleSAFDialogSdk30 } - try { - val newPath = getNewPath(path, useMediaFileExtension) ?: continue - activity?.renameFile(path, newPath, true) { success, useAndroid30Way -> - if (success) { - pathsCnt-- - if (pathsCnt == 0) { - callback(true) - } - } else { - ignoreClicks = false - if (useAndroid30Way) { - currentIncrementalNumber = 1 - stopLooping = true - renameAllFiles(validPaths, useMediaFileExtension, callback) + ignoreClicks = true + var pathsCnt = validPaths.size + numbersCnt = pathsCnt.toString().length + for (path in validPaths) { + if (stopLooping) { + return@handleSAFDialogSdk30 + } + + try { + val newPath = getNewPath(path, useMediaFileExtension) ?: continue + activity?.renameFile(path, newPath, true) { success, android30Format -> + if (success) { + pathsCnt-- + if (pathsCnt == 0) { + callback(true) + } } else { - activity?.toast(R.string.unknown_error_occurred) + ignoreClicks = false + if (android30Format != Android30RenameFormat.NONE) { + currentIncrementalNumber = 1 + stopLooping = true + renameAllFiles(validPaths, useMediaFileExtension, android30Format, callback) + } else { + activity?.toast(R.string.unknown_error_occurred) + } } } + } catch (e: Exception) { + activity?.showErrorToast(e) } - } catch (e: Exception) { - activity?.showErrorToast(e) } + stopLooping = false } - - stopLooping = false } } @@ -167,26 +174,59 @@ class RenamePatternTab(context: Context, attrs: AttributeSet) : RelativeLayout(c } } - private fun renameAllFiles(paths: List, useMediaFileExtension: Boolean, callback: (success: Boolean) -> Unit) { + private fun renameAllFiles( + paths: List, + useMediaFileExtension: Boolean, + android30Format: Android30RenameFormat, + callback: (success: Boolean) -> Unit + ) { val fileDirItems = paths.map { File(it).toFileDirItem(context) } val uriPairs = context.getFileUrisFromFileDirItems(fileDirItems) val validPaths = uriPairs.first val uris = uriPairs.second + val activity = activity activity?.updateSDK30Uris(uris) { success -> if (success) { try { uris.forEachIndexed { index, uri -> val path = validPaths[index] val newFileName = getNewPath(path, useMediaFileExtension)?.getFilenameFromPath() ?: return@forEachIndexed - val values = ContentValues().apply { - put(MediaStore.Images.Media.DISPLAY_NAME, newFileName) + when (android30Format) { + Android30RenameFormat.SAF -> { + val sourceFile = File(path).toFileDirItem(context) + val newPath = "${path.getParentPath()}/$newFileName" + val destinationFile = sourceFile.copy(path = newPath, name = newFileName) + if (activity.copySingleFileSdk30(sourceFile, destinationFile)) { + if (!activity.baseConfig.keepLastModified) { + File(newPath).setLastModified(System.currentTimeMillis()) + } + activity.contentResolver.delete(uri, null) + activity.updateInMediaStore(path, newPath) + activity.scanPathsRecursively(arrayListOf(newPath)) + } + } + Android30RenameFormat.CONTENT_RESOLVER -> { + val values = ContentValues().apply { + put(MediaStore.Images.Media.DISPLAY_NAME, newFileName) + } + context.contentResolver.update(uri, values, null, null) + } + Android30RenameFormat.NONE -> { + activity.runOnUiThread { + callback(true) + } + return@forEachIndexed + } } - - context.contentResolver.update(uri, values, null, null) } - callback(true) + activity.runOnUiThread { + callback(true) + } } catch (e: Exception) { - callback(false) + activity.runOnUiThread { + activity.showErrorToast(e) + callback(false) + } } } } diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/views/RenameSimpleTab.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/views/RenameSimpleTab.kt index 2fa6a525f..9f3821fc3 100644 --- a/commons/src/main/kotlin/com/simplemobiletools/commons/views/RenameSimpleTab.kt +++ b/commons/src/main/kotlin/com/simplemobiletools/commons/views/RenameSimpleTab.kt @@ -9,6 +9,7 @@ import com.simplemobiletools.commons.R import com.simplemobiletools.commons.activities.BaseSimpleActivity import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.interfaces.RenameTab +import com.simplemobiletools.commons.models.Android30RenameFormat import kotlinx.android.synthetic.main.tab_rename_simple.view.* import java.io.File @@ -44,8 +45,9 @@ class RenameSimpleTab(context: Context, attrs: AttributeSet) : RelativeLayout(co } val validPaths = paths.filter { activity?.getDoesFilePathExist(it) == true } - val sdFilePath = validPaths.firstOrNull { activity?.isPathOnSD(it) == true } ?: validPaths.firstOrNull() - if (sdFilePath == null) { + val firstPath = validPaths.firstOrNull() + val sdFilePath = validPaths.firstOrNull { activity?.isPathOnSD(it) == true } ?: firstPath + if (firstPath == null || sdFilePath == null) { activity?.toast(R.string.unknown_error_occurred) return } @@ -55,61 +57,73 @@ class RenameSimpleTab(context: Context, attrs: AttributeSet) : RelativeLayout(co return@handleSAFDialog } - ignoreClicks = true - var pathsCnt = validPaths.size - for (path in validPaths) { - if (stopLooping) { - return@handleSAFDialog + activity?.handleSAFDialogSdk30(firstPath) { + if (!it) { + return@handleSAFDialogSdk30 } - val fullName = path.getFilenameFromPath() - var dotAt = fullName.lastIndexOf(".") - if (dotAt == -1) { - dotAt = fullName.length - } + ignoreClicks = true + var pathsCnt = validPaths.size + for (path in validPaths) { + if (stopLooping) { + return@handleSAFDialogSdk30 + } - val name = fullName.substring(0, dotAt) - val extension = if (fullName.contains(".")) ".${fullName.getFilenameExtension()}" else "" + val fullName = path.getFilenameFromPath() + var dotAt = fullName.lastIndexOf(".") + if (dotAt == -1) { + dotAt = fullName.length + } - val newName = if (append) { - "$name$valueToAdd$extension" - } else { - "$valueToAdd$fullName" - } + val name = fullName.substring(0, dotAt) + val extension = if (fullName.contains(".")) ".${fullName.getFilenameExtension()}" else "" - val newPath = "${path.getParentPath()}/$newName" - - if (activity?.getDoesFilePathExist(newPath) == true) { - continue - } - - activity?.renameFile(path, newPath, true) { success, useAndroid30Way -> - if (success) { - pathsCnt-- - if (pathsCnt == 0) { - callback(true) - } + val newName = if (append) { + "$name$valueToAdd$extension" } else { - ignoreClicks = false - if (useAndroid30Way) { - stopLooping = true - renameAllFiles(validPaths, append, valueToAdd, callback) + "$valueToAdd$fullName" + } + + val newPath = "${path.getParentPath()}/$newName" + + if (activity?.getDoesFilePathExist(newPath) == true) { + continue + } + + activity?.renameFile(path, newPath, true) { success, android30Format -> + if (success) { + pathsCnt-- + if (pathsCnt == 0) { + callback(true) + } } else { - activity?.toast(R.string.unknown_error_occurred) + ignoreClicks = false + if (android30Format != Android30RenameFormat.NONE) { + stopLooping = true + renameAllFiles(validPaths, append, valueToAdd, android30Format, callback) + } else { + activity?.toast(R.string.unknown_error_occurred) + } } } } + stopLooping = false } - - stopLooping = false } } - private fun renameAllFiles(paths: List, appendString: Boolean, stringToAdd: String, callback: (success: Boolean) -> Unit) { + private fun renameAllFiles( + paths: List, + appendString: Boolean, + stringToAdd: String, + android30Format: Android30RenameFormat, + callback: (success: Boolean) -> Unit + ) { val fileDirItems = paths.map { File(it).toFileDirItem(context) } val uriPairs = context.getFileUrisFromFileDirItems(fileDirItems) val validPaths = uriPairs.first val uris = uriPairs.second + val activity = activity activity?.updateSDK30Uris(uris) { success -> if (success) { try { @@ -131,15 +145,42 @@ class RenameSimpleTab(context: Context, attrs: AttributeSet) : RelativeLayout(co "$stringToAdd$fullName" } - val values = ContentValues().apply { - put(MediaStore.Images.Media.DISPLAY_NAME, newName) + when (android30Format) { + Android30RenameFormat.SAF -> { + val sourceFile = File(path).toFileDirItem(activity) + val newPath = "${path.getParentPath()}/$newName" + val destinationFile = sourceFile.copy(path = newPath, name = newName) + if (activity.copySingleFileSdk30(sourceFile, destinationFile)) { + if (!activity.baseConfig.keepLastModified) { + File(newPath).setLastModified(System.currentTimeMillis()) + } + activity.contentResolver.delete(uri, null) + activity.updateInMediaStore(path, newPath) + activity.scanPathsRecursively(arrayListOf(newPath)) + } + } + Android30RenameFormat.CONTENT_RESOLVER -> { + val values = ContentValues().apply { + put(MediaStore.Images.Media.DISPLAY_NAME, newName) + } + context.contentResolver.update(uri, values, null, null) + } + Android30RenameFormat.NONE -> { + activity.runOnUiThread { + callback(true) + } + return@forEachIndexed + } } - - context.contentResolver.update(uri, values, null, null) } - callback(true) + activity.runOnUiThread { + callback(true) + } } catch (e: Exception) { - callback(false) + activity.runOnUiThread { + activity.showErrorToast(e) + callback(false) + } } } }