read files from Andoid/data and Android/obb
This commit is contained in:
parent
16120313b2
commit
08fbc4a0cd
7 changed files with 307 additions and 64 deletions
|
@ -221,12 +221,37 @@ abstract class BaseSimpleActivity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
Log.i(TAG, "onActivityResult: partition=$partition")
|
||||
Log.i(TAG, "onActivityResult: checkedDocumentPath=$checkedDocumentPath")
|
||||
Log.i(TAG, "onActivityResult: treeUri=${resultData?.data}")
|
||||
val sdOtgPattern = Pattern.compile(SD_OTG_SHORT)
|
||||
|
||||
if (requestCode == OPEN_DOCUMENT_TREE) {
|
||||
if (requestCode == OPEN_DOCUMENT_TREE_PRIMARY) {
|
||||
if (resultCode == Activity.RESULT_OK && resultData != null && resultData.data != null) {
|
||||
if (isProperInternalRoot(resultData.data!!)) {
|
||||
if (resultData.dataString == baseConfig.primaryTreeUri) {
|
||||
toast(R.string.sd_card_usb_same)
|
||||
return
|
||||
}
|
||||
|
||||
val treeUri = resultData.data
|
||||
baseConfig.primaryTreeUri = treeUri.toString()
|
||||
|
||||
val takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
applicationContext.contentResolver.takePersistableUriPermission(treeUri!!, takeFlags)
|
||||
funAfterSAFPermission?.invoke(true)
|
||||
funAfterSAFPermission = null
|
||||
} else {
|
||||
toast(R.string.wrong_root_selected)
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
||||
startActivityForResult(intent, requestCode)
|
||||
}
|
||||
} else {
|
||||
funAfterSAFPermission?.invoke(false)
|
||||
}
|
||||
} else if (requestCode == OPEN_DOCUMENT_TREE_SD) {
|
||||
if (resultCode == Activity.RESULT_OK && resultData != null && resultData.data != null) {
|
||||
val isProperPartition = partition.isEmpty() || !sdOtgPattern.matcher(partition).matches() || (sdOtgPattern.matcher(partition).matches() && resultData.dataString!!.contains(partition))
|
||||
if (isAndroidDataRoot(checkedDocumentPath) || (isProperSDFolder(resultData.data!!) && isProperPartition)) {
|
||||
if (isProperSDFolder(resultData.data!!) && isProperPartition) {
|
||||
if (resultData.dataString == baseConfig.OTGTreeUri) {
|
||||
toast(R.string.sd_card_usb_same)
|
||||
return
|
||||
|
@ -247,7 +272,7 @@ abstract class BaseSimpleActivity : AppCompatActivity() {
|
|||
if (resultCode == Activity.RESULT_OK && resultData != null && resultData.data != null) {
|
||||
val isProperPartition = partition.isEmpty() || !sdOtgPattern.matcher(partition).matches() || (sdOtgPattern.matcher(partition).matches() && resultData.dataString!!.contains(partition))
|
||||
if (isProperOTGFolder(resultData.data!!) && isProperPartition) {
|
||||
if (resultData.dataString == baseConfig.treeUri) {
|
||||
if (resultData.dataString == baseConfig.sdTreeUri) {
|
||||
funAfterSAFPermission?.invoke(false)
|
||||
toast(R.string.sd_card_usb_same)
|
||||
return
|
||||
|
@ -277,7 +302,7 @@ abstract class BaseSimpleActivity : AppCompatActivity() {
|
|||
|
||||
private fun saveTreeUri(resultData: Intent) {
|
||||
val treeUri = resultData.data
|
||||
baseConfig.treeUri = treeUri.toString()
|
||||
baseConfig.sdTreeUri = treeUri.toString()
|
||||
|
||||
val takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
applicationContext.contentResolver.takePersistableUriPermission(treeUri!!, takeFlags)
|
||||
|
@ -287,7 +312,10 @@ abstract class BaseSimpleActivity : AppCompatActivity() {
|
|||
|
||||
private fun isProperOTGFolder(uri: Uri) = isExternalStorageDocument(uri) && isRootUri(uri) && !isInternalStorage(uri)
|
||||
|
||||
private fun isRootUri(uri: Uri) = DocumentsContract.getTreeDocumentId(uri).endsWith(":")
|
||||
@SuppressLint("NewApi")
|
||||
private fun isProperInternalRoot(uri: Uri) = isExternalStorageDocument(uri) && isRootUri(uri) && isInternalStorage(uri)
|
||||
|
||||
private fun isRootUri(uri: Uri) = uri.lastPathSegment?.endsWith(":") ?: false
|
||||
|
||||
private fun isInternalStorage(uri: Uri) = isExternalStorageDocument(uri) && DocumentsContract.getTreeDocumentId(uri).contains("primary")
|
||||
|
||||
|
@ -354,6 +382,19 @@ abstract class BaseSimpleActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
fun handlePrimarySAFDialog(path: String, callback: (success: Boolean) -> Unit): Boolean {
|
||||
return if (!packageName.startsWith("com.simplemobiletools")) {
|
||||
callback(true)
|
||||
false
|
||||
} else if (isShowingSAFPrimaryDialog(path)) {
|
||||
funAfterSAFPermission = callback
|
||||
true
|
||||
} else {
|
||||
callback(true)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun handleOTGPermission(callback: (success: Boolean) -> Unit) {
|
||||
if (baseConfig.OTGTreeUri.isNotEmpty()) {
|
||||
callback(true)
|
||||
|
|
|
@ -36,13 +36,13 @@ import com.simplemobiletools.commons.dialogs.*
|
|||
import com.simplemobiletools.commons.helpers.*
|
||||
import com.simplemobiletools.commons.models.*
|
||||
import com.simplemobiletools.commons.views.MyTextView
|
||||
import kotlinx.android.synthetic.main.dialog_title.view.*
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.FileOutputStream
|
||||
import java.io.OutputStream
|
||||
import java.util.*
|
||||
import kotlin.collections.HashMap
|
||||
import kotlinx.android.synthetic.main.dialog_title.view.*
|
||||
|
||||
fun AppCompatActivity.updateActionBarTitle(text: String, color: Int = baseConfig.primaryColor) {
|
||||
supportActionBar?.title = Html.fromHtml("<font color='${color.getContrastColor().toHex()}'>$text</font>")
|
||||
|
@ -117,14 +117,14 @@ fun Activity.isAppInstalledOnSDCard(): Boolean = try {
|
|||
}
|
||||
|
||||
fun BaseSimpleActivity.isShowingSAFDialog(path: String): Boolean {
|
||||
return if (((isPathOnSD(path) || isAndroidDataRoot(path)) && !isSDCardSetAsDefaultStorage() && (baseConfig.treeUri.isEmpty() || !hasProperStoredTreeUri(false)))) {
|
||||
return if ((isPathOnSD(path) && !isSDCardSetAsDefaultStorage() && (baseConfig.sdTreeUri.isEmpty() || !hasProperStoredTreeUri(false)))) {
|
||||
runOnUiThread {
|
||||
if (!isDestroyed && !isFinishing) {
|
||||
WritePermissionDialog(this, false) {
|
||||
Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
|
||||
putExtra("android.content.extra.SHOW_ADVANCED", true)
|
||||
try {
|
||||
startActivityForResult(this, OPEN_DOCUMENT_TREE)
|
||||
startActivityForResult(this, OPEN_DOCUMENT_TREE_SD)
|
||||
checkedDocumentPath = path
|
||||
return@apply
|
||||
} catch (e: Exception) {
|
||||
|
@ -132,7 +132,38 @@ fun BaseSimpleActivity.isShowingSAFDialog(path: String): Boolean {
|
|||
}
|
||||
|
||||
try {
|
||||
startActivityForResult(this, OPEN_DOCUMENT_TREE)
|
||||
startActivityForResult(this, OPEN_DOCUMENT_TREE_SD)
|
||||
checkedDocumentPath = path
|
||||
} catch (e: Exception) {
|
||||
toast(R.string.unknown_error_occurred)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun BaseSimpleActivity.isShowingSAFPrimaryDialog(path: String): Boolean {
|
||||
return if (isSAFOnlyRoot(path) && (baseConfig.primaryTreeUri.isEmpty() || !hasProperStoredPrimaryTreeUri())) {
|
||||
runOnUiThread {
|
||||
if (!isDestroyed && !isFinishing) {
|
||||
WritePermissionDialog(this, false) {
|
||||
Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
|
||||
putExtra("android.content.extra.SHOW_ADVANCED", true)
|
||||
try {
|
||||
startActivityForResult(this, OPEN_DOCUMENT_TREE_PRIMARY)
|
||||
checkedDocumentPath = path
|
||||
return@apply
|
||||
} catch (e: Exception) {
|
||||
type = "*/*"
|
||||
}
|
||||
|
||||
try {
|
||||
startActivityForResult(this, OPEN_DOCUMENT_TREE_PRIMARY)
|
||||
checkedDocumentPath = path
|
||||
} catch (e: Exception) {
|
||||
toast(R.string.unknown_error_occurred)
|
||||
|
@ -496,7 +527,7 @@ fun BaseSimpleActivity.deleteFoldersBg(folders: List<FileDirItem>, deleteMediaOn
|
|||
var wasSuccess = false
|
||||
var needPermissionForPath = ""
|
||||
for (folder in folders) {
|
||||
if (needsStupidWritePermissions(folder.path) && baseConfig.treeUri.isEmpty()) {
|
||||
if (needsStupidWritePermissions(folder.path) && baseConfig.sdTreeUri.isEmpty()) {
|
||||
needPermissionForPath = folder.path
|
||||
break
|
||||
}
|
||||
|
@ -818,7 +849,7 @@ fun BaseSimpleActivity.getFileOutputStream(fileDirItem: FileDirItem, allowCreati
|
|||
|
||||
fun BaseSimpleActivity.showFileCreateError(path: String) {
|
||||
val error = String.format(getString(R.string.could_not_create_file), path)
|
||||
baseConfig.treeUri = ""
|
||||
baseConfig.sdTreeUri = ""
|
||||
showErrorToast(error)
|
||||
}
|
||||
|
||||
|
|
|
@ -12,8 +12,8 @@ import android.os.Environment
|
|||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.provider.DocumentsContract
|
||||
import android.provider.DocumentsContract.Document
|
||||
import android.provider.MediaStore.*
|
||||
import android.provider.OpenableColumns
|
||||
import android.text.TextUtils
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.content.FileProvider
|
||||
|
@ -26,13 +26,19 @@ import java.io.File
|
|||
import java.io.FileInputStream
|
||||
import java.io.InputStream
|
||||
import java.net.URLDecoder
|
||||
import java.util.*
|
||||
import java.util.ArrayList
|
||||
import java.util.Collections
|
||||
import java.util.HashMap
|
||||
import java.util.HashSet
|
||||
import java.util.regex.Pattern
|
||||
|
||||
// http://stackoverflow.com/a/40582634/1967672
|
||||
fun Context.getSDCardPath(): String {
|
||||
val directories = getStorageDirectories().filter {
|
||||
!it.equals(getInternalStoragePath()) && !it.equals("/storage/emulated/0", true) && (baseConfig.OTGPartition.isEmpty() || !it.endsWith(baseConfig.OTGPartition))
|
||||
!it.equals(getInternalStoragePath()) && !it.equals(
|
||||
"/storage/emulated/0",
|
||||
true
|
||||
) && (baseConfig.OTGPartition.isEmpty() || !it.endsWith(baseConfig.OTGPartition))
|
||||
}
|
||||
|
||||
val fullSDpattern = Pattern.compile(SD_OTG_PATTERN)
|
||||
|
@ -121,12 +127,14 @@ fun Context.getStorageDirectories(): Array<String> {
|
|||
}
|
||||
|
||||
fun Context.getHumanReadablePath(path: String): String {
|
||||
return getString(when (path) {
|
||||
"/" -> R.string.root
|
||||
internalStoragePath -> R.string.internal
|
||||
otgPath -> R.string.usb
|
||||
else -> R.string.sd_card
|
||||
})
|
||||
return getString(
|
||||
when (path) {
|
||||
"/" -> R.string.root
|
||||
internalStoragePath -> R.string.internal
|
||||
otgPath -> R.string.usb
|
||||
else -> R.string.sd_card
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun Context.humanizePath(path: String): String {
|
||||
|
@ -138,14 +146,24 @@ fun Context.humanizePath(path: String): String {
|
|||
}
|
||||
}
|
||||
|
||||
fun Context.getInternalStoragePath() = if (File("/storage/emulated/0").exists()) "/storage/emulated/0" else Environment.getExternalStorageDirectory().absolutePath.trimEnd('/')
|
||||
fun Context.getInternalStoragePath() =
|
||||
if (File("/storage/emulated/0").exists()) "/storage/emulated/0" else Environment.getExternalStorageDirectory().absolutePath.trimEnd('/')
|
||||
|
||||
fun Context.isPathOnSD(path: String) = sdCardPath.isNotEmpty() && path.startsWith(sdCardPath)
|
||||
|
||||
fun Context.isPathOnOTG(path: String) = otgPath.isNotEmpty() && path.startsWith(otgPath)
|
||||
|
||||
fun Context.isAndroidDataRoot(path: String) = (path == (internalStoragePath.plus(ANDROID_DIR)) || path == otgPath.plus(ANDROID_DIR) || path == sdCardPath.plus(ANDROID_DIR))
|
||||
val DIRS_ACCESSIBLE_ONLY_WITH_SAF = listOf("/Android")
|
||||
|
||||
fun Context.getSAFOnlyDirs(): List<String> {
|
||||
return DIRS_ACCESSIBLE_ONLY_WITH_SAF.map { "$internalStoragePath$it" }
|
||||
}
|
||||
|
||||
fun Context.isSAFOnlyRoot(path: String): Boolean {
|
||||
val dirs = getSAFOnlyDirs()
|
||||
val result = dirs.any { path.startsWith(it) }
|
||||
return result
|
||||
}
|
||||
|
||||
// no need to use DocumentFile if an SD card is set as the default storage
|
||||
fun Context.needsStupidWritePermissions(path: String) = (isPathOnSD(path) || isPathOnOTG(path)) && !isSDCardSetAsDefaultStorage()
|
||||
|
@ -153,18 +171,27 @@ fun Context.needsStupidWritePermissions(path: String) = (isPathOnSD(path) || isP
|
|||
fun Context.isSDCardSetAsDefaultStorage() = sdCardPath.isNotEmpty() && Environment.getExternalStorageDirectory().absolutePath.equals(sdCardPath, true)
|
||||
|
||||
fun Context.hasProperStoredTreeUri(isOTG: Boolean): Boolean {
|
||||
val uri = if (isOTG) baseConfig.OTGTreeUri else baseConfig.treeUri
|
||||
val uri = if (isOTG) baseConfig.OTGTreeUri else baseConfig.sdTreeUri
|
||||
val hasProperUri = contentResolver.persistedUriPermissions.any { it.uri.toString() == uri }
|
||||
if (!hasProperUri) {
|
||||
if (isOTG) {
|
||||
baseConfig.OTGTreeUri = ""
|
||||
} else {
|
||||
baseConfig.treeUri = ""
|
||||
baseConfig.sdTreeUri = ""
|
||||
}
|
||||
}
|
||||
return hasProperUri
|
||||
}
|
||||
|
||||
fun Context.hasProperStoredPrimaryTreeUri(): Boolean {
|
||||
val uri = baseConfig.primaryTreeUri
|
||||
val hasProperUri = contentResolver.persistedUriPermissions.any { it.uri.toString() == uri }
|
||||
if (!hasProperUri) {
|
||||
baseConfig.primaryTreeUri = ""
|
||||
}
|
||||
return hasProperUri
|
||||
}
|
||||
|
||||
fun Context.isAStorageRootFolder(path: String): Boolean {
|
||||
val trimmed = path.trimEnd('/')
|
||||
return trimmed.isEmpty() || trimmed.equals(internalStoragePath, true) || trimmed.equals(sdCardPath, true) || trimmed.equals(otgPath, true)
|
||||
|
@ -202,7 +229,7 @@ fun Context.getFastDocumentFile(path: String): DocumentFile? {
|
|||
|
||||
val relativePath = Uri.encode(path.substring(baseConfig.sdCardPath.length).trim('/'))
|
||||
val externalPathPart = baseConfig.sdCardPath.split("/").lastOrNull(String::isNotEmpty)?.trim('/') ?: return null
|
||||
val fullUri = "${baseConfig.treeUri}/document/$externalPathPart%3A$relativePath"
|
||||
val fullUri = "${baseConfig.sdTreeUri}/document/$externalPathPart%3A$relativePath"
|
||||
return DocumentFile.fromSingleUri(this, Uri.parse(fullUri))
|
||||
}
|
||||
|
||||
|
@ -230,7 +257,7 @@ fun Context.getDocumentFile(path: String): DocumentFile? {
|
|||
}
|
||||
|
||||
return try {
|
||||
val treeUri = Uri.parse(if (isOTG) baseConfig.OTGTreeUri else baseConfig.treeUri)
|
||||
val treeUri = Uri.parse(if (isOTG) baseConfig.OTGTreeUri else baseConfig.sdTreeUri)
|
||||
var document = DocumentFile.fromTreeUri(applicationContext, treeUri)
|
||||
val parts = relativePath.split("/").filter { it.isNotEmpty() }
|
||||
for (part in parts) {
|
||||
|
@ -447,60 +474,90 @@ fun Context.getOTGItems(path: String, shouldShowHidden: Boolean, getProperFileSi
|
|||
callback(items)
|
||||
}
|
||||
|
||||
const val MIME_TYPE_IS_DIRECTORY = "vnd.android.document/directory"
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
fun Context.getStorageItems(path: String, shouldShowHidden: Boolean, getProperFileSize: Boolean, callback: (ArrayList<FileDirItem>) -> Unit) {
|
||||
fun Context.getStorageItemsWithTreeUri(path: String, shouldShowHidden: Boolean, getProperFileSize: Boolean, callback: (ArrayList<FileDirItem>) -> Unit) {
|
||||
val items = ArrayList<FileDirItem>()
|
||||
val treeUri = baseConfig.treeUri
|
||||
val document = getFastDocumentFile(path)
|
||||
val files = document?.listFiles()
|
||||
val childrenUri = try {
|
||||
DocumentsContract.buildChildDocumentsUriUsingTree(treeUri.toUri(), document?.uri.toString())
|
||||
val rootDocumentFile = try {
|
||||
DocumentFile.fromTreeUri(applicationContext, baseConfig.primaryTreeUri.toUri())
|
||||
} catch (e: Exception) {
|
||||
showErrorToast(e)
|
||||
baseConfig.treeUri = ""
|
||||
baseConfig.primaryTreeUri = ""
|
||||
null
|
||||
}
|
||||
if (childrenUri == null) {
|
||||
|
||||
if (rootDocumentFile == null) {
|
||||
callback(items)
|
||||
return
|
||||
}
|
||||
|
||||
contentResolver.query(childrenUri, null, null, null)
|
||||
?.use { cursor ->
|
||||
val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
||||
val mimeIndex = cursor.getColumnIndex("mime_type")
|
||||
while (cursor.moveToNext()) {
|
||||
val name = cursor.getString(nameIndex) ?: continue
|
||||
val treeUri = baseConfig.primaryTreeUri.toUri()
|
||||
// uri should be a concatenation of the tree uri and the path without the internal storage path
|
||||
val relativePath = path.substring(baseConfig.internalStoragePath.length).trim('/')
|
||||
val documentId = "primary:$relativePath"
|
||||
val childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(treeUri, documentId)
|
||||
val projection = arrayOf(Document.COLUMN_DOCUMENT_ID, Document.COLUMN_DISPLAY_NAME, Document.COLUMN_MIME_TYPE)
|
||||
|
||||
val rawCursor = contentResolver.query(childrenUri, projection, null, null)!!
|
||||
val cursor = ExternalStorageProviderHack.transformQueryResult(childrenUri, rawCursor)
|
||||
cursor.use {
|
||||
if (cursor.moveToFirst()) {
|
||||
do {
|
||||
val documentId = cursor.getStringValue(Document.COLUMN_DOCUMENT_ID)
|
||||
val name = cursor.getStringValue(Document.COLUMN_DISPLAY_NAME)
|
||||
val mimeType = cursor.getStringValue(Document.COLUMN_MIME_TYPE)
|
||||
val isDirectory = mimeType == Document.MIME_TYPE_DIR
|
||||
val filePath = documentId.substring("primary:".length)
|
||||
if (!shouldShowHidden && name.startsWith(".")) {
|
||||
continue
|
||||
}
|
||||
|
||||
val mimeType = cursor.getString(mimeIndex)
|
||||
val isDirectory = mimeType == MIME_TYPE_IS_DIRECTORY
|
||||
|
||||
val decodedPath = internalStoragePath + "/" + URLDecoder.decode(filePath, "UTF-8")
|
||||
val fileSize = when {
|
||||
getProperFileSize -> 0L
|
||||
getProperFileSize -> getFileSize(treeUri, documentId)
|
||||
isDirectory -> 0L
|
||||
else -> 0L
|
||||
else -> getFileSize(treeUri, documentId)
|
||||
}
|
||||
|
||||
val childrenCount = if (isDirectory) {
|
||||
0
|
||||
getChildrenCount(treeUri, documentId)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
|
||||
val lastModified = 0L
|
||||
val fileDirItem = FileDirItem(path, name, isDirectory, childrenCount, fileSize, lastModified)
|
||||
val lastModified = System.currentTimeMillis()
|
||||
val fileDirItem = FileDirItem(decodedPath, name, isDirectory, childrenCount, fileSize, lastModified)
|
||||
items.add(fileDirItem)
|
||||
}
|
||||
} while (cursor.moveToNext())
|
||||
}
|
||||
|
||||
}
|
||||
callback(items)
|
||||
}
|
||||
|
||||
fun Context.getChildrenCount(treeUri: Uri, documentId: String): Int {
|
||||
val projection = arrayOf(Document.COLUMN_DOCUMENT_ID)
|
||||
val childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(treeUri, documentId)
|
||||
val rawCursor = contentResolver.query(childrenUri, projection, null, null, null)!!
|
||||
val cursor = ExternalStorageProviderHack.transformQueryResult(childrenUri, rawCursor)
|
||||
val count = cursor.count
|
||||
return count
|
||||
}
|
||||
|
||||
fun Context.getFileSize(treeUri: Uri, documentId: String): Long {
|
||||
val projection = arrayOf(Document.COLUMN_SIZE)
|
||||
val childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(treeUri, documentId)
|
||||
val rawCursor = contentResolver.query(childrenUri, projection, null, null, null)!!
|
||||
val cursor = ExternalStorageProviderHack.transformQueryResult(childrenUri, rawCursor)
|
||||
var size = 0L
|
||||
cursor.use { c ->
|
||||
if (c.moveToFirst()) {
|
||||
size = c.getLongValue(Document.COLUMN_SIZE)
|
||||
}
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
|
||||
fun Context.trySAFFileDelete(fileDirItem: FileDirItem, allowDeleteFolder: Boolean = false, callback: ((wasSuccess: Boolean) -> Unit)? = null) {
|
||||
var fileDeleted = tryFastDocumentDelete(fileDirItem.path, allowDeleteFolder)
|
||||
if (!fileDeleted) {
|
||||
|
@ -509,7 +566,7 @@ fun Context.trySAFFileDelete(fileDirItem: FileDirItem, allowDeleteFolder: Boolea
|
|||
try {
|
||||
fileDeleted = (document.isFile || allowDeleteFolder) && DocumentsContract.deleteDocument(applicationContext.contentResolver, document.uri)
|
||||
} catch (ignored: Exception) {
|
||||
baseConfig.treeUri = ""
|
||||
baseConfig.sdTreeUri = ""
|
||||
baseConfig.sdCardPath = ""
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,7 +55,9 @@ import com.simplemobiletools.commons.models.SharedTheme
|
|||
import com.simplemobiletools.commons.views.*
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.ArrayList
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
fun Context.getSharedPrefs() = getSharedPreferences(PREFS_KEY, Context.MODE_PRIVATE)
|
||||
|
||||
|
@ -489,7 +491,7 @@ fun Context.updateSDCardPath() {
|
|||
val oldPath = baseConfig.sdCardPath
|
||||
baseConfig.sdCardPath = getSDCardPath()
|
||||
if (oldPath != baseConfig.sdCardPath) {
|
||||
baseConfig.treeUri = ""
|
||||
baseConfig.sdTreeUri = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,9 @@ import com.simplemobiletools.commons.extensions.getInternalStoragePath
|
|||
import com.simplemobiletools.commons.extensions.getSDCardPath
|
||||
import com.simplemobiletools.commons.extensions.getSharedPrefs
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.Calendar
|
||||
import java.util.HashSet
|
||||
import java.util.Locale
|
||||
|
||||
open class BaseConfig(val context: Context) {
|
||||
protected val prefs = context.getSharedPrefs()
|
||||
|
@ -24,9 +26,13 @@ open class BaseConfig(val context: Context) {
|
|||
get() = prefs.getInt(LAST_VERSION, 0)
|
||||
set(lastVersion) = prefs.edit().putInt(LAST_VERSION, lastVersion).apply()
|
||||
|
||||
var treeUri: String
|
||||
get() = prefs.getString(TREE_URI, "")!!
|
||||
set(uri) = prefs.edit().putString(TREE_URI, uri).apply()
|
||||
var primaryTreeUri: String
|
||||
get() = prefs.getString(PRIMARY_TREE_URI, "")!!
|
||||
set(uri) = prefs.edit().putString(PRIMARY_TREE_URI, uri).apply()
|
||||
|
||||
var sdTreeUri: String
|
||||
get() = prefs.getString(SD_TREE_URI, "")!!
|
||||
set(uri) = prefs.edit().putString(SD_TREE_URI, uri).apply()
|
||||
|
||||
var OTGTreeUri: String
|
||||
get() = prefs.getString(OTG_TREE_URI, "")!!
|
||||
|
|
|
@ -8,7 +8,8 @@ import android.os.Looper
|
|||
import android.util.Log
|
||||
import com.simplemobiletools.commons.R
|
||||
import com.simplemobiletools.commons.overloads.times
|
||||
import java.util.*
|
||||
import java.util.HashMap
|
||||
import java.util.LinkedHashMap
|
||||
|
||||
const val APP_NAME = "app_name"
|
||||
const val APP_LICENSES = "app_licenses"
|
||||
|
@ -61,7 +62,8 @@ const val YEAR_SECONDS = YEAR_MINUTES * 60
|
|||
const val PREFS_KEY = "Prefs"
|
||||
const val APP_RUN_COUNT = "app_run_count"
|
||||
const val LAST_VERSION = "last_version"
|
||||
const val TREE_URI = "tree_uri_2"
|
||||
const val SD_TREE_URI = "tree_uri_2"
|
||||
const val PRIMARY_TREE_URI = "primary_tree_uri_2"
|
||||
const val OTG_TREE_URI = "otg_tree_uri_2"
|
||||
const val SD_CARD_PATH = "sd_card_path_2"
|
||||
const val OTG_REAL_PATH = "otg_real_path_2"
|
||||
|
@ -183,8 +185,9 @@ const val LICENSE_SMS_MMS = 134217728
|
|||
const val LICENSE_APNG = 268435456
|
||||
|
||||
// global intents
|
||||
const val OPEN_DOCUMENT_TREE = 1000
|
||||
const val OPEN_DOCUMENT_TREE_PRIMARY = 1000
|
||||
const val OPEN_DOCUMENT_TREE_OTG = 1001
|
||||
const val OPEN_DOCUMENT_TREE_SD = 1002
|
||||
const val REQUEST_SET_AS = 1002
|
||||
const val REQUEST_EDIT_IMAGE = 1003
|
||||
const val SELECT_EXPORT_SETTINGS_FILE_INTENT = 1004
|
||||
|
@ -305,9 +308,6 @@ const val DATE_FORMAT_FOURTEEN = "yy/MM/dd"
|
|||
const val TIME_FORMAT_12 = "hh:mm a"
|
||||
const val TIME_FORMAT_24 = "HH:mm"
|
||||
|
||||
//storage
|
||||
const val ANDROID_DIR = "/Android"
|
||||
|
||||
val appIconColorStrings = arrayListOf(
|
||||
".Red",
|
||||
".Pink",
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
package com.simplemobiletools.commons.helpers
|
||||
|
||||
import android.database.Cursor
|
||||
import android.database.MatrixCursor
|
||||
import android.database.MergeCursor
|
||||
import android.net.Uri
|
||||
import android.provider.DocumentsContract
|
||||
import com.simplemobiletools.commons.extensions.getStringValue
|
||||
|
||||
// On Android 11, ExternalStorageProvider no longer returns Android/data and Android/obb as children
|
||||
// of the Android directory on primary storage. However, the two child directories are actually
|
||||
// still accessible.
|
||||
// https://github.com/zhanghai/MaterialFiles/blob/master/app/src/main/java/me/zhanghai/android/files/provider/document/resolver/ExternalStorageProviderPrimaryAndroidDataHack.kt
|
||||
object ExternalStorageProviderHack {
|
||||
private const val EXTERNAL_STORAGE_PROVIDER_AUTHORITY = "com.android.externalstorage.documents"
|
||||
private const val EXTERNAL_STORAGE_PROVIDER_PRIMARY_ANDROID_DOCUMENT_ID = "primary:Android"
|
||||
private const val EXTERNAL_STORAGE_PROVIDER_PRIMARY_ANDROID_DATA_DOCUMENT_ID =
|
||||
"primary:Android/data"
|
||||
private const val EXTERNAL_STORAGE_PROVIDER_PRIMARY_ANDROID_DATA_DISPLAY_NAME = "data"
|
||||
private const val EXTERNAL_STORAGE_PROVIDER_PRIMARY_ANDROID_OBB_DOCUMENT_ID =
|
||||
"primary:Android/obb"
|
||||
private const val EXTERNAL_STORAGE_PROVIDER_PRIMARY_ANDROID_OBB_DISPLAY_NAME = "obb"
|
||||
|
||||
private val CHILD_DOCUMENTS_CURSOR_COLUMN_NAMES = arrayOf(
|
||||
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
|
||||
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
|
||||
DocumentsContract.Document.COLUMN_MIME_TYPE,
|
||||
DocumentsContract.Document.COLUMN_LAST_MODIFIED,
|
||||
DocumentsContract.Document.COLUMN_SIZE,
|
||||
)
|
||||
|
||||
fun transformQueryResult(uri: Uri, cursor: Cursor): Cursor {
|
||||
val documentId = DocumentsContract.getDocumentId(uri)
|
||||
if (uri.authority == EXTERNAL_STORAGE_PROVIDER_AUTHORITY && documentId == EXTERNAL_STORAGE_PROVIDER_PRIMARY_ANDROID_DOCUMENT_ID) {
|
||||
var hasDataRow = false
|
||||
var hasObbRow = false
|
||||
try {
|
||||
while (cursor.moveToNext()) {
|
||||
when (cursor.getStringValue(DocumentsContract.Document.COLUMN_DOCUMENT_ID)) {
|
||||
EXTERNAL_STORAGE_PROVIDER_PRIMARY_ANDROID_DATA_DOCUMENT_ID ->
|
||||
hasDataRow = true
|
||||
EXTERNAL_STORAGE_PROVIDER_PRIMARY_ANDROID_OBB_DOCUMENT_ID ->
|
||||
hasObbRow = true
|
||||
}
|
||||
if (hasDataRow && hasObbRow) {
|
||||
break
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
cursor.moveToPosition(-1)
|
||||
}
|
||||
if (hasDataRow && hasObbRow) {
|
||||
return cursor
|
||||
}
|
||||
val extraCursor = MatrixCursor(CHILD_DOCUMENTS_CURSOR_COLUMN_NAMES)
|
||||
if (!hasDataRow) {
|
||||
extraCursor.newRow()
|
||||
.add(
|
||||
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
|
||||
EXTERNAL_STORAGE_PROVIDER_PRIMARY_ANDROID_DATA_DOCUMENT_ID
|
||||
)
|
||||
.add(
|
||||
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
|
||||
EXTERNAL_STORAGE_PROVIDER_PRIMARY_ANDROID_DATA_DISPLAY_NAME
|
||||
)
|
||||
.add(
|
||||
DocumentsContract.Document.COLUMN_MIME_TYPE,
|
||||
DocumentsContract.Document.MIME_TYPE_DIR
|
||||
)
|
||||
.add(
|
||||
DocumentsContract.Document.COLUMN_LAST_MODIFIED,
|
||||
System.currentTimeMillis()
|
||||
)
|
||||
.add(
|
||||
DocumentsContract.Document.COLUMN_SIZE,
|
||||
0L
|
||||
)
|
||||
}
|
||||
if (!hasObbRow) {
|
||||
extraCursor.newRow()
|
||||
.add(
|
||||
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
|
||||
EXTERNAL_STORAGE_PROVIDER_PRIMARY_ANDROID_OBB_DOCUMENT_ID
|
||||
)
|
||||
.add(
|
||||
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
|
||||
EXTERNAL_STORAGE_PROVIDER_PRIMARY_ANDROID_OBB_DISPLAY_NAME
|
||||
)
|
||||
.add(
|
||||
DocumentsContract.Document.COLUMN_MIME_TYPE,
|
||||
DocumentsContract.Document.MIME_TYPE_DIR
|
||||
)
|
||||
.add(
|
||||
DocumentsContract.Document.COLUMN_LAST_MODIFIED,
|
||||
System.currentTimeMillis()
|
||||
)
|
||||
.add(
|
||||
DocumentsContract.Document.COLUMN_SIZE,
|
||||
0L
|
||||
)
|
||||
}
|
||||
return MergeCursor(arrayOf(cursor, extraCursor))
|
||||
}
|
||||
return cursor
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue