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);
}
  1. Create the crate under springboard/apps/, with androios-app as a dependency (springboard/sdk). springboard/apps/calculator is a complete example.
  2. Add manifest.toml, a 180 × 180 PNG named in icon, and a CHANGELOG.md with an entry for the version.
  3. Build for the tablet: cargo build --release -p hello --target aarch64-linux-android.
  4. Pack and publish: tools/aap.py pack apps/hello, then publish-app.sh with the resulting .aap file.

Which kind?

Most apps are app. Use another kind only in the cases below.

kindUse it for
app (default)Anything a user opens and can remove. Updated from the store without a restart.
system-appA stock app that ships with the system. Preinstalled, cannot be removed, otherwise the same as app.
systemContent with no screen, such as the keyboard's emoji font and word lists. It has no exec, is never launched, and updates silently.
serviceA 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.

FieldRequiredMeaning
idyesReverse-DNS name, letters, digits, ., - and _. Must match the id() the app returns.
nameyesThe name shown on the home screen and in the store.
versionyesSemantic version, x.y.z. Must have a matching changelog entry.
execyes, except systemThe executable's path inside the package. Relative only.
iconyesPath to a 180 × 180 PNG inside the package.
min_shellyesLowest shell version that can run this app.
summaryyesOne line for the store listing.
categoryyesOne of Productivity, Utilities, Books, Entertainment or System.
kindnoapp (default), system-app, system or service.
permissionsnoList of permissions, see below.
architecturenoaarch64-android (default) or x86_64-linux for PC apps. Each catalogue lists only its own architecture, plus system packages.
providesnoOnly 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.

PermissionMeaning
networkThe app connects to the internet.
storageThe app keeps files in its data folder.
audioThe app plays or records sound.
cameraThe app uses the camera.
photosThe app reads the photo library.
notificationsThe app sends notifications.
remote-desktopReserved 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() }
}

Clipboard and desktop

Testing

There is no app previewer yet. The home-screen preview in tools/preview.sh renders only the home screen.

Data and logs

Icon and look

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

Updates on the device

Store API

RequestReturns
GET /api/v1/apps.jsonThe 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.jsonThe 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.jsonThe 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.

DirectionMessages
Shell to appConfigure, Touch, Key, Release, Resume, Suspend, Quit, Open, Activate, Command, Clipboard, Context, ContextAction
App to shellHello, 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.