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:
darthpaul 2022-04-09 00:02:29 +01:00
parent d8a0479340
commit ef97774abc
4 changed files with 131 additions and 53 deletions

View file

@ -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)) {

View file

@ -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))
}
}
}

View file

@ -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
}

View file

@ -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)
}
}
}
}