@mentra/bluetooth-sdk 3.2.1-dev.314 → 3.3.0-beta.942
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.
- package/README.md +72 -10
- package/android/build.gradle +15 -0
- package/android/lc3Lib/build.gradle +6 -1
- package/android/src/debug/java/com/mentra/bluetoothsdk/sgcs/NimoDiagnostics.kt +368 -0
- package/android/src/debug/java/com/mentra/bluetoothsdk/sgcs/NimoFramebufferProtocol.kt +156 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkAnalytics.kt +63 -14
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkAnalyticsTracker.kt +55 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkDefaults.kt +4 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkInstallId.kt +23 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +8 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkUpdateCheck.kt +184 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkUsageTracker.kt +388 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +2 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +133 -26
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +37 -13
- package/android/src/main/java/com/mentra/bluetoothsdk/GeneratedChangelogCatalog.kt +3 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/GeneratedReleaseMetadata.kt +6 -6
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +227 -36
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraLocalNetworkModule.kt +24 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/NetworkDiagnostics.kt +25 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/VersionInfoResponseAccumulator.kt +13 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/debug/BleTraceLogger.kt +3 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/requests/DisplayRequests.kt +1 -1
- package/android/src/main/java/com/mentra/bluetoothsdk/services/Foreground.kt +33 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/services/G2ConnectionRecovery.kt +47 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ClassicAudioConnectionTracker.kt +4 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/ClassicAudioServiceDiscovery.kt +70 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.kt +2 -2
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1TextSanitizer.kt +11 -32
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +72 -21
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/LatinTextSanitizer.kt +39 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.kt +194 -40
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +12 -8
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Nimo.kt +314 -395
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/NimoCanvasCodec.kt +247 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/NimoCanvasCoordinator.kt +238 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/NimoCanvasEncoder.kt +58 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/NimoCanvasSession.kt +180 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +11 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SelectedDeviceAddress.kt +17 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SerializedGattCallback.kt +5 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/G2Text.kt +7 -0
- package/android/src/release/java/com/mentra/bluetoothsdk/sgcs/NimoDiagnostics.kt +20 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/BluetoothSdkAnalyticsTrackerTest.kt +58 -3
- package/android/src/test/java/com/mentra/bluetoothsdk/BluetoothSdkUpdateCheckTest.kt +124 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/BluetoothSdkUsageTrackerTest.kt +263 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/DashboardContentFormatterTest.kt +2 -2
- package/android/src/test/java/com/mentra/bluetoothsdk/DeviceIdentityPairingTest.kt +59 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/DeviceManagerSceneHandoffTest.kt +666 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/G2TextTest.kt +13 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/NetworkDiagnosticsTest.kt +55 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/ScanCompletionTest.kt +5 -1
- package/android/src/test/java/com/mentra/bluetoothsdk/VersionInfoResponseAccumulatorTest.kt +71 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/debug/BleTraceLoggerTest.kt +69 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/services/G2ConnectionRecoveryTest.kt +51 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/ClassicAudioServiceDiscoveryTest.kt +124 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/G1PairingTest.kt +105 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/G1TextSanitizerTest.kt +29 -4
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/G2DisplayThreadTest.kt +120 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/G2PairingTargetTest.kt +90 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/MentraLiveClassicDisconnectTest.kt +133 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/MentraLiveFileTransferSessionTest.kt +55 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/MentraLiveGattCallbackTest.kt +33 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/MentraLiveIncidentDispatchTest.kt +126 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/MentraLivePairingTargetTest.kt +65 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/MentraLiveWifiCredentialLogTest.kt +55 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/MentraNexPairingTest.kt +151 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/NimoBitmapAcceptanceTest.kt +105 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/NimoCanvasCodecTest.kt +233 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/NimoCanvasCoordinatorTest.kt +348 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/NimoCanvasSessionTest.kt +339 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/NimoMicReconnectTest.kt +93 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/NimoProtocolTest.kt +11 -0
- package/android/src/test/java/com/mentra/bluetoothsdk/sgcs/SelectedDeviceAddressTest.kt +27 -0
- package/android/src/testDebug/java/com/mentra/bluetoothsdk/sgcs/NimoDiagnosticsTest.kt +287 -0
- package/android/src/testDebug/java/com/mentra/bluetoothsdk/sgcs/NimoFramebufferProtocolTest.kt +127 -0
- package/android/src/testDebug/java/com/mentra/bluetoothsdk/sgcs/NimoLifecycleTest.kt +288 -0
- package/android/src/testDebug/java/com/mentra/bluetoothsdk/sgcs/NimoReadinessDriverTest.kt +205 -0
- package/build/BluetoothSdk.types.d.ts +16 -0
- package/build/BluetoothSdk.types.d.ts.map +1 -1
- package/build/BluetoothSdk.types.js +1 -0
- package/build/BluetoothSdk.types.js.map +1 -1
- package/build/_private/BluetoothSdkModule.d.ts +4 -0
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
- package/build/_private/BluetoothSdkModule.js +5 -1
- package/build/_private/BluetoothSdkModule.js.map +1 -1
- package/build/_private/MentraLocalNetworkModule.d.ts +23 -0
- package/build/_private/MentraLocalNetworkModule.d.ts.map +1 -1
- package/build/_private/MentraLocalNetworkModule.js.map +1 -1
- package/build/_private/nativeLogConsole.d.ts +19 -0
- package/build/_private/nativeLogConsole.d.ts.map +1 -1
- package/build/_private/nativeLogConsole.js +12 -0
- package/build/_private/nativeLogConsole.js.map +1 -1
- package/build/generated/changelogCatalog.d.ts +5 -2
- package/build/generated/changelogCatalog.d.ts.map +1 -1
- package/build/generated/changelogCatalog.js +6 -2
- package/build/generated/changelogCatalog.js.map +1 -1
- package/build/generated/releaseMetadata.js +6 -6
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -0
- package/build/index.js.map +1 -1
- package/build/ota-transport/index.d.ts +4 -0
- package/build/ota-transport/index.d.ts.map +1 -1
- package/build/ota-transport/index.js +2 -0
- package/build/ota-transport/index.js.map +1 -1
- package/ios/BluetoothSdkModule.swift +17 -4
- package/ios/MentraBluetoothSDK.podspec +3 -2
- package/ios/Source/BluetoothBackgroundMode.swift +32 -0
- package/ios/Source/BluetoothSdkDefaults.swift +7 -2
- package/ios/Source/Bridge.swift +15 -2
- package/ios/Source/DeviceManager.swift +102 -66
- package/ios/Source/DeviceStore.swift +26 -7
- package/ios/Source/GeneratedChangelogCatalog.swift +3 -2
- package/ios/Source/GeneratedReleaseMetadata.swift +6 -6
- package/ios/Source/MentraBluetoothSDK.swift +313 -54
- package/ios/Source/audio/GlassesMicWatchdog.swift +33 -0
- package/ios/Source/internal/BluetoothAvailability.swift +6 -0
- package/ios/Source/internal/BluetoothSdkAnalytics.swift +87 -13
- package/ios/Source/internal/BluetoothSdkAnalyticsTracker.swift +54 -1
- package/ios/Source/internal/BluetoothSdkInstallId.swift +21 -0
- package/ios/Source/internal/BluetoothSdkUpdateCheck.swift +223 -0
- package/ios/Source/internal/BluetoothSdkUsageTracker.swift +428 -0
- package/ios/Source/requests/DisplayRequests.swift +1 -1
- package/ios/Source/requests/VersionInfoResponseAccumulator.swift +11 -1
- package/ios/Source/sgcs/Ar99.swift +1 -2
- package/ios/Source/sgcs/G1.swift +68 -38
- package/ios/Source/sgcs/G1ConnectionTarget.swift +13 -0
- package/ios/Source/sgcs/G2.swift +261 -247
- package/ios/Source/sgcs/G2ConnectionSession.swift +101 -0
- package/ios/Source/sgcs/Mach1.swift +19 -7
- package/ios/Source/sgcs/MentraLive.swift +272 -197
- package/ios/Source/sgcs/MentraLiveFileTransferSession.swift +137 -0
- package/ios/Source/sgcs/MentraLivePairingAdvertisement.swift +47 -0
- package/ios/Source/sgcs/MentraNex.swift +8 -31
- package/ios/Source/sgcs/NexConnectionTarget.swift +15 -0
- package/ios/Source/sgcs/Nimo.swift +280 -408
- package/ios/Source/sgcs/NimoCanvasCodec.swift +272 -0
- package/ios/Source/sgcs/NimoCanvasCoordinator.swift +233 -0
- package/ios/Source/sgcs/NimoCanvasImage.swift +53 -0
- package/ios/Source/sgcs/NimoCanvasSession.swift +166 -0
- package/ios/Source/sgcs/NimoDiscovery.swift +49 -0
- package/ios/Source/sgcs/SGCManager.swift +25 -1
- package/ios/Source/sgcs/VuzixConnectionTarget.swift +15 -0
- package/ios/Source/status/DeviceStatus.swift +1 -0
- package/ios/Source/stt/SherpaOnnxTranscriber.swift +49 -189
- package/ios/Source/utils/AudioSessionMonitor.swift +44 -8
- package/ios/Source/utils/DemandDrivenAudioWorker.swift +150 -0
- package/ios/Source/utils/G1Text.swift +2 -24
- package/ios/Source/utils/G1TextSanitizer.swift +27 -0
- package/ios/Source/utils/G2Text.swift +11 -0
- package/ios/Source/utils/LatinTextSanitizer.swift +48 -0
- package/ios/Tests/BluetoothAudioRouteTests.swift +26 -0
- package/ios/Tests/BluetoothBackgroundModeTests.swift +32 -0
- package/ios/Tests/BluetoothSdkAnalyticsTrackerTests.swift +54 -3
- package/ios/Tests/BluetoothSdkUpdateCheckTests.swift +153 -0
- package/ios/Tests/BluetoothSdkUsageTrackerTests.swift +296 -0
- package/ios/Tests/DashboardContentFormatterTests.swift +2 -2
- package/ios/Tests/DashboardRenderingTests.swift +31 -0
- package/ios/Tests/DemandDrivenAudioWorkerTests.swift +157 -0
- package/ios/Tests/DeviceIdentityPairingTests.swift +49 -0
- package/ios/Tests/DeviceManagerReconnectTests.swift +362 -0
- package/ios/Tests/G1PairingTests.swift +162 -0
- package/ios/Tests/G1TextSanitizerTests.swift +32 -0
- package/ios/Tests/G2ConnectionSessionTests.swift +172 -0
- package/ios/Tests/G2TextTests.swift +10 -0
- package/ios/Tests/IncidentLogDispatchTests.swift +111 -0
- package/ios/Tests/MentraLiveFileTransferSessionTests.swift +47 -0
- package/ios/Tests/MentraLiveReconnectPolicyTests.swift +58 -0
- package/ios/Tests/MentraLiveSessionReadinessTests.swift +163 -0
- package/ios/Tests/NimoCanvasCodecTests.swift +217 -0
- package/ios/Tests/NimoCanvasSessionTests.swift +181 -0
- package/ios/Tests/NimoCanvasTransportTests.swift +186 -0
- package/ios/Tests/NimoDeviceCommandsTests.swift +16 -0
- package/ios/Tests/NimoDiscoveryTests.swift +79 -0
- package/ios/Tests/SGCConnectionTargetTests.swift +48 -0
- package/ios/Tests/VersionInfoCommandTests.swift +194 -0
- package/ios/Tests/VersionInfoResponseAccumulatorTests.swift +65 -0
- package/ios/Tests/VideoRecordingCommandTests.swift +81 -0
- package/ios/Tests/WifiCredentialLoggingTests.swift +42 -0
- package/package.json +2 -2
- package/plugin/build/withIos.d.ts +2 -0
- package/plugin/build/withIos.js +12 -1
- package/src/BluetoothSdk.types.ts +15 -0
- package/src/_private/BluetoothSdkModule.ts +10 -1
- package/src/_private/MentraLocalNetworkModule.ts +28 -0
- package/src/_private/nativeLogConsole.ts +19 -0
- package/src/generated/changelogCatalog.ts +6 -2
- package/src/generated/releaseMetadata.ts +6 -6
- package/src/index.ts +1 -0
- package/src/ota-transport/index.ts +10 -1
|
@@ -13,6 +13,7 @@ import androidx.core.content.ContextCompat
|
|
|
13
13
|
import com.mentra.bluetoothsdk.controllers.ControllerManager
|
|
14
14
|
import com.mentra.bluetoothsdk.controllers.R1
|
|
15
15
|
import com.mentra.bluetoothsdk.services.ForegroundService
|
|
16
|
+
import com.mentra.bluetoothsdk.services.G2ConnectionRecovery
|
|
16
17
|
import com.mentra.bluetoothsdk.services.PhoneMic
|
|
17
18
|
import com.mentra.bluetoothsdk.sgcs.Ar99
|
|
18
19
|
import com.mentra.bluetoothsdk.sgcs.G1
|
|
@@ -34,7 +35,10 @@ import com.mentra.bluetoothsdk.utils.MicTypes
|
|
|
34
35
|
import com.mentra.bluetoothsdk.utils.PhoneAudioMonitor
|
|
35
36
|
import com.mentra.lc3Lib.Lc3Cpp
|
|
36
37
|
import com.mentra.bluetoothsdk.stt.SherpaOnnxTranscriber
|
|
38
|
+
import kotlin.coroutines.resume
|
|
39
|
+
import kotlinx.coroutines.suspendCancellableCoroutine
|
|
37
40
|
import kotlinx.coroutines.CoroutineScope
|
|
41
|
+
import kotlinx.coroutines.withContext
|
|
38
42
|
import kotlinx.coroutines.Dispatchers
|
|
39
43
|
import kotlinx.coroutines.launch
|
|
40
44
|
import java.text.SimpleDateFormat
|
|
@@ -45,12 +49,15 @@ import java.util.concurrent.Executors
|
|
|
45
49
|
import java.util.concurrent.TimeUnit
|
|
46
50
|
import kotlin.jvm.JvmStatic
|
|
47
51
|
|
|
48
|
-
class DeviceManager {
|
|
52
|
+
class DeviceManager internal constructor(initializeHardware: Boolean) {
|
|
53
|
+
constructor() : this(initializeHardware = true)
|
|
49
54
|
companion object {
|
|
50
55
|
|
|
51
56
|
@Volatile
|
|
52
57
|
private var _instance: DeviceManager? = null
|
|
53
58
|
|
|
59
|
+
internal fun isInitialized(): Boolean = _instance != null
|
|
60
|
+
|
|
54
61
|
@JvmStatic
|
|
55
62
|
fun getInstance(): DeviceManager {
|
|
56
63
|
return _instance
|
|
@@ -59,6 +66,10 @@ class DeviceManager {
|
|
|
59
66
|
}
|
|
60
67
|
|
|
61
68
|
// MARK: - Unique (Android)
|
|
69
|
+
private val connectionRecovery = G2ConnectionRecovery(Bridge.getContext())
|
|
70
|
+
private val recoveryLock = Any()
|
|
71
|
+
private var recoverG2Connection = false
|
|
72
|
+
private var recoveryListenerId: String? = null
|
|
62
73
|
private var serviceStarted = false
|
|
63
74
|
private val mainHandler = Handler(Looper.getMainLooper())
|
|
64
75
|
private val executor: ExecutorService = Executors.newSingleThreadExecutor()
|
|
@@ -79,7 +90,8 @@ class DeviceManager {
|
|
|
79
90
|
// MARK: - End Unique
|
|
80
91
|
|
|
81
92
|
// MARK: - Properties
|
|
82
|
-
|
|
93
|
+
// Read by the welcome executor as well as device lifecycle handlers.
|
|
94
|
+
@Volatile var sgc: SGCManager? = null
|
|
83
95
|
var controller: ControllerManager? = null
|
|
84
96
|
|
|
85
97
|
// settings:
|
|
@@ -283,7 +295,22 @@ class DeviceManager {
|
|
|
283
295
|
|
|
284
296
|
init {
|
|
285
297
|
Bridge.log("DeviceManager: init()")
|
|
298
|
+
recoveryListenerId = DeviceStore.store.addListener { category, changes ->
|
|
299
|
+
if (category == "bluetooth" && changes.keys.any { it in G2ConnectionRecovery.keys }) {
|
|
300
|
+
synchronized(recoveryLock) {
|
|
301
|
+
if (recoverG2Connection) {
|
|
302
|
+
connectionRecovery.save(DeviceStore.store.getCategory("bluetooth"))
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
286
307
|
initializeViewStates()
|
|
308
|
+
if (initializeHardware) initializeHardwareServices()
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Keep host routing usable without starting audio codecs, receivers or services.
|
|
312
|
+
// The public constructor always initializes the hardware environment.
|
|
313
|
+
private fun initializeHardwareServices() {
|
|
287
314
|
startForegroundService()
|
|
288
315
|
// setupPermissionMonitoring()
|
|
289
316
|
setupBluetoothStateMonitoring()
|
|
@@ -1354,7 +1381,8 @@ class DeviceManager {
|
|
|
1354
1381
|
Bridge.log("MAN: Cleaning up previous sgc type: ${sgc?.type}")
|
|
1355
1382
|
sgc?.cleanup()
|
|
1356
1383
|
sgc = null
|
|
1357
|
-
|
|
1384
|
+
DeviceStore.apply("glasses", "micEnabled", false)
|
|
1385
|
+
resetConnectionReadyState()
|
|
1358
1386
|
}
|
|
1359
1387
|
|
|
1360
1388
|
if (sgc != null) {
|
|
@@ -1477,12 +1505,16 @@ class DeviceManager {
|
|
|
1477
1505
|
return
|
|
1478
1506
|
}
|
|
1479
1507
|
|
|
1508
|
+
// A new identity must never inherit the previous glasses' address when
|
|
1509
|
+
// pairing supplied only a name. Same-device reconnects retain their cache.
|
|
1510
|
+
val identityChanged = pendingDeviceName.isNotEmpty() &&
|
|
1511
|
+
(pendingDeviceName != deviceName || sgc?.type != defaultWearable)
|
|
1512
|
+
if (identityChanged || pendingDeviceAddress.isNotEmpty()) {
|
|
1513
|
+
deviceAddress = pendingDeviceAddress
|
|
1514
|
+
}
|
|
1480
1515
|
if (pendingDeviceName.isNotEmpty()) {
|
|
1481
1516
|
deviceName = pendingDeviceName
|
|
1482
1517
|
}
|
|
1483
|
-
if (pendingDeviceAddress.isNotEmpty()) {
|
|
1484
|
-
deviceAddress = pendingDeviceAddress
|
|
1485
|
-
}
|
|
1486
1518
|
clearPendingConnection()
|
|
1487
1519
|
|
|
1488
1520
|
val readyKey = "${sgc?.type}:${deviceName}"
|
|
@@ -1497,6 +1529,10 @@ class DeviceManager {
|
|
|
1497
1529
|
Bridge.log("MAN: handleDeviceReady() ${sgc?.type}")
|
|
1498
1530
|
resetMicHealth()
|
|
1499
1531
|
defaultWearable = sgc?.type ?: ""
|
|
1532
|
+
synchronized(recoveryLock) {
|
|
1533
|
+
recoverG2Connection = defaultWearable == DeviceTypes.G2
|
|
1534
|
+
connectionRecovery.save(DeviceStore.store.getCategory("bluetooth"))
|
|
1535
|
+
}
|
|
1500
1536
|
searching = false
|
|
1501
1537
|
|
|
1502
1538
|
syncSystemTimeOnceForConnection(readyKey)
|
|
@@ -1504,6 +1540,7 @@ class DeviceManager {
|
|
|
1504
1540
|
// re-apply display height/depth after reconnection
|
|
1505
1541
|
mainHandler.postDelayed(
|
|
1506
1542
|
{
|
|
1543
|
+
val device = sgc ?: return@postDelayed
|
|
1507
1544
|
val h =
|
|
1508
1545
|
(DeviceStore.store.get("bluetooth", "dashboard_height") as? Number)
|
|
1509
1546
|
?.toInt()
|
|
@@ -1512,19 +1549,24 @@ class DeviceManager {
|
|
|
1512
1549
|
(DeviceStore.store.get("bluetooth", "dashboard_depth") as? Number)
|
|
1513
1550
|
?.toInt()
|
|
1514
1551
|
?: dashboardDepth // canonical default (2), not 1
|
|
1515
|
-
val d = rawDepth.coerceIn(1, 4)
|
|
1516
|
-
|
|
1552
|
+
val d = if (device.type == DeviceTypes.NIMO) rawDepth.coerceIn(0, 10) else rawDepth.coerceIn(1, 4)
|
|
1553
|
+
device.setDashboardPosition(h, d)
|
|
1517
1554
|
},
|
|
1518
1555
|
2000
|
|
1519
1556
|
)
|
|
1520
1557
|
|
|
1521
|
-
//
|
|
1558
|
+
// A full-canvas device can opt out so this temporary message does not
|
|
1559
|
+
// erase the host's connected-edge scene replay.
|
|
1522
1560
|
if (shouldSendBootingMessage) {
|
|
1523
1561
|
shouldSendBootingMessage = false
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1562
|
+
val device = sgc
|
|
1563
|
+
if (device?.showConnectionConfirmation == true) {
|
|
1564
|
+
executor.execute {
|
|
1565
|
+
if (sgc !== device) return@execute
|
|
1566
|
+
device.sendTextWall("// MentraOS Connected")
|
|
1567
|
+
Thread.sleep(3000)
|
|
1568
|
+
if (sgc === device) device.clearDisplay()
|
|
1569
|
+
}
|
|
1528
1570
|
}
|
|
1529
1571
|
}
|
|
1530
1572
|
|
|
@@ -1537,10 +1579,11 @@ class DeviceManager {
|
|
|
1537
1579
|
handleMach1Ready() // Z100 uses same initialization as Mach1
|
|
1538
1580
|
}
|
|
1539
1581
|
|
|
1540
|
-
//
|
|
1541
|
-
//
|
|
1582
|
+
// Disconnect clears micEnabled but preserves the consumers' audio requests.
|
|
1583
|
+
// Recompute that derived flag before selecting a microphone; replaying an
|
|
1584
|
+
// unchanged should_send_* setting is deduplicated by DeviceStore.apply().
|
|
1542
1585
|
Bridge.log("MAN: Re-applying microphone settings after reconnection")
|
|
1543
|
-
|
|
1586
|
+
setMicState()
|
|
1544
1587
|
|
|
1545
1588
|
// send to the server our battery status:
|
|
1546
1589
|
Bridge.sendBatteryStatus(sgc?.batteryLevel ?: -1, false)
|
|
@@ -1580,7 +1623,11 @@ class DeviceManager {
|
|
|
1580
1623
|
mainHandler.postDelayed(sync, 3000)
|
|
1581
1624
|
}
|
|
1582
1625
|
|
|
1583
|
-
private fun
|
|
1626
|
+
private fun resetConnectionReadyState() {
|
|
1627
|
+
// Suppress duplicate readiness only within one connection, never across
|
|
1628
|
+
// a genuine disconnect/reconnect of the same glasses inside two seconds.
|
|
1629
|
+
lastReadyHandledKey = ""
|
|
1630
|
+
lastReadyHandledAtMs = 0L
|
|
1584
1631
|
pendingSystemTimeSync?.let { mainHandler.removeCallbacks(it) }
|
|
1585
1632
|
pendingSystemTimeSync = null
|
|
1586
1633
|
lastSystemTimeSyncConnectionKey = ""
|
|
@@ -1598,7 +1645,7 @@ class DeviceManager {
|
|
|
1598
1645
|
|
|
1599
1646
|
fun handleDeviceDisconnected() {
|
|
1600
1647
|
Bridge.log("MAN: Device disconnected")
|
|
1601
|
-
|
|
1648
|
+
resetConnectionReadyState()
|
|
1602
1649
|
resetMicHealth()
|
|
1603
1650
|
DeviceStore.apply("glasses", "headUp", false)
|
|
1604
1651
|
DeviceStore.apply(
|
|
@@ -1677,7 +1724,7 @@ class DeviceManager {
|
|
|
1677
1724
|
dashboardSceneCleanupPending = false
|
|
1678
1725
|
val elementIds = pendingDashboardSceneElementIds.toList()
|
|
1679
1726
|
pendingDashboardSceneElementIds.clear()
|
|
1680
|
-
if (elementIds.isNotEmpty()) {
|
|
1727
|
+
if (elementIds.isNotEmpty() && sgc?.sceneHandoffRequiresClear == true) {
|
|
1681
1728
|
sgc?.clearSceneElements(elementIds)
|
|
1682
1729
|
}
|
|
1683
1730
|
}
|
|
@@ -1714,7 +1761,7 @@ class DeviceManager {
|
|
|
1714
1761
|
sceneStates[stateIndex] = null
|
|
1715
1762
|
if (stateIndex == 1 && dashboardSceneCleanupPending) {
|
|
1716
1763
|
pendingDashboardSceneElementIds.addAll(prevFrame.elements.map { it.id })
|
|
1717
|
-
} else if (layoutType != "clear_view") {
|
|
1764
|
+
} else if (layoutType != "clear_view" && shouldClearSceneHandoff(stateIndex)) {
|
|
1718
1765
|
sgc?.clearSceneElements(prevFrame.elements.map { it.id })
|
|
1719
1766
|
}
|
|
1720
1767
|
}
|
|
@@ -1781,7 +1828,7 @@ class DeviceManager {
|
|
|
1781
1828
|
val prevLegacyType = viewStates[stateIndex].layoutType
|
|
1782
1829
|
val cleanupDeferred = stateIndex == 1 && dashboardSceneCleanupPending
|
|
1783
1830
|
if (
|
|
1784
|
-
!cleanupDeferred &&
|
|
1831
|
+
!cleanupDeferred && shouldClearSceneHandoff(stateIndex) &&
|
|
1785
1832
|
prevLegacyType.isNotEmpty() &&
|
|
1786
1833
|
prevLegacyType != "clear_view" &&
|
|
1787
1834
|
prevLegacyType != "scene"
|
|
@@ -1797,7 +1844,7 @@ class DeviceManager {
|
|
|
1797
1844
|
// blank.
|
|
1798
1845
|
if (stateIndex == 1 && dashboardSceneCleanupPending) {
|
|
1799
1846
|
pendingDashboardSceneElementIds.addAll(prevFrame.elements.map { it.id })
|
|
1800
|
-
} else {
|
|
1847
|
+
} else if (shouldClearSceneHandoff(stateIndex)) {
|
|
1801
1848
|
sgc?.clearSceneElements(prevFrame.elements.map { it.id })
|
|
1802
1849
|
}
|
|
1803
1850
|
frame = frame.copy(replay = true, elements = frame.elements.map { it.copy(change = "created") })
|
|
@@ -1816,6 +1863,15 @@ class DeviceManager {
|
|
|
1816
1863
|
}
|
|
1817
1864
|
}
|
|
1818
1865
|
|
|
1866
|
+
// Hidden-view updates only change their replay slot. They must never clear
|
|
1867
|
+
// the visible view, and full-frame adapters need no separate handoff sweep.
|
|
1868
|
+
private fun shouldClearSceneHandoff(stateIndex: Int): Boolean {
|
|
1869
|
+
val visibleIndex = if (headUp && contextualDashboard) 1 else 0
|
|
1870
|
+
val device = sgc ?: return false
|
|
1871
|
+
return stateIndex == visibleIndex && !screenDisabled && device.fullyBooted &&
|
|
1872
|
+
!device.type.contains(DeviceTypes.SIMULATED) && device.sceneHandoffRequiresClear
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1819
1875
|
/** Guarded scene dispatch — mirrors sendCurrentState's send conditions. */
|
|
1820
1876
|
private fun dispatchSceneFrame(stateIndex: Int, frame: SceneFrame) {
|
|
1821
1877
|
if (screenDisabled) return
|
|
@@ -1933,8 +1989,11 @@ class DeviceManager {
|
|
|
1933
1989
|
sgc?.requestSavedWifiNetworks(requestId, sid) ?: false
|
|
1934
1990
|
|
|
1935
1991
|
fun sendIncidentId(incidentId: String, apiBaseUrl: String? = null) {
|
|
1992
|
+
val driver = sgc ?: throw BluetoothSdkException(
|
|
1993
|
+
"glasses_not_connected", "Cannot request incident logs because glasses are not connected.",
|
|
1994
|
+
)
|
|
1936
1995
|
Bridge.log("MAN: Sending incidentId to glasses for log upload: $incidentId")
|
|
1937
|
-
|
|
1996
|
+
driver.sendIncidentId(incidentId, apiBaseUrl)
|
|
1938
1997
|
}
|
|
1939
1998
|
|
|
1940
1999
|
/** Push a notification into the glasses' own notification centre; rejects unsupported or disconnected devices. */
|
|
@@ -2389,10 +2448,20 @@ class DeviceManager {
|
|
|
2389
2448
|
}
|
|
2390
2449
|
|
|
2391
2450
|
fun disconnect(clearPendingIdentity: Boolean = true) {
|
|
2451
|
+
synchronized(recoveryLock) {
|
|
2452
|
+
recoverG2Connection = false
|
|
2453
|
+
connectionRecovery.clear()
|
|
2454
|
+
}
|
|
2392
2455
|
sgc?.clearDisplay()
|
|
2393
|
-
|
|
2456
|
+
// NIMO owns a background canvas encoder and this path discards its instance.
|
|
2457
|
+
// A link-level reconnect keeps the instance (and encoder) through disconnect().
|
|
2458
|
+
val device = sgc
|
|
2459
|
+
if (device is Nimo) device.cleanup() else device?.disconnect()
|
|
2394
2460
|
sgc = null // Clear the SGC reference after disconnect
|
|
2395
|
-
|
|
2461
|
+
// This cache belongs to the discarded connection. Keep consumer demand,
|
|
2462
|
+
// but require a new mic-enable command when replacement glasses are ready.
|
|
2463
|
+
DeviceStore.apply("glasses", "micEnabled", false)
|
|
2464
|
+
resetConnectionReadyState()
|
|
2396
2465
|
resetMicHealth()
|
|
2397
2466
|
searching = false
|
|
2398
2467
|
micEnabled = false
|
|
@@ -2439,7 +2508,42 @@ class DeviceManager {
|
|
|
2439
2508
|
controller = null
|
|
2440
2509
|
}
|
|
2441
2510
|
|
|
2511
|
+
private var unpairInProgress = false
|
|
2512
|
+
|
|
2513
|
+
/** Explicit user Unpair, separate from passive pairing cleanup and logout. */
|
|
2514
|
+
suspend fun unpair() = withContext(Dispatchers.Main) {
|
|
2515
|
+
check(!unpairInProgress) { "Unpair already in progress" }
|
|
2516
|
+
unpairInProgress = true
|
|
2517
|
+
try {
|
|
2518
|
+
val target = sgc
|
|
2519
|
+
val nimo = target as? Nimo
|
|
2520
|
+
val savedNimoAddress = when {
|
|
2521
|
+
defaultWearable == DeviceTypes.NIMO -> deviceAddress
|
|
2522
|
+
pendingWearable == DeviceTypes.NIMO -> pendingDeviceAddress
|
|
2523
|
+
else -> null
|
|
2524
|
+
}
|
|
2525
|
+
if (nimo != null) {
|
|
2526
|
+
suspendCancellableCoroutine<Boolean> { continuation ->
|
|
2527
|
+
nimo.resetForUnpair { if (continuation.isActive) continuation.resume(it) }
|
|
2528
|
+
}
|
|
2529
|
+
check(sgc === target) { "Glasses changed during Unpair" }
|
|
2530
|
+
// Drop the encrypted link before removing the phone's bond.
|
|
2531
|
+
nimo.disconnect()
|
|
2532
|
+
nimo.removeBluetoothBond()
|
|
2533
|
+
} else if (target == null && savedNimoAddress != null) {
|
|
2534
|
+
Nimo.removeBluetoothBond(savedNimoAddress)
|
|
2535
|
+
}
|
|
2536
|
+
forget()
|
|
2537
|
+
} finally {
|
|
2538
|
+
unpairInProgress = false
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
|
|
2442
2542
|
fun forget() {
|
|
2543
|
+
synchronized(recoveryLock) {
|
|
2544
|
+
recoverG2Connection = false
|
|
2545
|
+
connectionRecovery.clear()
|
|
2546
|
+
}
|
|
2443
2547
|
Bridge.log("MAN: Forgetting smart glasses")
|
|
2444
2548
|
|
|
2445
2549
|
val live = sgc as? MentraLive
|
|
@@ -2451,7 +2555,8 @@ class DeviceManager {
|
|
|
2451
2555
|
// session state without calling disconnect() again (that would hit a dead instance
|
|
2452
2556
|
// and leave a destroyed MentraLive retained for the next scan).
|
|
2453
2557
|
sgc = null
|
|
2454
|
-
|
|
2558
|
+
DeviceStore.apply("glasses", "micEnabled", false)
|
|
2559
|
+
resetConnectionReadyState()
|
|
2455
2560
|
searching = false
|
|
2456
2561
|
micEnabled = false
|
|
2457
2562
|
updateMicState()
|
|
@@ -2554,6 +2659,8 @@ class DeviceManager {
|
|
|
2554
2659
|
|
|
2555
2660
|
// MARK: Cleanup
|
|
2556
2661
|
fun cleanup() {
|
|
2662
|
+
recoveryListenerId?.let { DeviceStore.store.removeListener(it) }
|
|
2663
|
+
recoveryListenerId = null
|
|
2557
2664
|
stopBluetoothStateMonitoring()
|
|
2558
2665
|
|
|
2559
2666
|
micReinitRunnable?.let { mainHandler.removeCallbacks(it) }
|
|
@@ -142,6 +142,7 @@ object DeviceStore {
|
|
|
142
142
|
store.set("bluetooth", "gallery_mode", true)
|
|
143
143
|
store.set("bluetooth", "voice_activity_detection_enabled", BluetoothSdkDefaults.VOICE_ACTIVITY_DETECTION_ENABLED)
|
|
144
144
|
store.set("bluetooth", "loudness_gate_enabled", BluetoothSdkDefaults.LOUDNESS_GATE_ENABLED)
|
|
145
|
+
store.set("bluetooth", "auto_power_off_enabled", BluetoothSdkDefaults.AUTO_POWER_OFF_ENABLED)
|
|
145
146
|
store.set("bluetooth", "screen_disabled", false)
|
|
146
147
|
store.set("bluetooth", "button_photo_size", "max")
|
|
147
148
|
store.set("bluetooth", "button_max_recording_time", 10)
|
|
@@ -219,14 +220,30 @@ object DeviceStore {
|
|
|
219
220
|
}
|
|
220
221
|
|
|
221
222
|
// BLUETOOTH:
|
|
223
|
+
"bluetooth" to "contextual_dashboard" -> {
|
|
224
|
+
if (value is Boolean) {
|
|
225
|
+
CoroutineScope(Dispatchers.Main.immediate).launch {
|
|
226
|
+
// The setting changes the selected view even without a new head event.
|
|
227
|
+
// Re-read state on Main and retain the manager's readiness/display guards.
|
|
228
|
+
if (store.get("glasses", "headUp") == true) {
|
|
229
|
+
DeviceManager.getInstance().sendCurrentState()
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
222
234
|
"bluetooth" to "brightness" -> {
|
|
223
235
|
val b = (value as? Number)?.toInt() ?: 50
|
|
224
236
|
val auto = (store.get("bluetooth", "auto_brightness") as? Boolean) ?: true
|
|
225
237
|
CoroutineScope(Dispatchers.Main).launch {
|
|
226
|
-
DeviceManager.getInstance().sgc
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
238
|
+
val device = DeviceManager.getInstance().sgc ?: return@launch
|
|
239
|
+
device.setBrightness(b, auto)
|
|
240
|
+
if (device.showBrightnessConfirmation) {
|
|
241
|
+
device.sendTextWall("Set brightness to $b%")
|
|
242
|
+
delay(800) // 0.8 seconds
|
|
243
|
+
if (DeviceManager.getInstance().sgc === device) {
|
|
244
|
+
device.clearDisplay()
|
|
245
|
+
}
|
|
246
|
+
}
|
|
230
247
|
}
|
|
231
248
|
}
|
|
232
249
|
"bluetooth" to "auto_brightness" -> {
|
|
@@ -234,16 +251,17 @@ object DeviceStore {
|
|
|
234
251
|
val auto = (value as? Boolean) ?: true
|
|
235
252
|
val autoBrightnessChanged = (oldValue as? Boolean) != auto
|
|
236
253
|
CoroutineScope(Dispatchers.Main).launch {
|
|
237
|
-
DeviceManager.getInstance().sgc
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
)
|
|
254
|
+
val device = DeviceManager.getInstance().sgc ?: return@launch
|
|
255
|
+
device.setBrightness(b, auto)
|
|
256
|
+
if (autoBrightnessChanged && device.showBrightnessConfirmation) {
|
|
257
|
+
device.sendTextWall(
|
|
258
|
+
if (auto) "Enabled auto brightness"
|
|
259
|
+
else "Disabled auto brightness"
|
|
260
|
+
)
|
|
245
261
|
delay(800) // 0.8 seconds
|
|
246
|
-
DeviceManager.getInstance().sgc
|
|
262
|
+
if (DeviceManager.getInstance().sgc === device) {
|
|
263
|
+
device.clearDisplay()
|
|
264
|
+
}
|
|
247
265
|
}
|
|
248
266
|
}
|
|
249
267
|
}
|
|
@@ -291,6 +309,12 @@ object DeviceStore {
|
|
|
291
309
|
"bluetooth" to "loudness_gate_enabled" -> {
|
|
292
310
|
DeviceManager.getInstance().sgc?.sendLoudnessGateSetting()
|
|
293
311
|
}
|
|
312
|
+
"bluetooth" to "auto_power_off_enabled" -> {
|
|
313
|
+
Bridge.log(
|
|
314
|
+
"DeviceStore: auto_power_off_enabled changed to $value — sending cs_swit type 11"
|
|
315
|
+
)
|
|
316
|
+
DeviceManager.getInstance().sgc?.sendAutoPowerOffSetting()
|
|
317
|
+
}
|
|
294
318
|
// Deliberately has no seeded default above: the key starts absent so
|
|
295
319
|
// that a session where the engine has not authorized tuning can only
|
|
296
320
|
// ever send a reset.
|
|
@@ -3,8 +3,9 @@ package com.mentra.bluetoothsdk
|
|
|
3
3
|
/** Generated from /changelogs. Do not edit directly. */
|
|
4
4
|
internal val GENERATED_RELEASE_CHANGELOGS: List<ReleaseChangelog> =
|
|
5
5
|
listOf(
|
|
6
|
-
ReleaseChangelog(version = "3.
|
|
7
|
-
ReleaseChangelog(version = "3.2.
|
|
6
|
+
ReleaseChangelog(version = "3.3.0", markdown = "This release is under active development. User-facing changes will be documented as they land."),
|
|
7
|
+
ReleaseChangelog(version = "3.2.1", markdown = "- **Livestreamer:** \"Stream Here\" now works without a Wi-Fi network. Improved stream startup and connection reliability.\n- **Photos and gallery:** More reliable photo capture and gallery syncing.\n- **Mentra AI:** Reliability improvements and bug fixes.\n- **Miniapps:** Smoother transitions, with back buttons and gestures that take you to the previous screen inside a miniapp.\n- **Pairing and connections:** Improved G1 pairing and G2 background reconnection.\n- **Glasses updates:** Fixed repeated update checks and improved recovery after interrupted updates.\n- **Mentra Maps:** Bug fixes.\n- **NIMO:** Improved text and map layouts.\n- **Self-hosting:** Connect the Mentra App to your own self-hosted Mentra backend."),
|
|
8
|
+
ReleaseChangelog(version = "3.2.0", markdown = "- Improved glasses connectivity and Wi-Fi setup.\n\n- Breaking Bluetooth SDK change: `forgetWifiNetwork(ssid)` now returns `WifiForgetResult` instead of `WifiStatusChangeEvent`. Migrate status-only consumers to the semantic `outcome`; the requested network remains `ssid`, while optional post-command connectivity is reported by `connected`, `currentSsid`, and `localIp`."),
|
|
8
9
|
ReleaseChangelog(version = "3.1.1", markdown = "- Improved Google login on iOS.\n- Pairing no longer requires phone microphone access."),
|
|
9
10
|
ReleaseChangelog(version = "3.1.0", markdown = "**Mentra App and glasses**\n\n- Improved glasses pairing and reconnection, including on iPad.\n- Improved Android background reliability for captions and miniapps.\n- Added Xingyi AR99 support.\n- More reliable photo and video capture, with improved shutter sounds and privacy-light timing. Taking photos no longer interrupts your running miniapp.\n- Added video thumbnails and smoother gallery browsing.\n- Easier Mentra Live updates through your phone, with clearer progress and better recovery from interruptions.\n\n**Miniapp updates**\n\n- **Mentra AI:** Control whether photos are included with questions. Improved visual answers and connected-app follow-ups.\n- **Captions:** Added offline transcription settings and top/bottom positioning on supported displays.\n- **Translation:** Restored all 60 languages.\n- **Mentra Maps:** Clearer directions on G1/G2, less display flickering, and improved Android offline routing.\n- **Livestreamer:** More video-quality controls and custom streaming options, plus improved startup and sharing.\n- **Merge:** Tap to expand an insight; swipe down or back to dismiss it.\n- **Mentra Notes:** Smoother back navigation.\n- **Recorder:** Fixed audio sharing."),
|
|
10
11
|
)
|
|
@@ -2,10 +2,10 @@ package com.mentra.bluetoothsdk
|
|
|
2
2
|
|
|
3
3
|
/** Generated by release CI. Do not edit in a release checkout. */
|
|
4
4
|
internal object GeneratedReleaseMetadata {
|
|
5
|
-
const val FAMILY_BASE_VERSION: String = "3.
|
|
6
|
-
const val RELEASE_IDENTITY: String = "3.
|
|
7
|
-
const val RELEASE_SET_ID: String = "mentra-3.
|
|
8
|
-
const val SOURCE_COMMIT: String = "
|
|
9
|
-
const val OTA_MANIFEST_URL: String = "https://artifactscdn.mentraglass.com/Mentra-Community/MentraOS/releases/mentra-builds-v3.
|
|
10
|
-
const val OTA_MANIFEST_SHA256: String = "
|
|
5
|
+
const val FAMILY_BASE_VERSION: String = "3.3.0"
|
|
6
|
+
const val RELEASE_IDENTITY: String = "3.3.0-beta.942"
|
|
7
|
+
const val RELEASE_SET_ID: String = "mentra-3.3.0-beta.942"
|
|
8
|
+
const val SOURCE_COMMIT: String = "5c18d26dfc6feb9070d4705e36a38ae074e242b1"
|
|
9
|
+
const val OTA_MANIFEST_URL: String = "https://artifactscdn.mentraglass.com/Mentra-Community/MentraOS/releases/mentra-builds-v3.3.0/mentra-live-ota-3.3.0-beta.942.json"
|
|
10
|
+
const val OTA_MANIFEST_SHA256: String = "94e1ba8266968b358748b30a6384882a6bc9df23aab38a01d786d448bdd044c9"
|
|
11
11
|
}
|