Loading commons/src/main/kotlin/com/simplemobiletools/commons/activities/BaseSimpleActivity.kt +17 −17 Original line number Diff line number Diff line Loading @@ -14,7 +14,6 @@ import android.support.v4.app.ActivityCompat import android.support.v4.util.Pair import android.support.v7.app.AppCompatActivity import android.text.Html import android.util.Log import android.view.MenuItem import android.view.WindowManager import com.simplemobiletools.commons.R Loading @@ -25,6 +24,7 @@ import com.simplemobiletools.commons.dialogs.WritePermissionDialog import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.helpers.* import com.simplemobiletools.commons.interfaces.CopyMoveListener import com.simplemobiletools.commons.models.FileDirItem import java.io.File import java.util.* Loading Loading @@ -168,7 +168,6 @@ open class BaseSimpleActivity : AppCompatActivity() { fun handleSAFDialog(file: File, callback: () -> Unit): Boolean { return if (isShowingSAFDialog(file, baseConfig.treeUri, OPEN_DOCUMENT_TREE)) { Log.e("DEBUG", "SAF dialog") funAfterSAFPermission = callback true } else { Loading @@ -177,7 +176,7 @@ open class BaseSimpleActivity : AppCompatActivity() { } } fun copyMoveFilesTo(files: ArrayList<File>, source: String, destination: String, isCopyOperation: Boolean, copyPhotoVideoOnly: Boolean, fun copyMoveFilesTo(fileDirItems: ArrayList<FileDirItem>, source: String, destination: String, isCopyOperation: Boolean, copyPhotoVideoOnly: Boolean, copyHidden: Boolean, callback: () -> Unit) { if (source == destination) { toast(R.string.source_and_destination_same) Loading @@ -193,31 +192,32 @@ open class BaseSimpleActivity : AppCompatActivity() { handleSAFDialog(destinationFolder) { copyMoveCallback = callback if (isCopyOperation) { startCopyMove(files, destinationFolder, isCopyOperation, copyPhotoVideoOnly, copyHidden) startCopyMove(fileDirItems, destinationFolder, isCopyOperation, copyPhotoVideoOnly, copyHidden) } else { if (isPathOnSD(source) || isPathOnSD(destination) || files.first().isDirectory || isNougatPlus()) { if (isPathOnSD(source) || isPathOnSD(destination) || fileDirItems.first().isDirectory || isNougatPlus()) { handleSAFDialog(File(source)) { startCopyMove(files, destinationFolder, isCopyOperation, copyPhotoVideoOnly, copyHidden) startCopyMove(fileDirItems, destinationFolder, isCopyOperation, copyPhotoVideoOnly, copyHidden) } } else { toast(R.string.moving) val updatedFiles = ArrayList<File>(files.size * 2) updatedFiles.addAll(files) val updatedFiles = ArrayList<FileDirItem>(fileDirItems.size * 2) updatedFiles.addAll(fileDirItems) try { for (oldFile in files) { val newFile = File(destinationFolder, oldFile.name) if (!newFile.exists() && oldFile.renameTo(newFile)) { for (oldFileDirItem in fileDirItems) { val newFile = File(destinationFolder, oldFileDirItem.name) if (!newFile.exists() && File(oldFileDirItem.path).renameTo(newFile)) { if (!baseConfig.keepLastModified) { newFile.setLastModified(System.currentTimeMillis()) } updateInMediaStore(oldFile, newFile) updatedFiles.add(newFile) updateInMediaStore(oldFileDirItem.path, newFile.absolutePath) updatedFiles.add(newFile.toFileDirItem()) } } scanFiles(updatedFiles) { val updatedPaths = updatedFiles.map { it.path } as ArrayList<String> scanPaths(updatedPaths) { runOnUiThread { copyMoveListener.copySucceeded(false, files.size * 2 == updatedFiles.size) copyMoveListener.copySucceeded(false, fileDirItems.size * 2 == updatedFiles.size) } } } catch (e: Exception) { Loading @@ -228,7 +228,7 @@ open class BaseSimpleActivity : AppCompatActivity() { } } private fun startCopyMove(files: ArrayList<File>, destinationFolder: File, isCopyOperation: Boolean, copyPhotoVideoOnly: Boolean, copyHidden: Boolean) { private fun startCopyMove(files: ArrayList<FileDirItem>, destinationFolder: File, isCopyOperation: Boolean, copyPhotoVideoOnly: Boolean, copyHidden: Boolean) { checkConflict(files, destinationFolder, 0, LinkedHashMap()) { toast(if (isCopyOperation) R.string.copying else R.string.moving) val pair = Pair(files, destinationFolder) Loading @@ -236,7 +236,7 @@ open class BaseSimpleActivity : AppCompatActivity() { } } private fun checkConflict(files: ArrayList<File>, destinationFolder: File, index: Int, conflictResolutions: LinkedHashMap<String, Int>, private fun checkConflict(files: ArrayList<FileDirItem>, destinationFolder: File, index: Int, conflictResolutions: LinkedHashMap<String, Int>, callback: (resolutions: LinkedHashMap<String, Int>) -> Unit) { if (index == files.size) { callback(conflictResolutions) Loading commons/src/main/kotlin/com/simplemobiletools/commons/asynctasks/CopyMoveTask.kt +24 −22 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.helpers.CONFLICT_OVERWRITE import com.simplemobiletools.commons.helpers.CONFLICT_SKIP import com.simplemobiletools.commons.interfaces.CopyMoveListener import com.simplemobiletools.commons.models.FileDirItem import java.io.File import java.io.FileInputStream import java.io.InputStream Loading @@ -24,14 +25,14 @@ import java.lang.ref.WeakReference import java.util.* class CopyMoveTask(val activity: BaseSimpleActivity, val copyOnly: Boolean = false, val copyMediaOnly: Boolean, val conflictResolutions: LinkedHashMap<String, Int>, listener: CopyMoveListener, val copyHidden: Boolean) : AsyncTask<Pair<ArrayList<File>, File>, Void, Boolean>() { listener: CopyMoveListener, val copyHidden: Boolean) : AsyncTask<Pair<ArrayList<FileDirItem>, File>, Void, Boolean>() { private val INITIAL_PROGRESS_DELAY = 3000L private val PROGRESS_RECHECK_INTERVAL = 500L private var mListener: WeakReference<CopyMoveListener>? = null private var mMovedFiles: ArrayList<File> = ArrayList() private var mMovedFiles: ArrayList<FileDirItem> = ArrayList() private var mDocuments = LinkedHashMap<String, DocumentFile?>() private lateinit var mFiles: ArrayList<File> private lateinit var mFiles: ArrayList<FileDirItem> private var mFileCountToCopy = 0 // progress indication Loading @@ -49,7 +50,7 @@ class CopyMoveTask(val activity: BaseSimpleActivity, val copyOnly: Boolean = fal mNotificationBuilder = NotificationCompat.Builder(activity) } override fun doInBackground(vararg params: Pair<ArrayList<File>, File>): Boolean? { override fun doInBackground(vararg params: Pair<ArrayList<FileDirItem>, File>): Boolean? { if (params.isEmpty()) { return false } Loading @@ -62,7 +63,7 @@ class CopyMoveTask(val activity: BaseSimpleActivity, val copyOnly: Boolean = fal for (file in mFiles) { val newFile = File(pair.second, file.name) if (!newFile.exists() || getConflictResolution(newFile) != CONFLICT_SKIP) { mMaxSize += (file.getProperSize(copyHidden) / 1000).toInt() mMaxSize += (File(file.path).getProperSize(copyHidden) / 1000).toInt() } } Loading @@ -80,7 +81,7 @@ class CopyMoveTask(val activity: BaseSimpleActivity, val copyOnly: Boolean = fal mFileCountToCopy-- continue } else if (resolution == CONFLICT_OVERWRITE) { activity.deleteFilesBg(arrayListOf(newFile), true) activity.deleteFileBg(newFile.toFileDirItem(), true) } } Loading @@ -95,7 +96,8 @@ class CopyMoveTask(val activity: BaseSimpleActivity, val copyOnly: Boolean = fal activity.deleteFiles(mMovedFiles) {} } activity.scanFiles(mFiles) {} val paths = mFiles.map { it.path } as ArrayList<String> activity.scanPaths(paths) {} return true } Loading Loading @@ -151,7 +153,7 @@ class CopyMoveTask(val activity: BaseSimpleActivity, val copyOnly: Boolean = fal } } private fun copy(source: File, destination: File) { private fun copy(source: FileDirItem, destination: File) { if (source.isDirectory) { copyDirectory(source, destination) } else { Loading @@ -159,29 +161,29 @@ class CopyMoveTask(val activity: BaseSimpleActivity, val copyOnly: Boolean = fal } } private fun copyDirectory(source: File, destination: File) { private fun copyDirectory(source: FileDirItem, destination: File) { if (!activity.createDirectorySync(destination)) { val error = String.format(activity.getString(R.string.could_not_create_folder), destination.absolutePath) activity.showErrorToast(error) return } val children = source.list() val children = File(source.path).list() for (child in children) { val newFile = File(destination, child) if (newFile.exists()) { continue } val oldFile = File(source, child) copy(oldFile, newFile) val oldFile = File(source.path, child) copy(oldFile.toFileDirItem(), newFile) } mMovedFiles.add(source) } private fun copyFile(source: File, destination: File) { if (copyMediaOnly && !source.absolutePath.isImageVideoGif()) { mCurrentProgress += source.length() private fun copyFile(source: FileDirItem, destination: File) { if (copyMediaOnly && !source.path.isImageVideoGif()) { mCurrentProgress += source.size return } Loading @@ -189,7 +191,7 @@ class CopyMoveTask(val activity: BaseSimpleActivity, val copyOnly: Boolean = fal if (!activity.createDirectorySync(directory)) { val error = String.format(activity.getString(R.string.could_not_create_folder), directory.absolutePath) activity.showErrorToast(error) mCurrentProgress += source.length() mCurrentProgress += source.size return } Loading @@ -201,9 +203,9 @@ class CopyMoveTask(val activity: BaseSimpleActivity, val copyOnly: Boolean = fal mDocuments[destination.parent] = activity.getFileDocument(destination.parent) } out = activity.getFileOutputStreamSync(destination.absolutePath, source.getMimeType(), mDocuments[destination.parent]) out = activity.getFileOutputStreamSync(destination.absolutePath, source.path.getMimeType(), mDocuments[destination.parent]) inputStream = FileInputStream(source) inputStream = FileInputStream(File(source.path)) val buffer = ByteArray(DEFAULT_BUFFER_SIZE) var bytes = inputStream.read(buffer) Loading @@ -213,10 +215,10 @@ class CopyMoveTask(val activity: BaseSimpleActivity, val copyOnly: Boolean = fal bytes = inputStream.read(buffer) } if (source.length() == destination.length()) { if (source.size == destination.length()) { mMovedFiles.add(source) if (activity.baseConfig.keepLastModified) { copyOldLastModified(source, destination) copyOldLastModified(source.path, destination) } else { activity.scanFile(destination) {} } Loading @@ -229,13 +231,13 @@ class CopyMoveTask(val activity: BaseSimpleActivity, val copyOnly: Boolean = fal } } private fun copyOldLastModified(source: File, destination: File) { private fun copyOldLastModified(sourcePath: String, destination: File) { 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(source.absolutePath) var selectionArgs = arrayOf(sourcePath) val cursor = activity.applicationContext.contentResolver.query(uri, projection, selection, selectionArgs, null) cursor?.use { Loading commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Activity.kt +51 −38 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ import com.simplemobiletools.commons.dialogs.SecurityDialog import com.simplemobiletools.commons.dialogs.WhatsNewDialog import com.simplemobiletools.commons.dialogs.WritePermissionDialog import com.simplemobiletools.commons.helpers.* import com.simplemobiletools.commons.models.FileDirItem import com.simplemobiletools.commons.models.Release import com.simplemobiletools.commons.models.SharedTheme import com.simplemobiletools.commons.views.MyTextView Loading Loading @@ -297,7 +298,7 @@ fun BaseSimpleActivity.checkWhatsNew(releases: List<Release>, currVersion: Int) baseConfig.lastVersion = currVersion } fun BaseSimpleActivity.deleteFolders(folders: ArrayList<File>, deleteMediaOnly: Boolean = true, callback: ((wasSuccess: Boolean) -> Unit)? = null) { fun BaseSimpleActivity.deleteFolders(folders: ArrayList<FileDirItem>, deleteMediaOnly: Boolean = true, callback: ((wasSuccess: Boolean) -> Unit)? = null) { if (Looper.myLooper() == Looper.getMainLooper()) { Thread { deleteFoldersBg(folders, deleteMediaOnly, callback) Loading @@ -307,12 +308,12 @@ fun BaseSimpleActivity.deleteFolders(folders: ArrayList<File>, deleteMediaOnly: } } fun BaseSimpleActivity.deleteFoldersBg(folders: ArrayList<File>, deleteMediaOnly: Boolean = true, callback: ((wasSuccess: Boolean) -> Unit)? = null) { fun BaseSimpleActivity.deleteFoldersBg(folders: ArrayList<FileDirItem>, deleteMediaOnly: Boolean = true, callback: ((wasSuccess: Boolean) -> Unit)? = null) { var wasSuccess = false var needPermissionForPath = "" for (file in folders) { if (needsStupidWritePermissions(file.absolutePath) && baseConfig.treeUri.isEmpty()) { needPermissionForPath = file.absolutePath for (folder in folders) { if (needsStupidWritePermissions(folder.path) && baseConfig.treeUri.isEmpty()) { needPermissionForPath = folder.path break } } Loading @@ -331,7 +332,7 @@ fun BaseSimpleActivity.deleteFoldersBg(folders: ArrayList<File>, deleteMediaOnly } } fun BaseSimpleActivity.deleteFolder(folder: File, deleteMediaOnly: Boolean = true, callback: ((wasSuccess: Boolean) -> Unit)? = null) { fun BaseSimpleActivity.deleteFolder(folder: FileDirItem, deleteMediaOnly: Boolean = true, callback: ((wasSuccess: Boolean) -> Unit)? = null) { if (Looper.myLooper() == Looper.getMainLooper()) { Thread { deleteFolderBg(folder, deleteMediaOnly, callback) Loading @@ -341,7 +342,8 @@ fun BaseSimpleActivity.deleteFolder(folder: File, deleteMediaOnly: Boolean = tru } } fun BaseSimpleActivity.deleteFolderBg(folder: File, deleteMediaOnly: Boolean = true, callback: ((wasSuccess: Boolean) -> Unit)? = null) { fun BaseSimpleActivity.deleteFolderBg(fileDirItem: FileDirItem, deleteMediaOnly: Boolean = true, callback: ((wasSuccess: Boolean) -> Unit)? = null) { val folder = File(fileDirItem.path) if (folder.exists()) { val filesArr = folder.listFiles() if (filesArr == null) { Loading @@ -352,17 +354,17 @@ fun BaseSimpleActivity.deleteFolderBg(folder: File, deleteMediaOnly: Boolean = t val filesList = (filesArr as Array).toList() val files = filesList.filter { !deleteMediaOnly || it.isImageVideoGif() } for (file in files) { deleteFileBg(file, false) { } deleteFileBg(file.toFileDirItem(), false) { } } if (folder.listFiles()?.isEmpty() == true) { deleteFileBg(folder, true) { } deleteFileBg(fileDirItem, true) { } } } callback?.invoke(true) } fun BaseSimpleActivity.deleteFiles(files: ArrayList<File>, allowDeleteFolder: Boolean = false, callback: ((wasSuccess: Boolean) -> Unit)? = null) { fun BaseSimpleActivity.deleteFiles(files: ArrayList<FileDirItem>, allowDeleteFolder: Boolean = false, callback: ((wasSuccess: Boolean) -> Unit)? = null) { if (Looper.myLooper() == Looper.getMainLooper()) { Thread { deleteFilesBg(files, allowDeleteFolder, callback) Loading @@ -372,18 +374,19 @@ fun BaseSimpleActivity.deleteFiles(files: ArrayList<File>, allowDeleteFolder: Bo } } fun BaseSimpleActivity.deleteFilesBg(files: ArrayList<File>, allowDeleteFolder: Boolean = false, callback: ((wasSuccess: Boolean) -> Unit)? = null) { fun BaseSimpleActivity.deleteFilesBg(files: ArrayList<FileDirItem>, allowDeleteFolder: Boolean = false, callback: ((wasSuccess: Boolean) -> Unit)? = null) { if (files.isEmpty()) { callback?.invoke(true) return } var wasSuccess = false handleSAFDialog(files[0]) { handleSAFDialog(File(files[0].path)) { files.forEachIndexed { index, file -> deleteFileBg(file, allowDeleteFolder) { if (it) if (it) { wasSuccess = true } if (index == files.size - 1) { callback?.invoke(wasSuccess) Loading @@ -393,21 +396,22 @@ fun BaseSimpleActivity.deleteFilesBg(files: ArrayList<File>, allowDeleteFolder: } } fun BaseSimpleActivity.deleteFile(file: File, allowDeleteFolder: Boolean = false, callback: ((wasSuccess: Boolean) -> Unit)? = null) { fun BaseSimpleActivity.deleteFile(fileDirItem: FileDirItem, allowDeleteFolder: Boolean = false, callback: ((wasSuccess: Boolean) -> Unit)? = null) { if (Looper.myLooper() == Looper.getMainLooper()) { Thread { deleteFileBg(file, allowDeleteFolder, callback) deleteFileBg(fileDirItem, allowDeleteFolder, callback) }.start() } else { deleteFileBg(file, allowDeleteFolder, callback) deleteFileBg(fileDirItem, allowDeleteFolder, callback) } } @SuppressLint("NewApi") fun BaseSimpleActivity.deleteFileBg(file: File, allowDeleteFolder: Boolean = false, callback: ((wasSuccess: Boolean) -> Unit)? = null) { var fileDeleted = !file.exists() || file.delete() fun BaseSimpleActivity.deleteFileBg(fileDirItem: FileDirItem, allowDeleteFolder: Boolean = false, callback: ((wasSuccess: Boolean) -> Unit)? = null) { val file = File(fileDirItem.path) var fileDeleted = !isPathOnOTG(fileDirItem.path) && (!file.exists() || file.delete()) if (fileDeleted) { rescanDeletedFile(file) { rescanDeletedFile(fileDirItem) { callback?.invoke(true) } } else { Loading @@ -415,31 +419,40 @@ fun BaseSimpleActivity.deleteFileBg(file: File, allowDeleteFolder: Boolean = fal fileDeleted = deleteRecursively(file) } if (!fileDeleted && isPathOnSD(file.absolutePath)) { if (!fileDeleted) { if (isPathOnSD(fileDirItem.path)) { handleSAFDialog(file) { fileDeleted = tryFastDocumentDelete(file, allowDeleteFolder) trySAFFileDelete(fileDirItem, allowDeleteFolder, callback) } } else if (isPathOnOTG(fileDirItem.path)) { trySAFFileDelete(fileDirItem, allowDeleteFolder, callback) } } } } @SuppressLint("NewApi") fun BaseSimpleActivity.trySAFFileDelete(fileDirItem: FileDirItem, allowDeleteFolder: Boolean = false, callback: ((wasSuccess: Boolean) -> Unit)? = null) { var fileDeleted = tryFastDocumentDelete(fileDirItem, allowDeleteFolder) if (!fileDeleted) { val document = getFileDocument(file.absolutePath) if (document != null && (file.isDirectory == document.isDirectory)) { val document = getFileDocument(fileDirItem.path) if (document != null && (fileDirItem.isDirectory == document.isDirectory)) { fileDeleted = (document.isFile == true || allowDeleteFolder) && DocumentsContract.deleteDocument(applicationContext.contentResolver, document.uri) } } if (fileDeleted) { rescanDeletedFile(file) { rescanDeletedFile(fileDirItem) { callback?.invoke(true) } } } } } } fun BaseSimpleActivity.rescanDeletedFile(file: File, callback: (() -> Unit)? = null) { if (deleteFromMediaStore(file)) { fun BaseSimpleActivity.rescanDeletedFile(fileDirItem: FileDirItem, callback: (() -> Unit)? = null) { if (deleteFromMediaStore(fileDirItem.path)) { callback?.invoke() } else { MediaScannerConnection.scanFile(applicationContext, arrayOf(file.absolutePath), null, { s, uri -> MediaScannerConnection.scanFile(applicationContext, arrayOf(fileDirItem.path), null, { s, uri -> try { applicationContext.contentResolver.delete(uri, null, null) } catch (e: Exception) { Loading Loading @@ -493,7 +506,7 @@ fun BaseSimpleActivity.renameFile(oldFile: File, newFile: File, callback: ((succ try { val uri = DocumentsContract.renameDocument(applicationContext.contentResolver, document.uri, newFile.name) if (document.uri != uri) { updateInMediaStore(oldFile, newFile) updateInMediaStore(oldFile.absolutePath, newFile.absolutePath) scanFiles(arrayListOf(oldFile, newFile)) { if (!baseConfig.keepLastModified) { updateLastModified(newFile, System.currentTimeMillis()) Loading @@ -510,7 +523,7 @@ fun BaseSimpleActivity.renameFile(oldFile: File, newFile: File, callback: ((succ } } else if (oldFile.renameTo(newFile)) { if (newFile.isDirectory) { deleteFromMediaStore(oldFile) deleteFromMediaStore(oldFile.path) scanFile(newFile) { callback?.invoke(true) } Loading @@ -518,7 +531,7 @@ fun BaseSimpleActivity.renameFile(oldFile: File, newFile: File, callback: ((succ if (!baseConfig.keepLastModified) { newFile.setLastModified(System.currentTimeMillis()) } updateInMediaStore(oldFile, newFile) updateInMediaStore(oldFile.absolutePath, newFile.absolutePath) scanFile(newFile) { callback?.invoke(true) } Loading commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Context-storage.kt +37 −22 Original line number Diff line number Diff line Loading @@ -109,6 +109,8 @@ fun Context.isPathOnSD(path: String) = sdCardPath.isNotEmpty() && path.startsWit fun Context.isPathOnOTG(path: String) = path.startsWith(OTG_PATH) fun Context.isFileDirItemOnOTG(fileDirItem: FileDirItem) = fileDirItem.path.startsWith(OTG_PATH) fun Context.needsStupidWritePermissions(path: String) = isPathOnSD(path) && isLollipopPlus() @SuppressLint("NewApi") Loading @@ -134,8 +136,8 @@ fun Context.getMyFileUri(file: File): Uri { } @SuppressLint("NewApi") fun Context.tryFastDocumentDelete(file: File, allowDeleteFolder: Boolean): Boolean { val document = getFastDocument(file) fun Context.tryFastDocumentDelete(fileDirItem: FileDirItem, allowDeleteFolder: Boolean): Boolean { val document = getFastDocument(fileDirItem) return if (document?.isFile == true || allowDeleteFolder) { DocumentsContract.deleteDocument(contentResolver, document?.uri) } else { Loading @@ -144,13 +146,23 @@ fun Context.tryFastDocumentDelete(file: File, allowDeleteFolder: Boolean): Boole } @SuppressLint("NewApi") fun Context.getFastDocument(file: File): DocumentFile? { if (!isLollipopPlus() || baseConfig.sdCardPath.isEmpty()) fun Context.getFastDocument(fileDirItem: FileDirItem): DocumentFile? { if (!isLollipopPlus()) { return null } val relativePath = Uri.encode(file.absolutePath.substring(baseConfig.sdCardPath.length).trim('/')) val sdCardPathPart = baseConfig.sdCardPath.split("/").filter(String::isNotEmpty).last().trim('/') val fullUri = "${baseConfig.treeUri}/document/$sdCardPathPart%3A$relativePath" val isOTG = isFileDirItemOnOTG(fileDirItem) if (!isOTG && baseConfig.sdCardPath.isEmpty()) { return null } val startString = if (isOTG) OTG_PATH else baseConfig.sdCardPath val basePath = if (isOTG) baseConfig.OTGBasePath else baseConfig.sdCardPath val treeUri = if (isOTG) baseConfig.OTGTreeUri else baseConfig.treeUri val relativePath = Uri.encode(fileDirItem.path.substring(startString.length).trim('/')) val externalPathPart = basePath.split("/").last(String::isNotEmpty).trim('/') val fullUri = "$treeUri/document/$externalPathPart%3A$relativePath" return DocumentFile.fromSingleUri(this, Uri.parse(fullUri)) } Loading Loading @@ -198,33 +210,33 @@ fun getPaths(file: File): ArrayList<String> { return paths } fun Context.getFileUri(file: File) = when { file.isImageSlow() -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI file.isVideoSlow() -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI fun Context.getFileUri(path: String) = when { path.isImageSlow() -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI path.isVideoSlow() -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI else -> MediaStore.Files.getContentUri("external") } // these functions update the mediastore instantly, MediaScannerConnection.scanFile takes some time to really get applied fun Context.deleteFromMediaStore(file: File): Boolean { fun Context.deleteFromMediaStore(path: String): Boolean { return try { val where = "${MediaStore.MediaColumns.DATA} = ?" val args = arrayOf(file.absolutePath) contentResolver.delete(getFileUri(file), where, args) == 1 val args = arrayOf(path) contentResolver.delete(getFileUri(path), where, args) == 1 } catch (e: Exception) { false } } fun Context.updateInMediaStore(oldFile: File, newFile: File) { fun Context.updateInMediaStore(oldPath: String, newPath: String) { Thread { val values = ContentValues().apply { put(MediaStore.MediaColumns.DATA, newFile.absolutePath) put(MediaStore.MediaColumns.DISPLAY_NAME, newFile.name) put(MediaStore.MediaColumns.TITLE, newFile.name) put(MediaStore.MediaColumns.DATA, newPath) put(MediaStore.MediaColumns.DISPLAY_NAME, newPath.getFilenameFromPath()) put(MediaStore.MediaColumns.TITLE, newPath.getFilenameFromPath()) } val uri = getFileUri(oldFile) val uri = getFileUri(oldPath) val selection = "${MediaStore.MediaColumns.DATA} = ?" val selectionArgs = arrayOf(oldFile.absolutePath) val selectionArgs = arrayOf(oldPath) try { contentResolver.update(uri, values, selection, selectionArgs) Loading @@ -238,7 +250,7 @@ fun Context.updateLastModified(file: File, lastModified: Long) { put(MediaStore.MediaColumns.DATE_MODIFIED, lastModified) } file.setLastModified(lastModified) val uri = getFileUri(file) val uri = getFileUri(file.absolutePath) val selection = "${MediaStore.MediaColumns.DATA} = ?" val selectionArgs = arrayOf(file.absolutePath) Loading Loading @@ -271,12 +283,15 @@ fun Context.getOTGItems(path: String, callback: (ArrayList<FileDirItem>) -> Unit if (first != null) { val fullPath = first.uri.toString() val nameStartIndex = fullPath.lastIndexOf(first.name) val basePath = fullPath.substring(0, nameStartIndex) var basePath = fullPath.substring(0, nameStartIndex) if (basePath.endsWith("%3A")) { basePath = basePath.substring(0, basePath.length - 3) } baseConfig.OTGBasePath = basePath } } val basePath = baseConfig.OTGBasePath val basePath = "${baseConfig.OTGBasePath}%3A" for (file in files) { if (file.exists()) { val filePath = file.uri.toString().substring(basePath.length) Loading commons/src/main/kotlin/com/simplemobiletools/commons/extensions/File.kt +3 −0 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
commons/src/main/kotlin/com/simplemobiletools/commons/activities/BaseSimpleActivity.kt +17 −17 Original line number Diff line number Diff line Loading @@ -14,7 +14,6 @@ import android.support.v4.app.ActivityCompat import android.support.v4.util.Pair import android.support.v7.app.AppCompatActivity import android.text.Html import android.util.Log import android.view.MenuItem import android.view.WindowManager import com.simplemobiletools.commons.R Loading @@ -25,6 +24,7 @@ import com.simplemobiletools.commons.dialogs.WritePermissionDialog import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.helpers.* import com.simplemobiletools.commons.interfaces.CopyMoveListener import com.simplemobiletools.commons.models.FileDirItem import java.io.File import java.util.* Loading Loading @@ -168,7 +168,6 @@ open class BaseSimpleActivity : AppCompatActivity() { fun handleSAFDialog(file: File, callback: () -> Unit): Boolean { return if (isShowingSAFDialog(file, baseConfig.treeUri, OPEN_DOCUMENT_TREE)) { Log.e("DEBUG", "SAF dialog") funAfterSAFPermission = callback true } else { Loading @@ -177,7 +176,7 @@ open class BaseSimpleActivity : AppCompatActivity() { } } fun copyMoveFilesTo(files: ArrayList<File>, source: String, destination: String, isCopyOperation: Boolean, copyPhotoVideoOnly: Boolean, fun copyMoveFilesTo(fileDirItems: ArrayList<FileDirItem>, source: String, destination: String, isCopyOperation: Boolean, copyPhotoVideoOnly: Boolean, copyHidden: Boolean, callback: () -> Unit) { if (source == destination) { toast(R.string.source_and_destination_same) Loading @@ -193,31 +192,32 @@ open class BaseSimpleActivity : AppCompatActivity() { handleSAFDialog(destinationFolder) { copyMoveCallback = callback if (isCopyOperation) { startCopyMove(files, destinationFolder, isCopyOperation, copyPhotoVideoOnly, copyHidden) startCopyMove(fileDirItems, destinationFolder, isCopyOperation, copyPhotoVideoOnly, copyHidden) } else { if (isPathOnSD(source) || isPathOnSD(destination) || files.first().isDirectory || isNougatPlus()) { if (isPathOnSD(source) || isPathOnSD(destination) || fileDirItems.first().isDirectory || isNougatPlus()) { handleSAFDialog(File(source)) { startCopyMove(files, destinationFolder, isCopyOperation, copyPhotoVideoOnly, copyHidden) startCopyMove(fileDirItems, destinationFolder, isCopyOperation, copyPhotoVideoOnly, copyHidden) } } else { toast(R.string.moving) val updatedFiles = ArrayList<File>(files.size * 2) updatedFiles.addAll(files) val updatedFiles = ArrayList<FileDirItem>(fileDirItems.size * 2) updatedFiles.addAll(fileDirItems) try { for (oldFile in files) { val newFile = File(destinationFolder, oldFile.name) if (!newFile.exists() && oldFile.renameTo(newFile)) { for (oldFileDirItem in fileDirItems) { val newFile = File(destinationFolder, oldFileDirItem.name) if (!newFile.exists() && File(oldFileDirItem.path).renameTo(newFile)) { if (!baseConfig.keepLastModified) { newFile.setLastModified(System.currentTimeMillis()) } updateInMediaStore(oldFile, newFile) updatedFiles.add(newFile) updateInMediaStore(oldFileDirItem.path, newFile.absolutePath) updatedFiles.add(newFile.toFileDirItem()) } } scanFiles(updatedFiles) { val updatedPaths = updatedFiles.map { it.path } as ArrayList<String> scanPaths(updatedPaths) { runOnUiThread { copyMoveListener.copySucceeded(false, files.size * 2 == updatedFiles.size) copyMoveListener.copySucceeded(false, fileDirItems.size * 2 == updatedFiles.size) } } } catch (e: Exception) { Loading @@ -228,7 +228,7 @@ open class BaseSimpleActivity : AppCompatActivity() { } } private fun startCopyMove(files: ArrayList<File>, destinationFolder: File, isCopyOperation: Boolean, copyPhotoVideoOnly: Boolean, copyHidden: Boolean) { private fun startCopyMove(files: ArrayList<FileDirItem>, destinationFolder: File, isCopyOperation: Boolean, copyPhotoVideoOnly: Boolean, copyHidden: Boolean) { checkConflict(files, destinationFolder, 0, LinkedHashMap()) { toast(if (isCopyOperation) R.string.copying else R.string.moving) val pair = Pair(files, destinationFolder) Loading @@ -236,7 +236,7 @@ open class BaseSimpleActivity : AppCompatActivity() { } } private fun checkConflict(files: ArrayList<File>, destinationFolder: File, index: Int, conflictResolutions: LinkedHashMap<String, Int>, private fun checkConflict(files: ArrayList<FileDirItem>, destinationFolder: File, index: Int, conflictResolutions: LinkedHashMap<String, Int>, callback: (resolutions: LinkedHashMap<String, Int>) -> Unit) { if (index == files.size) { callback(conflictResolutions) Loading
commons/src/main/kotlin/com/simplemobiletools/commons/asynctasks/CopyMoveTask.kt +24 −22 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.helpers.CONFLICT_OVERWRITE import com.simplemobiletools.commons.helpers.CONFLICT_SKIP import com.simplemobiletools.commons.interfaces.CopyMoveListener import com.simplemobiletools.commons.models.FileDirItem import java.io.File import java.io.FileInputStream import java.io.InputStream Loading @@ -24,14 +25,14 @@ import java.lang.ref.WeakReference import java.util.* class CopyMoveTask(val activity: BaseSimpleActivity, val copyOnly: Boolean = false, val copyMediaOnly: Boolean, val conflictResolutions: LinkedHashMap<String, Int>, listener: CopyMoveListener, val copyHidden: Boolean) : AsyncTask<Pair<ArrayList<File>, File>, Void, Boolean>() { listener: CopyMoveListener, val copyHidden: Boolean) : AsyncTask<Pair<ArrayList<FileDirItem>, File>, Void, Boolean>() { private val INITIAL_PROGRESS_DELAY = 3000L private val PROGRESS_RECHECK_INTERVAL = 500L private var mListener: WeakReference<CopyMoveListener>? = null private var mMovedFiles: ArrayList<File> = ArrayList() private var mMovedFiles: ArrayList<FileDirItem> = ArrayList() private var mDocuments = LinkedHashMap<String, DocumentFile?>() private lateinit var mFiles: ArrayList<File> private lateinit var mFiles: ArrayList<FileDirItem> private var mFileCountToCopy = 0 // progress indication Loading @@ -49,7 +50,7 @@ class CopyMoveTask(val activity: BaseSimpleActivity, val copyOnly: Boolean = fal mNotificationBuilder = NotificationCompat.Builder(activity) } override fun doInBackground(vararg params: Pair<ArrayList<File>, File>): Boolean? { override fun doInBackground(vararg params: Pair<ArrayList<FileDirItem>, File>): Boolean? { if (params.isEmpty()) { return false } Loading @@ -62,7 +63,7 @@ class CopyMoveTask(val activity: BaseSimpleActivity, val copyOnly: Boolean = fal for (file in mFiles) { val newFile = File(pair.second, file.name) if (!newFile.exists() || getConflictResolution(newFile) != CONFLICT_SKIP) { mMaxSize += (file.getProperSize(copyHidden) / 1000).toInt() mMaxSize += (File(file.path).getProperSize(copyHidden) / 1000).toInt() } } Loading @@ -80,7 +81,7 @@ class CopyMoveTask(val activity: BaseSimpleActivity, val copyOnly: Boolean = fal mFileCountToCopy-- continue } else if (resolution == CONFLICT_OVERWRITE) { activity.deleteFilesBg(arrayListOf(newFile), true) activity.deleteFileBg(newFile.toFileDirItem(), true) } } Loading @@ -95,7 +96,8 @@ class CopyMoveTask(val activity: BaseSimpleActivity, val copyOnly: Boolean = fal activity.deleteFiles(mMovedFiles) {} } activity.scanFiles(mFiles) {} val paths = mFiles.map { it.path } as ArrayList<String> activity.scanPaths(paths) {} return true } Loading Loading @@ -151,7 +153,7 @@ class CopyMoveTask(val activity: BaseSimpleActivity, val copyOnly: Boolean = fal } } private fun copy(source: File, destination: File) { private fun copy(source: FileDirItem, destination: File) { if (source.isDirectory) { copyDirectory(source, destination) } else { Loading @@ -159,29 +161,29 @@ class CopyMoveTask(val activity: BaseSimpleActivity, val copyOnly: Boolean = fal } } private fun copyDirectory(source: File, destination: File) { private fun copyDirectory(source: FileDirItem, destination: File) { if (!activity.createDirectorySync(destination)) { val error = String.format(activity.getString(R.string.could_not_create_folder), destination.absolutePath) activity.showErrorToast(error) return } val children = source.list() val children = File(source.path).list() for (child in children) { val newFile = File(destination, child) if (newFile.exists()) { continue } val oldFile = File(source, child) copy(oldFile, newFile) val oldFile = File(source.path, child) copy(oldFile.toFileDirItem(), newFile) } mMovedFiles.add(source) } private fun copyFile(source: File, destination: File) { if (copyMediaOnly && !source.absolutePath.isImageVideoGif()) { mCurrentProgress += source.length() private fun copyFile(source: FileDirItem, destination: File) { if (copyMediaOnly && !source.path.isImageVideoGif()) { mCurrentProgress += source.size return } Loading @@ -189,7 +191,7 @@ class CopyMoveTask(val activity: BaseSimpleActivity, val copyOnly: Boolean = fal if (!activity.createDirectorySync(directory)) { val error = String.format(activity.getString(R.string.could_not_create_folder), directory.absolutePath) activity.showErrorToast(error) mCurrentProgress += source.length() mCurrentProgress += source.size return } Loading @@ -201,9 +203,9 @@ class CopyMoveTask(val activity: BaseSimpleActivity, val copyOnly: Boolean = fal mDocuments[destination.parent] = activity.getFileDocument(destination.parent) } out = activity.getFileOutputStreamSync(destination.absolutePath, source.getMimeType(), mDocuments[destination.parent]) out = activity.getFileOutputStreamSync(destination.absolutePath, source.path.getMimeType(), mDocuments[destination.parent]) inputStream = FileInputStream(source) inputStream = FileInputStream(File(source.path)) val buffer = ByteArray(DEFAULT_BUFFER_SIZE) var bytes = inputStream.read(buffer) Loading @@ -213,10 +215,10 @@ class CopyMoveTask(val activity: BaseSimpleActivity, val copyOnly: Boolean = fal bytes = inputStream.read(buffer) } if (source.length() == destination.length()) { if (source.size == destination.length()) { mMovedFiles.add(source) if (activity.baseConfig.keepLastModified) { copyOldLastModified(source, destination) copyOldLastModified(source.path, destination) } else { activity.scanFile(destination) {} } Loading @@ -229,13 +231,13 @@ class CopyMoveTask(val activity: BaseSimpleActivity, val copyOnly: Boolean = fal } } private fun copyOldLastModified(source: File, destination: File) { private fun copyOldLastModified(sourcePath: String, destination: File) { 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(source.absolutePath) var selectionArgs = arrayOf(sourcePath) val cursor = activity.applicationContext.contentResolver.query(uri, projection, selection, selectionArgs, null) cursor?.use { Loading
commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Activity.kt +51 −38 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ import com.simplemobiletools.commons.dialogs.SecurityDialog import com.simplemobiletools.commons.dialogs.WhatsNewDialog import com.simplemobiletools.commons.dialogs.WritePermissionDialog import com.simplemobiletools.commons.helpers.* import com.simplemobiletools.commons.models.FileDirItem import com.simplemobiletools.commons.models.Release import com.simplemobiletools.commons.models.SharedTheme import com.simplemobiletools.commons.views.MyTextView Loading Loading @@ -297,7 +298,7 @@ fun BaseSimpleActivity.checkWhatsNew(releases: List<Release>, currVersion: Int) baseConfig.lastVersion = currVersion } fun BaseSimpleActivity.deleteFolders(folders: ArrayList<File>, deleteMediaOnly: Boolean = true, callback: ((wasSuccess: Boolean) -> Unit)? = null) { fun BaseSimpleActivity.deleteFolders(folders: ArrayList<FileDirItem>, deleteMediaOnly: Boolean = true, callback: ((wasSuccess: Boolean) -> Unit)? = null) { if (Looper.myLooper() == Looper.getMainLooper()) { Thread { deleteFoldersBg(folders, deleteMediaOnly, callback) Loading @@ -307,12 +308,12 @@ fun BaseSimpleActivity.deleteFolders(folders: ArrayList<File>, deleteMediaOnly: } } fun BaseSimpleActivity.deleteFoldersBg(folders: ArrayList<File>, deleteMediaOnly: Boolean = true, callback: ((wasSuccess: Boolean) -> Unit)? = null) { fun BaseSimpleActivity.deleteFoldersBg(folders: ArrayList<FileDirItem>, deleteMediaOnly: Boolean = true, callback: ((wasSuccess: Boolean) -> Unit)? = null) { var wasSuccess = false var needPermissionForPath = "" for (file in folders) { if (needsStupidWritePermissions(file.absolutePath) && baseConfig.treeUri.isEmpty()) { needPermissionForPath = file.absolutePath for (folder in folders) { if (needsStupidWritePermissions(folder.path) && baseConfig.treeUri.isEmpty()) { needPermissionForPath = folder.path break } } Loading @@ -331,7 +332,7 @@ fun BaseSimpleActivity.deleteFoldersBg(folders: ArrayList<File>, deleteMediaOnly } } fun BaseSimpleActivity.deleteFolder(folder: File, deleteMediaOnly: Boolean = true, callback: ((wasSuccess: Boolean) -> Unit)? = null) { fun BaseSimpleActivity.deleteFolder(folder: FileDirItem, deleteMediaOnly: Boolean = true, callback: ((wasSuccess: Boolean) -> Unit)? = null) { if (Looper.myLooper() == Looper.getMainLooper()) { Thread { deleteFolderBg(folder, deleteMediaOnly, callback) Loading @@ -341,7 +342,8 @@ fun BaseSimpleActivity.deleteFolder(folder: File, deleteMediaOnly: Boolean = tru } } fun BaseSimpleActivity.deleteFolderBg(folder: File, deleteMediaOnly: Boolean = true, callback: ((wasSuccess: Boolean) -> Unit)? = null) { fun BaseSimpleActivity.deleteFolderBg(fileDirItem: FileDirItem, deleteMediaOnly: Boolean = true, callback: ((wasSuccess: Boolean) -> Unit)? = null) { val folder = File(fileDirItem.path) if (folder.exists()) { val filesArr = folder.listFiles() if (filesArr == null) { Loading @@ -352,17 +354,17 @@ fun BaseSimpleActivity.deleteFolderBg(folder: File, deleteMediaOnly: Boolean = t val filesList = (filesArr as Array).toList() val files = filesList.filter { !deleteMediaOnly || it.isImageVideoGif() } for (file in files) { deleteFileBg(file, false) { } deleteFileBg(file.toFileDirItem(), false) { } } if (folder.listFiles()?.isEmpty() == true) { deleteFileBg(folder, true) { } deleteFileBg(fileDirItem, true) { } } } callback?.invoke(true) } fun BaseSimpleActivity.deleteFiles(files: ArrayList<File>, allowDeleteFolder: Boolean = false, callback: ((wasSuccess: Boolean) -> Unit)? = null) { fun BaseSimpleActivity.deleteFiles(files: ArrayList<FileDirItem>, allowDeleteFolder: Boolean = false, callback: ((wasSuccess: Boolean) -> Unit)? = null) { if (Looper.myLooper() == Looper.getMainLooper()) { Thread { deleteFilesBg(files, allowDeleteFolder, callback) Loading @@ -372,18 +374,19 @@ fun BaseSimpleActivity.deleteFiles(files: ArrayList<File>, allowDeleteFolder: Bo } } fun BaseSimpleActivity.deleteFilesBg(files: ArrayList<File>, allowDeleteFolder: Boolean = false, callback: ((wasSuccess: Boolean) -> Unit)? = null) { fun BaseSimpleActivity.deleteFilesBg(files: ArrayList<FileDirItem>, allowDeleteFolder: Boolean = false, callback: ((wasSuccess: Boolean) -> Unit)? = null) { if (files.isEmpty()) { callback?.invoke(true) return } var wasSuccess = false handleSAFDialog(files[0]) { handleSAFDialog(File(files[0].path)) { files.forEachIndexed { index, file -> deleteFileBg(file, allowDeleteFolder) { if (it) if (it) { wasSuccess = true } if (index == files.size - 1) { callback?.invoke(wasSuccess) Loading @@ -393,21 +396,22 @@ fun BaseSimpleActivity.deleteFilesBg(files: ArrayList<File>, allowDeleteFolder: } } fun BaseSimpleActivity.deleteFile(file: File, allowDeleteFolder: Boolean = false, callback: ((wasSuccess: Boolean) -> Unit)? = null) { fun BaseSimpleActivity.deleteFile(fileDirItem: FileDirItem, allowDeleteFolder: Boolean = false, callback: ((wasSuccess: Boolean) -> Unit)? = null) { if (Looper.myLooper() == Looper.getMainLooper()) { Thread { deleteFileBg(file, allowDeleteFolder, callback) deleteFileBg(fileDirItem, allowDeleteFolder, callback) }.start() } else { deleteFileBg(file, allowDeleteFolder, callback) deleteFileBg(fileDirItem, allowDeleteFolder, callback) } } @SuppressLint("NewApi") fun BaseSimpleActivity.deleteFileBg(file: File, allowDeleteFolder: Boolean = false, callback: ((wasSuccess: Boolean) -> Unit)? = null) { var fileDeleted = !file.exists() || file.delete() fun BaseSimpleActivity.deleteFileBg(fileDirItem: FileDirItem, allowDeleteFolder: Boolean = false, callback: ((wasSuccess: Boolean) -> Unit)? = null) { val file = File(fileDirItem.path) var fileDeleted = !isPathOnOTG(fileDirItem.path) && (!file.exists() || file.delete()) if (fileDeleted) { rescanDeletedFile(file) { rescanDeletedFile(fileDirItem) { callback?.invoke(true) } } else { Loading @@ -415,31 +419,40 @@ fun BaseSimpleActivity.deleteFileBg(file: File, allowDeleteFolder: Boolean = fal fileDeleted = deleteRecursively(file) } if (!fileDeleted && isPathOnSD(file.absolutePath)) { if (!fileDeleted) { if (isPathOnSD(fileDirItem.path)) { handleSAFDialog(file) { fileDeleted = tryFastDocumentDelete(file, allowDeleteFolder) trySAFFileDelete(fileDirItem, allowDeleteFolder, callback) } } else if (isPathOnOTG(fileDirItem.path)) { trySAFFileDelete(fileDirItem, allowDeleteFolder, callback) } } } } @SuppressLint("NewApi") fun BaseSimpleActivity.trySAFFileDelete(fileDirItem: FileDirItem, allowDeleteFolder: Boolean = false, callback: ((wasSuccess: Boolean) -> Unit)? = null) { var fileDeleted = tryFastDocumentDelete(fileDirItem, allowDeleteFolder) if (!fileDeleted) { val document = getFileDocument(file.absolutePath) if (document != null && (file.isDirectory == document.isDirectory)) { val document = getFileDocument(fileDirItem.path) if (document != null && (fileDirItem.isDirectory == document.isDirectory)) { fileDeleted = (document.isFile == true || allowDeleteFolder) && DocumentsContract.deleteDocument(applicationContext.contentResolver, document.uri) } } if (fileDeleted) { rescanDeletedFile(file) { rescanDeletedFile(fileDirItem) { callback?.invoke(true) } } } } } } fun BaseSimpleActivity.rescanDeletedFile(file: File, callback: (() -> Unit)? = null) { if (deleteFromMediaStore(file)) { fun BaseSimpleActivity.rescanDeletedFile(fileDirItem: FileDirItem, callback: (() -> Unit)? = null) { if (deleteFromMediaStore(fileDirItem.path)) { callback?.invoke() } else { MediaScannerConnection.scanFile(applicationContext, arrayOf(file.absolutePath), null, { s, uri -> MediaScannerConnection.scanFile(applicationContext, arrayOf(fileDirItem.path), null, { s, uri -> try { applicationContext.contentResolver.delete(uri, null, null) } catch (e: Exception) { Loading Loading @@ -493,7 +506,7 @@ fun BaseSimpleActivity.renameFile(oldFile: File, newFile: File, callback: ((succ try { val uri = DocumentsContract.renameDocument(applicationContext.contentResolver, document.uri, newFile.name) if (document.uri != uri) { updateInMediaStore(oldFile, newFile) updateInMediaStore(oldFile.absolutePath, newFile.absolutePath) scanFiles(arrayListOf(oldFile, newFile)) { if (!baseConfig.keepLastModified) { updateLastModified(newFile, System.currentTimeMillis()) Loading @@ -510,7 +523,7 @@ fun BaseSimpleActivity.renameFile(oldFile: File, newFile: File, callback: ((succ } } else if (oldFile.renameTo(newFile)) { if (newFile.isDirectory) { deleteFromMediaStore(oldFile) deleteFromMediaStore(oldFile.path) scanFile(newFile) { callback?.invoke(true) } Loading @@ -518,7 +531,7 @@ fun BaseSimpleActivity.renameFile(oldFile: File, newFile: File, callback: ((succ if (!baseConfig.keepLastModified) { newFile.setLastModified(System.currentTimeMillis()) } updateInMediaStore(oldFile, newFile) updateInMediaStore(oldFile.absolutePath, newFile.absolutePath) scanFile(newFile) { callback?.invoke(true) } Loading
commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Context-storage.kt +37 −22 Original line number Diff line number Diff line Loading @@ -109,6 +109,8 @@ fun Context.isPathOnSD(path: String) = sdCardPath.isNotEmpty() && path.startsWit fun Context.isPathOnOTG(path: String) = path.startsWith(OTG_PATH) fun Context.isFileDirItemOnOTG(fileDirItem: FileDirItem) = fileDirItem.path.startsWith(OTG_PATH) fun Context.needsStupidWritePermissions(path: String) = isPathOnSD(path) && isLollipopPlus() @SuppressLint("NewApi") Loading @@ -134,8 +136,8 @@ fun Context.getMyFileUri(file: File): Uri { } @SuppressLint("NewApi") fun Context.tryFastDocumentDelete(file: File, allowDeleteFolder: Boolean): Boolean { val document = getFastDocument(file) fun Context.tryFastDocumentDelete(fileDirItem: FileDirItem, allowDeleteFolder: Boolean): Boolean { val document = getFastDocument(fileDirItem) return if (document?.isFile == true || allowDeleteFolder) { DocumentsContract.deleteDocument(contentResolver, document?.uri) } else { Loading @@ -144,13 +146,23 @@ fun Context.tryFastDocumentDelete(file: File, allowDeleteFolder: Boolean): Boole } @SuppressLint("NewApi") fun Context.getFastDocument(file: File): DocumentFile? { if (!isLollipopPlus() || baseConfig.sdCardPath.isEmpty()) fun Context.getFastDocument(fileDirItem: FileDirItem): DocumentFile? { if (!isLollipopPlus()) { return null } val relativePath = Uri.encode(file.absolutePath.substring(baseConfig.sdCardPath.length).trim('/')) val sdCardPathPart = baseConfig.sdCardPath.split("/").filter(String::isNotEmpty).last().trim('/') val fullUri = "${baseConfig.treeUri}/document/$sdCardPathPart%3A$relativePath" val isOTG = isFileDirItemOnOTG(fileDirItem) if (!isOTG && baseConfig.sdCardPath.isEmpty()) { return null } val startString = if (isOTG) OTG_PATH else baseConfig.sdCardPath val basePath = if (isOTG) baseConfig.OTGBasePath else baseConfig.sdCardPath val treeUri = if (isOTG) baseConfig.OTGTreeUri else baseConfig.treeUri val relativePath = Uri.encode(fileDirItem.path.substring(startString.length).trim('/')) val externalPathPart = basePath.split("/").last(String::isNotEmpty).trim('/') val fullUri = "$treeUri/document/$externalPathPart%3A$relativePath" return DocumentFile.fromSingleUri(this, Uri.parse(fullUri)) } Loading Loading @@ -198,33 +210,33 @@ fun getPaths(file: File): ArrayList<String> { return paths } fun Context.getFileUri(file: File) = when { file.isImageSlow() -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI file.isVideoSlow() -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI fun Context.getFileUri(path: String) = when { path.isImageSlow() -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI path.isVideoSlow() -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI else -> MediaStore.Files.getContentUri("external") } // these functions update the mediastore instantly, MediaScannerConnection.scanFile takes some time to really get applied fun Context.deleteFromMediaStore(file: File): Boolean { fun Context.deleteFromMediaStore(path: String): Boolean { return try { val where = "${MediaStore.MediaColumns.DATA} = ?" val args = arrayOf(file.absolutePath) contentResolver.delete(getFileUri(file), where, args) == 1 val args = arrayOf(path) contentResolver.delete(getFileUri(path), where, args) == 1 } catch (e: Exception) { false } } fun Context.updateInMediaStore(oldFile: File, newFile: File) { fun Context.updateInMediaStore(oldPath: String, newPath: String) { Thread { val values = ContentValues().apply { put(MediaStore.MediaColumns.DATA, newFile.absolutePath) put(MediaStore.MediaColumns.DISPLAY_NAME, newFile.name) put(MediaStore.MediaColumns.TITLE, newFile.name) put(MediaStore.MediaColumns.DATA, newPath) put(MediaStore.MediaColumns.DISPLAY_NAME, newPath.getFilenameFromPath()) put(MediaStore.MediaColumns.TITLE, newPath.getFilenameFromPath()) } val uri = getFileUri(oldFile) val uri = getFileUri(oldPath) val selection = "${MediaStore.MediaColumns.DATA} = ?" val selectionArgs = arrayOf(oldFile.absolutePath) val selectionArgs = arrayOf(oldPath) try { contentResolver.update(uri, values, selection, selectionArgs) Loading @@ -238,7 +250,7 @@ fun Context.updateLastModified(file: File, lastModified: Long) { put(MediaStore.MediaColumns.DATE_MODIFIED, lastModified) } file.setLastModified(lastModified) val uri = getFileUri(file) val uri = getFileUri(file.absolutePath) val selection = "${MediaStore.MediaColumns.DATA} = ?" val selectionArgs = arrayOf(file.absolutePath) Loading Loading @@ -271,12 +283,15 @@ fun Context.getOTGItems(path: String, callback: (ArrayList<FileDirItem>) -> Unit if (first != null) { val fullPath = first.uri.toString() val nameStartIndex = fullPath.lastIndexOf(first.name) val basePath = fullPath.substring(0, nameStartIndex) var basePath = fullPath.substring(0, nameStartIndex) if (basePath.endsWith("%3A")) { basePath = basePath.substring(0, basePath.length - 3) } baseConfig.OTGBasePath = basePath } } val basePath = baseConfig.OTGBasePath val basePath = "${baseConfig.OTGBasePath}%3A" for (file in files) { if (file.exists()) { val filePath = file.uri.toString().substring(basePath.length) Loading
commons/src/main/kotlin/com/simplemobiletools/commons/extensions/File.kt +3 −0 File changed.Preview size limit exceeded, changes collapsed. Show changes