Sideload an APK onto a Pixel Watch straight from your phone. No computer, no cable, no adb
binary on your PATH — the app speaks the ADB wire protocol itself: mDNS discovery,
Wireless-debugging pairing (SPAKE2), TLS transport, a sync: file push, then pm install on the
watch.
Requires Android 8.0+ on the phone and a Wear OS 3+ watch (Pixel Watch 1/2/3, Galaxy Watch 4+, and anything else with Wireless debugging).
Wear OS "debug over Bluetooth" is not a phone-to-watch feature. It's a tunnel the Wear OS companion
app opens for a computer plugged into the phone over USB
(adb forward tcp:4444 localabstract:/adb-hub). There is no supported way for a third-party phone
app to drive that hub, and Android's SELinux policy blocks reaching another app's local socket
anyway. Wireless debugging over Wi-Fi is what Google actually exposes, it's considerably faster,
and it's what WatchPush uses.
Both devices need to be on the same Wi-Fi network and the same subnet — guest networks and AP-isolated networks won't work.
- On the watch — Settings → System → About → tap Build number seven times to unlock Developer options, then Settings → Developer options → Wireless debugging → on.
- Step 1: pick the
.apk. WatchPush shows the parsed label, package name, version and size, and flags whether the APK declaresandroid.hardware.type.watch. - Step 2: pair — first time only. On the watch tap Pair new device; it shows an IP, a
pairing port, and a six-digit code. In the app tap Scan network and tap the endpoint
labelled
pair(or tap Pair… and type it), enter the code, tap Pair. - Step 2: connect. Back on the watch's Wireless debugging screen there's a different port
next to the IP. Tap the
connectendpoint from the scan, or type IP + port, then Connect. The banner turns green and names the watch. - Step 3: Install on watch. Live progress bar during the transfer; the activity log records
every step and reproduces any
pm installerror verbatim.
Pairing is one-time — the app keeps an RSA key pair in its private storage, exactly like
~/.android/adbkey on a desktop. Reconnecting after a watch reboot only needs step 4, but note the
watch picks a new port every time Wireless debugging restarts.
| Symptom | Cause |
|---|---|
| Scan finds nothing | Devices on different networks, or AP isolation on the router. Type the IP manually. |
Connection refused |
Wireless debugging switched itself off, or the port changed after a reboot. |
| Connect hangs, then fails | Not paired yet, or the pairing was dropped — pair again. |
INSTALL_FAILED_* |
The watch rejected the APK (wrong ABI, signature clash with an installed copy, downgrade). The full pm output is in the log. |
| File | Role |
|---|---|
| AdbKeys.kt | Generates and persists the RSA-2048 key pair plus a self-signed X509 certificate (BouncyCastle). |
| WatchAdb.kt | AbsAdbConnectionManager singleton holding the live connection. |
| AdbDiscovery.kt | NsdManager mDNS scan for _adb-tls-pairing._tcp / _adb-tls-connect._tcp, filtering out this phone's own services. |
| AdbTransfer.kt | ADB sync: SEND/DATA/DONE push in 64 KB chunks, and shell: command execution. |
| ApkStager.kt | Copies the picked content:// APK into the cache and parses its manifest. |
| MainViewModel.kt | State machine; all ADB work runs under runInterruptible(Dispatchers.IO) so it cancels cleanly. |
| ui/ | Compose Material 3 — Material You dynamic colour on Android 12+, a Pixel-blue scheme below that, large corner radii, pill buttons, themed monochrome launcher icon. |
The install path is push-then-install (/data/local/tmp/… → pm install -r -t -d → rm) rather
than a streamed cmd package install -S, because it yields an accurate byte-level progress bar.
./gradlew :app:assembleReleaseJDK 17 and an Android SDK with platform 35 are required; point local.properties at your SDK
(sdk.dir=...).
Release signing is opt-in. Without a keystore the release build falls back to the debug key so a
fresh clone still builds. To sign with your own key, create keystore.properties in the repo root:
storeFile=keystore/my-release.jks
storePassword=…
keyAlias=…
keyPassword=…Both that file and keystore/ are gitignored. Published releases are signed with a key that is
not in this repository — a self-built APK will therefore not upgrade an installed release
build; uninstall first.
WatchPush holds an ADB key that a watch has authorised, which is a lot of power over that device.
It only ever opens two services — sync: to write the APK and shell: to run pm install — and
it never phones home; there is no analytics, no network access beyond the local ADB socket. Keep
Wireless debugging switched off on the watch when you're not using it.
GPL-3.0-or-later — see LICENSE.
The app links spake2-java (LGPL-3.0) via
libadb-android (GPL-3.0-or-later OR Apache-2.0),
which makes GPL-3.0 the compatible choice for the whole work. Also uses
Conscrypt (Apache-2.0) for the bundled TLSv1.3 provider —
required to reach a remote adbd without hidden-API access — and
Bouncy Castle (MIT) for certificate generation.
- Single APKs only. Split APKs,
.apkmand.xapkbundles would needinstall-createplus multipleinstall-writesessions. - The install runs in the ViewModel rather than a foreground service, so backgrounding the app mid-transfer can let the system kill the job. Fine for the tens of seconds a watch APK takes.
- Untested against physical hardware by the author at time of first release — the protocol work is built to spec, but please open an issue if your watch disagrees.