G2 native notification service - #3688
Conversation
There was a problem hiding this comment.
2 issues found across 14 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="mobile/modules/bluetooth-sdk/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt">
<violation number="1" location="mobile/modules/bluetooth-sdk/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt:1915">
P1: Native notification file frames can be dropped during ordinary display bursts because they bypass the existing serialized BLE writer; route them through the same writer and fail/retry on rejected queue admission instead of waiting for an ACK that cannot arrive.</violation>
<violation number="2" location="mobile/modules/bluetooth-sdk/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt:4160">
P2: The notification centre is marked armed optimistically: `notificationCentreArmed = true` is set immediately after `pinControlPlane(...)` with no confirmation that either control-plane write (CTRL, then WHITELIST_CTRL) was actually transmitted or accepted by the glasses. `pinControlPlane` uses `sendToGlasses`, which is fire-and-forget (WRITE_TYPE_NO_RESPONSE, no ack wait), and the surrounding comments explicitly note that back-to-back writes on this service have been seen to drop. If the CTRL write is dropped or rejected (`CMD_COMM_RSP` with `NOT_SUPPORT`), the centre stays off, yet the code then proceeds to push file notifications that will go nowhere or be silently rejected — and because `notificationCentreArmed` is only reset on disconnect, the incorrect armed state persists for the rest of the connection, so every subsequent notification skips the (never-successful) arming. Consider verifying delivery/acceptance (e.g., correlate the `CMD_COMM_RSP`/ack for these commands before arming, or at minimum re-try arming on the next push when a push reports a timeout/rejection).</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| } | ||
| for ((index, packet) in packets.withIndex()) { | ||
| if (index > 0) delay(BLE_PACKET_GAP_MS) | ||
| writeTo(rightFileWriteChar, rightGatt, packet, "RIGHT") |
There was a problem hiding this comment.
P1: Native notification file frames can be dropped during ordinary display bursts because they bypass the existing serialized BLE writer; route them through the same writer and fail/retry on rejected queue admission instead of waiting for an ACK that cannot arrive.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At mobile/modules/bluetooth-sdk/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt, line 1915:
<comment>Native notification file frames can be dropped during ordinary display bursts because they bypass the existing serialized BLE writer; route them through the same writer and fail/retry on rejected queue admission instead of waiting for an ACK that cannot arrive.</comment>
<file context>
@@ -1672,6 +1864,152 @@ class G2 : SGCManager() {
+ }
+ for ((index, packet) in packets.withIndex()) {
+ if (index > 0) delay(BLE_PACKET_GAP_MS)
+ writeTo(rightFileWriteChar, rightGatt, packet, "RIGHT")
+ }
+ }
</file context>
| // every notification absent from the stored whitelist. We push no whitelist: | ||
| // per-app filtering already happens phone-side in the notification listener. | ||
| pinControlPlane(notifEnable = 1, whitelistDisable = 1) | ||
| notificationCentreArmed = true |
There was a problem hiding this comment.
P2: The notification centre is marked armed optimistically: notificationCentreArmed = true is set immediately after pinControlPlane(...) with no confirmation that either control-plane write (CTRL, then WHITELIST_CTRL) was actually transmitted or accepted by the glasses. pinControlPlane uses sendToGlasses, which is fire-and-forget (WRITE_TYPE_NO_RESPONSE, no ack wait), and the surrounding comments explicitly note that back-to-back writes on this service have been seen to drop. If the CTRL write is dropped or rejected (CMD_COMM_RSP with NOT_SUPPORT), the centre stays off, yet the code then proceeds to push file notifications that will go nowhere or be silently rejected — and because notificationCentreArmed is only reset on disconnect, the incorrect armed state persists for the rest of the connection, so every subsequent notification skips the (never-successful) arming. Consider verifying delivery/acceptance (e.g., correlate the CMD_COMM_RSP/ack for these commands before arming, or at minimum re-try arming on the next push when a push reports a timeout/rejection).
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At mobile/modules/bluetooth-sdk/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt, line 4160:
<comment>The notification centre is marked armed optimistically: `notificationCentreArmed = true` is set immediately after `pinControlPlane(...)` with no confirmation that either control-plane write (CTRL, then WHITELIST_CTRL) was actually transmitted or accepted by the glasses. `pinControlPlane` uses `sendToGlasses`, which is fire-and-forget (WRITE_TYPE_NO_RESPONSE, no ack wait), and the surrounding comments explicitly note that back-to-back writes on this service have been seen to drop. If the CTRL write is dropped or rejected (`CMD_COMM_RSP` with `NOT_SUPPORT`), the centre stays off, yet the code then proceeds to push file notifications that will go nowhere or be silently rejected — and because `notificationCentreArmed` is only reset on disconnect, the incorrect armed state persists for the rest of the connection, so every subsequent notification skips the (never-successful) arming. Consider verifying delivery/acceptance (e.g., correlate the `CMD_COMM_RSP`/ack for these commands before arming, or at minimum re-try arming on the next push when a push reports a timeout/rejection).</comment>
<file context>
@@ -3722,6 +4062,145 @@ class G2 : SGCManager() {
+ // every notification absent from the stored whitelist. We push no whitelist:
+ // per-app filtering already happens phone-side in the notification listener.
+ pinControlPlane(notifEnable = 1, whitelistDisable = 1)
+ notificationCentreArmed = true
+ Bridge.log("G2/NOTIF: centre armed (on-glass filtering disabled)")
+ }
</file context>
The phone id is "$packageName-${sbn.key}" — never numeric — so every push
minted a fresh synthetic msg_id and updates stacked as duplicate cards on
the glasses. Memoize phone id -> msg_id (LRU, 512 entries) so re-posts of
the same notification reuse their msg_id and replace the card in place.
Pin the real id shape in the bridge test fixture.
There was a problem hiding this comment.
All reported issues were addressed across 2 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
- Fail sendFile before arming the ack when no FILE WRITE characteristic is bound, instead of stalling the pump for the full 15s ack timeout. - Skip minted msg_ids still mapped to a live phone id, so the synthetic counter wrapping cannot make the glasses replace the wrong card. - Drop the notification-centre armed latch when the glasses refuse a CTRL/WHITELIST_CTRL pin, so the next push re-pins instead of pushing into a disabled centre. - Remove trailing whitespace in DEFAULT_CONNECT_OPTIONS.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit ce87eb9. Configure here.

Native notification service
The dashboard on the G2 is permanent (until firmware changes such things). Using Mentra without full support of the firmware screens is inconvenient. So, this usage of a new file service, for adding native notification support, is a first step. it appears that the news, and stocks screen use the same methodology.
The native notification indicator is very nice - it has a lesser depth and can overlay apps.
G2 firmware manages notifications at two layers
a) Control plane
b) Data layer
They define a file transfer service, in which one pushes notifications as JSON.
This PR enables an option in the super settings to use the native notification path.
It is parallel to the Notify route, and the distribution of notifications to miniapps.
Android only for now, because that is all I can test (and build) e2e myself.
Summary by cubic
Adds an Android-only path to mirror phone notifications into the G2’s native notification centre via the Even File Service, running alongside the existing Notify/miniapp flow. Adds a Super Settings toggle, engine bridge, BLE file-service support in
@mentra/bluetooth-sdk, and tests.New Features
G2NotificationBridgelistens to@mentra/crustphone_notificationand calls@mentra/bluetooth-sdksendPhoneNotification; reads the toggle per-notification; idempotent start/stop; unit tests added.msg_idmapping (LRU) so updates replace in place; never logs notification text.NativePhoneNotification; fixes@mentra/crustevent typings sophone_notification/phone_notification_dismissedare top-level events.Migration
Written for commit 9b904a1. Summary will update on new commits.
Note
Medium Risk
Large new G2 BLE file-transfer and notification protocol surface with timing/ack edge cases, but gated behind an off-by-default setting and additive to existing Notify behavior.
Overview
Adds an optional, Android-only path that pushes captured phone notifications into the G2’s on-glasses notification centre, in parallel with the existing Notify / miniapp flow (nothing is replaced when the toggle is off).
Engine & UI: New local setting
g2_native_notifications(off by default) and a Super Settings toggle when Android + G2.G2NotificationBridgesubscribes tophone_notificationfrom Crust and callsBluetoothSdk.sendPhoneNotification, gated per event on the toggle and device model.Crust.typesmovesphone_notificationevents to the top-level event map.Bluetooth SDK: Exposes
sendPhoneNotificationwithNativePhoneNotificationtypes; iOS stubs only (ANCS). On Android G2, implements the Even notification control plane (enable centre, disable on-glass whitelist filtering) and file service (dedicated FILE GATT chars, right-leg writes, START/DATA/RESULT_CHECK with paced writes, mutex-serialized transfers, strict 2-byte ack handling, custom CRC-32). Notification bodies go as JSON over the file channel with stablemsg_idmapping so updates replace cards; a bounded queue with DROP_OLDEST drains on a background coroutine.Reviewed by Cursor Bugbot for commit ce87eb9. Bugbot is set up for automated code reviews on this repo. Configure here.