app: make the relay come back by itself, and report within seconds
Two faults found by watching it run. The heartbeat was the only cadence, so the first pass ran before mDNS discovery finished, saw nothing, and then sat silent for two minutes - precisely when someone has just switched the relay on and is watching for it to work. It now polls every five seconds until the first successful report, then settles. And nothing restarted the service after a reboot or an app update, which is the most common way it dies during development. A relay that has quietly stopped is worse than one never switched on: the endpoint it last published keeps looking authoritative while pointing at a port nothing is listening on. RelayBootReceiver handles BOOT_COMPLETED and MY_PACKAGE_REPLACED, debug builds only, and does nothing unless the relay was already on. Verified end to end: installing this build triggered the receiver and the phone reported its endpoint 7 seconds later, unattended. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
97515f7090
commit
7a88a2d4b9
@@ -21,6 +21,8 @@
|
|||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||||
<!-- Only so the relay's ongoing status is visible; the service runs either way. -->
|
<!-- Only so the relay's ongoing status is visible; the service runs either way. -->
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
<!-- So the relay survives a reboot of a device left running it (see RelayBootReceiver). -->
|
||||||
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
@@ -73,6 +75,19 @@
|
|||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:foregroundServiceType="dataSync" />
|
android:foregroundServiceType="dataSync" />
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Exported because the system delivers these broadcasts; the receiver itself starts
|
||||||
|
nothing unless the relay was already switched on in a debug build.
|
||||||
|
-->
|
||||||
|
<receiver
|
||||||
|
android:name=".RelayBootReceiver"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
|
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="${applicationId}.fileprovider"
|
android:authorities="${applicationId}.fileprovider"
|
||||||
|
|||||||
@@ -56,6 +56,11 @@ class AdbRelayService : Service() {
|
|||||||
r.start()
|
r.start()
|
||||||
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
|
// Poll quickly until the first endpoint has actually been reported, then settle into
|
||||||
|
// the heartbeat. Discovery takes a few seconds, so a loop that only ever waited the
|
||||||
|
// heartbeat would see nothing on its first pass and then sit silent for two minutes —
|
||||||
|
// exactly when someone has just switched the relay on and is watching for it to work.
|
||||||
|
var reportedOnce = false
|
||||||
while (true) {
|
while (true) {
|
||||||
val ep = r.lastEndpoint
|
val ep = r.lastEndpoint
|
||||||
if (ep != null) {
|
if (ep != null) {
|
||||||
@@ -66,13 +71,14 @@ class AdbRelayService : Service() {
|
|||||||
val result = post(settings, ep)
|
val result = post(settings, ep)
|
||||||
status = if (result == null) {
|
status = if (result == null) {
|
||||||
lastPosted = wire
|
lastPosted = wire
|
||||||
|
reportedOnce = true
|
||||||
"reported $wire"
|
"reported $wire"
|
||||||
} else {
|
} else {
|
||||||
"found $wire, but reporting failed: $result"
|
"found $wire, but reporting failed: $result"
|
||||||
}
|
}
|
||||||
notify(status)
|
notify(status)
|
||||||
}
|
}
|
||||||
delay(HEARTBEAT_MS)
|
delay(if (reportedOnce) HEARTBEAT_MS else STARTUP_POLL_MS)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -143,6 +149,9 @@ class AdbRelayService : Service() {
|
|||||||
*/
|
*/
|
||||||
private const val HEARTBEAT_MS = 120_000L
|
private const val HEARTBEAT_MS = 120_000L
|
||||||
|
|
||||||
|
/** Retry cadence before the first successful report; cheap, and only ever runs at start. */
|
||||||
|
private const val STARTUP_POLL_MS = 5_000L
|
||||||
|
|
||||||
fun start(ctx: Context) {
|
fun start(ctx: Context) {
|
||||||
val i = Intent(ctx, AdbRelayService::class.java)
|
val i = Intent(ctx, AdbRelayService::class.java)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) ctx.startForegroundService(i)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) ctx.startForegroundService(i)
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2026 Echolot contributors
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
package app.echo_lot.app
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Brings the relay back after a reboot or an app update, without anyone opening the app.
|
||||||
|
*
|
||||||
|
* The relay's whole purpose is to keep answering "where is this device" while the device sits on a
|
||||||
|
* shelf unattended. Starting it only from [MainActivity] meant it silently did not come back from
|
||||||
|
* either event — and a relay that has quietly stopped is worse than one that was never switched
|
||||||
|
* on, because the endpoint it last published keeps looking authoritative while pointing at a port
|
||||||
|
* nothing is listening on.
|
||||||
|
*
|
||||||
|
* `MY_PACKAGE_REPLACED` matters as much as boot here: installing a new build is the single most
|
||||||
|
* common way this service dies during development, which is exactly when it is being relied on.
|
||||||
|
*/
|
||||||
|
class RelayBootReceiver : BroadcastReceiver() {
|
||||||
|
override fun onReceive(ctx: Context, intent: Intent) {
|
||||||
|
if (!BuildConfig.DEBUG) return
|
||||||
|
when (intent.action) {
|
||||||
|
Intent.ACTION_BOOT_COMPLETED, Intent.ACTION_MY_PACKAGE_REPLACED -> {
|
||||||
|
if (Settings(ctx).adbRelayEnabled) {
|
||||||
|
runCatching { AdbRelayService.start(ctx) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user