Skip to content

Fixes for 32-bit ARM Android (armeabi-v7a) - #6

Draft
kevinaboos wants to merge 6 commits into
dock_current_tab_robrixfrom
android_armv7_robrix
Draft

kevinaboos wants to merge 6 commits into
dock_current_tab_robrixfrom
android_armv7_robrix

Conversation

@kevinaboos

@kevinaboos kevinaboos commented Oct 25, 2026 •

Copy link
Copy Markdown
Owner

Fixes for running on 32-bit ARM Android (armv7, ABI armeabi-v7a), so Robrix can ship an APK for devices with a 32-bit Android system. Many of those devices run Android 8 or 9, so the last commit adds a file provider that Robrix needs to share attachments there. Each change is a separate commit.

Fixes

  1. DrawVars: padding so instance fields follow dyn_instances on 32-bit targets. This ports the DrawVars part of upstream commit 100b70c, so upstream dev already has it.
    • On 32-bit targets the 8-byte-aligned struct ended 4 bytes past dyn_instances.
    • That shifted every draw call's #[live] instance fields (rect_pos, rect_size, colors, glyph data, ...) by one f32 lane, so the whole UI would draw wrong on armv7.
    • A const assert now keeps that gap at zero. Without the padding it fails to compile for armv7 and passes for aarch64.
  2. Android: correct gettid syscall number on 32-bit ARM and x86.
    • These use the legacy syscall table, where 178 (the old fallback) is rt_sigqueueinfo.
    • android_entry made that call with garbage arguments on every launch on armv7.
    • The result is only used for OpenXR, but the stray syscall itself could do damage.
  3. cargo-makepad: look for NDK shared libs in the armv7 sysroot directory.
    • The NDK keeps armv7 libraries under sysroot/usr/lib/arm-linux-androideabi, not under the clang wrapper prefix armv7a-linux-androideabi.
    • So an armv7 APK/AAB never bundled a needed NDK library such as libc++_shared.so. That would crash at load time as soon as any dependency used C++. Nothing in Robrix's tree does today.
    • Adds a unit test.
  4. Images: cap decoded images at 128 MiB on 32-bit native targets (it is 1 GiB elsewhere).
    • Decoding holds a few cap-sized buffers at once: the decoder output, the RGBA buffer and an EXIF-rotated copy. Several 1 GiB buffers don't fit in a 32-bit address space.
    • So a large or crafted image (about 1 MB on the wire) aborted the whole app on an infallible allocation, instead of failing with DimensionsTooLarge.
    • 128 MiB is 32 MP of RGBA, which still covers typical phone photos.
  5. SharedBytes: link mmap64 on Android and glibc. Their plain mmap takes a 32-bit off_t on 32-bit targets, so the 64-bit offset we pass landed where the ABI doesn't look. This isn't reached on Android today: resource paths are build-host paths, so the file open fails first.
  6. cargo-makepad: add MakepadFileProvider, a read-only file provider that apps can declare.
    • On Android 8 and 9, an app can only share a private file through a content:// URI from one of its own providers. Makepad apps don't bundle androidx's FileProvider. A provider class must be compiled into the APK, so robius-share, whose Java is loaded at runtime, can't ship one either.
    • It is compiled into every APK's classes.dex, but does nothing unless the app's AndroidManifest.xml.template declares it. The meta-data dev.makepad.android.MakepadFileProvider.cacheDirs lists the cache subdirectories it serves (the class's Javadoc shows the declaration).
    • It is read-only and serves files only to apps that were granted the URI. Like FileProvider, it refuses to start if it is exported or doesn't grant URI permissions. It resolves .. and symlinks before checking that a file is inside a listed directory.
    • Robrix declares it for robius-share (Support 32-bit ARM Android (armeabi-v7a), and share files on Android 8 and 9 project-robius/robius#30).

Testing

  • All 61 cargo-makepad tests pass, including the new one.
  • makepad-draw: all 36 image_cache tests pass on the host, including the new one, and its unit tests type-check for armv7.
  • makepad-platform passes cargo check on armv7, aarch64 and the host, plus the draw_list test with the updated DrawVars literal.
  • makepad-shared-bytes tests pass on x86_64 and as a 32-bit (i686) process, with the mmap path actually taken, so the mmap64 link works on 32-bit glibc. They don't reproduce the bug itself: the old declaration happened to work on i686, and only goes wrong on armv7's calling convention.
  • Robrix's whole tree, with this branch patched in, passes cargo check for armv7-linux-androideabi and aarch64-linux-android.
  • Robrix's CI, with its Cargo.lock pointing at this branch, builds and links the armv7 and arm64 APKs with the real NDK. That also covers javac, d8 and aapt for the provider.
  • MakepadFileProvider:
    • It compiles cleanly with cargo-makepad's javac flags against the API 28 and API 33 stubs.

    • Robolectric tests on the Android 8.0 and 8.1 runtimes cover it, together with robius-share. They check:

      • serving files and their name/size;
      • .., encoded /, symlinks and sibling directories, which all stay refused;
      • the meta-data lookup and parsing;
      • the exported/grant checks;
      • read-only modes.

      They fail on deliberately broken variants. The tests live outside the repo.

  • Not verified: a run on a device.

Notes

🤖 Generated with Claude Code

https://claude.ai/code/session_01Va5dxPCRyW8YN5b1Yt1Awy

…gets

Port of the `DrawVars` part of upstream commit 100b70c. On 32-bit targets (e.g., armv7 Android)
the 8-byte-aligned struct ended 4 bytes past `dyn_instances`, so every draw call's `#[live]`
instance fields were uploaded one f32 lane off. A const assert now keeps that gap at zero.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Va5dxPCRyW8YN5b1Yt1Awy
These use the legacy syscall table, where 178 is `rt_sigqueueinfo`. `android_entry` made that call
with garbage arguments on every launch on armv7.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Va5dxPCRyW8YN5b1Yt1Awy
The NDK keeps armv7 libraries under `sysroot/usr/lib/arm-linux-androideabi`, not under the clang
wrapper prefix `armv7a-linux-androideabi`, so an armv7 APK/AAB never bundled a needed NDK library
such as `libc++_shared.so`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Va5dxPCRyW8YN5b1Yt1Awy
Decoding holds a few buffers of up to the cap at once (the decoder output, the RGBA buffer and an
EXIF-rotated copy), and on a 32-bit target (e.g., armv7 Android) several 1 GiB ones don't fit in
the address space. A large or crafted image (~1 MB on the wire) then aborted the whole app on an
infallible allocation, instead of failing with `DimensionsTooLarge`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Va5dxPCRyW8YN5b1Yt1Awy
… 32-bit `off_t` on 32-bit

The 64-bit offset we pass otherwise lands where the 32-bit ABI doesn't look, so the kernel got a
garbage offset on armv7. Not reached on Android today (resource paths don't exist on a device).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Va5dxPCRyW8YN5b1Yt1Awy
… apps

To share a private file with another app, an app gives it a `content://`
URI from one of its own providers. Android 10+ apps can copy the file to
MediaStore instead. On Android 8 and 9, without storage permissions, a
provider is the only way. androidx's FileProvider does this for most apps,
but Makepad apps don't bundle androidx. A provider must also be declared in
the app's manifest, and its class compiled into the APK so the app's class
loader can load it. So libraries whose Java is loaded at runtime, such as
robius-share, can't ship one.

MakepadFileProvider is compiled into every APK but does nothing until an
app declares it in its manifest template. It serves files from the cache
subdirectories named in its meta-data, read-only, to apps that were
granted the URI. Like FileProvider, it refuses to run if it is exported or
doesn't grant URI permissions. It resolves `..` and symlinks before
checking that a file is inside a listed directory.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Va5dxPCRyW8YN5b1Yt1Awy
kevinaboos pushed a commit to project-robius/robrix that referenced this pull request Oct 26, 2026
Both branches now also carry the Android 8/9 file-sharing fix:
cargo-makepad's MakepadFileProvider (kevinaboos/makepad#6) and
robius-share's use of the app's provider (project-robius/robius#30). The
next commit declares that provider in Robrix's manifest. An APK built with
a cargo-makepad that lacks the class would crash at launch, so this pin
has to land first. Only the two git revisions change.

Like the previous TEMPORARY commit, replace this before merging, with
`cargo update -p robius-location -p makepad-widgets` once those PRs merge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Va5dxPCRyW8YN5b1Yt1Awy
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants