sdk 30 changes for gallery
- in FilePickerDialog, handle when file is picked from a directory restricted on SDK 30+ - Activity.openEditorInten - do not set the flags for read and write permissions if the app does not have them - Activity.getFileOutputStream - add cases for creating output stream for restricted paths on SDK30+, also for Android/[data|obb] - Context.ensurePublicUri - add cases for creation of public uri for restricted paths on SDK30+, also for Android/[data|obb] - add Context.getPicturesDirectoryPath to get the Pictures folder path used to save media files that are edited from restricted paths on SDK30+ - add Context.isInSubFolderInDownloadDir - used to determine if a path is a sub folder in the Download directory; useful for determining whether to show the MediaStore.createWriteRequest prompt for files in the Download directory
This commit is contained in:
parent
d8a0479340
commit
ef97774abc
4 changed files with 131 additions and 53 deletions
|
@ -216,6 +216,15 @@ class FilePickerDialog(
|
|||
}
|
||||
}
|
||||
}
|
||||
} else if (activity.isRestrictedWithSAFSdk30(currPath)) {
|
||||
if (activity.isInDownloadDir(currPath)) {
|
||||
val file = File(currPath)
|
||||
if ((pickFile && file.isFile) || (!pickFile && file.isDirectory)) {
|
||||
sendSuccess()
|
||||
}
|
||||
} else {
|
||||
activity.toast(R.string.system_folder_restriction)
|
||||
}
|
||||
} else {
|
||||
val file = File(currPath)
|
||||
if ((pickFile && file.isFile) || (!pickFile && file.isDirectory)) {
|
||||
|
|
|
@ -9,6 +9,7 @@ import android.content.pm.ApplicationInfo
|
|||
import android.content.pm.PackageManager
|
||||
import android.media.RingtoneManager
|
||||
import android.net.Uri
|
||||
import android.os.Environment
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.os.TransactionTooLargeException
|
||||
|
@ -455,7 +456,9 @@ fun Activity.openEditorIntent(path: String, forceChooser: Boolean, applicationId
|
|||
Intent().apply {
|
||||
action = Intent.ACTION_EDIT
|
||||
setDataAndType(newUri, getUriMimeType(path, newUri))
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
if (!isRPlus() || (isRPlus() && (hasProperStoredDocumentUriSdk30(path) || Environment.isExternalStorageManager()))) {
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
}
|
||||
|
||||
val parent = path.getParentPath()
|
||||
val newFilename = "${path.getFilenameFromPath().substringBeforeLast('.')}_1"
|
||||
|
@ -463,10 +466,12 @@ fun Activity.openEditorIntent(path: String, forceChooser: Boolean, applicationId
|
|||
val newFilePath = File(parent, "$newFilename.$extension")
|
||||
|
||||
val outputUri = if (isPathOnOTG(path)) newUri else getFinalUriFromPath("$newFilePath", applicationId)
|
||||
val resInfoList = packageManager.queryIntentActivities(this, PackageManager.MATCH_DEFAULT_ONLY)
|
||||
for (resolveInfo in resInfoList) {
|
||||
val packageName = resolveInfo.activityInfo.packageName
|
||||
grantUriPermission(packageName, outputUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
if (!isRPlus()) {
|
||||
val resInfoList = packageManager.queryIntentActivities(this, PackageManager.MATCH_DEFAULT_ONLY)
|
||||
for (resolveInfo in resInfoList) {
|
||||
val packageName = resolveInfo.activityInfo.packageName
|
||||
grantUriPermission(packageName, outputUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}
|
||||
}
|
||||
|
||||
putExtra(MediaStore.EXTRA_OUTPUT, outputUri)
|
||||
|
@ -1095,49 +1100,86 @@ fun Activity.hideKeyboard(view: View) {
|
|||
}
|
||||
|
||||
fun BaseSimpleActivity.getFileOutputStream(fileDirItem: FileDirItem, allowCreatingNewFile: Boolean = false, callback: (outputStream: OutputStream?) -> Unit) {
|
||||
if (needsStupidWritePermissions(fileDirItem.path)) {
|
||||
handleSAFDialog(fileDirItem.path) {
|
||||
if (!it) {
|
||||
return@handleSAFDialog
|
||||
}
|
||||
val targetFile = File(fileDirItem.path)
|
||||
when {
|
||||
isRestrictedSAFOnlyRoot(fileDirItem.path) -> {
|
||||
handleAndroidSAFDialog(fileDirItem.path) {
|
||||
if (!it) {
|
||||
return@handleAndroidSAFDialog
|
||||
}
|
||||
|
||||
var document = getDocumentFile(fileDirItem.path)
|
||||
if (document == null && allowCreatingNewFile) {
|
||||
document = getDocumentFile(fileDirItem.getParentPath())
|
||||
val uri = getAndroidSAFUri(fileDirItem.path)
|
||||
if (!getDoesFilePathExist(fileDirItem.path)) {
|
||||
createAndroidSAFFile(fileDirItem.path)
|
||||
}
|
||||
callback.invoke(applicationContext.contentResolver.openOutputStream(uri))
|
||||
}
|
||||
}
|
||||
needsStupidWritePermissions(fileDirItem.path) -> {
|
||||
handleSAFDialog(fileDirItem.path) {
|
||||
if (!it) {
|
||||
return@handleSAFDialog
|
||||
}
|
||||
|
||||
if (document == null) {
|
||||
showFileCreateError(fileDirItem.path)
|
||||
callback(null)
|
||||
return@handleSAFDialog
|
||||
}
|
||||
var document = getDocumentFile(fileDirItem.path)
|
||||
if (document == null && allowCreatingNewFile) {
|
||||
document = getDocumentFile(fileDirItem.getParentPath())
|
||||
}
|
||||
|
||||
if (!getDoesFilePathExist(fileDirItem.path)) {
|
||||
document = document.createFile("", fileDirItem.name) ?: getDocumentFile(fileDirItem.path)
|
||||
}
|
||||
if (document == null) {
|
||||
showFileCreateError(fileDirItem.path)
|
||||
callback(null)
|
||||
return@handleSAFDialog
|
||||
}
|
||||
|
||||
if (document?.exists() == true) {
|
||||
try {
|
||||
callback(applicationContext.contentResolver.openOutputStream(document.uri))
|
||||
} catch (e: FileNotFoundException) {
|
||||
showErrorToast(e)
|
||||
if (!getDoesFilePathExist(fileDirItem.path)) {
|
||||
document = getDocumentFile(fileDirItem.path) ?: document.createFile("", fileDirItem.name)
|
||||
}
|
||||
|
||||
if (document?.exists() == true) {
|
||||
try {
|
||||
callback(applicationContext.contentResolver.openOutputStream(document.uri))
|
||||
} catch (e: FileNotFoundException) {
|
||||
showErrorToast(e)
|
||||
callback(null)
|
||||
}
|
||||
} else {
|
||||
showFileCreateError(fileDirItem.path)
|
||||
callback(null)
|
||||
}
|
||||
} else {
|
||||
showFileCreateError(fileDirItem.path)
|
||||
callback(null)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val file = File(fileDirItem.path)
|
||||
if (file.parentFile?.exists() == false) {
|
||||
file.parentFile.mkdirs()
|
||||
}
|
||||
isAccessibleWithSAFSdk30(fileDirItem.path) -> {
|
||||
handleSAFDialogSdk30(fileDirItem.path) {
|
||||
if (!it) {
|
||||
return@handleSAFDialogSdk30
|
||||
}
|
||||
|
||||
try {
|
||||
callback(FileOutputStream(file))
|
||||
} catch (e: Exception) {
|
||||
callback(null)
|
||||
callback.invoke(
|
||||
try {
|
||||
val uri = createDocumentUriUsingFirstParentTreeUri(fileDirItem.path)
|
||||
if (!getDoesFilePathExist(fileDirItem.path)) {
|
||||
createSAFFileSdk30(fileDirItem.path)
|
||||
}
|
||||
applicationContext.contentResolver.openOutputStream(uri)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
} ?: createCasualFileOutputStream(this, targetFile)
|
||||
)
|
||||
}
|
||||
}
|
||||
isRestrictedWithSAFSdk30(fileDirItem.path) -> {
|
||||
callback.invoke(
|
||||
try {
|
||||
val fileUri = getFileUrisFromFileDirItems(arrayListOf(fileDirItem)).second
|
||||
applicationContext.contentResolver.openOutputStream(fileUri.first())
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
} ?: createCasualFileOutputStream(this, targetFile)
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
callback.invoke(createCasualFileOutputStream(this, targetFile))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,15 +41,15 @@ fun Context.isAccessibleWithSAFSdk30(path: String): Boolean {
|
|||
|
||||
fun Context.getFirstParentLevel(path: String): Int {
|
||||
return when {
|
||||
isSPlus() && (isInAndroidDir(path) || isInDownloadDir(path)) -> 1
|
||||
isRPlus() && isInDownloadDir(path) -> 1
|
||||
isSPlus() && (isInAndroidDir(path) || isInSubFolderInDownloadDir(path)) -> 1
|
||||
isRPlus() && isInSubFolderInDownloadDir(path) -> 1
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.isRestrictedWithSAFSdk30(path: String): Boolean {
|
||||
if (path.startsWith(recycleBinPath) || isExternalStorageManager()) {
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
val level = getFirstParentLevel(path)
|
||||
|
@ -70,6 +70,21 @@ fun Context.isInDownloadDir(path: String): Boolean {
|
|||
return firstParentDir.equals(DOWNLOAD_DIR, true)
|
||||
}
|
||||
|
||||
fun Context.isInSubFolderInDownloadDir(path: String): Boolean {
|
||||
if (path.startsWith(recycleBinPath)) {
|
||||
return false
|
||||
}
|
||||
val firstParentDir = path.getFirstParentDirName(this, 1)
|
||||
return if (firstParentDir == null) {
|
||||
false
|
||||
} else {
|
||||
val startsWithDownloadDir = firstParentDir.startsWith(DOWNLOAD_DIR, true)
|
||||
val hasAtLeast1PathSegment = firstParentDir.split("/").filter { it.isNotEmpty() }.size > 1
|
||||
val firstParentPath = path.getFirstParentPath(this, 1)
|
||||
startsWithDownloadDir && hasAtLeast1PathSegment && File(firstParentPath).isDirectory
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.isInAndroidDir(path: String): Boolean {
|
||||
if (path.startsWith(recycleBinPath)) {
|
||||
return false
|
||||
|
@ -238,3 +253,8 @@ fun Context.buildDocumentUriSdk30(fullPath: String): Uri {
|
|||
val documentId = "${storageId}:$relativePath"
|
||||
return DocumentsContract.buildDocumentUri(EXTERNAL_STORAGE_PROVIDER_AUTHORITY, documentId)
|
||||
}
|
||||
|
||||
fun Context.getPicturesDirectoryPath(fullPath: String): String {
|
||||
val basePath = fullPath.getBasePath(this)
|
||||
return File(basePath, Environment.DIRECTORY_PICTURES).absolutePath
|
||||
}
|
||||
|
|
|
@ -325,18 +325,25 @@ fun Context.getMimeTypeFromUri(uri: Uri): String {
|
|||
}
|
||||
|
||||
fun Context.ensurePublicUri(path: String, applicationId: String): Uri? {
|
||||
return if (isRestrictedSAFOnlyRoot(path)) {
|
||||
getAndroidSAFUri(path)
|
||||
} else if (isPathOnOTG(path)) {
|
||||
getDocumentFile(path)?.uri
|
||||
} else {
|
||||
val uri = Uri.parse(path)
|
||||
if (uri.scheme == "content") {
|
||||
uri
|
||||
} else {
|
||||
val newPath = if (uri.toString().startsWith("/")) uri.toString() else uri.path
|
||||
val file = File(newPath)
|
||||
getFilePublicUri(file, applicationId)
|
||||
return when {
|
||||
hasProperStoredAndroidTreeUri(path) && isRestrictedSAFOnlyRoot(path) -> {
|
||||
getAndroidSAFUri(path)
|
||||
}
|
||||
hasProperStoredDocumentUriSdk30(path) && isAccessibleWithSAFSdk30(path) -> {
|
||||
createDocumentUriUsingFirstParentTreeUri(path)
|
||||
}
|
||||
isPathOnOTG(path) -> {
|
||||
getDocumentFile(path)?.uri
|
||||
}
|
||||
else -> {
|
||||
val uri = Uri.parse(path)
|
||||
if (uri.scheme == "content") {
|
||||
uri
|
||||
} else {
|
||||
val newPath = if (uri.toString().startsWith("/")) uri.toString() else uri.path
|
||||
val file = File(newPath)
|
||||
getFilePublicUri(file, applicationId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue