3.7 KiB
Over-the-air (OTA) updates — deployment schema v0
The app checks a stable channel URL, follows it to a per-build manifest, then downloads the APK. Version comparison uses major.minor.build (packed into Android versionCode at build time). Deploy never computes versionCode by hand.
The client runs checks and downloads in OtaUpdateService (a standalone Service, not embedded in cast/receiver services). Launch checks enqueue work there; APK download uses a foreground service (dataSync) with a progress notification.
MQTT is fully supported for OTA sources using retained topic payloads (mqtt://... / mqtts://...). The URI path is treated as the exact topic to subscribe.
URL layout
https://host/v0/ota/channel/stable.json
https://host/v0/ota/00/00.{major}/00.{major}.{minor}/android_cast_00.{major}.{minor}.{build}.otapkg
https://host/v0/ota/00/00.{major}/00.{major}.{minor}/android_cast_00.{major}.{minor}.{build}_sign.json
https://host/v0/ota/00/00.{major}/00.{major}.{minor}/android_cast_00.{major}.{minor}.{build}_manifest.json
v0— deployment schema version (default channel).00afterota/— OTA payload format revision within v0.- Components are zero-padded to 2 digits in paths and filenames (
00.01.05).
Versioning
In app/build.gradle (overridable via local.properties):
ota.major=0
ota.minor=1
ota.build=0
Packed rule (each part 0–99):
versionCode = major * 10000 + minor * 100 + build
Example: 0.1.5 → versionCode 105.
JSON files
v0/ota/channel/stable.json
{
"schema": "v0",
"manifestUrl": "https://host/v0/ota/00/00.01/00.01.05/android_cast_00.01.05_manifest.json"
}
*_manifest.json
{
"schema": "v0",
"major": 0,
"minor": 1,
"build": 5,
"versionName": "0.1.5",
"apkUrl": "https://host/v0/ota/00/00.01/00.01.05/android_cast_00.01.05.otapkg",
"signUrl": "https://host/v0/ota/00/00.01/00.01.05/android_cast_00.01.05_sign.json",
"mandatory": false,
"releaseNotes": ""
}
*_sign.json
{
"schema": "v0",
"sha256": "…"
}
The app loads sha256 from sign.json when the manifest omits it.
MQTT transport
Use the same JSON payload shapes as HTTP, but publish them as retained messages on topics that match your URI paths:
mqtt://host:1883/v0/ota/channel/stable.json-> retained payload isstable.jsoncontentmqtt://host:1883/v0/ota/00/..._manifest.json-> retained payload is manifest JSONmqtt://host:1883/v0/ota/00/..._sign.json-> retained payload is sign JSONmqtt://host:1883/v0/ota/00/...otapkg-> retained payload is raw APK/otapkg bytes
When ota.trusted=true and build is debug, strict checksum/sign enforcement is relaxed for that source. Release builds always remain strict.
Publish from a release APK
chmod +x scripts/generate-ota-v0.sh
./scripts/generate-ota-v0.sh app/build/outputs/apk/release/app-release.apk https://your-host:port ./ota-publish
Upload the contents of ota-publish/ to your web root so https://your-host/v0/ota/... resolves.
App configuration
local.properties (not committed):
ota.channel.url=https://your-host/v0/ota/channel/stable.json
ota.major=0
ota.minor=1
ota.build=0
Or set the channel URL under Developer settings → App updates (OTA).
Legacy ota.manifest.url still works as a direct manifest URL fallback.
Checklist
- Bump
ota.major/ota.minor/ota.build(or Gradle defaults). - Build signed release APK (
versionCodeis derived automatically). - Run
generate-ota-v0.shand uploadv0/ota/**. - Confirm
stable.jsonpoints at the new*_manifest.json.