Skip to content

Latest commit

 

History

History
319 lines (277 loc) · 19 KB

android.md

File metadata and controls

319 lines (277 loc) · 19 KB

Android APKs on Vinix

Vinix's ARM64 desktop runs Android APKs with Android Translation Layer and ART. ART executes the application's DEX bytecode; ATL implements Android framework APIs. The desktop uses a V backend that renders directly into a Vinix WS surface and receives native WS input. GTK and X11 are absent from this APK execution path. The runtime, its helpers and ARM64 JNI libraries execute directly on the ARM64 CPU. Java bytecode interpretation is provided by ART with its JIT disabled.

Vinix uses 16 KiB memory pages. The standard Alpine ART package assumes 4 KiB, so the builder requires a source-built ART overlay adapted for 16 KiB pages. The APK native-library linker also needs matching 16 KiB page rounding. The source-built ATL overlay supplies matching native helpers, framework DEX, Android resources and fonts from one pinned upstream commit. The Alpine package's framework is replaced as a unit so its Java and JNI APIs stay in sync. It verifies the pinned source, checked-in patch, compiler flag and every output file's hash, architecture and load-segment alignment before staging it. A missing or incompatible overlay fails the build. ART's androidfw library and ATL are patched together to expose real NDK configuration snapshots and qualifiers; rebuild both overlays when their checked-in patches change.

First build ART on a native ARM64 Alpine Linux host. On an ARM64 Mac, an ARM64 Linux VM using hardware virtualization is suitable for building the runtime. ART's build generates and executes native host tools, so a macOS cross compiler alone cannot build it. Enable Alpine's edge/main, edge/community and edge/testing repositories and install its ART build dependencies:

apk add --upgrade build-base bash python3 zip curl pkgconf patch \
  bionic_translation-dev bsd-compat-headers expat-dev icu-dev java-common \
  libbsd-dev libcap-dev libpng-dev libselinux-dev libunwind-dev lz4-dev meson \
  openjdk8-jdk openssl-dev valgrind-dev wolfssl-jni-dev xz-dev zlib-dev vixl-dev
ln -sf python3 /usr/bin/python

The pinned dependency ABI uses musl 1.2.6-r4, GCC/libstdc++ 15.2.0-r9, ICU 78.1-r0 and VIXL 8.0.0-r0. Match the package lock when rebuilding; the build script and manifest record the exact source, patch, compiler and dependency packages. --dependency-cache DIR records available package archive checksums for reproducing that build:

build-support/android/build-art.sh --output /path/to/art-runtime
build-support/android/build-bionic.sh --output /path/to/bionic-runtime

Build ATL on the same native ARM64 Alpine host with its framework dependencies. The build uses OpenJDK 8 for Java compilation and Java 17 or newer for the checksum-pinned D8 compiler:

apk add alsa-lib-dev android-build-tools art_standalone-dev libandroidfw-dev \
  gtk4.0-dev libgudev-dev libsecret-dev libdrm-dev libportal-dev \
  ffmpeg-dev mesa-dev openxr-dev sqlite-dev vulkan-loader-dev \
  wayland-dev wayland-protocols webkit2gtk-6.0-dev openjdk17-jre-headless
PATH=/usr/lib/jvm/java-8-openjdk/bin:$PATH \
  build-support/android/build-atl.sh --output /path/to/atl-runtime \
  --art-runtime /path/to/art-runtime --java /usr/lib/jvm/java-17-openjdk/bin/java

Copy all three resulting directories to the build host. Desugar ART's own Java boot libraries using the pinned D8 compiler; this removes unresolved Java lambda bootstrap calls while retaining the classes and resources. Application APKs remain unchanged. This host step requires Java 17 or newer. Then stage the native runtime and optional calculator APK. The staging host also needs the maintained V compiler, curl and aarch64-linux-musl-gcc:

build-support/android/art-bootclasspath \
  --build-dir build-aarch64-android/aarch64/java-build --art-runtime /path/to/art-runtime
./scripts/build-android-aarch64.sh --art-runtime /path/to/art-runtime \
  --bionic-runtime /path/to/bionic-runtime --atl-runtime /path/to/atl-runtime \
  --with-calculator
./scripts/build-desktop-aarch64.sh --compact-initramfs --with-android
./scripts/run-desktop-aarch64.sh --no-build --mem=12288

The default ART overlay location is build-aarch64-android/aarch64/art-runtime; the native-library linker defaults to build-aarch64-android/aarch64/bionic-runtime, and ATL defaults to build-aarch64-android/aarch64/atl-runtime. VINIX_ANDROID_ART_RUNTIME, VINIX_ANDROID_BIONIC_RUNTIME and VINIX_ANDROID_ATL_RUNTIME override those locations. The runtime builder installs the checksum-pinned Alpine ARM64 dependency closure and patched ART under /opt/vinix-android-aarch64. Its musl loader and libraries remain private to APK processes. Upstream ATL build dependencies still include GTK, but the native launcher does not load it or start an X server. It installs no CPU translator. packages.lock.json, runtime-manifest.json, art-runtime-manifest.json, bionic-runtime-manifest.json and atl-runtime-manifest.json preserve provenance. --update-lock explicitly regenerates the Alpine package selection.

The private libc is rebuilt from checksum-pinned musl 1.2.6 with the recorded Alpine patches, heap retention and Android allocation accounting. Its build must preserve the packaged libc's exports and use 16 KiB-compatible ELF segments. usr/share/vinix/musl-build.json records its source, patch and payload hashes; Android, Roblox and desktop staging verify that receipt.

APKs normally retain the launching Unix identity. Administrators can explicitly select real application UID/GID credentials before ART starts using the native launcher credential option. It requires prepared application-owned data and home directories and preserves the native surface/input transport. Ordinary desktop launches keep their existing identity.

Android's 80-byte ARM64 mallinfo reports actual live payload bytes, reusable slot capacity, anonymous allocation-group mappings and peak mapped bytes. Allocator metadata arenas are excluded. This private allocator leaves unused ELF image-page space with its image so borrowed library backing cannot distort heap statistics. Counters and allocation state share musl's fork lock. The ordinary desktop allocator keeps its existing behavior.

Open Android Calculator from Start or Quick Launch. The sample is the developer's unchanged Arity 1.1 APK, extracted from its official source archive. The archive and APK are verified against recorded SHA256 hashes. Click the input field above the history list before typing; this version uses keyboard input.

Arity 1.1 calculating 123+456 as 579 on native Vinix WS

Vinix supplies the single title bar and window controls. Android menus, views, dialogs and Canvas content render inside that native surface. The native WS backend describes the implemented API subset, JNI build and surface protocol.

On 21 October 2026, native WS checks exercised these unchanged open-source APKs through actual QMP keyboard/pointer input in an ARM64 Vinix guest:

APK Verified workflow
Arity 1.1 Type 123+456 and inspect the application’s 579 result.
Editor 1.98 Enter edit mode through Activity recreation, type text, save through the filename dialog, clear the document, and reopen the saved file.
Open Sudoku 2.5.2 Open Easy and a puzzle, dismiss the reusable hints, select Single number mode, enter 7 by touch, return to the list, and reopen with that value retained.

Editor APK after reopening its saved document

Open Sudoku APK with an entered value retained after reopening

These are QEMU framebuffer captures. The test uses the existing Android Calculator desktop launcher slot for each APK, so that outer caption remains in the captures. The APKs supply the document editor, puzzle boards and data. Native workflow evidence and reproduction instructions record APK hashes, backend provenance, screenshots and serial logs. The guest also inspects real process maps and rejects GTK/GDK/X11 libraries or X server processes. Sudoku’s popup tabs and advanced operations are outside these checks.

