@mentra/bluetooth-sdk 3.3.0-dev.421 → 3.3.0-dev.423

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +0 -7
  2. package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +3 -17
  3. package/android/src/main/java/com/mentra/bluetoothsdk/GeneratedReleaseMetadata.kt +5 -5
  4. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +0 -11
  5. package/android/src/main/java/com/mentra/bluetoothsdk/services/Foreground.kt +0 -21
  6. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.kt +31 -213
  7. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SerializedGattCallback.kt +2 -13
  8. package/android/src/main/java/com/mentra/bluetoothsdk/utils/MessageChunkReassembler.java +6 -41
  9. package/build/BluetoothSdk.types.d.ts +0 -10
  10. package/build/BluetoothSdk.types.d.ts.map +1 -1
  11. package/build/BluetoothSdk.types.js.map +1 -1
  12. package/build/_private/BluetoothSdkModule.d.ts +0 -9
  13. package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
  14. package/build/_private/BluetoothSdkModule.js +0 -2
  15. package/build/_private/BluetoothSdkModule.js.map +1 -1
  16. package/build/generated/releaseMetadata.js +5 -5
  17. package/build/generated/releaseMetadata.js.map +1 -1
  18. package/ios/Source/BluetoothSdkDefaults.swift +1 -1
  19. package/ios/Source/GeneratedReleaseMetadata.swift +5 -5
  20. package/package.json +2 -2
  21. package/src/BluetoothSdk.types.ts +0 -10
  22. package/src/_private/BluetoothSdkModule.ts +0 -13
  23. package/src/generated/releaseMetadata.ts +5 -5
  24. package/android/src/main/java/com/mentra/bluetoothsdk/debug/BleEvidenceLog.kt +0 -454
  25. package/android/src/test/java/com/mentra/bluetoothsdk/WifiScanEvidenceTest.kt +0 -117
  26. package/android/src/test/java/com/mentra/bluetoothsdk/debug/BleEvidenceLogTest.kt +0 -282
  27. package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/MentraLiveEvidenceTest.kt +0 -603
  28. package/android/src/test/java/com/mentra/bluetoothsdk/utils/MessageChunkReassemblerOriginTest.kt +0 -51
  29. package/build/_private/diagnosticRender.d.ts +0 -12
  30. package/build/_private/diagnosticRender.d.ts.map +0 -1
  31. package/build/_private/diagnosticRender.js +0 -27
  32. package/build/_private/diagnosticRender.js.map +0 -1
  33. package/src/_private/diagnosticRender.ts +0 -35
@@ -43,7 +43,6 @@ import com.mentra.bluetoothsdk.DeviceStore
43
43
  import com.mentra.bluetoothsdk.ObservableStore
44
44
  import com.mentra.bluetoothsdk.incomingGlassesMessageAckId
45
45
  import com.mentra.bluetoothsdk.wifiResponseEnvelopeIsValid
46
- import com.mentra.bluetoothsdk.debug.BleEvidenceLog
47
46
  import com.mentra.bluetoothsdk.debug.BleTraceLogger
48
47
  import com.mentra.bluetoothsdk.utils.BlePhotoUploadService
49
48
  import com.mentra.bluetoothsdk.utils.ConnTypes
