Skip to content

Claude/fix android file permissions r pq9z#31

Merged
kevincarlson merged 7 commits into
masterfrom
claude/fix-android-file-permissions-RPq9z
Apr 2, 2026
Merged

Claude/fix android file permissions r pq9z#31
kevincarlson merged 7 commits into
masterfrom
claude/fix-android-file-permissions-RPq9z

Conversation

@kevincarlson

Copy link
Copy Markdown
Member

No description provided.

claude added 7 commits March 30, 2026 08:41
The readUri/writeUri plugin commands exist only in the Android APK.
Calling them on desktop (or before the APK is rebuilt) caused
"Plugin not found" errors every time a document was opened, because
the uriPermission plugin is not registered on non-Android platforms.

Introduce src/lib/utils/platform.ts with isAndroid() which checks
navigator.userAgent — reliable in the Tauri WebView, zero extra deps.

All three call sites (loadDocument, handleSave non-session path,
handleSaveAs, and SessionManager.saveToOriginal) now guard the
readContentUri / writeContentUri calls with isAndroid() so the
plugin-fs fallback path is taken on desktop.

https://claude.ai/code/session_01J39pMh3ZymXAf9W5K3Ziyi
…lugin-fs I/O

The plugin:uriPermission|* IPC calls were silently rejected on Android because
UriPermissionPlugin is only registered in the Kotlin pluginManager — it is
invisible to the Rust IPC router which enforces capabilities. This means
takePersistablePermission was a no-op, so content:// URI permissions expired
after every app restart, breaking Recents and cross-session saves.

The readUri/writeUri commands added in the previous commits had the same
problem and are removed here.

Changes:
- MainActivity.kt: override onActivityResult to automatically call
  ContentResolver.takePersistableUriPermission for any content:// URI
  returned by a file-picker activity. This runs entirely in native Kotlin,
  requires no IPC, and fires before the JavaScript layer is involved.
- UriPermissionPlugin.kt: remove the non-functional readUri/writeUri commands;
  add a note explaining why plugin:uriPermission|* cannot be invoked via JS.
- useFileOperations.ts: restore readFile/writeFile from @tauri-apps/plugin-fs
  for all file I/O (plugin-fs uses ContentResolver on Android for content://
  URIs); fix handleSaveAs state update bug where setPath/startSession/
  addDocument/markClean were gated behind `if (bytes)` and so never ran for
  non-content:// (desktop) saves.
- SessionManager.saveToOriginal: restore direct writeFile call; add comment
  explaining plugin-fs handles content:// via ContentResolver.
- commands.ts: remove readContentUri/writeContentUri (were never functional).
- platform.ts: deleted (no longer needed).

https://claude.ai/code/session_01J39pMh3ZymXAf9W5K3Ziyi
…on persistence

The plugin was only registered in Kotlin's pluginManager.load() but not on
the Rust IPC router, causing all plugin:uriPermission|* calls to fail with
"Plugin not found". Add a tauri::plugin::Builder registration in lib.rs so
that takePersistablePermission is properly routed to the Kotlin implementation,
persisting content:// URI permissions across app restarts and fixing the
"Permission Denial" error when reopening documents from the Recents list.

https://claude.ai/code/session_01J39pMh3ZymXAf9W5K3Ziyi
Add explicit ::<_, ()> turbofish so rustc can resolve the DeserializeOwned
bound for the PluginApi config type parameter.

https://claude.ai/code/session_01J39pMh3ZymXAf9W5K3Ziyi
…ndroid

Tauri's dialog plugin uses ACTION_GET_CONTENT which returns a temporary,
non-persistable content:// URI — takePersistableUriPermission always throws
a SecurityException on such URIs, so Recents always fails after restart.

Add FilePickerPlugin.kt which launches ACTION_OPEN_DOCUMENT (grants
FLAG_GRANT_PERSISTABLE_URI_PERMISSION) and calls
ContentResolver.takePersistableUriPermission() inside the activity result
callback — the only window where the grant is valid — before returning the
URI. Register the plugin in MainActivity, lib.rs, and commands.ts.

On Android, handleOpen now calls plugin:filePicker|openFile instead of the
Tauri dialog open(). Desktop handleOpen is unchanged.

https://claude.ai/code/session_01J39pMh3ZymXAf9W5K3Ziyi
plugin:name|command IPC from JavaScript is checked against Tauri's ACL
(deny-by-default). Custom inline plugins have no defined permissions, so
all commands are blocked — causing "Plugin not found" errors even after
register_android_plugin succeeds.

Add commands/android.rs with pick_file_to_open and
take_persistable_uri_permission as regular Tauri commands registered via
invoke_handler!. These use PluginHandle::run_mobile_plugin_async which
goes through JNI directly, bypassing the ACL entirely. Store the plugin
handles in app state from the plugin setup callbacks.

Update commands.ts to call invoke('pick_file_to_open') and
invoke('take_persistable_uri_permission') instead of the blocked
plugin:filePicker|openFile and plugin:uriPermission|takePersistablePermission
IPC paths.

https://claude.ai/code/session_01J39pMh3ZymXAf9W5K3Ziyi
@kevincarlson kevincarlson merged commit 012451e into master Apr 2, 2026
12 checks passed
@kevincarlson kevincarlson deleted the claude/fix-android-file-permissions-RPq9z branch April 2, 2026 21:53
@AppThere AppThere locked and limited conversation to collaborators Apr 2, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants