Finds Syncloud devices on the local network over mDNS and opens them.
./gradlew clean testDebugUnitTest assemble bundleRelease -Pversion=26.09.1
The version is a build property, not a value in the source. CI passes the git
tag. Tags are year.month or year.month.patch and the code is
year * 10000 + month * 100 + patch, so 26.09.1 gives versionCode 260901.
The patch component exists so a second release inside the same month still
increases the code, which is what Play requires; without it the only way to
ship twice in one month is to invent a fake month and wedge every real one
after it. Without the property the build is 0.01, which is fine for local
work and can never be published.
The release build is minified and obfuscated by R8. proguard-rules.pro keeps
the core.*.model classes and the three result types intact, because jackson
maps those to json by field name and renaming them breaks sign-in and the
device list at runtime. Instrumented tests run against the debug build, so
they cannot catch that; the smoke CI step installs the real minified release
on the device and fails the build if it does not start.
The runner attaches two docker networks to every container, and android
configures only one interface, so the redroid hostname can resolve to the
address android never brought up and adb fails with no route to host.
ci/adb_device.sh probes for an endpoint that actually answers and caches it
for the later steps, instead of trusting dns.
Release signing comes from the environment, not from a file. CI supplies
KEY_STORE (base64 keystore), ANDROID_STORE_FILE, ANDROID_STORE_PASSWORD,
ANDROID_KEY_ALIAS and ANDROID_KEY_PASSWORD as Drone secrets. Without them
the release build is unsigned, which is fine for local work.
ci.syncloud.org builds every
push and tag, and publishes to /home/artifact/repo/android/<build>:
syncloud-<version>.apk sideloading, github releases
syncloud-<version>.aab google play
screenshots/discovery-with-device.png discovery screen with a real device
screenshots/01-auth .. 06-settings.png every screen, rendered by robolectric
discovery-logcat.txt device log, discovery tags only
instrument.log instrumented test output
diagnostics/smoke-logcat.txt device log from the minified release
Before a tag publishes, check play queries the play developer reporting api
for crash and anr anomalies and fails the build if any are open, and the
publish itself validates the edit before committing it, which is where missing
store declarations surface. Neither check can see the app optimization
warnings in the console, as those have no api.
Tagging is the only way to release. It publishes the apk and the aab to a github release and uploads the bundle to the play internal track, from where you promote it in the console. Pushes to any branch build and test but publish nothing.
Unit tests and screenshots run on the JVM. The screenshots are rendered with Robolectric and Roborazzi, so they need no emulator, and are recorded fresh on every run rather than compared against a baseline.
Discovery is tested against a real device. The pipeline runs the Syncloud
platform image as a service in systemd mode, so avahi advertises exactly as it
does on hardware, and runs Android in a redroid container beside it. The
instrumented tests drive DiscoveryManager through the platform's own
NsdManager and assert a device is found and listed.
An emulator cannot do this: its guest sits behind QEMU user mode NAT, which does not carry multicast, so mDNS never reaches it. redroid runs Android on the host kernel and gets ordinary docker networking.
The build host therefore needs binder_linux loaded with binderfs mounted, and
the repository marked trusted in Drone, since both services run privileged.
- Take
syncloud-<version>.aabfrom the build artifacts or the github release - Play Console -> Syncloud -> Production -> Create new release
- Upload the aab and roll out
Play has required app bundles for updates since November 2021. The apk remains for sideloading and github releases.
Devices advertise _ssh._tcp named syncloud on <host> from
/etc/avahi/services/syncloud.service in the rootfs image. The app browses that
type with NsdManager and matches the name.
NsdManager only listens for multicast, so an access point that filters
multicast to wireless clients makes discovery blind while the devices are still
reachable. A unicast fallback runs alongside it, querying from an ephemeral port
so responders reply directly, and reports whatever answers.
Generated with:
keytool -genkey -v -keystore syncloud.keystore -alias syncloud \
-keyalg RSA -keysize 2048 -validity 10000
Valid to 2041. The package name and this key's SHA-256 are registered for Android developer verification.