feat(file): 添加文件下载权限管理和回退机制

- 实现 PublicDownloads 目录失败时自动回退到 Sandbox 目录
- 添加 Android 11+ 所有文件访问权限检查功能
- 提供跳转到文件访问权限设置页面的 Intent 方法
- 重构目录解析逻辑为独立的 resolveBaseDir 函数
- 增强文件下载的异常处理和日志记录
- 修复空白文件名处理逻辑,确保默认文件名为 download.bin
这个提交包含在:
XuqmGroup 2026-06-10 20:22:32 +08:00
父节点 2bd497ead4
当前提交 510bb8439b

查看文件

@ -254,7 +254,28 @@ object FileSDK {
android.util.Log.d("XWV", "saveBlobDownload: fileName=$fileName, dest=$destination, b64Len=${base64Data.length}")
val bytes = android.util.Base64.decode(base64Data, android.util.Base64.DEFAULT)
android.util.Log.d("XWV", "saveBlobDownload: decoded ${bytes.size} bytes")
val baseDir = when (destination) {
val safeName = fileName.takeIf { it.isNotBlank() } ?: "download.bin"
// 尝试目标目录,如果 PublicDownloads 失败则回退到 Sandbox
val baseDir = resolveBaseDir(context, destination)
val target = uniqueFile(baseDir, safeName)
android.util.Log.d("XWV", "saveBlobDownload: writing to ${target.absolutePath}")
return try {
target.writeBytes(bytes)
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(
@ -262,22 +283,39 @@ object FileSDK {
android.os.Environment.DIRECTORY_DOWNLOADS
),
appName,
).apply {
val created = mkdirs()
android.util.Log.d("XWV", "saveBlobDownload: PublicDownloads dir=$absolutePath, created=$created, exists=${exists()}")
}
).apply { mkdirs() }
}
FileDownloadDestination.Sandbox -> {
(context.getExternalFilesDir(null) ?: context.filesDir).also {
android.util.Log.d("XWV", "saveBlobDownload: Sandbox dir=${it.absolutePath}")
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)
}
val target = uniqueFile(baseDir, fileName.takeIf { it.isNotBlank() } ?: "download.bin")
android.util.Log.d("XWV", "saveBlobDownload: writing to ${target.absolutePath}")
target.writeBytes(bytes)
android.util.Log.d("XWV", "saveBlobDownload: done, size=${target.length()}")
return target
}
/**