@@ -598,12 +597,6 @@ class MentraLive : SGCManager() {
598
597
  private val gattLifecycleHandler = Handler(Looper.getMainLooper())
599
598
  // Owned exclusively by the main looper, including callback validation and mutation.
600
599
  private var gattEpoch = 0L
601
- // Provenance of the current accepted connection and wire epoch. Copied into each accepted
602
- // notification before decoding; never re-read to label an already received message.
603
- private var evidenceOrigin: BleEvidenceLog.Origin? = null
604
- // Evidence boundary of the current bleSessionGeneration (seq of its ble_generation record).
605
- @Volatile private var evidenceWriteGeneration = 0L
606
- @Volatile private var evidenceWriteGenerationValue = -1L
607
600
  private var connectionRequestEpoch = 0L
608
601
  @Volatile private var gattTeardownToken: Long? = null
609
602
  @Volatile private var gattTeardownTimeoutRunnable: Runnable? = null
@@ -657,9 +650,7 @@ class MentraLive : SGCManager() {
657
650
  val commandType: String,
658
651
  val requestId: String?,
659
652
  val appId: String?,
660
- val messageId: Long?,
661
- // Wi-Fi scan request id; every native write of that command is recorded against it.
662
- val scanId: String? = null
653
+ val messageId: Long?
663
654
  )
664
655
 
665
656
  private data class BleWriteTrace(
@@ -675,11 +666,7 @@ class MentraLive : SGCManager() {
675
666
  val packedBytes: Int,
676
667
  val wakeup: Boolean,
677
668
  val chunked: Boolean,
678
- val queuedAtMs: Long,
679
- val scanId: String? = null,
680
- // Provenance captured when the write is queued; later outcomes never re-read state.
681
- val evidenceConnection: Long = 0L,
682
- val evidenceWriteGeneration: Long? = null
669
+ val queuedAtMs: Long
683
670
  )
684
671
 
685
672
  private data class QueuedBleWrite(
@@ -1037,7 +1024,6 @@ class MentraLive : SGCManager() {
1037
1024
  this.type = DeviceTypes.LIVE
1038
1025
  this.hasMic = true
1039
1026
  this.context = Bridge.getContext()
1040
- recordWriteGeneration(bleSessionGeneration.get())
1041
1027
 
1042
1028
  // Initialize bluetooth adapter
1043
1029
  val bluetoothManager =
@@ -1193,9 +1179,6 @@ class MentraLive : SGCManager() {
1193
1179
  if (state == ConnTypes.DISCONNECTED) {
1194
1180
  DeviceStore.apply("glasses", "hotspotOtaVersion", 0)
1195
1181
  }
1196
- if (state == ConnTypes.DISCONNECTED) {
1197
- closeEvidenceConnection("disconnected")
1198
- }
1199
1182
  val isEqual = state == connectionState
1200
1183
  if (isEqual) {
1201
1184
  if (state == ConnTypes.DISCONNECTED) {
@@ -1709,7 +1692,6 @@ class MentraLive : SGCManager() {
1709
1692
  }
1710
1693
  val token = gattTeardownBarrier.beginTeardown()
1711
1694
  gattTeardownToken = token
1712
- closeEvidenceConnection("teardown")
1713
1695
  // Teardown owns session invalidation, including the timeout/replacement paths that do
1714
1696
  // not receive the normal remote-disconnect cleanup callback.
1715
1697
  cancelMtuWatchdog()
@@ -1783,34 +1765,6 @@ class MentraLive : SGCManager() {
1783
1765
  mtuSetupGate.cancel()
1784
1766
  }
1785
1767
 
1786
- private fun closeEvidenceConnection(reason: String) {
1787
- evidenceOrigin?.let { BleEvidenceLog.connectionClosed(it.connection, reason) }
1788
- evidenceOrigin = null
1789
- }
1790
-
1791
- /** RX/TX carry every JSON/K900 message; audio and file characteristics are never evidence. */
1792
- private fun isCommandCharacteristic(characteristic: BluetoothGattCharacteristic): Boolean =
1793
- characteristic.uuid == RX_CHAR_UUID || characteristic.uuid == TX_CHAR_UUID
1794
-
1795
- private fun recordScanSend(trace: BleWriteTrace?, outcome: String) {
1796
- val scanId = trace?.scanId ?: return
1797
- BleEvidenceLog.scanSend(
1798
- scanId,
1799
- outcome,
1800
- trace.chunkIndex ?: 0,
1801
- trace.totalChunks ?: 1,
1802
- trace.evidenceConnection,
1803
- trace.evidenceWriteGeneration
1804
- )
1805
- }
1806
-
1807
- /** Records the send-queue generation boundary that queued writes will reference. */
1808
- private fun recordWriteGeneration(generation: Long) {
1809
- evidenceWriteGeneration =
1810
- BleEvidenceLog.bleGeneration(evidenceOrigin?.connection ?: 0L, generation)
1811
- evidenceWriteGenerationValue = generation
1812
- }
1813
-
1814
1768
  /**
1815
1769
  * Reject callbacks delivered by a GATT object from an earlier connection attempt. Without
1816
1770
  * this identity check, a late disconnect can tear down the active connection and clear its
@@ -2322,15 +2276,6 @@ class MentraLive : SGCManager() {
2322
2276
  // This lets connectGatt return and install the identity before validation.
2323
2277
  gattLifecycleHandler.post { work() }
2324
2278
  }, { gatt -> epoch == gattEpoch && isCurrentGattCallback(gatt) }) {
2325
- override fun onStaleNotification(
2326
- gatt: BluetoothGatt,
2327
- characteristic: BluetoothGattCharacteristic
2328
- ) {
2329
- if (isCommandCharacteristic(characteristic)) {
2330
- BleEvidenceLog.receiveRejected("stale_gatt", null)
2331
- }
2332
- }
2333
-
2334
2279
  override fun handleConnectionStateChange(
2335
2280
  gatt: BluetoothGatt,
2336
2281
  status: Int,
@@ -2368,8 +2313,6 @@ class MentraLive : SGCManager() {
2368
2313
  isConnecting = false
2369
2314
  isConnected = true
2370
2315
  connectedDevice = gatt.device
2371
- closeEvidenceConnection("replaced")
2372
- evidenceOrigin = BleEvidenceLog.connectionAccepted(gatt.device.address)
2373
2316
  classicAudioConnectionTracker.setTarget(gatt.device.address)
2374
2317
  refreshClassicAudioConnectionState(gatt.device)
2375
2318
  emitConnectedPendingDeviceForPairingScan()
@@ -2441,7 +2384,6 @@ class MentraLive : SGCManager() {
2441
2384
  )
2442
2385
  isConnected = false
2443
2386
  isConnecting = false
2444
- closeEvidenceConnection("remote_disconnect")
2445
2387
 
2446
2388
  connectedDevice = null
2447
2389
  glassesReady = false // Reset ready state on disconnect
@@ -2758,10 +2700,6 @@ class MentraLive : SGCManager() {
2758
2700
  else null
2759
2701
  inFlightBleWriteTrace = null
2760
2702
  inFlightBleWriteStartedAtMs = 0L
2761
- recordScanSend(
2762
- trace,
2763
- if (status == BluetoothGatt.GATT_SUCCESS) "write_ok" else "write_failed"
2764
- )
2765
2703
 
2766
2704
  if (status == BluetoothGatt.GATT_SUCCESS) {
2767
2705
  // Bridge.log("LIVE: Characteristic write successful");
@@ -2821,13 +2759,8 @@ class MentraLive : SGCManager() {
2821
2759
  data: ByteArray
2822
2760
  ) {
2823
2761
  if (!isActiveGattCallback(gatt)) {
2824
- if (isCommandCharacteristic(characteristic)) {
2825
- BleEvidenceLog.receiveRejected("teardown", null)
2826
- }
2827
2762
  return
2828
2763
  }
2829
- // Immutable origin of this notification, captured before any decoding.
2830
- val origin = evidenceOrigin
2831
2764
 
2832
2765
  // Get thread ID for tracking thread issues
2833
2766
  val threadId = Thread.currentThread().id
@@ -2874,7 +2807,7 @@ class MentraLive : SGCManager() {
2874
2807
  }
2875
2808
 
2876
2809
  // Process command/JSON data on RX/TX characteristics
2877
- processReceivedData(data, data.size, origin)
2810
+ processReceivedData(data, data.size)
2878
2811
  }
2879
2812
 
2880
2813
  override fun handleDescriptorWrite(
@@ -3290,7 +3223,6 @@ class MentraLive : SGCManager() {
3290
3223
  var queuedWrite = sendQueue.poll()
3291
3224
  val currentGeneration = bleSessionGeneration.get()
3292
3225
  while (queuedWrite != null && queuedWrite.sessionGeneration != currentGeneration) {
3293
- recordScanSend(queuedWrite.trace, "stale_dropped")
3294
3226
  Bridge.log(
3295
3227
  "LIVE: Dropping stale queued write from BLE session " +
3296
3228
  queuedWrite.sessionGeneration +
@@ -3326,15 +3258,10 @@ class MentraLive : SGCManager() {
3326
3258
 
3327
3259
  /** Send data through BLE */
3328
3260
  private fun sendDataInternal(write: QueuedBleWrite?) {
3329
- if (write == null) {
3330
- return
3331
- }
3332
- if (!isConnected) {
3333
- recordScanSend(write.trace, "gatt_refused")
3261
+ if (!isConnected || write == null) {
3334
3262
  return
3335
3263
  }
3336
3264
  if (write.sessionGeneration != bleSessionGeneration.get()) {
3337
- recordScanSend(write.trace, "stale_dropped")
3338
3265
  Bridge.log("LIVE: Dropping stale BLE write before GATT transmission")
3339
3266
  return
3340
3267
  }
@@ -3342,7 +3269,6 @@ class MentraLive : SGCManager() {
3342
3269
  val gatt = bluetoothGatt
3343
3270
  val characteristic = txCharacteristic
3344
3271
  if (gatt == null || characteristic == null) {
3345
- recordScanSend(write.trace, "gatt_refused")
3346
3272
  return
3347
3273
  }
3348
3274
 
@@ -3352,7 +3278,6 @@ class MentraLive : SGCManager() {
3352
3278
  inFlightBleWriteTrace = write.trace
3353
3279
  inFlightBleWriteStartedAtMs = writeStartedAtMs
3354
3280
  val writeAccepted = gatt.writeCharacteristic(characteristic)
3355
- recordScanSend(write.trace, if (writeAccepted) "gatt_accepted" else "gatt_refused")
3356
3281
  logBleWriteTrace(
3357
3282
  "write_call",
3358
3283
  write.trace,
@@ -3369,7 +3294,6 @@ class MentraLive : SGCManager() {
3369
3294
  inFlightBleWriteStartedAtMs = 0L
3370
3295
  }
3371
3296
  } catch (e: Exception) {
3372
- recordScanSend(write.trace, "gatt_refused")
3373
3297
  Log.e(TAG, "Error sending data via BLE", e)
3374
3298
  logBleWriteTrace(
3375
3299
  "write_exception",
@@ -3396,16 +3320,9 @@ class MentraLive : SGCManager() {
3396
3320
  queuedAtMs =
3397
3321
  if (trace.queuedAtMs > 0L)
3398
3322
  trace.queuedAtMs
3399
- else System.currentTimeMillis(),
3400
- evidenceConnection = evidenceOrigin?.connection ?: 0L,
3401
- // A write carrying an older generation is already stale: no boundary.
3402
- evidenceWriteGeneration =
3403
- if (sessionGeneration == evidenceWriteGenerationValue)
3404
- evidenceWriteGeneration
3405
- else null
3323
+ else System.currentTimeMillis()
3406
3324
  )
3407
3325
  sendQueue.add(QueuedBleWrite(data, queuedTrace, sessionGeneration))
3408
- recordScanSend(queuedTrace, "queued")
3409
3326
  logBleChunkTrace(
3410
3327
  "queued",
3411
3328
  queuedTrace,
@@ -3626,7 +3543,6 @@ class MentraLive : SGCManager() {
3626
3543
  */
3627
3544
  private fun resetPendingAckState() {
3628
3545
  val nextGeneration = bleSessionGeneration.incrementAndGet()
3629
- recordWriteGeneration(nextGeneration)
3630
3546
  val pendingCount = pendingMessages.size
3631
3547
  pendingMessages.clear()
3632
3548
  if (pendingCount > 0) {
@@ -3955,7 +3871,7 @@ class MentraLive : SGCManager() {
3955
3871
  }
3956
3872
 
3957
3873
  /** Process data received from the glasses */
3958
- private fun processReceivedData(data: ByteArray?, size: Int, origin: BleEvidenceLog.Origin?) {
3874
+ private fun processReceivedData(data: ByteArray?, size: Int) {
3959
3875
  // Bridge.log("LIVE: Processing received data: " + bytesToHex(data));
3960
3876
 
3961
3877
  // Check if we have enough data
@@ -4022,7 +3938,7 @@ class MentraLive : SGCManager() {
4022
3938
  }
4023
3939
 
4024
3940
  if (cmdType == K900ProtocolUtils.CMD_TYPE_BINARY_MSG) {
4025
- processBinaryWireFrame(data, origin)
3941
+ processBinaryWireFrame(data)
4026
3942
  return
4027
3943
  }
4028
3944
 
@@ -4040,7 +3956,7 @@ class MentraLive : SGCManager() {
4040
3956
  Log.w(TAG, "Thread-$threadId: Rejected unsupported compact wire form")
4041
3957
  return
4042
3958
  }
4043
- processJsonMessage(expanded, origin)
3959
+ processJsonMessage(expanded)
4044
3960
  } else {
4045
3961
  Log.w(TAG, "Thread-" + threadId + ": Failed to parse K900 protocol data")
4046
3962
  }
@@ -4065,7 +3981,7 @@ class MentraLive : SGCManager() {
4065
3981
  val jsonStr = String(data, 0, size, StandardCharsets.UTF_8)
4066
3982
  if (jsonStr.startsWith("{") && jsonStr.endsWith("}")) {
4067
3983
  val json = JSONObject(jsonStr)
4068
- processJsonMessage(json, origin)
3984
+ processJsonMessage(json)
4069
3985
  } else {
4070
3986
  Log.w(TAG, "Received data that starts with '{' but is not valid JSON")
4071
3987
  }
@@ -4088,7 +4004,7 @@ class MentraLive : SGCManager() {
4088
4004
  }
4089
4005
 
4090
4006
  /** Process a JSON message */
4091
- private fun processJsonMessage(json: JSONObject, origin: BleEvidenceLog.Origin?) {
4007
+ private fun processJsonMessage(json: JSONObject) {
4092
4008
  // Demoted from INFO (Bridge.log) to DEBUG: per-type handlers below already log
4093
4009
  // the messages that matter, and full payloads can leak PII (wifi SSID, bt_mac,
4094
4010
  // OTA URLs) into the persisted file logger when they arrive every ~50ms during OTA.
@@ -4100,7 +4016,7 @@ class MentraLive : SGCManager() {
4100
4016
  GlassesLinkDiagnostics.recordInbound()
4101
4017
 
4102
4018
  if (MessageChunker.isChunkedMessage(json)) {
4103
- processChunkedJsonMessage(json, origin)
4019
+ processChunkedJsonMessage(json)
4104
4020
  return
4105
4021
  }
4106
4022
 
@@ -4119,7 +4035,7 @@ class MentraLive : SGCManager() {
4119
4035
 
4120
4036
  // Check if this is a K900 command format (has "C" field instead of "type")
4121
4037
  if (json.has("C")) {
4122
- processK900JsonMessage(json, origin)
4038
+ processK900JsonMessage(json)
4123
4039
  return
4124
4040
  }
4125
4041
 
@@ -4215,15 +4131,7 @@ class MentraLive : SGCManager() {
4215
4131
  // that reads "charging" for most of a discharging pack's range. Charging
4216
4132
  // state comes exclusively from the PMU charg bit in the sr_hrt heartbeat.
4217
4133
  val percent = json.optInt("percent", batteryLevel)
4218
- // A missing or invalid field keeps the previous fallback display value, but only a
4219
- // measured percentage carries provenance.
4220
- updateBatteryStatus(
4221
- percent,
4222
- isCharging,
4223
- measuredPercent(json, "percent")?.let {
4224
- BatteryProvenance("battery_status", origin, null)
4225
- }
4226
- )
4134
+ updateBatteryStatus(percent, isCharging)
4227
4135
  }
4228
4136
  "stream_controller_probe" -> {
4229
4137
  val values = mapOf("protocolVersion" to json.opt("protocolVersion"),
@@ -4398,8 +4306,6 @@ class MentraLive : SGCManager() {
4398
4306
  "wifi_scan_result" -> {
4399
4307
  // Process WiFi scan results
4400
4308
  val networks: MutableList<Map<String, Any>> = ArrayList()
4401
- // Evidence only: false when the network list is missing or not fully parsed.
4402
- var networksParsed = false
4403
4309
 
4404
4310
  if (json.has("networks_neo")) {
4405
4311
  try {
@@ -4417,7 +4323,6 @@ class MentraLive : SGCManager() {
4417
4323
  }
4418
4324
  networks.add(networkMap)
4419
4325
  }
4420
- networksParsed = true
4421
4326
 
4422
4327
  Bridge.log(
4423
4328
  "Received enhanced WiFi scan results: " +
@@ -4432,9 +4337,7 @@ class MentraLive : SGCManager() {
4432
4337
  val scanComplete =
4433
4338
  json.optBoolean("scan_complete", json.optBoolean("scanComplete", false))
4434
4339
  val scanId = json.optString("scanId", "").ifEmpty { null }
4435
- val eventId =
4436
- BleEvidenceLog.scanChunk(origin, scanId, networks, scanComplete, networksParsed)
4437
- Bridge.updateWifiScanResults(networks, scanComplete, scanId, eventId)
4340
+ Bridge.updateWifiScanResults(networks, scanComplete, scanId)
4438
4341
  }
4439
4342
  "token_status" -> {
4440
4343
  // Process coreToken acknowledgment
@@ -5308,7 +5211,7 @@ class MentraLive : SGCManager() {
5308
5211
  return value
5309
5212
  }
5310
5213
 
5311
- private fun processChunkedJsonMessage(json: JSONObject, origin: BleEvidenceLog.Origin?) {
5214
+ private fun processChunkedJsonMessage(json: JSONObject) {
5312
5215
  try {
5313
5216
  val chunkInfo = MessageChunker.getChunkInfo(json)
5314
5217
  if (chunkInfo == null) {
@@ -5326,24 +5229,19 @@ class MentraLive : SGCManager() {
5326
5229
  return
5327
5230
  }
5328
5231
 
5329
- val mixedBefore = incomingChunkReassembler.mixedOriginDrops
5330
5232
  val reassembled =
5331
5233
  incomingChunkReassembler.addChunk(
5332
5234
  chunkInfo.chunkId,
5333
5235
  chunkInfo.chunkIndex,
5334
5236
  chunkInfo.totalChunks,
5335
- chunkInfo.data,
5336
- origin
5237
+ chunkInfo.data
5337
5238
  )
5338
- if (incomingChunkReassembler.mixedOriginDrops != mixedBefore) {
5339
- BleEvidenceLog.receiveRejected("mixed_origin", origin)
5340
- }
5341
5239
  if (reassembled == null) {
5342
5240
  return
5343
5241
  }
5344
5242
 
5345
5243
  val reassembledJson = JSONObject(reassembled)
5346
- processJsonMessage(reassembledJson, origin)
5244
+ processJsonMessage(reassembledJson)
5347
5245
  } catch (e: Exception) {
5348
5246
  Log.e(TAG, "Error processing chunked JSON message", e)
5349
5247
  }
@@ -5551,7 +5449,7 @@ class MentraLive : SGCManager() {
5551
5449
  }
5552
5450
  }
5553
5451
 
5554
- private fun processK900JsonMessage(json: JSONObject, origin: BleEvidenceLog.Origin?) {
5452
+ private fun processK900JsonMessage(json: JSONObject) {
5555
5453
  val command = json.optString("C", "")
5556
5454
  // Bridge.log("LIVE: Processing K900 command: " + command);
5557
5455
 
@@ -5595,17 +5493,7 @@ class MentraLive : SGCManager() {
5595
5493
  }
5596
5494
  val charg = bodyObj.optInt("charg", -1)
5597
5495
  if (batteryPercentage != -1 && charg != -1)
5598
- updateBatteryStatus(
5599
- batteryPercentage,
5600
- charg == 1,
5601
- measuredPercent(bodyObj, "pt")?.let {
5602
- BatteryProvenance(
5603
- "k900_sr_hrt",
5604
- origin,
5605
- pmuChargingBit(bodyObj)
5606
- )
5607
- }
5608
- )
5496
+ updateBatteryStatus(batteryPercentage, charg == 1)
5609
5497
  }
5610
5498
  } catch (e: Exception) {
5611
5499
  Log.e(TAG, "Error parsing sr_hrt response", e)
@@ -5636,13 +5524,7 @@ class MentraLive : SGCManager() {
5636
5524
  // charging from voltage (>4.0V) reads "not charging" for most of a
5637
5525
  // genuinely-charging pack's range. Charging state comes exclusively
5638
5526
  // from the PMU charg bit in the sr_hrt heartbeat.
5639
- updateBatteryStatus(
5640
- batteryPercentage,
5641
- isCharging,
5642
- measuredPercent(bodyObj, "pt")?.let {
5643
- BatteryProvenance("k900_sr_batv", origin, null)
5644
- }
5645
- )
5527
+ updateBatteryStatus(batteryPercentage, isCharging)
5646
5528
  }
5647
5529
  } catch (e: Exception) {
5648
5530
  Log.e(TAG, "Error parsing sr_batv response", e)
@@ -5879,7 +5761,7 @@ class MentraLive : SGCManager() {
5879
5761
  Log.d(TAG, "🔓 Unwrapping and processing through standard message handler")
5880
5762
 
5881
5763
  // Process through the standard message handler
5882
- processJsonMessage(innerJson, origin)
5764
+ processJsonMessage(innerJson)
5883
5765
  return // Exit after processing
5884
5766
  }
5885
5767
  } catch (e: JSONException) {
@@ -5993,44 +5875,7 @@ class MentraLive : SGCManager() {
5993
5875
  * Update battery status and notify listeners Matches iOS MentraLive.swift updateBatteryStatus
5994
5876
  * pattern
5995
5877
  */
5996
- /**
5997
- * [source] names the decoded message. [pmuCharging] is set only when the PMU charg bit of
5998
- * this very message supplied [isCharging]; percent-only sources re-pass the inherited value.
5999
- */
6000
- /**
6001
- * Provenance of a battery update decoded from one notification. [pmuCharging] is set only
6002
- * when the PMU charg bit of that message supplied the charging state.
6003
- */
6004
- private data class BatteryProvenance(
6005
- val source: String,
6006
- val origin: BleEvidenceLog.Origin?,
6007
- val pmuCharging: Boolean?
6008
- )
6009
-
6010
- /**
6011
- * The sr_hrt PMU charging bit, only when `charg` is a number exactly equal to 0 or 1. The
6012
- * original value is compared without truncation, so 0.5 or 1.5 is not a bit.
6013
- */
6014
- private fun pmuChargingBit(body: JSONObject): Boolean? =
6015
- when ((body.opt("charg") as? Number)?.toDouble()) {
6016
- 1.0 -> true
6017
- 0.0 -> false
6018
- else -> null
6019
- }
6020
-
6021
- /** The field's value when it is an actual integer percentage, else null (missing/invalid). */
6022
- private fun measuredPercent(obj: JSONObject, key: String): Int? {
6023
- val value = obj.opt(key) as? Number ?: return null
6024
- val asDouble = value.toDouble()
6025
- if (asDouble != Math.floor(asDouble) || asDouble < 0 || asDouble > 100) return null
6026
- return asDouble.toInt()
6027
- }
6028
-
6029
- /**
6030
- * [provenance] is null for fallback or cached values: they are still displayed as before but
6031
- * carry no event id, so they can never count as a fresh measurement.
6032
- */
6033
- private fun updateBatteryStatus(level: Int, isCharging: Boolean, provenance: BatteryProvenance?) {
5878
+ private fun updateBatteryStatus(level: Int, isCharging: Boolean) {
6034
5879
  // Keep the field in sync: percent-only messages (battery_status/sr_batv) re-pass
6035
5880
  // it as the last-known charging state, so a stale field would clobber the value
6036
5881
  // the sr_hrt PMU charg bit established.
@@ -6041,11 +5886,7 @@ class MentraLive : SGCManager() {
6041
5886
  DeviceStore.apply("glasses", "charging", isCharging)
6042
5887
 
6043
5888
  if (level >= 0) {
6044
- val eventId =
6045
- provenance?.let {
6046
- BleEvidenceLog.battery(it.origin, level, it.source, it.pmuCharging)
6047
- }
6048
- Bridge.sendBatteryStatus(level, isCharging, eventId)
5889
+ Bridge.sendBatteryStatus(level, isCharging)
6049
5890
  }
6050
5891
  }
6051
5892
 
@@ -6222,17 +6063,7 @@ class MentraLive : SGCManager() {
6222
6063
  if (!scanId.isNullOrEmpty()) {
6223
6064
  json.put("scanId", scanId)
6224
6065
  }
6225
- val queued = sendJson(json, true, true)
6226
- if (!queued && !scanId.isNullOrEmpty()) {
6227
- BleEvidenceLog.scanSend(
6228
- scanId,
6229
- "not_queued",
6230
- 0,
6231
- 0,
6232
- evidenceOrigin?.connection ?: 0L,
6233
- evidenceWriteGeneration
6234
- )
6235
- }
6066
+ sendJson(json, true)
6236
6067
  Bridge.log("LIVE: Sending WiFi scan request to glasses")
6237
6068
  } catch (e: JSONException) {
6238
6069
  Log.e(TAG, "Error creating WiFi scan request", e)
@@ -9178,9 +9009,6 @@ class MentraLive : SGCManager() {
9178
9009
  * the per-session handshake attempt cap) instead of trusting stale v2-active state.
9179
9010
  */
9180
9011
  private fun resetWireNegotiationState() {
9181
- evidenceOrigin?.let {
9182
- evidenceOrigin = it.copy(wire = BleEvidenceLog.wireReset(it.connection))
9183
- }
9184
9012
  incomingChunkReassembler.clear()
9185
9013
  peerWireProtocolVersion = 0
9186
9014
  useBinaryWireProtocol = false
@@ -9416,7 +9244,7 @@ class MentraLive : SGCManager() {
9416
9244
  activateBinaryWireV2Session("LIVE: Peer confirmed BLE wire protocol v2")
9417
9245
  }
9418
9246
 
9419
- private fun processBinaryWireFrame(data: ByteArray, origin: BleEvidenceLog.Origin?) {
9247
+ private fun processBinaryWireFrame(data: ByteArray) {
9420
9248
  val info = K900ProtocolUtils.extractBinaryFragmentInfo(data) ?: run {
9421
9249
  Log.w(TAG, "Failed to parse binary wire frame")
9422
9250
  return
@@ -9433,19 +9261,14 @@ class MentraLive : SGCManager() {
9433
9261
  )
9434
9262
  }
9435
9263
 
9436
- val mixedBefore = incomingChunkReassembler.mixedOriginDrops
9437
9264
  val reassembled =
9438
9265
  incomingChunkReassembler.addBinaryFragment(
9439
9266
  info.msgId,
9440
9267
  info.fragIdx,
9441
9268
  info.fragCount,
9442
- info.payload,
9443
- origin
9269
+ info.payload
9444
9270
  )
9445
- if (incomingChunkReassembler.mixedOriginDrops != mixedBefore) {
9446
- BleEvidenceLog.receiveRejected("mixed_origin", origin)
9447
- }
9448
- if (reassembled == null) return
9271
+ ?: return
9449
9272
 
9450
9273
  try {
9451
9274
  val jsonStr = String(reassembled, StandardCharsets.UTF_8)
@@ -9461,7 +9284,7 @@ class MentraLive : SGCManager() {
9461
9284
  BleWireProtocol.PROTOCOL_V2,
9462
9285
  "glasses_to_phone"
9463
9286
  )
9464
- processJsonMessage(json, origin)
9287
+ processJsonMessage(json)
9465
9288
  } catch (e: JSONException) {
9466
9289
  Log.e(TAG, "Failed to parse reassembled binary wire JSON", e)
9467
9290
  }
@@ -9806,15 +9629,11 @@ class MentraLive : SGCManager() {
9806
9629
  private fun parseOutgoingBleCommandTraceInfo(payload: String): OutgoingBleCommandTraceInfo {
9807
9630
  return try {
9808
9631
  val obj = JSONObject(payload)
9809
- val commandType = obj.optString("type", "unknown")
9810
9632
  OutgoingBleCommandTraceInfo(
9811
- commandType = commandType,
9633
+ commandType = obj.optString("type", "unknown"),
9812
9634
  requestId = optNonBlankString(obj, "requestId"),
9813
9635
  appId = optNonBlankString(obj, "appId"),
9814
- messageId = if (obj.has("mId")) obj.optLong("mId") else null,
9815
- scanId =
9816
- if (commandType == "request_wifi_scan") optNonBlankString(obj, "scanId")
9817
- else null
9636
+ messageId = if (obj.has("mId")) obj.optLong("mId") else null
9818
9637
  )
9819
9638
  } catch (_: Exception) {
9820
9639
  OutgoingBleCommandTraceInfo(
@@ -9849,8 +9668,7 @@ class MentraLive : SGCManager() {
9849
9668
  packedBytes = packedBytes,
9850
9669
  wakeup = wakeup,
9851
9670
  chunked = chunked,
9852
- queuedAtMs = System.currentTimeMillis(),
9853
- scanId = commandInfo.scanId
9671
+ queuedAtMs = System.currentTimeMillis()
9854
9672
  )
9855
9673
  }
9856
9674
 
@@ -18,17 +18,6 @@ internal abstract class SerializedGattCallback(
18
18
  enqueue { if (isCurrent(gatt)) work() }
19
19
  }
20
20
 
21
- private fun dispatchNotification(
22
- gatt: BluetoothGatt,
23
- characteristic: BluetoothGattCharacteristic,
24
- work: () -> Unit,
25
- ) {
26
- enqueue { if (isCurrent(gatt)) work() else onStaleNotification(gatt, characteristic) }
27
- }
28
-
29
- /** A notification from a GATT that is no longer current was dropped on the owner queue. */
30
- protected open fun onStaleNotification(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {}
31
-
32
21
  final override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) =
33
22
  dispatch(gatt) { handleConnectionStateChange(gatt, status, newState) }
34
23
 
@@ -57,14 +46,14 @@ internal abstract class SerializedGattCallback(
57
46
  gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic,
58
47
  ) {
59
48
  val value = characteristic.value?.copyOf() ?: return
60
- dispatchNotification(gatt, characteristic) { handleCharacteristicChanged(gatt, characteristic, value) }
49
+ dispatch(gatt) { handleCharacteristicChanged(gatt, characteristic, value) }
61
50
  }
62
51
 
63
52
  final override fun onCharacteristicChanged(
64
53
  gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray,
65
54
  ) {
66
55
  val snapshot = value.copyOf()
67
- dispatchNotification(gatt, characteristic) { handleCharacteristicChanged(gatt, characteristic, snapshot) }
56
+ dispatch(gatt) { handleCharacteristicChanged(gatt, characteristic, snapshot) }
68
57
  }
69
58
 
70
59
  final override fun onDescriptorWrite(