feat(file): 添加文件下载权限管理和回退机制
- 实现 PublicDownloads 目录失败时自动回退到 Sandbox 目录 - 添加 Android 11+ 所有文件访问权限检查功能 - 提供跳转到文件访问权限设置页面的 Intent 方法 - 重构目录解析逻辑为独立的 resolveBaseDir 函数 - 增强文件下载的异常处理和日志记录 - 修复空白文件名处理逻辑,确保默认文件名为 download.bin
这个提交包含在:
父节点
2bd497ead4
当前提交
510bb8439b
@ -254,30 +254,68 @@ object FileSDK {
|
|||||||
android.util.Log.d("XWV", "saveBlobDownload: fileName=$fileName, dest=$destination, b64Len=${base64Data.length}")
|
android.util.Log.d("XWV", "saveBlobDownload: fileName=$fileName, dest=$destination, b64Len=${base64Data.length}")
|
||||||
val bytes = android.util.Base64.decode(base64Data, android.util.Base64.DEFAULT)
|
val bytes = android.util.Base64.decode(base64Data, android.util.Base64.DEFAULT)
|
||||||
android.util.Log.d("XWV", "saveBlobDownload: decoded ${bytes.size} bytes")
|
android.util.Log.d("XWV", "saveBlobDownload: decoded ${bytes.size} bytes")
|
||||||
val baseDir = when (destination) {
|
|
||||||
FileDownloadDestination.PublicDownloads -> {
|
val safeName = fileName.takeIf { it.isNotBlank() } ?: "download.bin"
|
||||||
val appName = context.applicationInfo.loadLabel(context.packageManager).toString()
|
|
||||||
File(
|
// 尝试目标目录,如果 PublicDownloads 失败则回退到 Sandbox
|
||||||
android.os.Environment.getExternalStoragePublicDirectory(
|
val baseDir = resolveBaseDir(context, destination)
|
||||||
android.os.Environment.DIRECTORY_DOWNLOADS
|
val target = uniqueFile(baseDir, safeName)
|
||||||
),
|
|
||||||
appName,
|
|
||||||
).apply {
|
|
||||||
val created = mkdirs()
|
|
||||||
android.util.Log.d("XWV", "saveBlobDownload: PublicDownloads dir=$absolutePath, created=$created, exists=${exists()}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
FileDownloadDestination.Sandbox -> {
|
|
||||||
(context.getExternalFilesDir(null) ?: context.filesDir).also {
|
|
||||||
android.util.Log.d("XWV", "saveBlobDownload: Sandbox dir=${it.absolutePath}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val target = uniqueFile(baseDir, fileName.takeIf { it.isNotBlank() } ?: "download.bin")
|
|
||||||
android.util.Log.d("XWV", "saveBlobDownload: writing to ${target.absolutePath}")
|
android.util.Log.d("XWV", "saveBlobDownload: writing to ${target.absolutePath}")
|
||||||
target.writeBytes(bytes)
|
return try {
|
||||||
android.util.Log.d("XWV", "saveBlobDownload: done, size=${target.length()}")
|
target.writeBytes(bytes)
|
||||||
return target
|
android.util.Log.d("XWV", "saveBlobDownload: done, size=${target.length()}")
|
||||||
|
target
|
||||||
|
} catch (e: java.io.IOException) {
|
||||||
|
android.util.Log.w("XWV", "saveBlobDownload: primary dir failed (${e.message}), falling back to Sandbox")
|
||||||
|
val fallbackDir = resolveBaseDir(context, FileDownloadDestination.Sandbox)
|
||||||
|
val fallbackTarget = uniqueFile(fallbackDir, safeName)
|
||||||
|
fallbackTarget.writeBytes(bytes)
|
||||||
|
android.util.Log.d("XWV", "saveBlobDownload: fallback done, ${fallbackTarget.absolutePath}, size=${fallbackTarget.length()}")
|
||||||
|
fallbackTarget
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resolveBaseDir(context: Context, destination: FileDownloadDestination): File = when (destination) {
|
||||||
|
FileDownloadDestination.PublicDownloads -> {
|
||||||
|
val appName = context.applicationInfo.loadLabel(context.packageManager).toString()
|
||||||
|
File(
|
||||||
|
android.os.Environment.getExternalStoragePublicDirectory(
|
||||||
|
android.os.Environment.DIRECTORY_DOWNLOADS
|
||||||
|
),
|
||||||
|
appName,
|
||||||
|
).apply { mkdirs() }
|
||||||
|
}
|
||||||
|
FileDownloadDestination.Sandbox -> {
|
||||||
|
context.getExternalFilesDir(null) ?: context.filesDir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否拥有"所有文件访问权限"(MANAGE_EXTERNAL_STORAGE)。
|
||||||
|
* Android 11+ 需要此权限才能写入公共 Downloads 目录。
|
||||||
|
* Android 10 及以下始终返回 true(使用 WRITE_EXTERNAL_STORAGE)。
|
||||||
|
*/
|
||||||
|
fun isManageStorageGranted(context: Context): Boolean {
|
||||||
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
Environment.isExternalStorageManager()
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回跳转到"所有文件访问权限"设置页的 Intent。
|
||||||
|
* 调用方应使用 startActivityForResult 或 registerForActivityResult 处理返回。
|
||||||
|
*/
|
||||||
|
fun requestManageStorageIntent(context: Context): Intent? {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) return null
|
||||||
|
return try {
|
||||||
|
Intent(android.provider.Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION).apply {
|
||||||
|
data = Uri.parse("package:${context.packageName}")
|
||||||
|
}
|
||||||
|
} catch (_: Exception) {
|
||||||
|
Intent(android.provider.Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户