fix(fs): read uncompressed Android assets from the cache copy (#3635)

In plugins/fs/android/src/main/java/FsPlugin.kt getFileDescriptor, for
uncompressed assets AssetManager.openFd returns an AssetFileDescriptor
whose parcel descriptor is the whole APK; its startOffset/length were
dropped and Rust read from offset 0, i.e. APK bytes. Only compressed
assets (openFd throws) took the cache copy path.

All assets now use the cache copy path. The cache file path is also checked
to stay below cacheDir/_assets. Kotlin not compiled here: unverified, needs
an on-device check.
This commit is contained in:
Lucas Fernandes Nogueira authored and GitHub committed 2026-10-09 13:20:34 -03:00
1 parent a934743b73
commit f81f0447cc
2 files changed
+22 -14

No files matched your search

@@ -0,0 +1,6 @@
---
fs: patch
fs-js: patch
---
Fixed reading uncompressed Android assets (`$RESOURCE` files such as images or audio that are stored uncompressed in the APK) returning the bytes of the APK instead of the asset. Assets are now always read from a copy in the app cache, like compressed ones.
+16 -14
View File
@@ -44,21 +44,23 @@ class FsPlugin(private val activity: Activity): Plugin(activity) {
if (args.uri.startsWith(app.tauri.TAURI_ASSETS_DIRECTORY_URI)) { if (args.uri.startsWith(app.tauri.TAURI_ASSETS_DIRECTORY_URI)) {
val path = args.uri.substring(app.tauri.TAURI_ASSETS_DIRECTORY_URI.length) val path = args.uri.substring(app.tauri.TAURI_ASSETS_DIRECTORY_URI.length)
try { // We cannot hand out a file descriptor of the asset itself: for uncompressed assets
val fd = activity.assets.openFd(path).parcelFileDescriptor?.detachFd() // `AssetManager.openFd` returns the descriptor of the whole APK with a start offset
res.put("fd", fd) // the Rust side cannot honor, and compressed assets have no descriptor at all.
} catch (e: IOException) { // So we copy the asset to the cache and get a fd from there.
// if the asset is compressed, we cannot open a file descriptor directly // This is a lot faster than serializing the file and sending it as invoke response
// so we copy it to the cache and get a fd from there // because on the Rust side we can leverage the custom protocol IPC and read the file directly.
// this is a lot faster than serializing the file and sending it as invoke response val assetsCacheDir = File(activity.cacheDir, "_assets")
// because on the Rust side we can leverage the custom protocol IPC and read the file directly val cacheFile = File(assetsCacheDir, path)
val cacheFile = File(activity.cacheDir, "_assets/$path") if (!cacheFile.canonicalPath.startsWith(assetsCacheDir.canonicalPath + File.separator)) {
cacheFile.parentFile?.mkdirs() invoke.reject("invalid asset path: $path")
copyAsset(path, cacheFile) return
val fd = ParcelFileDescriptor.open(cacheFile, ParcelFileDescriptor.parseMode(args.mode)).detachFd()
res.put("fd", fd)
} }
cacheFile.parentFile?.mkdirs()
copyAsset(path, cacheFile)
val fd = ParcelFileDescriptor.open(cacheFile, ParcelFileDescriptor.parseMode(args.mode)).detachFd()
res.put("fd", fd)
} else { } else {
val fd = activity.contentResolver.openAssetFileDescriptor( val fd = activity.contentResolver.openAssetFileDescriptor(
Uri.parse(args.uri), Uri.parse(args.uri),