# MeshBay — Android client A client, not a host: no node runs on a phone (`docs/MESHBAY_DESIGN.md` §11.3). The shell is a system WebView showing the interface **from the package** — `meshbay-hub/src/meshbay_hub/static/` copied at build time into `build/generated/`, never committed (§8.3) — with a bridge (`app/src/main/assets/bridge/meshbay-bridge.js`) that offers the page the same `window.meshbay` as the desktop preload, wherever it offers anything at all. Hub calls leave from native code, to the signed-in hub only. The device key, the bundle key and every node identity are held natively under an Android Keystore key; the page is told public keys and handed signatures, asked for by kind — never bytes. `meshbay-hub/tests/vectors/keyring.json` holds that keyring to the desktop's and to the specification. Downloads are written to disk as they arrive — into the folder chosen in Settings (a Storage Access Framework tree, as `.part` until complete) or the system Downloads collection (a pending entry until complete) — and the page holds an opaque id, never a URI. Uploads come through the system picker. Casting: the page pushes the decrypted stream to a local HTTP relay (a port of the desktop's `cast-relay.js`, bound to the Wi-Fi address only); receivers are found and driven through the platform cast SDK with the default media receiver. While a cast runs, a media-playback foreground service holds the CPU and the Wi-Fi, and the WebView is kept reported visible — without that, Chromium freezes the page 60 s after the screen goes off. Where play services are absent, the page is offered no cast at all. Music playing on the phone itself holds the same service and the same visibility, for as long as it plays (`playback:keep-alive`), so the next track still loads with the screen off. ```bash # needs JDK 17+ and an Android SDK (ANDROID_HOME, or sdk.dir in local.properties) ./gradlew assembleDebug # app/build/outputs/apk/debug/app-debug.apk ./gradlew testDebugUnitTest # JVM unit tests ``` The security contract is also pinned from the Python suite by reading this source: `packages/meshbay-hub/tests/test_android_shell.py`. Not built yet: phone-specific behaviour (back button, network handover, keeping a download alive with the screen off), signed releases. ## Icon The desktop client's icon, `meshbay-client/build/icon-square.png`, placed in the adaptive icon's safe zone (72 dp of the 108 dp layer, which is what every launcher mask leaves visible) over its own edge colour, so no mask crops the M. The five `mipmap-*/ic_launcher_foreground.png` are generated from it: ```python from PIL import Image, ImageDraw, ImageFilter src = Image.open("../meshbay-client/build/icon-square.png").convert("RGBA") for name, L in [("mdpi", 108), ("hdpi", 162), ("xhdpi", 216), ("xxhdpi", 324), ("xxxhdpi", 432)]: S = L * 72 // 108; b = max(2, S // 25) img = src.resize((S, S), Image.LANCZOS) mask = Image.new("L", (S, S), 0) ImageDraw.Draw(mask).rectangle([b, b, S - b - 1, S - b - 1], fill=255) img.putalpha(mask.filter(ImageFilter.GaussianBlur(b))) layer = Image.new("RGBA", (L, L), (0, 0, 0, 0)) layer.paste(img, ((L - S) // 2, (L - S) // 2), img) layer.save(f"app/src/main/res/mipmap-{name}/ic_launcher_foreground.png", optimize=True) ``` No monochrome layer: a themed icon keeps only the layer's alpha, and this one would be a filled square.