app: confirm before an enrollment link replaces an existing one
The scheme was already registered and the deep link already worked — it enrolled on arrival, with no confirmation. Now that the web UI offers the link as something to follow, that is one tap between a working enrollment and a replaced one, from a page that might be showing a link minted for a different device entirely. Enrolling is not additive: the new credential replaces the old, and on the previous server this device simply stops reporting. So the link is held and the user is asked, with both server URLs named — the question is "which server", and it cannot be answered without seeing both. The dialog says what survives, because that is the part someone hesitates over: uploads already on the old server stay there, runs stored on the phone are untouched, and the device reappears on the new server as a new device rather than carrying its history across. Enrolling also clears the cached canary zone. It describes the old server's deployment, and querying it against the new one would measure somebody else's zone and file the answer under this network. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
c639d58043
commit
30d9501507
@@ -75,6 +75,35 @@ class MainActivity : ComponentActivity() {
|
||||
screen = Screen.SETTINGS
|
||||
}
|
||||
}
|
||||
// Replacing an existing enrollment is asked about, never assumed. Following a
|
||||
// link from a web page is one tap, and the old credential does not survive it.
|
||||
vm.state.pendingEnroll?.let { pending ->
|
||||
androidx.compose.material3.AlertDialog(
|
||||
onDismissRequest = { vm.cancelEnroll() },
|
||||
title = { androidx.compose.material3.Text("Replace this device's server?") },
|
||||
text = {
|
||||
androidx.compose.material3.Text(
|
||||
"This device is already enrolled with " +
|
||||
"${pending.currentServer}.\n\n" +
|
||||
"Enrolling with ${pending.newServer} replaces that. Runs " +
|
||||
"already uploaded stay where they are, but this device " +
|
||||
"stops reporting to the old server and appears on the new " +
|
||||
"one as a new device.\n\n" +
|
||||
"Runs stored on this phone are not affected."
|
||||
)
|
||||
},
|
||||
confirmButton = {
|
||||
androidx.compose.material3.TextButton(onClick = { vm.confirmEnroll() }) {
|
||||
androidx.compose.material3.Text("Enroll here")
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
androidx.compose.material3.TextButton(onClick = { vm.cancelEnroll() }) {
|
||||
androidx.compose.material3.Text("Keep current server")
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
androidx.compose.runtime.LaunchedEffect(authUri) {
|
||||
if (authUri != null) {
|
||||
vm.completeSignIn(authUri)
|
||||
|
||||
@@ -42,6 +42,8 @@ data class UiState(
|
||||
val archiveStatus: String? = null,
|
||||
/** History, newest first. Refreshed after every run and whenever the history screen opens. */
|
||||
val history: List<app.echo_lot.archive.ArchivedRun> = emptyList(),
|
||||
/** An enrollment link waiting on confirmation, because this device is already enrolled. */
|
||||
val pendingEnroll: PendingEnroll? = null,
|
||||
/** Shell-tier readiness, shown before a run; null message = say nothing (Shizuku not installed). */
|
||||
val shizukuNotice: String? = null,
|
||||
val shizukuReady: Boolean = false,
|
||||
@@ -49,6 +51,15 @@ data class UiState(
|
||||
val shizukuState: ShizukuAvailability.State = ShizukuAvailability.State.NOT_INSTALLED,
|
||||
)
|
||||
|
||||
/**
|
||||
* An enrollment link that would replace an existing one, held until the user says so.
|
||||
*
|
||||
* Enrolling is not additive: the new credential replaces the old, and on the previous server this
|
||||
* device simply stops reporting. Following a link is one tap from a web page, which is not enough
|
||||
* deliberation to discard a working enrollment by accident.
|
||||
*/
|
||||
data class PendingEnroll(val link: String, val currentServer: String, val newServer: String)
|
||||
|
||||
/**
|
||||
* Drives one measurement run: device-tier probes (link snapshot, per-network ICMP) always run;
|
||||
* results assemble into a MeasurementDocument with a §7.3 summary. Lives in a ViewModel so a run
|
||||
@@ -255,9 +266,44 @@ class RunViewModel(app: Application) : AndroidViewModel(app) {
|
||||
|
||||
/** Redeems an enrollment link, from a paste or from an echolot:// deep link. */
|
||||
fun enroll(link: String, deviceName: String? = android.os.Build.MODEL) {
|
||||
// Already enrolled? Ask first. The old credential is gone the moment this succeeds, and a
|
||||
// link followed from a web page is one tap — far too little deliberation for that.
|
||||
if (settings.serverConfigured) {
|
||||
val target = app.echo_lot.protocol.EnrollmentLink.parse(link)?.controlUrl ?: link
|
||||
state = state.copy(
|
||||
pendingEnroll = PendingEnroll(
|
||||
link = link,
|
||||
currentServer = settings.serverUrl,
|
||||
newServer = target,
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
doEnroll(link, deviceName)
|
||||
}
|
||||
|
||||
/** The user confirmed replacing an existing enrollment. */
|
||||
fun confirmEnroll(deviceName: String? = android.os.Build.MODEL) {
|
||||
val pending = state.pendingEnroll ?: return
|
||||
state = state.copy(pendingEnroll = null)
|
||||
doEnroll(pending.link, deviceName)
|
||||
}
|
||||
|
||||
fun cancelEnroll() {
|
||||
state = state.copy(
|
||||
pendingEnroll = null,
|
||||
archiveStatus = "kept the existing enrollment; nothing changed",
|
||||
)
|
||||
}
|
||||
|
||||
private fun doEnroll(link: String, deviceName: String?) {
|
||||
viewModelScope.launch {
|
||||
state = state.copy(archiveStatus = "enrolling …")
|
||||
state = state.copy(archiveStatus = withContext(Dispatchers.IO) { store.enroll(link, deviceName) })
|
||||
val result = withContext(Dispatchers.IO) { store.enroll(link, deviceName) }
|
||||
// A new server means a new canary zone; the old one would describe somebody else's
|
||||
// deployment. Cleared rather than kept, and relearned from the next profile fetch.
|
||||
settings.canaryZone = ""
|
||||
state = state.copy(archiveStatus = result)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user