Merge pull request #1327 from KryptKode/ref/storage-methods
Clean up storage methods
This commit is contained in:
commit
ee9863c9c5
3 changed files with 119 additions and 111 deletions
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue