Developer docs
Build an app
Read this first. Apps currently target the Android-backed tablet build (aarch64-linux-android) and the PC desktop (x86_64-linux). The native Arch ARM64 target (aarch64-unknown-linux-gnu) is not ready for apps yet, and an Android binary will not run there. See the roadmap.
Every app is its own program. It draws into shared memory and talks to the shell over a socket. The store installs and updates apps without restarting the shell.
Quick start
This is the smallest app that builds. It draws nothing, so it only shows a blank surface.
use androios_app::{run, App, Ctx, Damage, Surface};
struct Hello;
impl App for Hello {
fn id(&self) -> &str { "is.example.hello" }
fn draw(&mut self, _ctx: &Ctx, s: &mut Surface, _t: f64) -> Damage {
let mut d = Damage::new(s.w, s.h);
d.all();
d
}
}
fn main() {
run(Hello);
}
- Create the crate under
springboard/apps/, withandroios-appas a dependency (springboard/sdk).springboard/apps/calculatoris a complete example. - Add
manifest.toml, a 180 × 180 PNG named inicon, and aCHANGELOG.mdwith an entry for the version. - Build for the tablet:
cargo build --release -p hello --target aarch64-linux-android. - Pack and publish:
tools/aap.py pack apps/hello, thenpublish-app.shwith the resulting.aapfile.
Which kind?
Most apps are app. Use another kind only in the cases below.
kind | Use it for |
|---|---|
app (default) | Anything a user opens and can remove. Updated from the store without a restart. |
system-app | A stock app that ships with the system. Preinstalled, cannot be removed, otherwise the same as app. |
system | Content with no screen, such as the keyboard's emoji font and word lists. It has no exec, is never launched, and updates silently. |
service | A background program with no screen, such as audio, networking or updates. The androiosd supervisor keeps it running and restarts it if it crashes. Services use their own socket. See springboard/docs/services.md. |
Manifest
Every package has a manifest.toml. The packer rejects a package if a required field is missing or a value is invalid.
| Field | Required | Meaning |
|---|---|---|
id | yes | Reverse-DNS name, letters, digits, ., - and _. Must match the id() the app returns. |
name | yes | The name shown on the home screen and in the store. |
version | yes | Semantic version, x.y.z. Must have a matching changelog entry. |
exec | yes, except system | The executable's path inside the package. Relative only. |
icon | yes | Path to a 180 × 180 PNG inside the package. |
min_shell | yes | Lowest shell version that can run this app. |
summary | yes | One line for the store listing. |
category | yes | One of Productivity, Utilities, Books, Entertainment or System. |
kind | no | app (default), system-app, system or service. |
permissions | no | List of permissions, see below. |
architecture | no | aarch64-android (default) or x86_64-linux for PC apps. Each catalogue lists only its own architecture, plus system packages. |
provides | no | Only for system packages. Names what the package supplies, such as ["keyboard"]. |
id = "is.olibuijr.calculator"
name = "Calculator"
version = "1.0.3"
exec = "calculator"
icon = "icon.png"
min_shell = "0.1.0"
summary = "Basic and scientific calculator with history."
category = "Utilities"
permissions = ["storage"]
Permissions. The list below is the full set the packer accepts.
| Permission | Meaning |
|---|---|
network | The app connects to the internet. |
storage | The app keeps files in its data folder. |
audio | The app plays or records sound. |
camera | The app uses the camera. |
photos | The app reads the photo library. |
notifications | The app sends notifications. |
remote-desktop | Reserved for the built-in Remote app. It needs network and storage. |
On the PC, the shell enforces network, storage, audio and remote-desktop, and rejects any other permission. Enforcement on the tablet is not documented yet. Declare only what the app really needs.
The app
The shell starts your executable and passes it a socket and a private data folder through environment variables. Your app implements the App trait. Only id and draw are required.
pub trait App {
fn id(&self) -> &str; // same as the manifest id
fn configure(&mut self, ctx: &Ctx) {} // size, scale, safe area, dark mode
fn event(&mut self, ctx: &Ctx, ev: Input) -> bool { false } // true when a new frame is needed
fn draw(&mut self, ctx: &Ctx, s: &mut Surface, t: f64) -> Damage;
fn suspend(&mut self, ctx: &Ctx) {} // leaving the foreground: save state now
fn resume(&mut self, ctx: &Ctx) {}
fn tick(&mut self, ctx: &Ctx, t: f64) -> bool { false } // runs while in the foreground
fn next_wake(&self, t: f64) -> Option<f64> { None } // when tick has work; None sleeps
fn controls(&self, ctx: &Ctx) -> Vec<Node> { Vec::new() } // accessibility labels
fn commands(&self, ctx: &Ctx) -> Commands { Commands::default() }
}
- Input.
Input::Touchfor touches,Input::Keyfor typed text,Input::Openfor a URL handed to the app,Input::CommandandInput::Activatefor shell actions, andInput::ContextandInput::ContextActionfor menus (see below). - Frames.
drawwrites premultiplied pixels with a stride equal to the width, and returns the area that changed. The first frame after a size change is always sent in full. - Keyboard. Call
ctx.wants_keyboard(true)to show the system keyboard. Typed text then arrives asInput::Key. - Other calls.
ctx.open_url,ctx.notify,ctx.haptic,ctx.log. - Scrolling. Use
kit::Scroller. Lists then scroll the same way as the system's own lists. - Suspend. The shell may close a background app at any time to free memory. Save state when you get
Suspend.
Clipboard and desktop
- Clipboard.
kit::clipboard::text()returns the current text, andkit::clipboard::set_text(&str)sets it. The SDK sends changes to your app as they happen. - Right-click menus (PC). A right-click arrives as
Input::Context { x, y }, in your app's pixels. Callctx.context_menu(x, y, items)with up to 32ContextItems, each with anid,labelandenabled. The chosen item comes back asInput::ContextAction(id). Disabled items never fire. - Remote sharing (PC).
ctx.remote_sharing(on)is only for the built-in Remote app. - Links. On the PC,
ctx.open_urlopens HTTP and HTTPS links in BifrOSt Browser.
Testing
There is no app previewer yet. The home-screen preview in tools/preview.sh renders only the home screen.
- Run
cargo test -p hellofor unit tests in your crate. - The Calculator crate has a
test_ui()helper that loads the system font and symbols. Copy it when you need to test drawing without a device. - Check the result on a real tablet with a screenshot before publishing.
Data and logs
- Data folder. The shell sets
ANDROIOS_DATAto/data/local/springboard/appdata/<id>, andctx.data_dirpoints at the same folder. Store everything your app needs there. Do not write outside it. - Crashes. A crashed app never takes the shell down. It shows its last frame, and it starts again on the next open.
- Exit log. When an app exits, the shell writes
springboard: app <id> exited <code>to its log. A code of 128 or more means the app was killed by a signal (128 + signal number). - Your own log. Call
ctx.log(text)to write a line to the shell log.
Icon and look
- The icon is a 180 × 180 PNG. The shell applies the rounded-square mask, so keep the important parts away from the corners.
- Use the
kitmodule for text, lists and navigation bars. Apps then match the system's type, colors and spacing without extra work. - Respect
Configure: dark mode, text size and reduced motion come from there.
Shipping
Packing
tools/aap.py pack <app dir> reads the manifest, checks it, takes the binary from target/aarch64-linux-android/release/ (or --bin) and writes an uncompressed tar with a .aap extension. It fails if the icon is not 180 × 180 or if the changelog has no entry for the version. tools/aap.py info <file.aap> prints what a package contains.
Changelog
Each version needs a heading in the form ## <version> — <date>, followed by - notes, newest first. The store shows the newest entry as "What's New" and the older ones as version history.
Publishing
publish-app.shcopies the package into the store and rebuilds the index. The store reads the app's details from its newest package.- A published version cannot be replaced. Bump the version for every change.
- PC apps are built for
x86_64-linux, setarchitecture = "x86_64-linux", and are published withbifrost pc app publish. - Submissions from other developers are not open yet.
Updates on the device
- Pull only. The tablet asks for updates. Nothing is pushed to it.
- When it checks. About 30 seconds after start, when Wi-Fi connects, and every 6 hours. It skips the check in low-power mode unless the device is charging.
- Apps and system parts install automatically unless turned off. System parts update silently.
- The OS (shell and boot script) downloads and checks the update, then asks before installing. It is in Settings > General > Software Update.
- Rollback. A new shell starts on probation. If it does not stay healthy, the boot script restores the previous build.
- OS releases are published with
bifrost publish, which needs a matching changelog entry.
Store API
| Request | Returns |
|---|---|
GET /api/v1/apps.json | The Android catalogue: the newest version of each app and system part, with release notes. Each entry has id, name, version, kind, summary, category, size, sha256, icon, package, min_shell and changelog. |
GET /api/v1/pc/apps.json | The PC catalogue, in the same format. |
GET /store/<id>/<file> | Packages and icons. Versioned .aap files never change and are cached for a year. Range requests work. |
GET /ota/springboard/latest.json | The OS update manifest (version, build, sha256, size, url, changelog), with the binary beside it. |
POST /diag/<name> | Diagnostic uploads. Only accepted from the local network. Not exposed publicly. |
Live catalogue: /api/v1/apps.json.
Protocol reference
The SDK handles all of this. Read it only if you are writing a client by hand.
- Socket. One
SOCK_SEQPACKETsocket, passed inANDROIOS_SOCK. Each message is one packet: a little-endianu32tag, then the fields. Unknown tags are ignored, so older apps and newer shells keep working together. - Buffers. Allocate two buffers with
memfd_create, orashmemon Android. Each isw × h × 4bytes. Send both file descriptors once inBuffers, usingSCM_RIGHTS. - Frames. Draw into the buffer the shell is not holding, and send
Frame { buf, damage }. Do not touch that buffer until the shell returns it withRelease { buf }. - Size. The shell sends
Configurebefore the first frame and on every rotation or resize. Reallocate and sendBuffersagain. - Versioning.
sdk_versionis1. It only goes up for breaking changes.
| Direction | Messages |
|---|---|
| Shell to app | Configure, Touch, Key, Release, Resume, Suspend, Quit, Open, Activate, Command, Clipboard, Context, ContextAction |
| App to shell | Hello, Buffers, Frame, WantsKeyboard, Haptic, OpenUrl, Notify, Log, SetClipboard, RemoteSharing, Controls, Commands, ContextMenu |
The byte-level encoding is in springboard/proto/src/lib.rs. It is shared by the shell and the SDK, and has round-trip tests.