screentinker/android/app
screentinker 3234c923a3
An APK download with no external storage went nowhere, silently (#266)
getExternalFilesDir() returns null whenever external storage is unavailable, and
on a signage panel that is not exotic: no emulated volume, a vendor ROM that never
mounts one, an ejected card, storage still unmounted early in boot.

Both APK download paths did:

    File(context.getExternalFilesDir(DIRECTORY_DOWNLOADS), name)

Java's File(File, String) treats a null parent as "no parent" and silently yields
a RELATIVE path, so the download targeted `ScreenTinker-x.y.z.apk` in the process
working directory — `/` — which is not writable. The write threw, the generic
catch swallowed it, and the caller reported only "failed to download or failed
signature verification".

That message is why this was expensive to find. The HTTP request SUCCEEDS (the
server logs a served download at the exact moment of each failure), so it does not
look like a download problem; the signing key is fine, so it does not look like a
verification problem; and because nothing is ever written there is no partial file
to find. It also never recovers — every attempt fails the same way, forever.

installFromUrl (the dashboard "Push an APK" button) carried the identical line, so
the obvious workaround for a panel in this state was broken by the same bug.

Both now use apkDir(), which falls back to internal storage. filesDir cannot be
unmounted: if it is gone the app is not running.

file_paths.xml gains a <files-path> for the same directory. The silent
PackageInstaller path streams the file itself and needs nothing there, but the
intent-based install FALLBACK resolves it through FileProvider and would throw
"Failed to find configured root" — turning an already-degraded panel into one that
cannot install at all.

⚠️ This cannot reach an affected panel over the air: the broken download path IS
the delivery mechanism, and Push APK shares the bug. A panel already in this state
needs one manual install to escape it.

Root cause is inferred from converging evidence on an Android 9 panel (HTTP served
at each failure, no APK anywhere on the device, the app's own external files dir
denied to its own UID, both paths failing identically, signing verified at parity
against the release it already installed). Handling a null return is correct
regardless — it must never become a relative path.
2026-08-13 14:32:34 -05:00
..
src An APK download with no external storage went nowhere, silently (#266) 2026-08-13 14:32:34 -05:00
build.gradle.kts chore(release): v1.9.34-alpha7 2026-08-13 13:41:46 -05:00
proguard-rules.pro Initial open source release 2026-04-08 12:14:53 -05:00