I think that's quite a lot of souce code for me to mention in this answer. Accessing the sdcard in addition to asking the user to indicate the access path, there is also a small shortcut from android 7 - android 9.
Here is my entire process to try to delete 1 file in sdcard written in kotlin language for your reference :
const val SDCARD_URI = "SDCARD_URI"
class ClearSdcardFileApi {
companion object {
fun getSdcardPath(mContext: Context): String? {
try {
val mStorageManager =
mContext.getSystemService(Context.STORAGE_SERVICE) as StorageManager
val storageVolumeClazz = Class.forName("android.os.storage.StorageVolume")
val getVolumeList = mStorageManager.javaClass.getMethod("getVolumeList")
val getPath = storageVolumeClazz.getMethod("getPath")
val isRemovable = storageVolumeClazz.getMethod("isRemovable")
val result = getVolumeList.invoke(mStorageManager) ?: return null
val length = Array.getLength(result)
for (i in 0 until length) {
val storageVolumeElement = Array.get(result, i)
val paths = getPath.invoke(storageVolumeElement) as String
val removable = isRemovable.invoke(storageVolumeElement) as Boolean
if (removable) {
return paths
}
}
} catch (e: Exception) {
e.printStackTrace()
return null
}
return null
}
fun isSdcardFilePath(context: Context, path: String): Boolean {
val pathSdcard = getPathSdcard(context) ?: return false
return path.startsWith(pathSdcard)
}
suspend fun clearFileSdcard(
activity: AppCompatActivity,
deletePaths: List<String>
): List<Boolean> {
return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N || Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
activity.openSafTreePicker(deletePaths)
} else {
activity.takeCardUriPermission(deletePaths)
}
}
private suspend fun AppCompatActivity.openSafTreePicker(
deletePaths: List<String>
) = suspendCoroutine<List<Boolean>> { continuation ->
val activityResultLauncher = activityResultRegistry.register(
System.currentTimeMillis().toString(),
ActivityResultContracts.StartIntentSenderForResult()
) { result ->
var deletedFiles = emptyList<Boolean>()
try {
deletedFiles = createPathAndDelete(this, result, deletePaths)
} catch (e: Exception) {
deletedFiles = emptyList()
} finally {
continuation.resume(deletedFiles)
}
}
requestPermissionSdcardPermission(
this,
activityResultLauncher,
deletePaths,
continuation
)
}
private suspend fun AppCompatActivity.takeCardUriPermission(
deletePaths: List<String>
) =
suspendCoroutine<List<Boolean>> { continuation ->
val activityResultLauncher = activityResultRegistry.register(
System.currentTimeMillis().toString(),
ActivityResultContracts.StartIntentSenderForResult()
) { result ->
var deletedFiles = emptyList<Boolean>()
try {
deletedFiles = createPathAndDelete(this, result, deletePaths)
} catch (e: Exception) {
} finally {
continuation.resume(deletedFiles)
}
}
requestPermissionSdcardPermission(
this,
activityResultLauncher,
deletePaths,
continuation
)
}
private fun findAndDeleteDocument(
activity: AppCompatActivity,
treeUri: Uri,
pathFileDelete: String
): Boolean {
activity.contentResolver.takePersistableUriPermission(
treeUri,
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
val pickedDir = DocumentFile.fromTreeUri(activity, treeUri)
return if (pickedDir != null) {
saveTreeUriToShare(activity, treeUri)
if (pickedDir.canWrite()) {
val listDirectoryPath = convertPathDelete(activity, pathFileDelete)
val documentFileDelete = findDocumentFile(activity, listDirectoryPath, pickedDir)
if (pickedDir.uri != documentFileDelete.uri) {
documentFileDelete.delete()
} else {
false
}
} else {
false
}
} else {
false
}
}
private fun saveTreeUriToShare(context: Context, treeUri: Uri) {
context.getSharedPreferences(context.applicationContext.packageName, MODE_PRIVATE)
.edit().putString(
SDCARD_URI, treeUri.toString()
).apply()
}
private fun getTreeUriFromShare(context: Context): String? {
return context.getSharedPreferences(
context.applicationContext.packageName,
MODE_PRIVATE
).getString(SDCARD_URI, null)
}
private fun hasUriSdcard(context: Context): Boolean {
val treeUriFromShare = getTreeUriFromShare(context) ?: return false
val pickedDir =
DocumentFile.fromTreeUri(context, Uri.parse(treeUriFromShare)) ?: return false
return pickedDir.canWrite()
}
private fun requestPermissionSdcardPermission(
activity: AppCompatActivity,
activityResultLauncher: ActivityResultLauncher<IntentSenderRequest>,
deletePaths: List<String>,
continuation: Continuation<List<Boolean>>
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
val pathSdcard = getPathSdcard(activity)
if (pathSdcard != null) {
if (hasUriSdcard(activity)) {
val treeUri = Uri.parse(getTreeUriFromShare(activity))
val deleteFiles = ArrayList<Boolean>()
deletePaths.forEach {
deleteFiles.add(
findAndDeleteDocument(
activity,
treeUri,
it
)
)
}
continuation.resume(deleteFiles)
} else {
val sdCard = File(pathSdcard)
val storageManager =
activity.getSystemService(Context.STORAGE_SERVICE) as StorageManager?
?: return
val volumeSdcard =
storageManager.getStorageVolume(sdCard) ?: return
val intent = volumeSdcard.createAccessIntent(null)
val pendingIntent =
PendingIntent.getActivity(
activity,
4010,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val intentSenderRequest =
IntentSenderRequest.Builder(pendingIntent.intentSender).build()
activityResultLauncher.launch(intentSenderRequest)
}
} else {
continuation.resume(emptyList())
}
} else {
if (hasUriSdcard(activity)) {
val treeUri = Uri.parse(getTreeUriFromShare(activity))
val deleteFiles = ArrayList<Boolean>()
deletePaths.forEach {
deleteFiles.add(
findAndDeleteDocument(
activity,
treeUri,
it
)
)
}
continuation.resume(deleteFiles)
} else {
val intent = Intent(ACTION_OPEN_DOCUMENT_TREE).apply {
flags =
FLAG_GRANT_PERSISTABLE_URI_PERMISSION or
FLAG_GRANT_READ_URI_PERMISSION or
FLAG_GRANT_WRITE_URI_PERMISSION
}
val pendingIntent =
PendingIntent.getActivity(
activity,
4011,
intent,
PendingIntent.FLAG_UPDATE_CURRENT
)
val intentSenderRequest =
IntentSenderRequest.Builder(pendingIntent.intentSender).build()
activityResultLauncher.launch(intentSenderRequest)
MainScope().launch {
Toast.makeText(
activity,
activity.resources.getString(R.string.please_choosing_disk_which_contains_selected_files),
Toast.LENGTH_LONG
).show()
}
}
}
}
private fun findDocumentFile(
context: Context,
subDeletedPath: List<String>,
pickedDir: DocumentFile
): DocumentFile {
var pickedDocument = pickedDir
val firstItem = subDeletedPath.firstOrNull() ?: return pickedDocument
pickedDir.listFiles().forEach { document ->
if (document.name == firstItem) {
pickedDocument = document
val filter = subDeletedPath.filter { it != firstItem }
if (filter.isEmpty()) return@forEach
pickedDocument = findDocumentFile(
context,
subDeletedPath.filter { it != firstItem },
pickedDocument
)
}
}
return pickedDocument
}
private fun convertPathDelete(
context: Context,
pathFileDelete: String
): List<String> {
val pathSdcard = getPathSdcard(context) ?: return emptyList()
val path = pathFileDelete.replace(pathSdcard, "")
return path.split(File.separator).filter { it.isNotEmpty() && it.isNotBlank() }.toList()
}
private fun getPathSdcard(context: Context): String? {
try {
val mStorageManager =
context.getSystemService(Context.STORAGE_SERVICE) as StorageManager
val storageVolumeClazz = Class.forName("android.os.storage.StorageVolume")
val getVolumeList = mStorageManager.javaClass.getMethod("getVolumeList")
val getPath = storageVolumeClazz.getMethod("getPath")
val isRemovable = storageVolumeClazz.getMethod("isRemovable")
val result = getVolumeList.invoke(mStorageManager) ?: return null
val length = Array.getLength(result)
for (i in 0 until length) {
val storageVolumeElement = Array.get(result, i)
val paths = getPath.invoke(storageVolumeElement) as String
val removable = isRemovable.invoke(storageVolumeElement) as Boolean
if (removable) {
return paths
}
}
} catch (e: Exception) {
e.printStackTrace()
return null
}
return null
}
private fun createPathAndDelete(
activity: AppCompatActivity,
result: ActivityResult,
deletePaths: List<String>
): List<Boolean> {
if (result.resultCode == Activity.RESULT_OK) {
val intent = result.data ?: return emptyList()
val treeUri = intent.data ?: return emptyList()
activity.contentResolver.takePersistableUriPermission(
treeUri,
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
val pathSdcard = getPathSdcard(activity)
if (pathSdcard != null) {
val nameSdcard: String =
pathSdcard.substring(pathSdcard.lastIndexOf(File.separator) + 1)
if (treeUri.toString().contains(nameSdcard)) {
saveTreeUriToShare(activity, treeUri)
}
}
val deleteFiles = ArrayList<Boolean>()
deletePaths.forEach {
deleteFiles.add(
findAndDeleteDocument(
activity,
treeUri,
it
)
)
}
return deleteFiles
} else {
return emptyList()
}
}
}
}