Merge pull request #1327 from KryptKode/ref/storage-methods

Clean up storage methods
This commit is contained in:
Tibor Kaputa 2022-02-28 12:26:43 +01:00 committed by GitHub
commit ee9863c9c5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 119 additions and 111 deletions

View file

@ -160,7 +160,7 @@ fun BaseSimpleActivity.isShowingSAFDialogForDeleteSdk30(path: String): Boolean {
WritePermissionDialog(this, Mode.SDK_30) {
Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
putExtra("android.content.extra.SHOW_ADVANCED", true)
putExtra(DocumentsContract.EXTRA_INITIAL_URI, createFirstParentDocumentUri(path))
putExtra(DocumentsContract.EXTRA_INITIAL_URI, createDocumentUriFromFirstParentTree(path))
try {
startActivityForResult(this, OPEN_DOCUMENT_TREE_FOR_DELETE_SDK_30)
checkedDocumentPath = path

View file

@ -0,0 +1,88 @@
package com.simplemobiletools.commons.extensions
import android.content.Context
import android.net.Uri
import android.os.Environment
import android.provider.DocumentsContract
import com.simplemobiletools.commons.helpers.EXTERNAL_STORAGE_PROVIDER_AUTHORITY
import com.simplemobiletools.commons.helpers.isRPlus
import com.simplemobiletools.commons.models.FileDirItem
private const val DOWNLOAD_DIR = "Download"
val DIRS_INACCESSIBLE_WITH_SAF_SDK_30 = listOf(DOWNLOAD_DIR)
fun Context.isAccessibleWithSAFSdk30(path: String): Boolean {
if (path.startsWith(recycleBinPath)) {
return false
}
val firstParentPath = path.getFirstParentPath(this)
val firstParentDir = path.getFirstParentDirName(this)
return isRPlus() && !Environment.isExternalStorageManager() && firstParentPath != path &&
DIRS_INACCESSIBLE_WITH_SAF_SDK_30.all {
firstParentDir != it
}
}
fun Context.createDocumentUriFromFirstParentTree(fullPath: String): Uri {
val storageId = getSAFStorageId(fullPath)
val rootParentDirName = fullPath.getFirstParentDirName(this)
val treeUri = DocumentsContract.buildTreeDocumentUri(EXTERNAL_STORAGE_PROVIDER_AUTHORITY, "$storageId:")
val documentId = "${storageId}:$rootParentDirName"
return DocumentsContract.buildDocumentUriUsingTree(treeUri, documentId)
}
fun Context.createFirstParentTreeUri(fullPath: String): Uri {
val storageId = getSAFStorageId(fullPath)
val rootParentDirName = fullPath.getFirstParentDirName(this)
val firstParentId = "$storageId:$rootParentDirName"
return DocumentsContract.buildTreeDocumentUri(EXTERNAL_STORAGE_PROVIDER_AUTHORITY, firstParentId)
}
fun Context.createDocumentUriUsingFirstParentTreeUri(fullPath: String): Uri {
val storageId = getSAFStorageId(fullPath)
val relativePath = when {
fullPath.startsWith(internalStoragePath) -> fullPath.substring(internalStoragePath.length).trim('/')
else -> fullPath.substringAfter(storageId).trim('/')
}
val treeUri = createFirstParentTreeUri(fullPath)
val documentId = "${storageId}:$relativePath"
return DocumentsContract.buildDocumentUriUsingTree(treeUri, documentId)
}
fun Context.createSdk30SAFDirectory(path: String): Boolean {
return try {
val treeUri = createFirstParentTreeUri(path)
val parentPath = path.getParentPath()
if (!getDoesFilePathExist(parentPath)) {
createAndroidSAFDirectory(parentPath)
}
val documentId = createAndroidSAFDocumentId(parentPath)
val parentUri = DocumentsContract.buildDocumentUriUsingTree(treeUri, documentId)
DocumentsContract.createDocument(contentResolver, parentUri, DocumentsContract.Document.MIME_TYPE_DIR, path.getFilenameFromPath()) != null
} catch (e: IllegalStateException) {
showErrorToast(e)
false
}
}
fun Context.deleteDocumentWithSAFSdk30(fileDirItem: FileDirItem, allowDeleteFolder: Boolean, callback: ((wasSuccess: Boolean) -> Unit)?) {
try {
var fileDeleted = false
if (fileDirItem.isDirectory.not() || allowDeleteFolder) {
val firstParentTreeUri = createFirstParentTreeUri(fileDirItem.path)
val fileUri = createDocumentUriUsingFirstParentTreeUri(fileDirItem.path)
fileDeleted = DocumentsContract.deleteDocument(contentResolver, fileUri)
}
if (fileDeleted) {
deleteFromMediaStore(fileDirItem.path)
callback?.invoke(true)
}
} catch (e: Exception) {
callback?.invoke(false)
showErrorToast(e)
}
}

View file

@ -30,6 +30,11 @@ import java.net.URLDecoder
import java.util.*
import java.util.regex.Pattern
private const val ANDROID_DATA_DIR = "/Android/data/"
private const val ANDROID_OBB_DIR = "/Android/obb/"
val DIRS_ACCESSIBLE_ONLY_WITH_SAF = listOf(ANDROID_DATA_DIR, ANDROID_OBB_DIR)
val Context.recycleBinPath: String get() = filesDir.absolutePath
// http://stackoverflow.com/a/40582634/1967672
fun Context.getSDCardPath(): String {
val directories = getStorageDirectories().filter {
@ -153,30 +158,10 @@ fun Context.isPathOnOTG(path: String) = otgPath.isNotEmpty() && path.startsWith(
fun Context.isPathOnInternalStorage(path: String) = internalStoragePath.isNotEmpty() && path.startsWith(internalStoragePath)
private const val ANDROID_DATA_DIR = "/Android/data/"
private const val ANDROID_OBB_DIR = "/Android/obb/"
private const val DOWNLOAD_DIR = "Download"
val DIRS_ACCESSIBLE_ONLY_WITH_SAF = listOf(ANDROID_DATA_DIR, ANDROID_OBB_DIR)
private val DIRS_INACCESSIBLE_WITH_SAF_SDK_30 = listOf(DOWNLOAD_DIR)
fun Context.getSAFOnlyDirs(): List<String> {
return DIRS_ACCESSIBLE_ONLY_WITH_SAF.map { "$internalStoragePath$it" }
}
fun Context.isAccessibleWithSAFSdk30(path: String): Boolean {
if (path.startsWith(filesDir.absolutePath)) {
return false
}
val firstParentPath = path.getFirstParentPath(this)
val firstParentDir = path.getFirstParentDirName(this)
return isRPlus() && !Environment.isExternalStorageManager() && firstParentPath != path &&
DIRS_INACCESSIBLE_WITH_SAF_SDK_30.all {
firstParentDir != it
}
}
fun Context.isSAFOnlyRoot(path: String): Boolean {
return getSAFOnlyDirs().any { "${path.trimEnd('/')}/".startsWith(it) }
}
@ -238,8 +223,8 @@ fun Context.storeAndroidTreeUri(path: String, treeUri: String) {
}
}
fun Context.createDocumentUri(fullPath: String): Uri {
val storageId = if (fullPath.startsWith('/')) {
fun Context.getSAFStorageId(fullPath: String): String {
return if (fullPath.startsWith('/')) {
when {
fullPath.startsWith(internalStoragePath) -> "primary"
else -> fullPath.substringAfter("/storage/", "").substringBefore('/')
@ -247,6 +232,10 @@ fun Context.createDocumentUri(fullPath: String): Uri {
} else {
fullPath.substringBefore(':', "").substringAfterLast('/')
}
}
fun Context.createDocumentUriFromRootTree(fullPath: String): Uri {
val storageId = getSAFStorageId(fullPath)
val relativePath = when {
fullPath.startsWith(internalStoragePath) -> fullPath.substring(internalStoragePath.length).trim('/')
@ -258,53 +247,6 @@ fun Context.createDocumentUri(fullPath: String): Uri {
return DocumentsContract.buildDocumentUriUsingTree(treeUri, documentId)
}
fun Context.createFirstParentDocumentUri(fullPath: String): Uri {
val storageId = if (fullPath.startsWith('/')) {
when {
fullPath.startsWith(internalStoragePath) -> "primary"
else -> fullPath.substringAfter("/storage/", "").substringBefore('/')
}
} else {
fullPath.substringBefore(':', "").substringAfterLast('/')
}
val rootParentDirName = fullPath.getFirstParentDirName(this)
val treeUri = DocumentsContract.buildTreeDocumentUri(EXTERNAL_STORAGE_PROVIDER_AUTHORITY, "$storageId:")
val documentId = "${storageId}:$rootParentDirName"
return DocumentsContract.buildDocumentUriUsingTree(treeUri, documentId)
}
fun Context.createFirstParentTreeUri(fullPath: String): Uri {
val storageId = if (fullPath.startsWith('/')) {
when {
fullPath.startsWith(internalStoragePath) -> "primary"
else -> fullPath.substringAfter("/storage/", "").substringBefore('/')
}
} else {
fullPath.substringBefore(':', "").substringAfterLast('/')
}
val rootParentDirName = fullPath.getFirstParentDirName(this)
val firstParentId = "$storageId:$rootParentDirName"
return DocumentsContract.buildTreeDocumentUri(EXTERNAL_STORAGE_PROVIDER_AUTHORITY, firstParentId)
}
fun Context.createDocumentUriUsingFirstParentTreeUri(fullPath: String): Uri {
val storageId = if (fullPath.startsWith('/')) {
when {
fullPath.startsWith(internalStoragePath) -> "primary"
else -> fullPath.substringAfter("/storage/", "").substringBefore('/')
}
} else {
fullPath.substringBefore(':', "").substringAfterLast('/')
}
val relativePath = when {
fullPath.startsWith(internalStoragePath) -> fullPath.substring(internalStoragePath.length).trim('/')
else -> fullPath.substringAfter(storageId).trim('/')
}
val treeUri = createFirstParentTreeUri(fullPath)
val documentId = "${storageId}:$relativePath"
return DocumentsContract.buildDocumentUriUsingTree(treeUri, documentId)
}
fun Context.createAndroidDataOrObbPath(fullPath: String): String {
return if (isAndroidDataDir(fullPath)) {
fullPath.getBasePath(this).trimEnd('/').plus(ANDROID_DATA_DIR)
@ -313,13 +255,12 @@ fun Context.createAndroidDataOrObbPath(fullPath: String): String {
}
}
fun Context.createAndroidDataOrObbUri(fullPath: String): Uri {
val path = createAndroidDataOrObbPath(fullPath)
return createDocumentUri(path)
return createDocumentUriFromRootTree(path)
}
fun Context.getStorageRootId(path: String) =
fun Context.getStorageRootIdForAndroidDir(path: String) =
getAndroidTreeUri(path).removeSuffix(if (isAndroidDataDir(path)) "%3AAndroid%2Fdata" else "%3AAndroid%2Fobb").substringAfterLast('/').trimEnd('/')
fun Context.isAStorageRootFolder(path: String): Boolean {
@ -607,9 +548,9 @@ fun Context.getOTGItems(path: String, shouldShowHidden: Boolean, getProperFileSi
@RequiresApi(Build.VERSION_CODES.O)
fun Context.getAndroidSAFFileItems(path: String, shouldShowHidden: Boolean, getProperFileSize: Boolean = true, callback: (ArrayList<FileDirItem>) -> Unit) {
val items = ArrayList<FileDirItem>()
val rootDocId = getStorageRootId(path)
val rootDocId = getStorageRootIdForAndroidDir(path)
val treeUri = getAndroidTreeUri(path).toUri()
val documentId = getAndroidSAFDocumentId(path)
val documentId = createAndroidSAFDocumentId(path)
val childrenUri = try {
DocumentsContract.buildChildDocumentsUriUsingTree(treeUri, documentId)
} catch (e: Exception) {
@ -635,7 +576,7 @@ fun Context.getAndroidSAFFileItems(path: String, shouldShowHidden: Boolean, getP
val mimeType = cursor.getStringValue(Document.COLUMN_MIME_TYPE)
val lastModified = cursor.getLongValue(Document.COLUMN_LAST_MODIFIED)
val isDirectory = mimeType == Document.MIME_TYPE_DIR
val filePath = docId.substring("${getStorageRootId(path)}:".length)
val filePath = docId.substring("${getStorageRootIdForAndroidDir(path)}:".length)
if (!shouldShowHidden && name.startsWith(".")) {
continue
}
@ -726,17 +667,17 @@ fun Context.getFileSize(treeUri: Uri, documentId: String): Long {
} ?: 0L
}
fun Context.getAndroidSAFDocumentId(path: String): String {
fun Context.createAndroidSAFDocumentId(path: String): String {
val basePath = path.getBasePath(this)
val relativePath = path.substring(basePath.length).trim('/')
val storageId = getStorageRootId(path)
val storageId = getStorageRootIdForAndroidDir(path)
return "$storageId:$relativePath"
}
fun Context.getAndroidSAFUri(path: String): Uri {
val treeUri = getAndroidTreeUri(path).toUri()
val documentId = getAndroidSAFDocumentId(path)
val documentId = createAndroidSAFDocumentId(path)
return DocumentsContract.buildDocumentUriUsingTree(treeUri, documentId)
}
@ -775,7 +716,7 @@ fun Context.getFastAndroidSAFDocument(path: String): DocumentFile? {
fun Context.getAndroidSAFChildrenUri(path: String): Uri {
val treeUri = getAndroidTreeUri(path).toUri()
val documentId = getAndroidSAFDocumentId(path)
val documentId = createAndroidSAFDocumentId(path)
return DocumentsContract.buildChildDocumentsUriUsingTree(treeUri, documentId)
}
@ -786,8 +727,7 @@ fun Context.createAndroidSAFDirectory(path: String): Boolean {
if (!getDoesFilePathExist(parentPath)) {
createAndroidSAFDirectory(parentPath)
}
val documentId = getAndroidSAFDocumentId(parentPath)
val documentId = createAndroidSAFDocumentId(parentPath)
val parentUri = DocumentsContract.buildDocumentUriUsingTree(treeUri, documentId)
DocumentsContract.createDocument(contentResolver, parentUri, Document.MIME_TYPE_DIR, path.getFilenameFromPath()) != null
} catch (e: IllegalStateException) {
@ -804,7 +744,7 @@ fun Context.createAndroidSAFFile(path: String): Boolean {
createAndroidSAFDirectory(parentPath)
}
val documentId = getAndroidSAFDocumentId(path.getParentPath())
val documentId = createAndroidSAFDocumentId(path.getParentPath())
val parentUri = DocumentsContract.buildDocumentUriUsingTree(treeUri, documentId)
DocumentsContract.createDocument(contentResolver, parentUri, path.getMimeType(), path.getFilenameFromPath()) != null
} catch (e: IllegalStateException) {
@ -816,7 +756,7 @@ fun Context.createAndroidSAFFile(path: String): Boolean {
fun Context.renameAndroidSAFDocument(oldPath: String, newPath: String): Boolean {
return try {
val treeUri = getAndroidTreeUri(oldPath).toUri()
val documentId = getAndroidSAFDocumentId(oldPath)
val documentId = createAndroidSAFDocumentId(oldPath)
val parentUri = DocumentsContract.buildDocumentUriUsingTree(treeUri, documentId)
DocumentsContract.renameDocument(contentResolver, parentUri, newPath.getFilenameFromPath()) != null
} catch (e: IllegalStateException) {
@ -825,29 +765,9 @@ fun Context.renameAndroidSAFDocument(oldPath: String, newPath: String): Boolean
}
}
fun Context.deleteDocumentWithSAFSdk30(fileDirItem: FileDirItem, allowDeleteFolder: Boolean, callback: ((wasSuccess: Boolean) -> Unit)?) {
try {
var fileDeleted = false
if (fileDirItem.isDirectory.not() || allowDeleteFolder) {
val firstParentTreeUri = createFirstParentTreeUri(fileDirItem.path)
val fileUri = createDocumentUriUsingFirstParentTreeUri(fileDirItem.path)
fileDeleted = DocumentsContract.deleteDocument(contentResolver, fileUri)
}
if (fileDeleted) {
deleteFromMediaStore(fileDirItem.path)
callback?.invoke(true)
}
} catch (e: Exception) {
callback?.invoke(false)
showErrorToast(e)
}
}
fun Context.getAndroidSAFFileSize(path: String): Long {
val treeUri = getAndroidTreeUri(path).toUri()
val documentId = getAndroidSAFDocumentId(path)
val documentId = createAndroidSAFDocumentId(path)
return getFileSize(treeUri, documentId)
}
@ -857,8 +777,8 @@ fun Context.getAndroidSAFFileCount(path: String, countHidden: Boolean): Int {
return 0
}
val documentId = getAndroidSAFDocumentId(path)
val rootDocId = getStorageRootId(path)
val documentId = createAndroidSAFDocumentId(path)
val rootDocId = getStorageRootIdForAndroidDir(path)
return getProperChildrenCount(rootDocId, treeUri, documentId, countHidden)
}
@ -868,8 +788,8 @@ fun Context.getAndroidSAFDirectChildrenCount(path: String, countHidden: Boolean)
return 0
}
val documentId = getAndroidSAFDocumentId(path)
val rootDocId = getStorageRootId(path)
val documentId = createAndroidSAFDocumentId(path)
val rootDocId = getStorageRootIdForAndroidDir(path)
return getDirectChildrenCount(rootDocId, treeUri, documentId, countHidden)
}
@ -879,7 +799,7 @@ fun Context.getAndroidSAFLastModified(path: String): Long {
return 0L
}
val documentId = getAndroidSAFDocumentId(path)
val documentId = createAndroidSAFDocumentId(path)
val projection = arrayOf(Document.COLUMN_LAST_MODIFIED)
val documentUri = DocumentsContract.buildDocumentUriUsingTree(treeUri, documentId)
return contentResolver.query(documentUri, projection, null, null, null)?.use { cursor ->
@ -893,7 +813,7 @@ fun Context.getAndroidSAFLastModified(path: String): Long {
fun Context.deleteAndroidSAFDirectory(path: String, allowDeleteFolder: Boolean = false, callback: ((wasSuccess: Boolean) -> Unit)? = null) {
val treeUri = getAndroidTreeUri(path).toUri()
val documentId = getAndroidSAFDocumentId(path)
val documentId = createAndroidSAFDocumentId(path)
try {
val uri = DocumentsContract.buildDocumentUriUsingTree(treeUri, documentId)
val document = DocumentFile.fromSingleUri(this, uri)