In my Android application I have a feature of renaming files. I have implemented this feature by creating a new file with new name on same path and then copying the content of original file to new file after all that I delete the file with old name.
The problem here is that the timestamp of the original file is not retained and the renamed file gets created with time when it was renamed.
What I have tried so far
1- I used file.renameTo() function.
2- 1 never works so I used setLastModified method for new file and passed it the last modified date of original file. Got this last modified date by using existingFile.lastModified().
3- After that in function where i am fetching files using Media Store I sorted the files by DATE_MODIFIED
4- Point 3 doesn't worked properly as it keeps showing renamed file on top with original file date, so I removed Sort by DATE_MODIFIED from media store and tried fetchedFiles.sortByDescending { it.getMFileDate() }
5- Point 4 also have same issue the renamed file shows up on top.
Code for renameTo function usage that didn't work properly
try {
val existingFile = File(selectedFile.getMAbsolute_path())
val separator = "."
val arrValues: Array<String> =
selectedFile.getMAbsolute_path()?.split(separator)!!.toTypedArray()
val newFileName = inputText
val newFilePath =
selectedFile.getMParent_file() + "/$newFileName.${arrValues[arrValues.size - 1]}"
val newFile = File(newFilePath)
existingFile.renameTo(newFile)
} catch (e: Exception) {
e.printStackTrace()
}
The above code renames the file on device version 8 but on Android version 7 it just removes the file. It doesn't shows up same results on different devices
Below is my current code:
Permissions
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
<uses-permission android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT" />
<uses-permission android:name="com.android.launcher.permission.READ_PHONE_STATE" />
UPDATED [23-12-2020]
I have made changes to proceedRenaming function, first i created new file object then copied it, after that i have set lastModified and at the end Media Store scan is called.
This is the right call hierarchy. It works fine on devices greater than or equal to Android Version 8 (OREO).
I have figured out the setLastModified function is not working on devices below Android Version 8 (OREO). It keeps on setting the current date to new file.
Rename Function
private fun proceedRenaming(
context: Context,
selectedFile: MyFileModel,
positionOfDeleted: Int,
inputText: String,
isFileShortcutCreated: Boolean
) {
try {
//delete shortcut if exists
deleteFileShortCut(context, selectedFile, positionOfDeleted)
val existingFile = File(selectedFile.getMAbsolute_path())
val separator = "."
val arrValues: Array<String> =
selectedFile.getMAbsolute_path()?.split(separator)!!.toTypedArray()
val newFileName = inputText
val newFilePath = selectedFile.getMParent_file() + "/$newFileName.${arrValues[arrValues.size - 1]}"
val newFile = File(newFilePath)
copy(existingFile, newFile)
//print the original last modified date
val sdf = SimpleDateFormat("MM/dd/yyyy")
val date1 = "" + sdf.format(existingFile.lastModified())
Log.d("Origina Date :", date1)
//set this date
//need convert the above date to milliseconds in long value
val newDate: Date = sdf.parse(date1)
newFile.setLastModified(newDate.time)
//print the latest last modified date
val date2 = "" + sdf.format(newFile.lastModified())
Log.i("Lastest Date : ", date2)
context?.sendBroadcast(
Intent(
Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,
Uri.fromFile(newFile)
)
)
if (existingFile.exists()) {
existingFile.delete()
existingFile.canonicalFile.delete()
if (existingFile.exists()) {
BaseApplication.applicationContext()
.deleteFile(existingFile.getName())
}
}
MediaScannerConnection.scanFile(
context,
arrayOf<String>(selectedFile.getMAbsolute_path().toString()),
null
) { path, uri ->
Log.i("ExternalStorage", "Scanned $path:")
Log.i("ExternalStorage", "-> uri=$uri")
context?.contentResolver
?.delete(uri, null, null)
}
selectedFile.setOldFileName(selectedFile.getMFile_name()!!)
selectedFile.setOldFileParentFileh(selectedFile.getMParent_file()!!)
selectedFile.setOldFilePath(selectedFile.getMAbsolute_path()!!)
selectedFile.setMAbsolute_path(newFilePath)
selectedFile.setMFile_name(newFileName)
selectedFile.setPosition(positionOfDeleted)
renamedFile.postValue(selectedFile)
if (isFileShortcutCreated) {
createFileShortCut(context, selectedFile, positionOfDeleted)
}
} catch (e: Exception) {
e.printStackTrace()
Toast.makeText(
context,
context?.getString(R.string.text_rename_error),
Toast.LENGTH_SHORT
).show()
}
}
Copy Function
@Throws(IOException::class)
fun copy(src: File?, dst: File?) {
val inStream = FileInputStream(src)
val outStream = FileOutputStream(dst)
val inChannel: FileChannel = inStream.getChannel()
val outChannel: FileChannel = outStream.getChannel()
inChannel.transferTo(0, inChannel.size(), outChannel)
inStream.close()
outStream.close()
}
Function Where I am fetching files from MediaStore
private fun readFiles(
args: Array<String?>,
where: String
): ArrayList<MyFileModel> {
val fetchedFiles = ArrayList<MyFileModel>()
var fileCursorExternal: Cursor? = null
var fileCursorInternal: Cursor? = null
try {
//Tables
val tableExternal = MediaStore.Files.getContentUri("external")
val tableInternal = MediaStore.Files.getContentUri("internal")
//Column
val column = arrayOf(
MediaStore.Files.FileColumns.DATE_ADDED,
MediaStore.MediaColumns.DATA,
MediaStore.MediaColumns.TITLE,
MediaStore.MediaColumns.SIZE,
MediaStore.Files.FileColumns.DATE_MODIFIED
)
//Sort by date
val orderBy = MediaStore.Files.FileColumns.DATE_MODIFIED
fileCursorExternal = context.contentResolver.query(
tableExternal,
column,
where,
args, "$orderBy DESC"
)
while (fileCursorExternal!!.moveToNext()) {
fetchedFiles.add(setMyFileModel(fileCursorExternal))
}
fileCursorInternal = context.contentResolver.query(
tableInternal,
column,
where,
args,
"$orderBy DESC"
)
while (fileCursorInternal!!.moveToNext()) {
fetchedFiles.add(setMyFileModel(fileCursorInternal))
}
} catch (ex: java.lang.Exception) {
ex.printStackTrace()
} finally {
fileCursorExternal?.close()
fileCursorInternal?.close()
}
//fetchedFiles.sortByDescending { it.getMFileDate() } //commented as not working
return fetchedFiles
}
Function Where the data from cursor is converted to MyFileModel class object
private fun setMyFileModel(cursor: Cursor): MyFileModel {
val MyFileModel = MyFileModel()
if (cursor != null) {
try {
MyFileModel.setMFileDate(
getReadableDate(
cursor.getLong(
cursor.getColumnIndexOrThrow(
MediaStore.Files.FileColumns.DATE_MODIFIED
)
)
)
)
MyFileModel.setMAbsolute_path(
cursor.getString(
cursor.getColumnIndexOrThrow(
MediaStore.MediaColumns.DATA
)
)
)
MyFileModel.setMFile_name(
cursor.getString(
cursor.getColumnIndexOrThrow(
MediaStore.MediaColumns.TITLE
)
)
)
MyFileModel.setMFile_size(
getReadableSize(
cursor.getLong(
cursor.getColumnIndexOrThrow(
MediaStore.MediaColumns.SIZE
)
)
)
)
MyFileModel.setFileType(getFileType(MyFileModel.getMAbsolute_path()).name)
if (MyFileModel.getMAbsolute_path() != null) {
val file = File(MyFileModel.getMAbsolute_path())
MyFileModel.setMParent_file(file.parent)
}
} catch (ex: Exception) {
ex.printStackTrace()
}
}
return MyFileModel
}
Function to convert long date to string
private fun getReadableDate(dateVal: Long): String {
try {
var date = dateVal
date *= 1000L
return SimpleDateFormat("dd MMM yyyy").format(Date(date))
} catch (ex: Exception) {
ex.printStackTrace()
}
return ""
}
Required Output
1- On renaming a file, new file should be created old file should be deleted.
2- New file should have timestamp of old file so that sorting is not disturbed.
What can I try to resolve this?