Quest ADB / HZDB Recovery Notes
Date: 2026-03-28
Scope: Meta Quest control behavior observed from
C:\Users\tillh\source\repos\ViscerealityCompanion while debugging Sussex
study-shell launch failures on a Quest 3S over Wi-Fi ADB and USB ADB.
Why This Exists
These notes capture the device-control findings that are easy to lose in shell history:
- which
adbandhzdbcommands were tested - which HorizonOS activities and services showed up in each blocked state
- which signals changed device state in a useful way
- which commands looked promising but did not solve the issue
- what the current best recovery sequence is
Current operator-facing docs and UI now use runtime / task pinning
wording. Any kiosk terminology below is historical context for the March
2026 lock-task behavior that was observed on this machine.
Command Setup For This Repo
If you want this repo to stage the current official Quest tooling first, run:
dotnet run --project src/ViscerealityCompanion.Cli -- tooling install-official
The shell snippets below keep plain adb for brevity. On a machine that is
using the managed LocalAppData cache directly, you can substitute:
$adb = Join-Path $env:LOCALAPPDATA 'ViscerealityCompanion\tooling\platform-tools\current\platform-tools\adb.exe'
$hzdb = Join-Path $env:LOCALAPPDATA 'ViscerealityCompanion\tooling\hzdb\current\hzdb.exe'
Primary Failure Shape
The recurring launch blocker was not a generic ADB disconnect. The device was reachable, but the shell sat in a Meta visual-limbo stack:
com.oculus.guardian/com.oculus.vrguardianservice.guardiandialog.GuardianDialogActivitycom.oculus.os.vrlockscreen/.SensorLockActivitycom.oculus.vrshell/.FocusPlaceholderActivitycom.oculus.os.clearactivity/.ClearActivitycom.oculus.vrshell/.HomeActivitycom.oculus.panelapp.settings/com.oculus.panelapp.quicksettings.QuickSettingsActivity
The characteristic blocked launch log line was:
Launch is blocked because: a Guardian dialog is currently showing.
The Guardian dialog that kept coming back was:
GUARDIAN_TRACKING_LOST_CONTINUE
Most Useful Inspection Commands
Use these first before assuming the device is just "awake" or "asleep":
$device = '192.168.2.56:5555'
adb -s $device shell dumpsys activity activities |
Select-String -Pattern 'ResumedActivity|mFocusedWindow|GuardianDialogActivity|FocusPlaceholderActivity|SensorLockActivity|ClearActivity|HomeActivity|QuickSettingsActivity'
$device = '192.168.2.56:5555'
adb -s $device shell dumpsys vrpowermanager
$device = '192.168.2.56:5555'
adb -s $device shell service list |
Select-String -Pattern 'guardian|sensorlock|vrfocus|vrpowermanager|tracking'
Useful services observed on this machine:
guardiansensorlockvrfocusvrpowermanagertracking
Selector note:
- For low-level tracing on 2026-03-28, the most reliable selector was the USB
serial
3487C10H3M017Q. adb -s 3487C10H3M017Q ...worked consistently over USB.hzdb device list --format jsonreturned[]in that USB-only state, so for shell-level tracing useadbdirectly and do not assumehzdbwill see the same transport path.
Android Input Stack Visibility
The Quest shell-visible Linux input stack was much smaller than the effective VR control surface:
adb -s 3487C10H3M017Q shell getevent -lp
adb -s 3487C10H3M017Q shell dumpsys input
Observed input devices:
/dev/input/event0gpio-keysKEY_VOLUMEUPKEY_SWITCHVIDEOMODE
/dev/input/event1pmic_pwrkeyKEY_POWER
/dev/input/event2pmic_resinKEY_VOLUMEDOWN
Important implication:
- The Touch controllers did not appear as normal
/dev/input/event*devices in the shell-visible input stack. - This means the right-controller Meta / menu button is not currently exposed
to
adb shell geteventthe way a normal Android keyboard or gamepad button would be.
Keylayout observations:
adb -s 3487C10H3M017Q shell ls /system/usr/keylayout
adb -s 3487C10H3M017Q shell ls /odm/usr/keylayout
adb -s 3487C10H3M017Q shell cat /system/usr/keylayout/Generic.kl
/system/usr/keylayout/Generic.klis readable and confirms that Android defines generic injectable keycodes such asHOME,MENU,BUTTON_MODE,GUIDE,SLEEP, andWAKEUP./odm/usr/keylayoutcontains Quest-specific files including:gpio-keys.kloculus-device.kl
- Shell could list those ODM files but could not read them:
cat: /odm/usr/keylayout/gpio-keys.kl: Permission denied
Working conclusion:
- The Quest almost certainly has device-specific input mappings beyond the generic Android keylayout, but part of that mapping sits behind permissions not available to the shell user.
- For now, treat the controller Meta / system button as a Meta-managed path, not as a normal shell-visible Android input device.
HZDB Command Mapping Observed On This Machine
hzdb is available and usable:
& $hzdb --version
Observed command behavior:
hzdb device wake --device <serial>- Sent a wake signal but did not clear the Guardian blocker by itself.
hzdb device proximity --disable --device <serial>- Broadcast
com.oculus.vrpowermanager.prox_close - From
STANDBY, this transitionedvrpowermanagertoHEADSET_MOUNTEDand triggeredSCREEN_ON.
- Broadcast
hzdb device proximity --enable --device <serial>- Broadcast
com.oculus.vrpowermanager.automation_disable - This explicitly dismissed the Guardian dialog and drove
vrpowermanagerintoSTANDBY.
- Broadcast
Important reliability note:
- Do not trust implicit device selection with
hzdb. - Pass
--device <serial>explicitly.
ADB Commands Tested
Commands That Helped
$device = '192.168.2.56:5555'
adb -s $device shell am broadcast -a com.oculus.vrpowermanager.automation_disable
Observed behavior:
GuardianDialogMgrloggedexplicitly dismissing dialogvrpowermanagerlogged transitionHEADSET_MOUNTED -> WAITING_FOR_SLEEP_MSG -> STANDBY
$device = '192.168.2.56:5555'
adb -s $device shell am broadcast -a com.oculus.vrpowermanager.prox_close
Observed behavior:
vrpowermanagerlogged transitionSTANDBY -> HEADSET_MOUNTEDSCREEN_ONfollowed- HorizonOS relaunched
SensorLockActivity
Commands That Did Not Solve It Alone
adb -s $device shell input keyevent 26
adb -s $device shell input keyevent 223
adb -s $device shell input keyevent 224
adb -s $device shell input keyevent 3
& $hzdb device wake --device $device
Observed behavior:
- They changed wakefulness or brought
HomeActivityforward, but did not reliably clear the Guardian tracking-loss blocker.
Power / Sleep / Wake Comparison
From a mounted baseline, these three inputs did not behave the same:
adb -s 3487C10H3M017Q shell input keyevent POWER
adb -s 3487C10H3M017Q shell input keyevent SLEEP
adb -s 3487C10H3M017Q shell input keyevent WAKEUP
Observed behavior:
| Command | Observed vrpowermanager effect |
Observed shell effect |
|---|---|---|
input keyevent POWER |
stayed HEADSET_MOUNTED in repeated snapshots |
no reliable state change from the tested mounted baselines |
input keyevent SLEEP |
HEADSET_MOUNTED -> STANDBY |
deterministic sleep / SCREEN_OFF behavior |
input keyevent WAKEUP |
STANDBY -> WARM_UP -> HEADSET_MOUNTED |
deterministic wake leg, typically relaunching SensorLockActivity |
Practical rule:
- For controlled ADB experiments,
SLEEP/WAKEUPare more informative and reproducible than rawPOWER. - Do not assume
input keyevent POWERis equivalent to pressing the headset's physical power button in the way Meta's UX stack handles it.
Injected High-Level Keycodes Versus Meta Button Semantics
The generic Android keycodes exist, but they do not obviously reproduce the dedicated Quest Meta-button behavior:
adb -s 3487C10H3M017Q shell input keyevent HOME
adb -s 3487C10H3M017Q shell input keyevent MENU
adb -s 3487C10H3M017Q shell input keyevent GUIDE
adb -s 3487C10H3M017Q shell input keyevent BUTTON_MODE
adb -s 3487C10H3M017Q shell input keyevent APP_SWITCH
Observed behavior from Guardian-blocked baselines:
HOME- moved
mCurrentFocus/mFocusedApptocom.oculus.vrshell/.HomeActivity - but
mTopFullscreenOpaqueWindowStatecould still remain the Guardian dialog
- moved
BUTTON_MODE- behaved similarly to
HOMEin the cleanest repeated run
- behaved similarly to
GUIDE- produced no visible state change in the clean repeated run
MENUandAPP_SWITCH- looked closer to
HOMEthan to a dedicated Quest system button in the coarse pass, but they need cleaner repeats before treating that as fully stable
- looked closer to
Source-tagged retry:
adb -s 3487C10H3M017Q shell input keyboard keyevent GUIDE
adb -s 3487C10H3M017Q shell input gamepad keyevent GUIDE
adb -s 3487C10H3M017Q shell input keyboard keyevent BUTTON_MODE
adb -s 3487C10H3M017Q shell input gamepad keyevent BUTTON_MODE
Observed behavior:
GUIDEstayed inert in both keyboard- and gamepad-source retriesBUTTON_MODEstill behaved like a route toHomeActivity, not like a dedicated Quest Meta-button press
Current conclusion:
- We do not currently have evidence that any shell-injected Android keycode reproduces the unremappable right-controller Meta / menu button semantics.
- The dedicated Quest system button path is probably handled above or beside the standard shell-visible Android input route.
Manual Right-Controller Meta / Menu Button Probe
Manual probe date:
- 2026-03-28
Trace artifact:
artifacts/verify/controller-meta-button-trace-20260328-143403/
Manual action:
- a human pressed the right-controller Meta / menu button three times while
three observers were running:
adb shell getevent -lt- filtered
adb logcat -v time - a
250 mspoll ofdumpsys windowplusdumpsys vrpowermanager
Observed result:
getevent.txt- remained empty
logcat.txt- remained empty with the tested filter
window-focus.txt- stayed stable for the entire capture
mCurrentFocus=...HomeActivitymTopFullscreenOpaqueWindowState=...FocusPlaceholderActivityVirtual proximity state: CLOSEState: HEADSET_MOUNTED
Immediate post-trace input inspection:
adb -s 3487C10H3M017Q shell dumpsys input | Select-String -Pattern 'RecentQueue:|KeyEvent' -Context 0,10
Observed result:
RecentQueuecontained sixKeyEvententries- that is consistent with three taps being delivered as three down/up pairs
- the queue did not reveal the actual keycode, only that
KeyEvents existed
Interpretation:
- The physical right-controller Meta / menu button does appear to enter Android's higher-level input-dispatch path.
- It still does not appear as a normal shell-visible Linux input event in the
accessible
/dev/input/event*set. - It also did not cause an observable shell-focus or
vrpowermanagerchange in this Home / FocusPlaceholder baseline. - So far, the strongest model is:
- controller Meta-button press becomes an internally routed
KeyEvent - but the underlying device path and semantic action are not exposed through the shell-level tools used here
- controller Meta-button press becomes an internally routed
What this does and does not prove:
- It does support the claim that the button is not "nothing"; the system really is receiving key events.
- It does not yet identify the keycode or prove equivalence to
HOME,BUTTON_MODE,GUIDE, or any other injectable Android key. - It does not yet show whether Meta consumes the event before ordinary shell surfaces can log or dispatch it further.
Power Button Versus Controller Meta Button Comparison
Comparison probe date:
- 2026-03-28
Trace artifact:
artifacts/verify/hardware-vs-meta-button-trace-20260328-143829/
Manual action:
- one headset hardware-button family was pressed twice
- the right-controller Meta / menu button was pressed twice
Observer setup:
adb shell getevent -lt- full
adb logcat -v time - a
350 mspoll of:dumpsys windowdumpsys vrpowermanagerdumpsys inputRecentQueue
Important result:
getevent.txt- captured both button families
- headset hardware button:
/dev/input/event1KEY_POWERdown / up pairs
- right-controller Meta / menu button:
/dev/input/event3- device name
Device 0xD2A88F3474ADDF66 KEY_FORWARDdown / up pairs
logcat-full.txt- captured the power-button route clearly
- included
SideFpsEventHandler: notifyPowerPressed - included
PowerManagerService: Going to sleep due to power_button - included
PowerManagerService: Waking up from Asleep (reason=WAKE_REASON_POWER_BUTTON, details=android.policy:POWER)
- the most useful combined read came from:
getevent.txt- the polled
RecentQueueplusvrpowermanager/ focus state - the power-manager lines in
logcat-full.txt
Observed split:
Power-button family
getevent.txtshowed twoKEY_POWERdown / up pairs on/dev/input/event1- at
2026-03-28T14:38:53.3100924+01:00, two freshKeyEvents appeared with ages338 msand171 ms - by
2026-03-28T14:38:53.8103598+01:00,vrpowermanagerhad moved toSTANDBYand shell focus was null - at
2026-03-28T14:38:55.7131775+01:00, another freshKeyEventpair appeared with ages387 msand162 ms - by that same poll, the headset was back in
HEADSET_MOUNTEDwithHomeActivityfocused again logcat-full.txtmatched the same sequence with explicit sleep / wake lines from the power manager
Controller Meta / menu family
getevent.txtshowed twoKEY_FORWARDdown / up pairs on/dev/input/event3- around
2026-03-28T14:38:58.0981404+01:00, two freshTouchModeEvent(inTouchMode=true)entries appeared with ages470 msand393 ms - no corresponding
STANDBY/ wake transition occurred HomeActivity,FocusPlaceholderActivity, andHEADSET_MOUNTEDall stayed stable- around
2026-03-28T14:39:02.9598341+01:00and2026-03-28T14:39:03.4625119+01:00, another fresh event cluster appeared:- one very fresh
KeyEventat47 ms - then
KeyEvents at622 msand382 ms - plus a
TouchModeEvent(inTouchMode=true)at25 ms
- one very fresh
- again, no shell-focus or
vrpowermanagerstate change followed
Working interpretation:
- The headset power-button path is easy to distinguish:
- it produces fresh dispatcher
KeyEvents - and it changes
vrpowermanagerstate in the expected sleep / wake pattern
- it produces fresh dispatcher
- The right-controller Meta / menu path looks different:
- it does surface at the shell-visible input layer in this later probe
- the best current low-level mapping is:
- physical right-controller Meta / menu button
/dev/input/event3KEY_FORWARD
- it can also produce fresh higher-level dispatcher activity
- that activity can include
TouchModeEvents and sometimesKeyEvents - and it does not force the shell / wake-state transitions that the power button does from this Home baseline
What remains unresolved:
- whether the
KEY_FORWARDmapping is stable across firmware / OS / focus states - whether
KEY_FORWARDis the real end-to-end semantic route or just the best current shell-visible label for a higher-level Quest-specific button path - the queue snapshots are partial and age-based, so they show event timing but not full semantics
- the exact mapping from physical controller Meta / menu press to Android / HorizonOS command semantics above that low-level event is still unknown
Promising But Permission-Blocked
The lockscreen package exposes:
oculus.lockscreen.action.UNLOCK
But shell cannot broadcast it directly:
Permission Denial: not allowed to send broadcast oculus.lockscreen.action.UNLOCK
The settings package exposes:
horizonos.appactions.settings.UPDATE_TRAVEL_MODE
But shell cannot call it directly either:
Requires permission horizonos.permission.APPACTIONS_QUERY_AND_EXECUTE
Injected Power / Sleep / Wake Trace
Trace artifact:
artifacts/verify/injected-key-trace-20260328-145008/
Injected sequence:
adb -s 3487C10H3M017Q shell input keyevent POWER
adb -s 3487C10H3M017Q shell input keyevent SLEEP
adb -s 3487C10H3M017Q shell input keyevent WAKEUP
adb -s 3487C10H3M017Q shell input keyevent FORWARD
adb -s 3487C10H3M017Q shell input keyevent BUTTON_MODE
adb -s 3487C10H3M017Q shell input keyevent GUIDE
adb -s 3487C10H3M017Q shell input keyevent HOME
Observed result:
getevent.txt- showed only device-add metadata
- did not show any of the injected key events
- the injected keys therefore entered above the Linux
/dev/input/event*layer
Power-family split:
- injected
POWER- did hit the real power-manager route in this run
logcat-full.txtcontained:SideFpsEventHandler: notifyPowerPressedPowerManagerService: Going to sleep due to power_button
state-poll.txtmoved fromHEADSET_MOUNTEDtoSTANDBY
- injected
SLEEP- while already in
STANDBY, it kept the device inSTANDBY - the useful log line was:
sleepRelease() calling goToSleep(GO_TO_SLEEP_REASON_SLEEP_BUTTON)
- while already in
- injected
WAKEUP- woke the device back to
HEADSET_MOUNTED - brought
SensorLockActivityback into the visible window stack
- woke the device back to
Working interpretation:
- injected
POWERis not reliably "nothing"; in this baseline it reached the same power-manager path as the physical headset button - earlier runs still showed
POWERbeing inert from other mounted baselines, so treat injectedPOWERas state-dependent rather than deterministic SLEEP/WAKEUPremain the cleaner primitives for controlled automation because their effect stays easier to reason about
Injected Menu-Key Comparison
Trace artifact:
artifacts/verify/injected-menu-key-trace-20260328-145216/
Recovered baseline before capture:
adb -s 3487C10H3M017Q shell am broadcast -a com.oculus.vrpowermanager.automation_disable
Start-Sleep -Milliseconds 500
adb -s 3487C10H3M017Q shell am broadcast -a com.oculus.vrpowermanager.prox_close
Start-Sleep -Milliseconds 800
adb -s 3487C10H3M017Q shell input keyevent HOME
The shell returned to HEADSET_MOUNTED with HomeActivity, but
SensorLockActivity was still present and Guardian could re-enter the stack.
Injected sequence:
adb -s 3487C10H3M017Q shell input keyevent FORWARD
adb -s 3487C10H3M017Q shell input keyevent BUTTON_MODE
adb -s 3487C10H3M017Q shell input keyevent GUIDE
adb -s 3487C10H3M017Q shell input keyevent HOME
adb -s 3487C10H3M017Q shell input keyevent MENU
Observed result:
getevent.txt- again stayed limited to device-add metadata
- none of the injected keys surfaced at the Linux input-device layer
- every injected key still created fresh dispatcher
KeyEventpairs indumpsys inputRecentQueue
Per-key behavior:
FORWARD- produced a fresh
KeyEventpair logcat-full.txtshowed an explicitandroid.intent.category.HOMElaunch ofcom.oculus.vrshell/.HomeActivity- it did not dismiss
GuardianDialogActivity
- produced a fresh
BUTTON_MODE- produced a fresh
KeyEventpair - by the next state polls,
HomeActivityhad moved toWindow #0 - no separate explicit
HomeActivitylaunch line was observed in this run
- produced a fresh
GUIDE- produced another explicit
android.intent.category.HOMElaunch ofcom.oculus.vrshell/.HomeActivity - it still did not dismiss
GuardianDialogActivity
- produced another explicit
HOME- produced a fresh
KeyEventpair - once
HomeActivitywas already atWindow #0, no additional distinct transition stood out
- produced a fresh
MENU- produced a fresh
KeyEventpair - no distinct shell or focus transition stood out in the captured baseline
- produced a fresh
Working interpretation:
FORWARD,BUTTON_MODE, andGUIDEare better thought of as "bring / relaunch Home" candidates than as true emulations of the right-controller Meta / system buttonMENUis not a useful stand-in for the Quest Meta / menu route in this shell state- none of the injected menu-like keys closed
GuardianDialogActivityorSensorLockActivity - so far, injected keys can help detect or provoke Home-routing behavior, but they are not a reliable "close accidental Meta menu / close system dialog" primitive on their own
Current Best Recovery Sequence
This was the first sequence that actually launched the Sussex Unity runtime:
$device = '192.168.2.56:5555'
adb -s $device shell am broadcast -a com.oculus.vrpowermanager.automation_disable
Start-Sleep -Milliseconds 500
adb -s $device shell am broadcast -a com.oculus.vrpowermanager.prox_close
Start-Sleep -Milliseconds 500
adb -s $device shell monkey -p com.Viscereality.LslTwin -c android.intent.category.LAUNCHER 1
What happened:
automation_disabledismissed the active Guardian dialogprox_closebrought the device back fromSTANDBYtoHEADSET_MOUNTED- launching immediately after that started
com.Viscereality.LslTwin/com.unity3d.player.UnityPlayerGameActivity
Why the timing matters:
- after a few seconds, Guardian can request
GUARDIAN_TRACKING_LOST_CONTINUEagain - launching quickly can beat that re-request window long enough for the Unity runtime to start
This is a workaround, not a complete semantic fix:
- it reproduces a useful launch path
- it does not yet positively "press Continue" on the Guardian dialog
Expected State Transitions
The recovery path that matched the logs most closely was:
- Blocked shell:
- Guardian dialog foreground
automation_disable- Guardian dismissed
vrpowermanagermoves toSTANDBY
prox_closevrpowermanagermoves toHEADSET_MOUNTEDSCREEN_ONSensorLockActivityis relaunched
- immediate app launch
- Unity activity can start before Guardian re-requests the tracking-loss dialog
Known Caveats
SensorLockActivitycan still appear during the wake leg.FocusPlaceholderActivityoften appears while the shell is juggling focus.ClearActivitycan temporarily become the foreground app during the same transition.- A clean
statussnapshot immediately after the sequence does not guarantee the Guardian dialog will not return a few seconds later.
Screenshot-confirmed "Finding position in room" limbo
New probes on 2026-03-28 separated the visible Guardian layer from the deeper SensorLock layer. The relevant screenshots and command outputs are under:
artifacts/verify/guardian-live-probe-20260328-150705/artifacts/verify/guardian-recovery-command-probe-20260328-150729/artifacts/verify/guardian-dialog-tap-probe-20260328-150817/artifacts/verify/guardian-key-nav-probe-20260328-150925/artifacts/verify/guardian-force-stop-probe-20260328-150954/artifacts/verify/sensorlock-vrpower-probe-20260328-151100/artifacts/verify/sensorlock-unlock-broadcast-probe-20260328-151230/artifacts/verify/sensorlock-dismiss-keyguard-probe-20260328-151331/
What the screenshots proved:
- the "cannot find position in room" lock is a real
GuardianDialogActivitydialog over a blackSensorLockActivitybackdrop - the visible dialog text is:
- title:
Finding position in room - primary button:
Continue without tracking - secondary button:
Turn on travel mode
- title:
dumpsys vrpowermanagercan still sayHEADSET_MOUNTEDwhile the display is visually blocked, so screenshots are mandatory for this state
How the limbo was reproduced:
input keyevent SLEEPinput keyevent WAKEUP- result: screenshot-confirmed black
SensorLockActivity input keyevent HOME- result: the Guardian dialog becomes visibly readable on top of the black background
What did not work from plain adb shell:
- low-level power emulation:
sendevent /dev/input/event1 ... KEY_POWER ...- result:
Permission denied - implication: the shell user cannot replay the true headset power button at the Linux input layer
- dialog interaction:
input tapon the screenshot-derived primary-button centersinput keyevent DPAD_CENTERinput keyevent ENTERinput keyevent TABinput keyevent DPAD_DOWNinput keyevent BUTTON_Ainput keyevent SPACE- result: no screenshot-confirmed change
- logs from the same sessions include
Input event injection from pid ... failed
- shell keyguard dismissal:
wm dismiss-keyguard- result: no screenshot-confirmed change
What partially worked:
am force-stop com.oculus.guardian- result:
GuardianDialogActivitydisappears - screenshot after the force-stop is still black
SensorLockActivityremains on top
- result:
What still did not recover the black SensorLock state:
am force-stop com.oculus.os.vrlockscreenam start -a android.intent.action.MAIN -c android.intent.category.HOME -n com.oculus.vrshell/.HomeActivityam broadcast -a com.oculus.vrpowermanager.automation_disableam broadcast -a com.oculus.vrpowermanager.prox_openam broadcast -a com.oculus.vrpowermanager.prox_closeam broadcast -a com.oculus.vrpowermanager.automation_enable
Important automation surface discovered in com.oculus.os.vrlockscreen:
- exported receiver action:
oculus.lockscreen.action.UNLOCK- receiver:
com.oculus.os.vrlockscreen/.KeyguardAutomationReceiver
- shell result:
SecurityException: Permission Denial: not allowed to send broadcast
- implication:
- Meta does expose a lockscreen automation path, but it is permission-gated away from the ordinary adb shell user
Additional service surfaces discovered on-device:
sensorlock: [vros.os.sensorlock.ISensorLock]guardian: [internal.horizonos.os.guardian.IGuardianManagerService]LockscreenService: [oculus.internal.ILockscreen]vros_lockscreen: [dev.vros.internal.os.lockscreen.ILockscreenService]oculus.internal.power.IVrPowerManager/default
Current working conclusion:
- from rootless adb shell, we can:
- trigger this limbo
- prove which visual layer is active with screenshots
- dismiss the Guardian dialog layer by force-stopping Guardian
- from rootless adb shell, we cannot yet:
- reproduce the real physical power-button recovery path
- accept
Continue without tracking - clear the remaining black
SensorLockActivity
Until a shell-allowed binder or privileged broadcast path is found, the physical headset power button still has a recovery capability that plain adb shell does not.
Metacam-verified return-to-Home probe
The screenshots and command outputs for this pass are under:
artifacts/verify/return-home-probe-20260328-161707/
New screenshot rule from this probe:
- for Quest shell / Guardian / Home-world debugging on this device, prefer
hzdb capture screenshot --method metacamover plainadb exec-out screencap -p - on the current Sussex public-companion path, the same
hzdbmetacam command produced stale repeated frames against the USB serial while the active Wi-Fi ADB selector returned fresh changing images, so the companion now prefers the live Wi-Fi endpoint for screenshot capture and falls back to USB only when Wi-Fi is unavailable - in this session, direct
screencapoften showed only the black background plus the performance HUD whilemetacamstill captured the realGuardianDialogActivitysurface - example command:
& $hzdb capture screenshot `
--device 3487C10H3M017Q `
--method metacam `
--output C:\path\to\shot.png
What the metacam-confirmed probe showed:
- restarting
vrshellcan re-surface the live Guardian UI even after the shell has fallen back to black-only captures - in this run, the recovered visible blocker was the second dialog:
- title:
Finding position in room - primary button:
Continue
- title:
uiautomator dumpstill exposed that dialog tree whilemetacamconfirmed it visually
What did not get back to visible Home without physical buttons once that second dialog was on screen:
am broadcast -a com.oculus.vrpowermanager.automation_disableam broadcast -a com.oculus.vrpowermanager.prox_closeam start -a android.intent.action.MAIN -c android.intent.category.HOMEam force-stop com.oculus.guardian- relaunching shell-owned surfaces after the Guardian force-stop:
com.oculus.vrshell/.HomeActivitycom.oculus.igvr/.IgvrHomeWorldCanvasActivitycom.oculus.explore/.ExploreActivity
- injected power-family keys from the live visible dialog:
input keyevent POWERinput keyevent SLEEPinput keyevent WAKEUP
- injected menu-family keys from the live visible dialog:
input keyevent FORWARDinput keyevent BUTTON_MODEinput keyevent GUIDE
- exact-button interaction attempts using the
uiautomatorbounds forContinue:input touchscreen tap 250 529input mouse tap 250 529input touchscreen motionevent DOWN/UP 250 529
- focus-driven button activation retries:
input keyevent TABinput gamepad keyevent BUTTON_Ainput dpad keyevent DPAD_CENTER
Additional shell-structure finding:
am task lock <TASK_ID>can pin Home underneath the blocker but does not clear the blocker itself- in this run, locking the current Home task moved the shell into
mLockTaskModeState=PINNEDwhile the visible metacam capture still showed the same GuardianContinuedialog - removing small shell overlay tasks such as
ToastsActivity,AnytimeUIActivity, andFocusPlaceholderActivityalso did not restore visible Home; in one repeat, stripping those layers simply let the Guardian dialog reappear visibly again
Current working conclusion from the metacam pass:
- we now have a reliable screenshot-backed way to tell whether the headset is
actually still on the Guardian blocker even when
adb screencapsuggests a featureless black frame - from a rootless shell on this build, we still do not have a command-only
path that reliably accepts the second Guardian
Continuedialog or restores visible Home the way the physical headset power button sometimes does
Returning To An Already-Running APK Without Relaunch
The screenshots and state captures for this pass are under:
artifacts/verify/sensorlock-return-probe-20260328-151714/
What was verified in that probe:
- baseline:
00-app-after-8s.pngand01-app-after-20s.pngshow the Sussex Unity app visibly running- the same captures show
com.Viscereality.LslTwin/com.unity3d.player.UnityPlayerGameActivityowning focus
- black shell / hidden-app control:
input keyevent HOME- screenshots
17-control-after-home-2s.png,18-control-after-home-10s.png, and19-control-after-home-22s.pngstay black for at least 22 seconds - the app task is still alive in recents as task
5997 HomeActivityownsmCurrentFocusSensorLockActivityis still present in the task stack behind that black state
- stricter SensorLock-focused black state:
input keyevent HOMEinput keyevent SLEEPinput keyevent WAKEUP- screenshots
21-black-sensorlock-after-home-sleep-wakeup-2s.pngand22-black-sensorlock-after-home-sleep-wakeup-10s.pngstay black 21-black-sensorlock-after-home-sleep-wakeup-2s-activities.txtshows:ResumedActivity=...SensorLockActivitymCurrentFocus=...SensorLockActivitymTopFullscreenOpaqueWindowState=...SensorLockActivity
- the Sussex task
5997is still present in recents and still has its Unity activity record
- task-level return without relaunch:
am task lock 5997- shell output in
23-task-lock-from-sensorlock-command.txt:Activity manager is in lockTaskMode - screenshots
23-after-task-lock-from-sensorlock-2s.pngand24-after-task-lock-from-sensorlock-10s.pngshow the already-running Unity app back on screen 23-after-task-lock-from-sensorlock-2s-activities.txtshows:ResumedActivity=...UnityPlayerGameActivitymCurrentFocus=...UnityPlayerGameActivitymTopFullscreenOpaqueWindowState=...UnityPlayerGameActivity
- no fresh
monkey,am start, or other app-launch intent was issued for that recovery - after inspection,
am task lock stopcleanly exits lock-task mode again
Working implication:
- if the Sussex APK is already running and you know its live task ID, shell can
recover the visible app from a screenshot-confirmed black Home / SensorLock
state without relaunching the APK by bringing that task to the front with
am task lock <TASK_ID> - the task ID is session-specific; on this device it was
5997during the captured run and was discoverable viadumpsys activity recents
Lock Task Versus Physical Meta Button
The relevant screenshots and trace outputs for this pass are under:
artifacts/verify/locktask-menu-probe-20260328-153109/artifacts/verify/physical-meta-locktask-trace-20260328-154003/
What am task lock <TASK_ID> meant in the tested Sussex run:
- shell output:
Activity manager is in lockTaskMode
- visible effect:
- the Sussex Unity app stayed visibly in front
- shell state:
11-after-correct-lock-activities.txtshowsUnityPlayerGameActivityas:ResumedActivitymCurrentFocusmTopFullscreenOpaqueWindowState
HomeActivityandSensorLockActivitystill remain elsewhere in the stack, so lock-task mode does not mean those shell activities disappear
What happened to menu-like injected keys while lock-task mode was active:
- tested keys:
FORWARDBUTTON_MODEGUIDEHOME
- screenshot-confirmed behavior:
FORWARD,BUTTON_MODE, andGUIDEleft the Unity scene visibly in front in:12-after-correct-lock-forward.png12-after-correct-lock-button_mode.png12-after-correct-lock-guide.png
- the matching activity dumps still show Unity owning focus and the top fullscreen opaque window
HOMEstill escaped the lock-task foreground and produced a black shell view in12-after-correct-lock-home.png
What happened to the physical controller Meta / menu button while lock-task mode was active:
- baseline locked screenshot before the physical presses:
physical-meta-locktask-trace-20260328-154003/00-ready-baseline.png
- screenshot after the physical presses:
physical-meta-locktask-trace-20260328-154003/01-after-physical-meta.png
- screenshot after exiting lock-task mode:
physical-meta-locktask-trace-20260328-154003/02-after-lock-stop.png
- visual result:
- the headset stayed on the Unity scene during the physical Meta-button presses
- low-level input result:
getevent-event3.txtstill recorded repeatedEV_KEY KEY_FORWARD DOWN/UPpairs- this confirms the controller Meta / menu button was still being pressed and observed at the low-level input layer
- shell-state result:
state-poll.txtkept showing Unity as:ResumedActivitymCurrentFocusmTopFullscreenOpaqueWindowState
- no screenshot-confirmed transition to Home, Guardian, or a visible system menu occurred during the locked run
Current conclusion:
- lock-task mode on this HorizonOS build is not a perfect global kiosk mode,
because injected
HOMEcan still break out - but it is the first screenshot-confirmed state found so far where the
physical controller Meta / menu button can still generate its normal
KEY_FORWARDsignal without producing a visible foreground change in the Sussex Unity app - that makes lock-task mode a strong candidate for "keep the app visually in
front and neutralize accidental Meta-menu presses" as long as the remaining
HOMEescape path is acceptable or separately handled
Unlocked comparison after leaving lock-task mode:
- dedicated trace:
physical-meta-unlocked-after-lock-trace-20260328-160100/
- transition setup:
01-locktask-state-after.txtshowsmLockTaskModeState=PINNED02-locktask-state-after-stop.txtshowsmLockTaskModeState=NONE02-after-lock-stop.pngstill shows the Sussex Unity app visibly in front
- physical input result after leaving lock-task mode:
04-getevent-event3.txtrecorded repeatedEV_KEY KEY_FORWARD DOWN/UPpairs during the user presses
- screenshot-confirmed visible result:
03-ready-unlocked.pngis the unlocked baseline05-after-physical-meta.pngstill shows the Unity scene- the user reported hearing the usual Meta-menu sound cue but never seeing a visual menu popup or close animation
- shell / log result:
05-after-physical-meta-activities.txtcapturedFocusPlaceholderActivityas:ResumedActivitymCurrentFocusmTopFullscreenOpaqueWindowState
04-logcat.txtshows transientFocusPlaceholderActivitylaunches at15:53:09and15:53:30- the same log also shows
FocusPlaceholderActivity: Finishing activityand focus returning toUnityPlayerGameActivity
Interpretation of the unlocked comparison:
- outside lock-task mode, the physical controller Meta / menu button can still
trigger shell-level
FocusPlaceholderActivityfocus churn and the familiar sound cue - however, in this run it still did not produce a screenshot-confirmed visible system menu
- this means "Meta button had an effect" and "user actually saw the system menu" are not the same event on this build
- compared with the locked run, the unlocked run is weaker as a protection state because the shell was allowed to steal focus transiently even though the headset never showed a visible menu in the captured screenshots
- for experiment control, treat lock-task mode as the stronger "visually
neutralize accidental Meta presses" state, and treat transient
FocusPlaceholderActivitybursts plusKEY_FORWARDevents as the current best signal that the Meta route was attempted without a visible shell escape
Confirmed Sussex Kiosk Workflow
The relevant confirmation artifacts for the final successful Sussex operator run are under:
artifacts/verify/recreate-locktask-state-20260328-173806/artifacts/verify/return-home-remaining-20260328-174319/
This is the first full operator-confirmed kiosk workflow from this machine that met both practical experiment requirements:
- while Sussex was active, the controller Meta / menu button had no visible effect and the operator reported no menu sound
- after the exit sequence, the headset returned cleanly to Meta Home and the controller Meta / menu button worked normally again
Confirmed kiosk entry shape:
- launch the Sussex APK normally first
- resolve the current Sussex task id from
dumpsys activity recents - pin that live task with
am task lock <TASK_ID> - treat kiosk entry as confirmed only when the activity dump shows all of:
mLockTaskModeState=PINNEDResumedActivity=...UnityPlayerGameActivitymCurrentFocus=...UnityPlayerGameActivitymTopFullscreenOpaqueWindowState=...UnityPlayerGameActivity
Confirmed kiosk exit stack:
adb -s $device shell am broadcast -a com.oculus.vrpowermanager.automation_disable
adb -s $device shell am task lock stop
adb -s $device shell am start -W -a android.intent.action.MAIN -c android.intent.category.HOME -n com.oculus.vrshell/.HomeActivity
adb -s $device shell am force-stop com.Viscereality.LslTwin
Confirmed kiosk exit success shape:
mLockTaskModeState=NONEmCurrentFocus=...HomeActivity- the Sussex package is force-stopped
- the top opaque shell window may be
HomeActivitydirectly or a Home-side UX layer such ascom.oculus.systemux/.VirtualObjectsActivity - operator-visible result: Meta Home is usable again and the controller Meta / menu button regains its normal behavior
Implementation note for ViscerealityCompanion:
- the Sussex study shell now treats this as its default runtime behavior
March 28 refinement:
- the exit path above remains the strongest confirmed operator workflow on this machine
- fresh kiosk entry from a clean Home baseline is not yet equally confirmed
- later unattended reruns showed two distinct entry failure modes:
artifacts/verify/manual-kiosk-cycle-20260328-193427/- normal launch from clean Home resumed Unity visibly
am task lock <TASK_ID>did not stick- the activity dump stayed at
mLockTaskModeState=NONE - operator result matched that weaker shell state: app visible, controller Meta/menu still usable, clean Home exit
artifacts/verify/proxclose-kiosk-entry-test-20260328-194029/- a timed
hzdb device proximity --disable --duration-ms 28800000did make task lock stick again - but the pinned foreground became
GuardianDialogActivity/SensorLockActivity, not the Unity activity - shell state after launch+lock:
mLockTaskModeState=PINNEDResumedActivity=...GuardianDialogActivitymCurrentFocus=...GuardianDialogActivitymTopFullscreenOpaqueWindowState=...GuardianDialogActivity
- a timed
artifacts/verify/home-auto-prox-launch-lock-20260328-194209/HOME -> HomeActivity -> automation_disable -> timed prox_close -> immediate launch -> task lockreproduced the same pinned-Guardian result
- working implication:
- the earlier operator-confirmed success should currently be treated as "confirmed kiosk exit from a good pinned runtime state", not as proof that clean Home -> launch -> pin is already deterministic
- timed virtual-close /
prox_closelooks like a real prerequisite for the pin to stick on this HorizonOS build - but unattended proximity-hold entry can pin the Guardian / SensorLock shell instead of the Unity task
- the next required validation is a coordinated wearer-on entry run:
keep the headset on, establish the timed proximity hold, launch
immediately, pin immediately, and confirm whether the Unity app rather than
Guardian owns:
ResumedActivitymCurrentFocusmTopFullscreenOpaqueWindowState
- the launch/stop toggle is expected to map to "launch in kiosk mode" and "exit kiosk mode" instead of a plain foreground app launch/force-stop cycle
- do not replace this with build-time Unity changes or with a different shell-side ritual unless a later trace proves the new path is more robust
- the Sussex shell now also exposes a
Capture Quest Screenshotbench-tool action that callshzdb capture screenshot --method metacamand keeps a visual-confirmation warning active until the operator captures a fresh image
April 2026 HorizonOS Update
The March 2026 kiosk findings below are now historical only. After the Meta / HorizonOS update observed on this machine by April 12, 2026:
- Sussex can still launch and exit correctly while the controller Meta / menu button remains active
- kiosk must no longer be described as a reliable Meta / menu-button lockout
- launch should be blocked while the headset reports asleep, with the operator
told
Wake the headset to enable launching - launch should also stay blocked while Guardian or other Meta visual blockers are active
- the public GUI no longer exposes remote headset wake/sleep controls for Sussex; manual headset wake/sleep is the only supported operator path for now
- screenshot and visible-scene confirmation remain the real success criteria
March 28 GUI Validation Update
Fresh validation against the patched public companion app confirmed two separate things:
- the new GUI behavior is correct: kiosk launch no longer hides the wake/launch
trace behind a silent pre-block, and the Sussex shell now reports the full
recovery attempt plus a
Quest visual confirmation pendingwarning when shell-level confirmation is insufficient - the remaining failure is still device-side: on the current HorizonOS build,
the headset can stay black or Guardian-blocked even after the companion
reports
HomeActivity,FocusPlaceholderActivity, or a recent Sussex task id
The GUI-triggered screenshot evidence from the patched app:
quest_20260328_181253.pngshowed a black visible scene after a kiosk-launch attempt even though the shell had already walked throughWAKEUP -> HOME -> HomeActivity -> automation_disable -> prox_close -> launch -> task-lock probe- the Sussex shell text correctly preserved the full trace and ended in
Launch completed ..., but kiosk mode was not fully confirmed - this means the current public app behavior should be treated as: shell-confirmed and screenshot-verified, not shell-confirmed-only
March 28 Confirmed GUI Kiosk Cycle
After tightening the Sussex task-id parser and narrowing the wake classifier so
that a normal Home-owned FocusPlaceholderActivity companion layer is no
longer misclassified as wake limbo, the public Sussex shell completed a clean
operator-confirmed GUI cycle from visible Home:
- starting point: usable Meta Home, with the controller Meta/menu button working normally
- GUI action:
Launch Kiosk Runtime - visible result: the Sussex APK opened cleanly
- controller result while the APK was active:
- the controller Meta/menu button was visually disabled
- no menu sound played
- the app stayed in front
- GUI action:
Exit Kiosk Runtime - visible result: the headset returned cleanly to Meta Home
- controller result after exit:
- the controller Meta/menu button worked normally again
This is the currently confirmed operator workflow for the Sussex shell:
- start from visible Meta Home
- launch through the Sussex GUI runtime toggle
- treat the app as in kiosk mode only after operator-side visual confirmation
- exit through the same GUI toggle
- confirm return to Home visually
Important remaining limitation:
- if the headset is already stuck in the black
SensorLockActivitylimbo family before the operator starts the Sussex GUI cycle, rootless shell still does not reliably recover it on this machine - in those already-blocked cases, a physical headset power-button press can still be required to restore visible Home before the GUI workflow should be retried
March 28 Physical Home Recovery Trace
Artifact bundle:
artifacts/verify/physical-home-recovery-trace-20260328-192059/
Operator-confirmed sequence:
- wearing the headset was not enough to recover a usable Home view
- pressing the physical headset power button once brought the headset into the main menu
- after that, the controller Meta/menu button could open and close the menu normally again
Trace-confirmed downstream path:
getevent.txtcaptured a real/dev/input/event1 KEY_POWER DOWN/UPlogcat-full.txtrecordedSideFpsEventHandler: notifyPowerPressed- immediately after that:
SensorLockActivityclosed- tracking resumed
GuardianDialogActivitywas explicitly dismissed and closed- Home-side overlays came back, including
com.oculus.systemux/com.oculus.panelapp.virtualobjects.VirtualObjectsActivity
state-poll.txtthen showed:ResumedActivity=HomeActivitymCurrentFocus=HomeActivitymTopFullscreenOpaqueWindowState=FocusPlaceholderActivitymLockTaskModeState=NONE
Practical interpretation:
- the physical headset power button is now confirmed as a privileged recovery path for the blocked tracking / Guardian-to-Home transition on this build
- the controller Meta/menu button regained its normal Home/menu behavior only after that recovery had already happened
- this trace still does not show the controller Meta/menu button as a normal
low-level
/dev/inputevent source in the same way as the physical power button
March 28 Post-Power Passthrough Follow-Up
Artifact bundle:
artifacts/verify/sussex-study-mode-live/
Observed follow-up after the later live Sussex kiosk run on 2026-03-28:
- after GUI-driven kiosk exit, the headset was still stuck in the black
SensorLockActivityfamily until a physical headset power-button press - after that press, shell state improved to:
ResumedActivity=HomeActivitymCurrentFocus=HomeActivity- initially
mTopFullscreenOpaqueWindowState=FocusPlaceholderActivity - later
mTopFullscreenOpaqueWindowState=ControlBarActivity
- however the matching metacam captures still showed passthrough-only room
imagery instead of a visible Meta menu:
debug-after-user-power-press.pngdebug-after-controlbar-launch.pngdebug-after-button-mode.pngdebug-after-guide.pngdebug-after-physical-controller-button.png
Extra attempts that still did not surface a visible menu in metacam:
- explicitly relaunching
com.oculus.vrshell/com.oculus.panelapp.controlbar.ControlBarActivity - injected menu-style keys from the recovered Home-side shell:
input keyevent BUTTON_MODEinput keyevent GUIDE
- pressing the physical controller Meta/menu button
Updated interpretation:
- do not treat
HomeActivity,FocusPlaceholderActivity, or evenControlBarActivityownership alone as proof that the operator is back at a visible Meta menu - on this machine, screenshot confirmation is still mandatory after kiosk exit even when the shell-side activity stack looks "home-like"
- the current build can still degrade into a passthrough limbo where Home-side activities exist, but no screenshot-confirmed Meta menu is visible
- one confirmed contributor to the failing unattended harness run was that the harness had been arming the 8h proximity hold before launch even though its helper name implied the opposite
- that meant the failing harness pass was not actually reproducing the earlier "headset on face, normal wear sensor" operator workflow that had exited cleanly
April 8 Off-Face Exit Failure
Artifact bundle:
artifacts/verify/headset-recovery-20260408-073525/
Observed during a later off-face Sussex cleanup attempt on 2026-04-08:
- shell-side
dumpsys activitystill looked home-like withHomeActivityplusFocusPlaceholderActivity dumpsys vrpowermanagersimultaneously reportedState: STANDBYwithVirtual proximity state: DISABLEDuiautomator dumpconfirmed the visible blocker was again the GuardianFinding position in roomdialog withContinue without tracking- after
automation_disable -> prox_close, the Guardian layer dismissed, but the deeper blocker becamecom.oculus.os.vrlockscreenwith the text:Press the power button to enable cameras and microphones - even after renewed physical power-button attempts, the session still did not produce a dependable screenshot-confirmed Meta Home recovery
Current proximity-state rule on this headset build:
Virtual proximity state: CLOSEmeans the directprox_closeoverride is actively holding the virtual wear sensor closed.Virtual proximity state: DISABLEDmeansautomation_disablerestored normal wear-sensor behavior.
Updated practical rule:
- do not use Sussex kiosk exit or
viscereality study stop sussex-universityas default off-face cleanup - if Sussex must be exited, prefer a user-worn manual quit with screenshot confirmation afterward
- for functionality tests, quitting the Sussex APK is not required
March 29 Double-Power Escape Hatch
Artifact bundle:
artifacts/verify/limbo-home-recovery-dead-end-20260329/
Observed dead-end:
- shell-side recovery returned
HomeActivityandFocusPlaceholderActivityagain, but the matching metacam captures still stayed passthrough-only:recovery-home-proof-20260329-1.pngrecovery-home-proof-20260329-2.png
- after
adb reboot, the headset came back tocom.oculus.guardian/.GuardianDialogActivity - from that fresh post-reboot state, running
automation_disable -> prox_close -> HomeActivityagain produced a black metacam frame instead of visible Home:recovery-home-proof-20260329-3.png
Confirmed escape:
- pressing the physical headset power button twice restored a real Home-side scene
recovery-home-proof-20260329-4-after-double-power.pngshows visible Meta Home / store content again- shell-side state after that recovery improved to a usable Home-side surface
with:
com.oculus.systemux/com.oculus.panelapp.virtualobjects.VirtualObjectsActivitycom.oculus.vrshell/com.oculus.panelapp.controlbar.ControlBarActivitycom.oculus.vrshell/.HomeActivity
Updated interpretation:
- from this specific March 29 dead-end, shell-only recovery was exhausted
- neither shell-owned
HomeActivitynor a fulladb rebootwas enough by itself - a double physical headset power-button press is now a confirmed stronger escape hatch than a single press for this passthrough/black Home-limbo family on the current build
Recommended Companion Behavior
For ViscerealityCompanion, prefer this recovery order when the wake path is
blocked by Guardian / ClearActivity / FocusPlaceholder:
- if the study APK is already known to be running, resolve its current task ID
from
dumpsys activity recentsand tryam task lock <TASK_ID>first - if that does not recover visibility, try
automation_disable - try
prox_close - proceed with launch immediately
- only fall back to raw power-key recovery if the task-lock and automation/prox sequence make no progress
Do not treat raw KEYCODE_POWER as the primary recovery for this specific
blocker.
Open Follow-Up Work
- Find whether a shell-allowed path exists to explicitly accept
GUARDIAN_TRACKING_LOST_CONTINUEinstead of merely dismissing / outracing it. - Test whether the current
FORWARD/GUIDEHome-routing behavior is stable while the Sussex Unity runtime is actually foregrounded, not just from shell baselines. - Determine whether
FocusPlaceholderActivitybursts are detectable quickly enough in the companion to count as a robust accidental-Meta-button signal even when no visible menu appears. - Check whether a follow-up action after
FORWARD/GUIDEcan dismissGuardianDialogActivityorSensorLockActivity, or whether those overlays are fully permission-gated. - Check whether
am task lock <TASK_ID>has any unwanted side effects for the operator flow, and whether a cleaner non-locking task-to-front command exists on this HorizonOS build. - If possible, add a future control experiment in the same session:
- one known headset hardware button press such as volume up
- one right-controller Meta / menu press
- compare which layers see each event
- Check whether the verification harness should keep polling foreground state after launch so it can observe late success more robustly.
- If a future session finds a reliable replacement for the race-based launch sequence, update this file and the central Quest pattern note together.