The V framework module implements the missing ActionBar, options menus, cursor adapter/lifecycle, ListActivity row dispatch, callbacks and dialog behavior. Recreation delivers the saved Bundle to a new Activity and restores EditText values with stable IDs. The WS backend provides native views, software Canvas, in-surface dialogs, asset reads, real SQLite queries and wakeable Looper queues. ScrollView commands update native coordinates; smooth scrolling changes position immediately. Themes apply a system base before app overlays, buttons use their default style, and ICU charset enumeration excludes converters the installed Java provider cannot construct. The normal ATL build generates the JNI declarations and packages the V library with source/tool hashes in its manifest. APK files are not patched.

Native WS preserves parent-relative bounds through layout and delivers OnLayoutChangeListener callbacks after onLayout, with both old and new coordinates. Registration is deduplicated and delivery uses a snapshot, so a callback can add or remove listeners. isLaidOut() becomes true after a successful layout and resets on window attachment changes. The signed layout regression checks these transitions, unchanged bounds, remeasurement and exception recovery.

Native WS drawing and pointer dispatch apply nested translationX and translationY values together with parent scrolling. Fractional offsets stay in floating point through hit testing and local touch coordinates. The signed translation regression checks native Canvas pixels across direct, software and hardware-fallback layers, negative offsets, child clicks and preserved raw touch coordinates. Screen and window location queries accumulate the same translations and parent scrolling, rounding once after the full ancestor chain. The regression also checks negative half values and locations outside the clipped window.

The native WS runner installs a streaming JCA provider for SHA-256, SHA-384 and SHA-512 before APK loading. It uses the private runtime's OpenSSL EVP implementation, including independent cloned digest state and reset after finalization. APK signature verification still authenticates the original contents and certificates. The signed tests/native-android-ws/digests-fixture.v guest regression compares known vectors and Bouncy Castle results and checks streaming, byte buffers, cloning, reset, invalid input and native handle cleanup.

Context.getMainExecutor() queues work on the main looper, including submissions from the main thread. Context wrappers preserve virtual base-context overrides; null tasks and submissions to a stopped handler are rejected. The signed tests/native-android-ws/executor-fixture.v guest regression checks dispatch, FIFO ordering, wrapper delegation and rejection behavior.

PackageManager.OnPermissionsChangedListener and its add/remove APIs resolve. Subscriptions are synchronized and duplicate registration is idempotent; removal releases the listener. Permission decisions remain fixed during the runtime's lifetime, so registration emits no change callback. This does not provide Android's runtime permission controller or grant/revoke service.

Wi-Fi scanning returns unavailable with empty results. The RTT API type resolves while its service and hardware feature remain absent. The signed Wi-Fi fixture checks both service lookup forms and these capability contracts. Android companions see Vinix's single foreground user through ActivityManager.getCurrentUser().

These application checks do not establish complete runtime conformance or mobile hardware support. The preserved runtime still fails the newer ANDROID-ATFORK-FAIL Android ABI exports preflight. Rebuild matching overlays for complete runtime qualification. The earlier GTK workflow report and calculator report remain historical evidence for that separate implementation.

The subsequent popular-app startup matrix probed eleven widely installed Google Play app identities. None reached a usable UI in that run. The report distinguishes standalone APKs from limited base-APK probes, records exact versions and signatures, and retains the first framework/JNI failure for each app. The Arity control still calculated 579 through the same runner.

The 26 October Maps372 replay completed its full 600-second observation with map tiles, Paris place search and details, driving and walking route selection, and successive Route Preview instructions. It used the final Framework371 build with the null-safe Intent.getDataString() repair. The report records the exact runtime inputs, source revision, complete external evidence bundle and SHA-256 checksums for genuine guest PNGs, including google-maps-running.png and paris-route-preview-ready.png. These checks cover anonymous use; live GPS navigation and map layer changes remain unverified. The subsequent Google account sign-in probe reproduced an Add account crash from a missing callback API. The framework now supplies the asynchronous account API and reports an unavailable authenticator through its future. A fresh guest verified that Add account returns to onboarding, the chooser remains responsive, and Skip still reaches Maps. Account authentication is currently unsupported.

The earlier 23 October Maps continuation displays live map tiles, Paris search and place photos, manually entered Versailles-to-Paris route estimates, and Route Preview. Preview's next and previous controls change the instruction and map; its overflow menu now opens with All traffic, Satellite and Terrain choices after adding ActionProvider support. The retained screenshots establish those interactions; the 15-minute observation was interrupted after these checks. GPS navigation and map layer changes remain unverified. The earlier overflow failure is retained separately with its raw startup_failed result.

The desktop launches APKs with a WS surface and input pipe. Inside such a native client connection, the public launcher accepts:

run-android /root/example.apk
run-android /root/example.apk -l com/example/MainActivity -w 480 -h 640

-l selects an activity using slash-separated names. -w/-h select surface dimensions between 64 and 4096 pixels. The launcher requires VINIX_SURFACE_PATH and WS input on stdin; an ordinary terminal does not provide that connection. App data is stored under $XDG_DATA_HOME/vinix/android, normally $HOME/.local/share/vinix/android. ANDROID_APP_DATA_DIR selects another root. The launcher supplies a finite 32 MiB stack limit; VINIX_ANDROID_STACK_KB overrides it. The compatibility library reports the usable initial stack within both Vinix's mapped reservation and that limit. Worker stack metadata remains supplied by musl. ARM ART uses its own low-address allocator for compressed object references.

The private compatibility library also implements Android's DSO-associated fork callbacks through real host callbacks. It preserves their order with ART's callbacks and removes them when the Android library finalizes. Since musl cannot unregister a host callback, the process retains an inert thunk after finalization. The table supports 128 successful Android registrations per process; further registrations return ENOMEM. Registering or finalizing handlers from inside a running fork callback is unsupported.

Compatibility follows the implemented Android API subset. Pure Java/DEX APKs do not require an architecture-specific native payload. Modern Android APIs, services and resource qualifiers can require additional implementation; the presence of an APK launcher does not establish compatibility with every application. The native WS loader supports Linux JNI providers, not Bionic NDK or GLES APK libraries. WebView only displays static HTML/text help content; modern web apps, advanced drawing and text editing remain incomplete.

In the earlier GTK/ATL execution path, the unchanged Roblox 2.738.1397 APK initializes its content providers and completes the ARM64 engine's JNI_OnLoad through native ATL/ART. The coherent framework, fork callbacks, real NDK configuration API, fortified I/O wrappers and private allocator statistics resolve its earlier missing native imports. Android resolver flags and EAI errors are translated at the Bionic boundary; its native loader permits recursive constructor loads and retains the DSO through callbacks. The launcher selects the private JKS trust store for Java HTTPS. Native Vinix fixtures verify real public HTTPS and rejection of an untrusted local certificate. XML <requestFocus /> tags are consumed during inflation and restore focus once children attach, respecting hidden views. The latest client launch then aborts on the missing View.OnCapturedPointerListener API. A usable Roblox screen, authentication and gameplay remain unverified. The Roblox desktop entry uses this private ATL/ART runtime. Its Bionic/GLES launch is not covered by the WS checks.

The historical X11-oriented guest harness remains available for the legacy ATL path and runtime preflights. It has not been ported to the WS backend; use the native workflow procedure linked above for WS application checks:

build-support/run-v-tool.sh tests/android/launcher-test.v
tests/android/art-runtime-test
tests/android/bootclasspath-test
tests/android/musl-runtime-test
tests/android/run --memory 12288

The guest test opens the unchanged calculator APK in the Vinix desktop, checks native stack and memory prerequisites, types 123+456 through the guest keyboard, verifies that the APK draws 579, and captures the actual framebuffer. Its test-only text observer does not calculate the result or modify the APK. Logs and screenshots are retained in build/android-smoke. The native runtime passed this test on 3 October 2026 in both the minimal test image and the built compact desktop image with 12 GiB RAM: the unchanged APK displayed 579 with zero key retries. The Java boot probe also passed date parsing, UTC formatting and stream ordering against the desugared boot libraries. These results cover that APK and those Java platform calls. --observe --apk /path/to/app.apk captures another APK's real window and logs without claiming a functional pass. See the harness instructions.