Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
105 commits
Select commit Hold shift + click to select a range
280242d
Pairs: more than one pair on a phone. Visor remembers the pair it con…
macedonga Oct 18, 2026
93015e6
Debug API: DEMO_PAIR puts a second pair on the home, a black Skyler n…
macedonga Oct 18, 2026
3595b04
Merge main into multi-pair
macedonga Oct 19, 2026
a0d5e6d
Link: a peer that leaves out AirShield's Multikey, as the Neural Band…
macedonga Oct 19, 2026
be963fe
Setup: pair_request carries the device's secondary certificate, Ident…
macedonga Oct 19, 2026
9197778
Status: the Meta Ray-Ban Display sends its device state as HNDeviceSt…
macedonga Oct 19, 2026
752d65a
Settings: the Display's rows as the Meta app's registry gives them by…
macedonga Oct 19, 2026
183b3f9
Neural Band: set up from the Display's settings with change owner ove…
macedonga Oct 19, 2026
885e049
Home: glasses that answer and turn this phone's key down (the Display…
macedonga Oct 20, 2026
be49d07
Display: the teleprompter, as a card on the home: the script in the g…
macedonga Oct 20, 2026
c9cbd7d
Display: Gestures, a card on the home: the six band gestures in a lis…
macedonga Oct 20, 2026
49f5cdc
Neural Band: the band takes one host at a time, so Visor leaves it to…
macedonga Oct 20, 2026
3fc0529
Glasses: the tampering lux is read as the float it is, and Visor know…
macedonga Oct 20, 2026
7059b21
Companion presence: when the associated glasses appear, the system bi…
macedonga Oct 20, 2026
1f60e7c
Notifications on the glasses: a notification removed on the phone is …
macedonga Oct 20, 2026
b10ed89
Presence: the glasses appearing reach only the pair Visor is for, thr…
macedonga Oct 20, 2026
2f9c904
Wi-Fi networks: the Display's glasses keep Wi-Fi networks on svc47, s…
macedonga Oct 20, 2026
38dfd23
Display: the Display's own screen on the phone, as a Mirror card on t…
macedonga Oct 20, 2026
7e091ee
Home: take out this branch's own take on a refused key, for #25's
macedonga Oct 20, 2026
74cddf5
Trust: glasses that refuse the key are EVICTED before the link is up
Oct 19, 2026
832d06a
Home: a refused key says so and opens Change owner, not "Asleep"
Oct 19, 2026
da47387
Home: a refused key with the Meta AI app installed names it
Oct 20, 2026
144f3a8
Refused key: glasses that turn the key down and still end link setup …
macedonga Oct 20, 2026
a588fe3
Presence: the permission Android wants to observe the companion's pre…
macedonga Oct 20, 2026
3c79f5e
Setup again: a known pair this phone is bonded to skips the pairing p…
macedonga Oct 20, 2026
ccce1a1
Merge branch 'main' into display
macedonga Oct 20, 2026
65734d3
Merge branch 'main' into display
macedonga Oct 20, 2026
9f69c18
Merge branch 'main' into display
macedonga Oct 20, 2026
0bbe30c
Merge branch 'main' into display
macedonga Oct 20, 2026
b8f8253
Display: Captions, a card on the home. Visor starts the Display's liv…
macedonga Oct 20, 2026
05fca16
Neural Band: Visor takes the band only for a lesson or an errand. It …
macedonga Oct 20, 2026
13dd691
Display: Mirror leaves the home until the Display's screencast answer…
macedonga Oct 20, 2026
ddee7ba
Display: the teleprompter knows captions or translation took the glas…
macedonga Oct 20, 2026
8acfe43
Debug API: SEND puts one message on a service and answers with what t…
macedonga Oct 20, 2026
155c913
Display: the glasses' apps fetch through the phone. Visor serves svc9…
macedonga Oct 20, 2026
e367418
Display: the glasses' apps on the phone's internet. Visor opens its o…
macedonga Oct 20, 2026
2b9d763
Display: Instagram's Reels, messages and likes work through the phone…
macedonga Oct 20, 2026
c17d53a
Link: Visor reconnects in seconds after a restart. The L2CAP channel …
macedonga Oct 20, 2026
7de8d4f
Display: the home's panels no longer wait 10 s for the phone. Visor a…
macedonga Oct 20, 2026
81307df
Display: Bloks screens from Meta and the phone's location. Visor answ…
macedonga Oct 20, 2026
2f383b0
Login: a refresh that cannot reach Meta says so, instead of saying th…
macedonga Oct 20, 2026
1027e5d
Display: the Briefing panel, and Stocks. Visor offers the home a Brie…
macedonga Oct 20, 2026
36d45ef
Home: a case reading taken while the case was on its charger is dropp…
macedonga Oct 20, 2026
37b7d24
Merge branch 'main' into display
macedonga Oct 20, 2026
fd83451
Merge branch 'main' into display
macedonga Oct 21, 2026
a124b3c
Merge branch 'main' into display
macedonga Oct 21, 2026
a64f8aa
Merge remote-tracking branch 'origin/main' into display
macedonga Oct 21, 2026
80715e6
Label the band's and other pairs' batteries; README lists Display, ba…
macedonga Oct 21, 2026
5ba028e
Display: Messages on the phone's texts. Visor serves svc70 for provid…
macedonga Oct 21, 2026
3f1dfe1
Display: photos sent from the glasses' Messages app go out as MMS. Vi…
macedonga Oct 21, 2026
445dd98
Display: answer the delivery check, the ping and motion activity. Vis…
macedonga Oct 22, 2026
546b53e
Display: recent calls in the Phone app. The Display's Phone app asks …
macedonga Oct 22, 2026
7950040
Display: album art on the music screen. With an Android phone the Dis…
macedonga Oct 22, 2026
668ac28
Display: Meta AI answers as cards. When the glasses are a Display, an…
macedonga Oct 23, 2026
b85c2bd
Neural Band: the buzz waits for the band's answer. Vibrate sends Hapt…
macedonga Oct 23, 2026
9f63afd
Display: share the captions transcript. The captions screen gets a Sh…
macedonga Oct 23, 2026
bf30bb0
Display: type to Meta AI from the phone. With a Display connected, a …
macedonga Oct 23, 2026
98ec7d8
Reconnect after Visor is updated. An update kills the glasses service…
macedonga Oct 23, 2026
d9a0f8c
Display: answer the tunnel's HTTP requests. Besides IP packets, the D…
macedonga Oct 23, 2026
18a48c1
Display: the glasses' own tools for Visor's model, and their device i…
macedonga Oct 23, 2026
4021a43
Live AI: the session keep-alive names its interaction. KeepWearableAi…
macedonga Oct 23, 2026
bfc0755
Display: like, launch and playlists on svc9012. A like from the Displ…
macedonga Oct 23, 2026
73594b6
Firmware updates from Meta. The glasses' firmware row opens a sheet t…
macedonga Oct 23, 2026
e61c3c4
Display: smart replies on chat notifications, and their answers sent.…
macedonga Oct 23, 2026
4a924b9
Display: calls placed and answered from the glasses. Visor serves the…
macedonga Oct 23, 2026
62c7d20
Neural Band: firmware updates from Meta. The band's settings get a Fi…
macedonga Oct 23, 2026
22a9817
Display: RCS chats in the Messages app from Google Messages' notifica…
macedonga Oct 23, 2026
dc87dfc
Display: capture state on svc2000. The glasses open media_capture_sta…
macedonga Oct 23, 2026
2c1ca01
Display: answer the lease service (svc41107). The Display asks the ph…
macedonga Oct 23, 2026
85d385c
Display: Mirror, the open answered and a stopped service told apart. …
macedonga Oct 23, 2026
efeb651
Display: put the Meta account and WhatsApp on the glasses (svc89). A …
macedonga Oct 23, 2026
ad33a17
Display: maps and turn-by-turn directions. Visor opens svc42 (smartgl…
macedonga Oct 23, 2026
ffa6d61
Display: web apps. Visor serves svc55074 for the Display's browser as…
macedonga Oct 23, 2026
af0b76c
Display: a Wi-Fi link when the Display asks for one. A svc41107 link …
macedonga Oct 23, 2026
6f2cebf
Display: link Facebook and Instagram to the glasses (svc89 type 6). T…
macedonga Oct 23, 2026
2cc0d82
Display: keep the internet tunnel from crowding the link. Tunnel fram…
macedonga Oct 23, 2026
e4e8ca7
Display: the Neural Band sets up on a clean install. Band setup only …
macedonga Oct 23, 2026
d2c8d01
Display: browse Meta's web app catalogue. The Web apps page gets Brow…
macedonga Oct 23, 2026
c7409f2
Live: decode H.264 as well as HEVC. The Display's camera service name…
macedonga Oct 23, 2026
35786d3
Fix four faults found in review. The Display's location request picks…
macedonga Oct 23, 2026
27bc3c2
Keep the Ray-Ban Meta as on main. A review of the branch against main…
macedonga Oct 23, 2026
8bb64e3
Merge branch 'main' into display
macedonga Oct 23, 2026
a8e5cf8
Display: stay on Bluetooth when the Display asks for a Wi-Fi link. Gr…
macedonga Oct 23, 2026
ca32c40
Neural Band: show the band the Display reports. A band set up on the …
macedonga Oct 23, 2026
7b2b385
Meta AI: answer typed questions in the app again. With a Display conn…
macedonga Oct 23, 2026
af07698
Display: place search from OpenStreetMap's Nominatim. The phone's geo…
macedonga Oct 23, 2026
622195a
Display: web apps are the glasses' own list. Visor kept its own list …
macedonga Oct 23, 2026
46ba29c
Display: read the Meta account's linked accounts as Meta lists them. …
macedonga Oct 23, 2026
26d2b58
Display: save a Wi-Fi network as one SaveNetwork (type 1). The Displa…
macedonga Oct 23, 2026
e2ac8e3
Neural Band: show the band as the Display pairs it. The band belongs …
macedonga Oct 23, 2026
af51bc0
Display: "Open on phone" opens the Accounts page. A Display app's lin…
macedonga Oct 23, 2026
153b7da
Meta AI: the Display's answer card carries the answer as its title. T…
macedonga Oct 23, 2026
1e350e0
Display: grant the Wi-Fi link the Display asks for, by moving the ses…
macedonga Oct 23, 2026
ed0e35c
Display: keep a session moved to Wi-Fi from being moved again. The se…
macedonga Oct 23, 2026
914b9b3
Landscape mode turns on Meta's landscape switch too (#35). The glasse…
macedonga Oct 24, 2026
03512ed
Landscape mode sets the media orientation the camera follows (#35). O…
macedonga Oct 24, 2026
43842dc
Landscape that works: the phone crops the captures the glasses mark l…
macedonga Oct 24, 2026
fca6a3f
Stabilize on the phone the videos the glasses leave shaking (#35). Fi…
macedonga Oct 24, 2026
cafe8b2
Merge branch 'main' into display
macedonga Oct 24, 2026
aec105b
Debug actions HEARING (start hearing correction and wait for it, stop…
macedonga Oct 25, 2026
28e1ed8
Merge branch 'main' into display
macedonga Oct 25, 2026
803673d
Merge branch 'main' into display
macedonga Oct 25, 2026
aa9c60f
Merge remote-tracking branch 'origin/main' into display
macedonga Oct 25, 2026
ccc54c0
Display: a quiet turn (#48) draws no answer card.
macedonga Oct 25, 2026
36fbea8
Merge remote-tracking branch 'origin/main' into display
macedonga Oct 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,27 @@ the frame style and language packs. Works on real glasses as of 2026-10-05.
`s` low-half. Any mistake shows up as `0x1043 INVALID_APP_IDENTITY`.
- Airship: accepting a file (or refusing it) must end with a drop on type 7, or the glasses'
send queue stalls behind it.
- DataX OK on the wire is status 0. 0x1000 is svc36's own result; sent as OK, it made the Display's
image loader retry every image.
- Replies on a channel the glasses opened go on `DataXConnection.mirror`. Replies on a channel Visor
opened keep the originator bit.

Display:

- svc28 device state comes wrapped in HNDeviceState `{1: stateCommon}`. Unwrapped as a plain
DeviceState, the battery reads 0%.
- svc41108: the Display takes the phone's internet state only on a channel the phone opens. Until
then its apps stay off the phone's internet.
- svc26 must be opened with an empty type 3 on every connection, or every share fails ("No remote
channel at identifier: MWA").
- svc70 folder names are lowercase. With "SENT" a sent text reloads as still sending.
- A text notification needs app id `com.android.sms`, provider 3 and the SMS thread id, or the
Messages app drops it.
- Captions and translation turn on only through a svc29 pack install and FEATURE_CHANGE. The same
pack on airship `language_resource` turns nothing on.
- Each Meta `/login` exchange ends the older sessions. Keep one token per run.
- With an Android phone the music screen ignores AVRCP cover art. The cover comes only over svc9012.
- The Phone app's recent calls wait on svc70 type 11, once per provider. svc11006 does not fill them.

## Build

Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,13 @@ Visor works with Ray-Ban Meta, Oakley Meta and the other Meta glasses. It speaks
| **Settings** | Every setting the glasses have, the way the Meta app shows them. Advanced holds the rest, assistant language packs and the firmware's feature flags. |
| **Outside the app** | A home screen widget, a Quick Settings tile, launcher shortcuts, and a Live Update notification while the Live view runs. |
| **Developer mode** | Other apps and automations can take photos and videos, import captures and hear about new ones. See [Developer mode](docs/developer-mode.md). |
| **More than one pair** | Keep several pairs on one phone. Home swipes between them, each with its name, batteries and a Connect button, and ends with a page to set up another. |
| **Meta Ray-Ban Display** | A teleprompter, live captions and translation, the Neural Band's gestures, the Briefing panel and saved Wi-Fi networks. The glasses' own apps reach the internet through the phone, with Instagram's Reels and messages among them. |
| **Neural Band** | Set up from the Display's settings, with its battery on Home and its wrist in Settings. |

## Requirements

- **Glasses:** a pair of Meta glasses, such as Ray-Ban Meta or Oakley Meta. Support for the Meta Ray-Ban Display is limited and lives on the [`display`](https://github.com/macedonga/visor/tree/display) branch.
- **Glasses:** a pair of Meta glasses, such as Ray-Ban Meta, Oakley Meta or the Meta Ray-Ban Display. Support for the Display and its Neural Band is newer and does not yet cover everything the Meta AI app does.
- **Phone:** Android 10 or later.
- **Meta account**, for setup. Log in with a password or an email code. Passkeys do not work.
- **For the assistant:** an API key from Anthropic or from an OpenAI-compatible service.
Expand Down
161 changes: 159 additions & 2 deletions app/src/debug/kotlin/com/macedonga/visor/debug/DebugReceiver.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import kotlinx.coroutines.withTimeoutOrNull
* READ_SETTINGS, MEDIA, IMPORT (--es handle a,b: by handle prefix), ASK (--es q), ONBOARD (--es page --ef t),
* MCP_PROBE / MCP_ADD (--es url --es name --es auth NONE|TOKEN|HEADER --es secret --es header), MCP_STATUS,
* MCP_TOOLS (what the model would get), MCP_CALL (--es tool exposed-name --es args json), MCP_REMOVE (--es id),
* DEMO_PAIR (--ez on false takes it away): a second pair on the home, a black Skyler, never connected or looked at.
* PHONE (--es do directions --es to --es mode | play --es q --es app | media --es c pause|resume|next|... | now).
*/
@AndroidEntryPoint
Expand All @@ -54,14 +55,30 @@ class DebugReceiver : BroadcastReceiver() {
@Inject lateinit var config: com.macedonga.visor.core.media.GlassesConfig
@Inject lateinit var liveSettings: com.macedonga.visor.core.media.LiveSettingsStore
@Inject lateinit var mcp: com.macedonga.visor.core.assistant.mcp.McpHub
@Inject lateinit var pairs: com.macedonga.visor.pairs.Pairs
@Inject lateinit var looks: com.macedonga.visor.stage.Looks
@Inject lateinit var phone: com.macedonga.visor.phone.PhoneActions
@Inject lateinit var prompter: com.macedonga.visor.core.connection.Teleprompter
@Inject lateinit var captions: com.macedonga.visor.core.connection.Captions
@Inject lateinit var languages: com.macedonga.visor.language.LanguageInstaller
@Inject lateinit var captionPacks: com.macedonga.visor.display.CaptionPacks
@Inject lateinit var screencast: com.macedonga.visor.core.media.Screencast
@Inject lateinit var requests: com.macedonga.visor.connection.RequestLog
@Inject lateinit var login: com.macedonga.visor.login.MetaLogin
@Inject lateinit var internet: com.macedonga.visor.connection.InternetSwitch
@Inject lateinit var developer: com.macedonga.visor.outside.DeveloperMode
@Inject lateinit var texts: com.macedonga.visor.connection.PhoneMessages
@Inject lateinit var wifiLink: com.macedonga.visor.connection.WifiLink
@Inject lateinit var hearing: com.macedonga.visor.core.assistant.Hearing

override fun onReceive(context: Context, intent: Intent) {
val action = intent.action?.substringAfterLast('.').orEmpty()
Log.i("Visor", "API: $action")
when (action) {
"STATUS" -> resultData = status()
// The phone's thread list as Visor reads it for the glasses' Messages: --ei count.
"THREADS" -> resultData = texts.describeThreads(intent.getIntExtra("count", 15))
"DEMO_PAIR" -> resultData = demoPair(intent.getBooleanExtra("on", true))
"CONNECT" -> resultData = runCatching { GlassesService.start(context); "connecting" }.getOrElse { "could not start the service: $it" }
"DISCONNECT" -> {
GlassesService.stop(context)
Expand Down Expand Up @@ -117,8 +134,10 @@ class DebugReceiver : BroadcastReceiver() {
"refused: unknown namespace or not connected"
}
}
// A notification built by hand: --ei type --ei provider --es title --es text --es sender --es pkg.
// A notification built by hand: --ei type --ei provider --es title --es text --es sender --es pkg --es thread
// (a text's thread in the SMS store, whose number becomes the sender's id).
"NOTIFY" -> {
val thread = intent.getStringExtra("thread").orEmpty()
val n = com.macedonga.visor.notify.GlassesNotification(
key = "visor|" + System.currentTimeMillis(),
pkg = intent.getStringExtra("pkg") ?: "org.telegram.messenger",
Expand All @@ -129,7 +148,8 @@ class DebugReceiver : BroadcastReceiver() {
type = intent.getIntExtra("type", 1),
provider = intent.getIntExtra("provider", 3),
sender = intent.getStringExtra("sender").orEmpty(),
senderId = intent.getStringExtra("sid").orEmpty(),
senderId = intent.getStringExtra("sid") ?: texts.addressesOf(thread).firstOrNull().orEmpty(),
threadId = thread,
)
connection.submit { it.sendOnService(com.macedonga.visor.notify.GlassesNotification.SVC, 1, n.envelope()) }
resultData = "sent type ${n.type} provider ${n.provider}"
Expand Down Expand Up @@ -194,6 +214,20 @@ class DebugReceiver : BroadcastReceiver() {
})
}
}
// Delete the enrolled custom wake word from the glasses.
"CWW_CLEAR" -> answerLater { if (assistant.wakeWord.clear()) "sent; the glasses answer with a result in the log" else "not sent" }
// Hearing correction: --es do correction (start, waited for) | stop | state.
"HEARING" -> answerLater {
when (intent.getStringExtra("do") ?: "state") {
"correction" -> {
hearing.startCorrection()
val on = hearing.awaitRunning(com.macedonga.visor.core.assistant.HearingMode.CORRECTION, 10_000)
"running=$on ${hearing.state.value}"
}
"stop" -> { hearing.stop(); hearing.awaitStopped(); "${hearing.state.value}" }
else -> "${hearing.state.value}"
}
}
// Restart only the glasses' assistant (it re-reads MobileConfig): live_caption_voice_id away and back.
"ASSISTANT_RESTART" -> answerLater { if (com.macedonga.visor.language.restartGlassesAssistant(settings)) "restarting the glasses' assistant" else "not connected" }
// Developer mode on or off: --ez on false.
Expand Down Expand Up @@ -313,6 +347,106 @@ class DebugReceiver : BroadcastReceiver() {
resultData = "asked for $id"
}
}
"PROMPTER" -> answerLater {
if (intent.getBooleanExtra("close", false)) prompter.close()
else prompter.show(
intent.getStringExtra("text") ?: "Testing the teleprompter from Visor.",
com.macedonga.visor.core.connection.PrompterLook(paged = intent.getBooleanExtra("paged", false), followVoice = intent.getBooleanExtra("voice", false)),
)
kotlinx.coroutines.delay(300)
withTimeoutOrNull(8_000) {
prompter.state.first { it.phase != com.macedonga.visor.core.connection.PrompterPhase.STARTING && it.phase != com.macedonga.visor.core.connection.PrompterPhase.CLOSING }
}
prompter.state.value.let { "${it.phase} refusal=${it.refusal}" }
}
// --es features a,b: what Meta has for these constellation features on this pair.
"PACKS" -> answerLater {
val features = intent.getStringExtra("features").orEmpty().split(',').map { it.trim() }.filter { it.isNotEmpty() }
runCatching { languages.offersFor(features) }.fold(
{ offers -> offers.joinToString("\n") { "${it.feature} ${it.resource} ${it.version} ${it.type} app ${it.appId} ${it.size} ${it.url}" }.ifEmpty { "none" } },
{ "failed: ${it.message}" },
)
}
// --ei svc --ei type --es hex: one message on a service; the answer is what the glasses said within 5 s.
"SEND" -> answerLater {
val svc = intent.getIntExtra("svc", 0)
val body = intent.getStringExtra("hex").orEmpty().chunked(2).filter { it.length == 2 }.map { it.toInt(16).toByte() }.toByteArray()
val heard = java.util.Collections.synchronizedList(mutableListOf<String>())
val watch = scope.launch {
launch { connection.serviceData.collect { if (it.serviceId == svc) heard += "type ${it.messageType}: " + it.payload.joinToString("") { b -> "%02x".format(b) } } }
launch { connection.warnings.collect { if (it.contains("service $svc ")) heard += it } }
}
connection.submit { it.sendOnService(svc, intent.getIntExtra("type", 0), body) }
kotlinx.coroutines.delay(5_000)
watch.cancel()
heard.joinToString("\n").ifEmpty { "nothing" }
}
// --es doc <client_doc_id> --es name <friendly name> --es vars <json>: one query on Meta's ar GraphQL, as the
// user; the answer's first 4000 characters.
"GRAPHQL" -> answerLater {
val text = login.graphql(
intent.getStringExtra("name").orEmpty(),
intent.getStringExtra("doc").orEmpty(),
intent.getStringExtra("vars64")?.let { String(android.util.Base64.decode(it, 0)) } ?: intent.getStringExtra("vars") ?: "{}",
)
if (!intent.getBooleanExtra("shape", false)) text.take(4000) else {
// Every path with its type, a string's length and start: the structure without the bulk.
val out = StringBuilder()
fun walk(path: String, v: Any?) {
when (v) {
is org.json.JSONObject -> v.keys().forEach { walk("$path.$it", v.opt(it)) }
is org.json.JSONArray -> { out.append("$path [${v.length()}]\n"); if (v.length() > 0) walk("$path[0]", v.opt(0)) }
is String -> out.append("$path str(${v.length}) ${v.take(60).replace('\n', ' ')}\n")
else -> out.append("$path = $v\n")
}
}
walk("", org.json.JSONObject(text))
out.toString().take(4000)
}
}
// --ez on false: tell the Display the phone is offline, so it keeps its apps off the phone's internet.
"INTERNET" -> {
internet.sharing = intent.getBooleanExtra("on", true)
resultData = "sharing=${internet.sharing}"
}
// The request log's newest entries, links cut at their query.
"REQUESTS" -> resultData = requests.items.value.takeLast(intent.getIntExtra("n", 30)).joinToString("\n") {
"${it.source} ${it.outcome ?: "running"} ${it.bytes}B ${it.millis}ms [${it.options}] ${it.url.substringBefore('?').take(160)}"
}.ifEmpty { "none" }
// --es key <MobileConfig param> --ez value true|false: one bool written to the glasses.
"MC_SET" -> answerLater {
config.send(mapOf(intent.getStringExtra("key").orEmpty() to intent.getBooleanExtra("value", false))) ?: "sent"
}
// --ez stop true to stop; the answer is the state once it settles.
"MIRROR" -> answerLater {
if (intent.getBooleanExtra("stop", false)) screencast.stop() else screencast.start()
kotlinx.coroutines.delay(500)
withTimeoutOrNull(40_000) { screencast.state.first { it.phase != com.macedonga.visor.core.media.ScreencastPhase.STARTING } }
screencast.state.value.toString()
}
// --es feature live_captions_en: Meta's pack installed through the glasses' OTA service.
"CAPTION_PACK" -> answerLater {
captionPacks.install(intent.getStringExtra("feature") ?: "live_captions_en")
kotlinx.coroutines.delay(500)
withTimeoutOrNull(600_000) { captionPacks.work.first { it !is com.macedonga.visor.display.PackWork.Working } }
captionPacks.work.value.toString()
}
"CAPTIONS" -> answerLater {
val self = intent.getStringExtra("lang") ?: "en"
when {
intent.getBooleanExtra("stop", false) -> captions.stop()
intent.getBooleanExtra("show", false) -> Unit
else -> captions.start(
if (intent.hasExtra("other")) com.macedonga.visor.core.connection.CaptionsMode.TRANSLATION else com.macedonga.visor.core.connection.CaptionsMode.CAPTIONS,
self, intent.getStringExtra("other") ?: self,
)
}
kotlinx.coroutines.delay(300)
withTimeoutOrNull(8_000) {
captions.state.first { it.phase == com.macedonga.visor.core.connection.CaptionsPhase.LIVE || it.phase == com.macedonga.visor.core.connection.CaptionsPhase.IDLE }
}
captions.state.value.let { s -> "${s.phase} ${s.mode} refusal=${s.refusal}\n" + s.lines.joinToString("\n") { "${if (it.done) "" else "… "}${it.text}${it.source?.let { src -> " ($src)" }.orEmpty()}" } }
}
"PHONE" -> answerLater {
when (intent.getStringExtra("do")) {
"directions" -> phone.directions(intent.getStringExtra("to").orEmpty(), intent.getStringExtra("mode").orEmpty()).toString()
Expand Down Expand Up @@ -366,6 +500,12 @@ class DebugReceiver : BroadcastReceiver() {
mcp.signIn.first { it is com.macedonga.visor.core.assistant.mcp.SignIn.Done || it is com.macedonga.visor.core.assistant.mcp.SignIn.Failed }
}.toString()
}
// A lease that needs Wi-Fi, held and let go as the Display's would be.
"WIFI_LINK" -> resultData = if (intent.getBooleanExtra("on", true)) {
wifiLink.hold("debug").let { "held: " + (wifiLink.refusal() ?: "moving") }
} else {
wifiLink.release("debug").let { "released" }
}
"MCP_REMOVE" -> {
mcp.store.remove(intent.getStringExtra("id").orEmpty())
resultData = "removed"
Expand All @@ -374,6 +514,19 @@ class DebugReceiver : BroadcastReceiver() {
}
}

private fun demoPair(on: Boolean): String {
if (!on) {
pairs.demo(null, null)
looks.demo(DEMO_SERIAL, null, null)
return "demo pair gone"
}
val look = com.macedonga.visor.core.identity.language.GlassesLook("skyler", "Skyler", "shiny_black", "Shiny Black", "clear", "Clear", null)
looks.demo(DEMO_SERIAL, look, DEMO_PICTURE)
val seen = com.macedonga.visor.home.PairSeen(glasses = 64, case = 88, at = System.currentTimeMillis() - 2 * 3_600_000L, folded = true)
pairs.demo(com.macedonga.visor.pairs.KnownPair(DEMO_SERIAL, "00:00:00:00:00:00"), seen)
return "demo pair on: swipe the home's stage"
}

private fun status(): String {
val s = connection.state.value
val b = s.device?.battery
Expand Down Expand Up @@ -411,5 +564,9 @@ class DebugReceiver : BroadcastReceiver() {

private companion object {
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
const val DEMO_SERIAL = "DEMO00SKYLER00"
// Meta's own picture of a black Skyler, as its asset service hands it out.
const val DEMO_PICTURE = "https://scontent.xx.fbcdn.net/mci_ab/uap/asset_manager/name_revision/?ab_b=e&ab_page=AssetManagerNameAndRevision" +
"&ab_entry=RBM2-Skyler-ShinyBlack-Clear-Concord&set=mwa_device_image_assets&revision=1837680503843762&density=3&mode=dark"
}
}
Loading
Loading