From caae65c78d2c5d5c6fb2bf4f8172819c8851e5bd Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Wed, 30 Sep 2026 21:30:09 -0400 Subject: [PATCH] Import from an SD card or card reader on Android Import was switched off on Android: `imports_supported` was true only for `target_os = "linux"`, and its comment said Android has no path to read a card by and nowhere to write the copies. Neither holds. With "all files access" (MANAGE_EXTERNAL_STORAGE, API 30) an app reads the root of an SD card or a USB card reader by path, `/storage/9C33-6BBD`, and the importer only ever writes into its own staging directory, which is a plain directory on Android too. So the engine runs unchanged; what was missing was finding the card and the permission. - The manifest declares MANAGE_EXTERNAL_STORAGE, and READ_EXTERNAL_STORAGE up to API 29 with requestLegacyExternalStorage, which is the same access on 28 and 29. - Cards.java lists the mounted non-primary volumes through StorageManager and opens the system "All files access" page for this app. dr_ui::cards is the JNI bridge, through saf's helpers. - The import page on Android asks for the permission with an "Allow access" button until it has it, rather than showing an empty list that reads as "no card", and watches for the grant so the list fills in when the user comes back from settings. Google Play restricts this permission to file managers and the like; DarkRoom is sideloaded, so that does not apply. --- .../android/AndroidManifest.xml | 28 ++- .../java/paris/tourolle/darkroom/Cards.java | 150 ++++++++++++++++ docs/dev/traceability.md | 122 ++++++------- docs/gestures.md | 48 +++--- platform/dr-plat/src/volumes.rs | 37 ++-- ui/dr-ui/src/cards.rs | 160 ++++++++++++++++++ ui/dr-ui/src/import_ui.rs | 101 ++++++++++- ui/dr-ui/src/lib.rs | 1 + ui/dr-ui/src/saf.rs | 6 +- ui/dr-ui/ui/app.slint | 4 + ui/dr-ui/ui/import.slint | 26 ++- 11 files changed, 568 insertions(+), 115 deletions(-) create mode 100644 apps/darkroom-android/android/java/paris/tourolle/darkroom/Cards.java create mode 100644 ui/dr-ui/src/cards.rs diff --git a/apps/darkroom-android/android/AndroidManifest.xml b/apps/darkroom-android/android/AndroidManifest.xml index 6df6aba..45e42af 100644 --- a/apps/darkroom-android/android/AndroidManifest.xml +++ b/apps/darkroom-android/android/AndroidManifest.xml @@ -4,9 +4,10 @@ Deliberately minimal: this packages the viewer for on-device testing (spike S2 needs Adreno and Mali hardware, which no emulator represents). Nothing - here is a distribution manifest yet. Only network access is declared: file - access needs no manifest permission because the library grid reads through - SAF, which grants per-tree at runtime (ARCH §6.9). + here is a distribution manifest yet. The library grid needs no storage + permission, because it reads through SAF, which grants per-tree at runtime + (ARCH §6.9); the one storage permission declared is for importing from a + camera card, which is read by path. Minimal is not the same as empty, and the entries below that are not the activity are the difference. A manifest is the only place a component can be @@ -21,13 +22,29 @@ WebDAV listing, thumbnail and image fetches. Without it Android refuses socket creation outright, and the failure is invisible — no panic to catch, no log line, just a worker thread that stops. Storage is the - separate case that genuinely needs no permission here, because SAF - grants per-tree at runtime (ARCH §6.9). --> + separate case: the library and album folders need no permission + here, because SAF grants per-tree at runtime (ARCH §6.9). --> + + + + @@ -53,6 +70,7 @@ android:icon="@mipmap/ic_launcher" android:hasCode="true" android:allowBackup="false" + android:requestLegacyExternalStorage="true" android:supportsRtl="true">