mirror of
https://github.com/MarekWo/mc-webui.git
synced 2026-10-30 23:51:57 +01:00
Closes the last open [CLAUDE] item in the Play release plan. The section only covered the .apk in this repository; the app has been on Google Play since 2026-09-05 and the two have to go out together or they drift apart. Adds what Play needs on top - an .aab rather than an .apk, a versionCode that has not been used, release notes per listing language, and the declaration a foreground service always triggers - plus the promote-from-library shortcut that made the first production review take hours instead of days. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
149 lines
7.4 KiB
Markdown
149 lines
7.4 KiB
Markdown
# Android companion app — source
|
|
|
|
The app users install is a thin WebView wrapper around their own mc-webui
|
|
instance: one screen for the server address, one full-screen WebView for the
|
|
interface itself. It contains no mesh logic and talks to nothing except the
|
|
server the user typed in.
|
|
|
|
The sources are here so that anyone installing the `.apk` can see what they are
|
|
installing, and build it themselves if they prefer. For install instructions,
|
|
see [Android App](../docs/android-app.md).
|
|
|
|
| | |
|
|
|---|---|
|
|
| **Published build** | [`mc-webui-wrapper.apk`](mc-webui-wrapper.apk) |
|
|
| **Package** | `it.wojtaszek.mc.wrapper` |
|
|
| **Min / target SDK** | 21 (Android 5.0) / 36 (Android 16) |
|
|
| **Permissions** | `INTERNET`; `CAMERA` for QR scanning; `POST_NOTIFICATIONS` for new-message alerts; `WRITE_EXTERNAL_STORAGE` (Android 9 and older) for saving downloads |
|
|
|
|
## Layout
|
|
|
|
```
|
|
src/
|
|
├── settings.gradle.kts, build.gradle.kts, gradle.properties
|
|
├── gradle/wrapper/gradle-wrapper.properties
|
|
└── app/
|
|
├── build.gradle.kts
|
|
└── src/main/
|
|
├── AndroidManifest.xml
|
|
├── assets/notification_shim.js ← window.Notification
|
|
├── java/it/wojtaszek/mc/wrapper/
|
|
│ ├── MainActivity.kt ← the whole app
|
|
│ └── MeshWatchService.kt ← background delivery
|
|
└── res/ layout, strings, icons
|
|
```
|
|
|
|
`MainActivity.kt` is the entire application. Worth knowing about it:
|
|
|
|
- The server address is stored in `SharedPreferences` and **only ever replaced
|
|
by the user**. A failed connection or a Back press shows the address form
|
|
pre-filled — it never wipes what was saved
|
|
- Back on the first mc-webui page asks: exit, change server, or cancel
|
|
- Links to other hosts (URLs in messages, the packet analyzer) and non-`http`
|
|
schemes are handed to the system, so the app stays on the user's instance
|
|
- The page's camera request (QR scanning) is mirrored to an Android permission
|
|
request; downloads go to the phone's Downloads folder via `DownloadManager`
|
|
|
|
## Notifications
|
|
|
|
Android's WebView ships **no Web Notifications API at all** — `window.Notification`
|
|
is simply undefined, so mc-webui detects that and greys its notification toggle
|
|
out as "Unavailable". Native code has to supply the missing piece:
|
|
|
|
- `assets/notification_shim.js` defines a stand-in `window.Notification` and
|
|
forwards every call to a `@JavascriptInterface` bridge in `MainActivity.kt`,
|
|
which posts through Android's own `NotificationManager`. **mc-webui itself
|
|
needs no changes** — the page uses the standard API and never knows
|
|
- The shim is injected from `onPageStarted`, which runs as the document begins
|
|
loading. That is comfortably ahead of `DOMContentLoaded`, where mc-webui reads
|
|
the permission and decides whether to enable the toggle
|
|
- The web permission states map onto Android's: below Android 13 posting needs
|
|
no permission so it is `granted` outright; after a refusal,
|
|
`shouldShowRequestPermissionRationale` is what separates "ask again"
|
|
(`default`) from "blocked for good" (`denied`)
|
|
- The notification channel is created at startup, which is also what puts the
|
|
app in Android's notification settings list at all
|
|
- **Background delivery** (from `versionCode 6`) is `MeshWatchService`, a
|
|
foreground service of type `remoteMessaging`. It does no work: it exists so
|
|
Android leaves the process out of the cached-process freezer, which is what
|
|
used to silence alerts minutes after backgrounding. The page keeps producing
|
|
every notification, so mutes, blocked senders, channel names, translations and
|
|
deep links stay in one place
|
|
- The service follows mc-webui's own notification toggle. The shim reads
|
|
`mc_notifications_enabled` from `localStorage` and reports it over the bridge —
|
|
read rather than asked of the page, because the wrapper connects to whichever
|
|
server its user typed in, very often an older mc-webui. It also wraps
|
|
`Storage.prototype.setItem`, since localStorage fires no event in the document
|
|
that wrote it
|
|
- `setRendererPriorityPolicy(RENDERER_PRIORITY_IMPORTANT, false)` goes with it.
|
|
The page renders in a process of its own, and WebView waives that process's
|
|
priority once the view stops being visible — a service that saves only the
|
|
host process would still lose the renderer under memory pressure
|
|
- Android 12 forbids starting a foreground service from the background, so the
|
|
service is only ever started while the activity is on screen: from the shim's
|
|
report and again from `onResume`
|
|
|
|
## Building
|
|
|
|
Open `src/` in Android Studio (the Gradle wrapper JAR is not checked in —
|
|
Android Studio supplies it) and let it sync. Then:
|
|
|
|
- **Debug build:** *Build → Build Bundle(s) / APK(s) → Build APK(s)* →
|
|
`app/build/outputs/apk/debug/app-debug.apk`. Fine for trying things out on
|
|
your own phone, not for publishing — it is signed with the throwaway debug
|
|
key and is marked debuggable
|
|
- **Release build:** *Build → Generate Signed App Bundle or APK → APK*, pick
|
|
the project keystore, choose the `release` variant, and let it build. The
|
|
result is what ships
|
|
|
|
### Signing
|
|
|
|
Android identifies an app by its package name **and its signing key**. An
|
|
update only installs over an existing app when both match, so a release signed
|
|
with a different key forces every user to uninstall first — losing their saved
|
|
server address. In practice this means:
|
|
|
|
- Use the **same keystore for every release**, from the first published one
|
|
- **Back it up** (and its passwords) somewhere that survives a reinstalled
|
|
laptop. There is no way to recover or reissue it
|
|
- Never commit the keystore or its passwords to this repository
|
|
|
|
### Publishing a new build
|
|
|
|
The app goes out through two channels, from one build of one commit. Do them in
|
|
the same sitting, or the `.apk` here and the Store drift apart.
|
|
|
|
**Both channels, first:**
|
|
|
|
1. Bump `versionCode` — Play rejects an upload that reuses one — and usually
|
|
`versionName`, in `src/app/build.gradle.kts`
|
|
2. Mention the change in [`docs/whatsnew.md`](../docs/whatsnew.md)
|
|
3. Check whether the change touches anything
|
|
[`docs/privacy-policy.html`](../docs/privacy-policy.html) claims: it lists
|
|
every permission and everything stored on the phone, in both languages, and
|
|
Play reviews it
|
|
|
|
**The `.apk` in this repository:**
|
|
|
|
4. Build the signed release **APK**
|
|
5. Copy it here as `mc-webui-wrapper.apk`
|
|
6. Update the version, size and **SHA-256** in
|
|
[`docs/android-app.md`](../docs/android-app.md) — `sha256sum` on Linux,
|
|
`Get-FileHash` in PowerShell
|
|
|
|
**Google Play** (`it.wojtaszek.mc.wrapper`):
|
|
|
|
7. Build a signed **App Bundle** (`.aab`) — Play does not take an APK
|
|
8. Play Console → *Production* → *Create new release* → upload the `.aab`
|
|
9. Answer whatever new declarations the build triggers. A new permission
|
|
usually asks for one; **a foreground service always does**, and its
|
|
declaration wants a link to a **video** showing the feature in use
|
|
10. Write the release notes in every language the listing has (en-US and pl-PL),
|
|
500 characters each
|
|
11. Send for review. A production review is normally hours to a couple of days
|
|
|
|
> **Where a change has already been tested by the closed track**, promote that
|
|
> exact build from *Releases overview → Add from library* instead of building
|
|
> again. Play has already reviewed the artifact, which is what made the first
|
|
> production review take hours rather than days.
|