@mentra/engine 0.1.0-beta.0 → 0.1.0-dev.1
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 +42 -1
- package/build/engine.d.ts +11 -3
- package/build/engine.d.ts.map +1 -1
- package/build/engine.js +7 -2
- package/build/engine.js.map +1 -1
- package/build/facades/dev.d.ts.map +1 -1
- package/build/facades/dev.js +9 -5
- package/build/facades/dev.js.map +1 -1
- package/build/facades/gallery.js +1 -1
- package/build/facades/gallery.js.map +1 -1
- package/build/facades/glasses.d.ts +1 -0
- package/build/facades/glasses.d.ts.map +1 -1
- package/build/facades/glasses.js +19 -4
- package/build/facades/glasses.js.map +1 -1
- package/build/facades/glassesSettings.d.ts.map +1 -1
- package/build/facades/glassesSettings.js +1 -0
- package/build/facades/glassesSettings.js.map +1 -1
- package/build/facades/miniapps.d.ts +4 -0
- package/build/facades/miniapps.d.ts.map +1 -1
- package/build/facades/miniapps.js +5 -0
- package/build/facades/miniapps.js.map +1 -1
- package/build/facades/ota.d.ts +11 -2
- package/build/facades/ota.d.ts.map +1 -1
- package/build/facades/ota.js +19 -4
- package/build/facades/ota.js.map +1 -1
- package/build/facades/pairing.d.ts +4 -0
- package/build/facades/pairing.d.ts.map +1 -1
- package/build/facades/pairing.js +10 -8
- package/build/facades/pairing.js.map +1 -1
- package/build/facades/phoneNotifications.d.ts +0 -4
- package/build/facades/phoneNotifications.d.ts.map +1 -1
- package/build/facades/phoneNotifications.js +4 -14
- package/build/facades/phoneNotifications.js.map +1 -1
- package/build/index.d.ts +6 -3
- package/build/index.d.ts.map +1 -1
- package/build/index.js +4 -2
- package/build/index.js.map +1 -1
- package/build/internal.d.ts +2 -2
- package/build/internal.d.ts.map +1 -1
- package/build/internal.js +1 -1
- package/build/internal.js.map +1 -1
- package/build/react/MentraLiveOtaFlow.d.ts +38 -0
- package/build/react/MentraLiveOtaFlow.d.ts.map +1 -0
- package/build/react/MentraLiveOtaFlow.js +528 -0
- package/build/react/MentraLiveOtaFlow.js.map +1 -0
- package/build/react/index.d.ts +2 -0
- package/build/react/index.d.ts.map +1 -0
- package/build/react/index.js +2 -0
- package/build/react/index.js.map +1 -0
- package/build/react/useEngineSnapshot.d.ts +2 -0
- package/build/react/useEngineSnapshot.d.ts.map +1 -0
- package/build/react/useEngineSnapshot.js +14 -0
- package/build/react/useEngineSnapshot.js.map +1 -0
- package/build/runtime/bootstrap.d.ts +24 -1
- package/build/runtime/bootstrap.d.ts.map +1 -1
- package/build/runtime/bootstrap.js +17 -0
- package/build/runtime/bootstrap.js.map +1 -1
- package/build/runtime/scanQrSeam.d.ts +7 -0
- package/build/runtime/scanQrSeam.d.ts.map +1 -0
- package/build/runtime/scanQrSeam.js +16 -0
- package/build/runtime/scanQrSeam.js.map +1 -0
- package/build/services/AppRegistry.d.ts +18 -8
- package/build/services/AppRegistry.d.ts.map +1 -1
- package/build/services/AppRegistry.js +44 -45
- package/build/services/AppRegistry.js.map +1 -1
- package/build/services/AudioCloudUplink.d.ts +9 -3
- package/build/services/AudioCloudUplink.d.ts.map +1 -1
- package/build/services/AudioCloudUplink.js +9 -3
- package/build/services/AudioCloudUplink.js.map +1 -1
- package/build/services/AudioPlaybackService.d.ts +9 -1
- package/build/services/AudioPlaybackService.d.ts.map +1 -1
- package/build/services/AudioPlaybackService.js +77 -32
- package/build/services/AudioPlaybackService.js.map +1 -1
- package/build/services/BlobStore.d.ts +2 -1
- package/build/services/BlobStore.d.ts.map +1 -1
- package/build/services/BlobStore.js +17 -9
- package/build/services/BlobStore.js.map +1 -1
- package/build/services/CloudAudioSubscriptionSync.d.ts.map +1 -1
- package/build/services/CloudAudioSubscriptionSync.js +13 -1
- package/build/services/CloudAudioSubscriptionSync.js.map +1 -1
- package/build/services/CloudClientService.d.ts +15 -0
- package/build/services/CloudClientService.d.ts.map +1 -1
- package/build/services/CloudClientService.js +75 -0
- package/build/services/CloudClientService.js.map +1 -1
- package/build/services/ForegroundLocationPermission.d.ts +13 -0
- package/build/services/ForegroundLocationPermission.d.ts.map +1 -0
- package/build/services/ForegroundLocationPermission.js +12 -0
- package/build/services/ForegroundLocationPermission.js.map +1 -0
- package/build/services/GlassesSettingsSync.d.ts.map +1 -1
- package/build/services/GlassesSettingsSync.js +9 -3
- package/build/services/GlassesSettingsSync.js.map +1 -1
- package/build/services/GlassesStatusProjection.d.ts +2 -1
- package/build/services/GlassesStatusProjection.d.ts.map +1 -1
- package/build/services/GlassesStatusProjection.js +15 -8
- package/build/services/GlassesStatusProjection.js.map +1 -1
- package/build/services/HotspotOtaTransport.d.ts +29 -0
- package/build/services/HotspotOtaTransport.d.ts.map +1 -0
- package/build/services/HotspotOtaTransport.js +146 -0
- package/build/services/HotspotOtaTransport.js.map +1 -0
- package/build/services/HotspotShutdown.d.ts +17 -0
- package/build/services/HotspotShutdown.d.ts.map +1 -0
- package/build/services/HotspotShutdown.js +29 -0
- package/build/services/HotspotShutdown.js.map +1 -0
- package/build/services/LocalDisplayManager.d.ts +15 -0
- package/build/services/LocalDisplayManager.d.ts.map +1 -1
- package/build/services/LocalDisplayManager.js +106 -20
- package/build/services/LocalDisplayManager.js.map +1 -1
- package/build/services/LocalMiniappRuntime.d.ts +67 -1
- package/build/services/LocalMiniappRuntime.d.ts.map +1 -1
- package/build/services/LocalMiniappRuntime.js +486 -126
- package/build/services/LocalMiniappRuntime.js.map +1 -1
- package/build/services/LocalMiniappStorage.d.ts +32 -0
- package/build/services/LocalMiniappStorage.d.ts.map +1 -0
- package/build/services/LocalMiniappStorage.js +66 -0
- package/build/services/LocalMiniappStorage.js.map +1 -0
- package/build/services/LocalMiniappUserIdentity.d.ts +20 -0
- package/build/services/LocalMiniappUserIdentity.d.ts.map +1 -0
- package/build/services/LocalMiniappUserIdentity.js +43 -0
- package/build/services/LocalMiniappUserIdentity.js.map +1 -0
- package/build/services/MicStateCoordinator.d.ts +42 -1
- package/build/services/MicStateCoordinator.d.ts.map +1 -1
- package/build/services/MicStateCoordinator.js +141 -3
- package/build/services/MicStateCoordinator.js.map +1 -1
- package/build/services/MiniappLauncher.d.ts.map +1 -1
- package/build/services/MiniappLauncher.js +23 -3
- package/build/services/MiniappLauncher.js.map +1 -1
- package/build/services/MiniappLiveness.d.ts +12 -0
- package/build/services/MiniappLiveness.d.ts.map +1 -0
- package/build/services/MiniappLiveness.js +16 -0
- package/build/services/MiniappLiveness.js.map +1 -0
- package/build/services/OtaArtifactDownloader.d.ts +65 -0
- package/build/services/OtaArtifactDownloader.d.ts.map +1 -0
- package/build/services/OtaArtifactDownloader.js +226 -0
- package/build/services/OtaArtifactDownloader.js.map +1 -0
- package/build/services/OtaAutoChain.d.ts +34 -0
- package/build/services/OtaAutoChain.d.ts.map +1 -0
- package/build/services/OtaAutoChain.js +83 -0
- package/build/services/OtaAutoChain.js.map +1 -0
- package/build/services/OtaErrorMapping.d.ts +18 -0
- package/build/services/OtaErrorMapping.d.ts.map +1 -0
- package/build/services/OtaErrorMapping.js +63 -0
- package/build/services/OtaErrorMapping.js.map +1 -0
- package/build/services/OtaInstallCoordinator.d.ts +94 -14
- package/build/services/OtaInstallCoordinator.d.ts.map +1 -1
- package/build/services/OtaInstallCoordinator.js +574 -130
- package/build/services/OtaInstallCoordinator.js.map +1 -1
- package/build/services/OtaService.d.ts.map +1 -1
- package/build/services/OtaService.js +8 -1
- package/build/services/OtaService.js.map +1 -1
- package/build/services/OtaUpdateCheckService.d.ts +38 -3
- package/build/services/OtaUpdateCheckService.d.ts.map +1 -1
- package/build/services/OtaUpdateCheckService.js +142 -22
- package/build/services/OtaUpdateCheckService.js.map +1 -1
- package/build/services/PhoneCalendarService.d.ts.map +1 -1
- package/build/services/PhoneCalendarService.js +22 -1
- package/build/services/PhoneCalendarService.js.map +1 -1
- package/build/services/PhoneCameraFovCoordinator.d.ts +2 -0
- package/build/services/PhoneCameraFovCoordinator.d.ts.map +1 -1
- package/build/services/PhoneCameraFovCoordinator.js +14 -0
- package/build/services/PhoneCameraFovCoordinator.js.map +1 -1
- package/build/services/PhoneNotificationsSync.d.ts.map +1 -1
- package/build/services/PhoneNotificationsSync.js +12 -10
- package/build/services/PhoneNotificationsSync.js.map +1 -1
- package/build/services/PhonePhotoCoordinator.d.ts +10 -3
- package/build/services/PhonePhotoCoordinator.d.ts.map +1 -1
- package/build/services/PhonePhotoCoordinator.js +21 -3
- package/build/services/PhonePhotoCoordinator.js.map +1 -1
- package/build/services/PhoneStreamCoordinator.d.ts +8 -1
- package/build/services/PhoneStreamCoordinator.d.ts.map +1 -1
- package/build/services/PhoneStreamCoordinator.js +125 -13
- package/build/services/PhoneStreamCoordinator.js.map +1 -1
- package/build/services/PhoneVideoCoordinator.d.ts +2 -0
- package/build/services/PhoneVideoCoordinator.d.ts.map +1 -1
- package/build/services/PhoneVideoCoordinator.js +10 -0
- package/build/services/PhoneVideoCoordinator.js.map +1 -1
- package/build/services/SceneRenderer.d.ts.map +1 -1
- package/build/services/SceneRenderer.js +15 -0
- package/build/services/SceneRenderer.js.map +1 -1
- package/build/services/SupportProfileSync.d.ts +22 -0
- package/build/services/SupportProfileSync.d.ts.map +1 -0
- package/build/services/SupportProfileSync.js +213 -0
- package/build/services/SupportProfileSync.js.map +1 -0
- package/build/services/TtsTextSanitizer.d.ts +5 -0
- package/build/services/TtsTextSanitizer.d.ts.map +1 -0
- package/build/services/TtsTextSanitizer.js +61 -0
- package/build/services/TtsTextSanitizer.js.map +1 -0
- package/build/services/WifiAdbRequest.d.ts +17 -0
- package/build/services/WifiAdbRequest.d.ts.map +1 -0
- package/build/services/WifiAdbRequest.js +35 -0
- package/build/services/WifiAdbRequest.js.map +1 -0
- package/build/services/asg/asgCameraApi.d.ts +3 -0
- package/build/services/asg/asgCameraApi.d.ts.map +1 -1
- package/build/services/asg/asgCameraApi.js +72 -2
- package/build/services/asg/asgCameraApi.js.map +1 -1
- package/build/services/asg/gallerySyncService.d.ts.map +1 -1
- package/build/services/asg/gallerySyncService.js +26 -14
- package/build/services/asg/gallerySyncService.js.map +1 -1
- package/build/services/asg/localNetworkTransport.d.ts +2 -2
- package/build/services/asg/localNetworkTransport.d.ts.map +1 -1
- package/build/services/asg/localNetworkTransport.js +6 -6
- package/build/services/asg/localNetworkTransport.js.map +1 -1
- package/build/services/audioPlaybackAssets.d.ts +8 -0
- package/build/services/audioPlaybackAssets.d.ts.map +1 -0
- package/build/services/audioPlaybackAssets.js +7 -0
- package/build/services/audioPlaybackAssets.js.map +1 -0
- package/build/services/glassesClockSync.js +2 -2
- package/build/services/glassesClockSync.js.map +1 -1
- package/build/services/manifestActions.d.ts +7 -0
- package/build/services/manifestActions.d.ts.map +1 -0
- package/build/services/manifestActions.js +25 -0
- package/build/services/manifestActions.js.map +1 -0
- package/build/services/otaDisplayState.d.ts +22 -11
- package/build/services/otaDisplayState.d.ts.map +1 -1
- package/build/services/otaDisplayState.js +36 -14
- package/build/services/otaDisplayState.js.map +1 -1
- package/build/services/otaInstallPolicy.d.ts +15 -3
- package/build/services/otaInstallPolicy.d.ts.map +1 -1
- package/build/services/otaInstallPolicy.js +24 -3
- package/build/services/otaInstallPolicy.js.map +1 -1
- package/build/services/otaManifestUrl.d.ts +2 -0
- package/build/services/otaManifestUrl.d.ts.map +1 -0
- package/build/services/{asgOtaVersionUrl.js → otaManifestUrl.js} +15 -5
- package/build/services/otaManifestUrl.js.map +1 -0
- package/build/services/slimStreamStatus.d.ts +11 -0
- package/build/services/slimStreamStatus.d.ts.map +1 -1
- package/build/services/slimStreamStatus.js +14 -4
- package/build/services/slimStreamStatus.js.map +1 -1
- package/build/stores/apps.d.ts.map +1 -1
- package/build/stores/apps.js +11 -0
- package/build/stores/apps.js.map +1 -1
- package/build/stores/bluetoothSettingKeys.d.ts.map +1 -1
- package/build/stores/bluetoothSettingKeys.js +2 -0
- package/build/stores/bluetoothSettingKeys.js.map +1 -1
- package/build/stores/gallerySync.d.ts +2 -0
- package/build/stores/gallerySync.d.ts.map +1 -1
- package/build/stores/gallerySync.js +2 -0
- package/build/stores/gallerySync.js.map +1 -1
- package/build/stores/glasses.d.ts.map +1 -1
- package/build/stores/glasses.js +3 -0
- package/build/stores/glasses.js.map +1 -1
- package/build/stores/settings.d.ts +17 -0
- package/build/stores/settings.d.ts.map +1 -1
- package/build/stores/settings.js +152 -19
- package/build/stores/settings.js.map +1 -1
- package/build/types/applet.d.ts +2 -0
- package/build/types/applet.d.ts.map +1 -1
- package/build/types/applet.js.map +1 -1
- package/build/types/enums.d.ts +1 -0
- package/build/types/enums.d.ts.map +1 -1
- package/build/types/enums.js +1 -0
- package/build/types/enums.js.map +1 -1
- package/build/types/hardware.d.ts +1 -0
- package/build/types/hardware.d.ts.map +1 -1
- package/build/types/hardware.js +14 -0
- package/build/types/hardware.js.map +1 -1
- package/build/utils/configuredDevHost.d.ts +6 -0
- package/build/utils/configuredDevHost.d.ts.map +1 -0
- package/build/utils/configuredDevHost.js +19 -0
- package/build/utils/configuredDevHost.js.map +1 -0
- package/build/utils/devMiniappLaunch.d.ts +19 -8
- package/build/utils/devMiniappLaunch.d.ts.map +1 -1
- package/build/utils/devMiniappLaunch.js +144 -21
- package/build/utils/devMiniappLaunch.js.map +1 -1
- package/build/utils/diagnosticContext.d.ts.map +1 -1
- package/build/utils/diagnosticContext.js +15 -17
- package/build/utils/diagnosticContext.js.map +1 -1
- package/build/utils/hardware/hardware.d.ts.map +1 -1
- package/build/utils/hardware/hardware.js +7 -3
- package/build/utils/hardware/hardware.js.map +1 -1
- package/build/utils/miniappDiagnostics.d.ts +64 -0
- package/build/utils/miniappDiagnostics.d.ts.map +1 -0
- package/build/utils/miniappDiagnostics.js +173 -0
- package/build/utils/miniappDiagnostics.js.map +1 -0
- package/build/utils/timers/timers.d.ts.map +1 -1
- package/build/utils/timers/timers.js +55 -18
- package/build/utils/timers/timers.js.map +1 -1
- package/package.json +27 -18
- package/src/assets/silence.wav +0 -0
- package/src/engine.ts +7 -2
- package/src/facades/dev.ts +9 -5
- package/src/facades/gallery.ts +1 -1
- package/src/facades/glasses.ts +17 -4
- package/src/facades/glassesSettings.ts +5 -0
- package/src/facades/miniapps.ts +7 -0
- package/src/facades/ota.ts +19 -4
- package/src/facades/pairing.ts +11 -7
- package/src/facades/phoneNotifications.ts +4 -13
- package/src/index.ts +26 -2
- package/src/internal.ts +2 -1
- package/src/react/MentraLiveOtaFlow.tsx +856 -0
- package/src/react/index.ts +7 -0
- package/src/react/useEngineSnapshot.ts +16 -0
- package/src/runtime/bootstrap.ts +33 -1
- package/src/runtime/scanQrSeam.ts +20 -0
- package/src/services/AppRegistry.ts +72 -48
- package/src/services/AudioCloudUplink.ts +9 -3
- package/src/services/AudioPlaybackService.ts +94 -34
- package/src/services/BlobStore.ts +22 -9
- package/src/services/CloudAudioSubscriptionSync.ts +13 -1
- package/src/services/CloudClientService.ts +74 -0
- package/src/services/ForegroundLocationPermission.ts +24 -0
- package/src/services/GlassesSettingsSync.ts +9 -3
- package/src/services/GlassesStatusProjection.ts +16 -8
- package/src/services/HotspotOtaTransport.ts +184 -0
- package/src/services/HotspotShutdown.ts +41 -0
- package/src/services/LocalDisplayManager.ts +112 -21
- package/src/services/LocalMiniappRuntime.ts +576 -130
- package/src/services/LocalMiniappStorage.ts +82 -0
- package/src/services/LocalMiniappUserIdentity.ts +46 -0
- package/src/services/MicStateCoordinator.ts +169 -4
- package/src/services/MiniappLauncher.ts +30 -3
- package/src/services/MiniappLiveness.ts +24 -0
- package/src/services/OtaArtifactDownloader.ts +312 -0
- package/src/services/OtaAutoChain.ts +106 -0
- package/src/services/OtaErrorMapping.ts +80 -0
- package/src/services/OtaInstallCoordinator.ts +645 -143
- package/src/services/OtaService.ts +11 -5
- package/src/services/OtaUpdateCheckService.ts +180 -20
- package/src/services/PhoneCalendarService.ts +27 -4
- package/src/services/PhoneCameraFovCoordinator.ts +15 -0
- package/src/services/PhoneNotificationsSync.ts +12 -10
- package/src/services/PhonePhotoCoordinator.ts +33 -7
- package/src/services/PhoneStreamCoordinator.ts +139 -25
- package/src/services/PhoneVideoCoordinator.ts +11 -0
- package/src/services/SceneRenderer.ts +16 -0
- package/src/services/SupportProfileSync.ts +218 -0
- package/src/services/TtsTextSanitizer.ts +66 -0
- package/src/services/WifiAdbRequest.ts +53 -0
- package/src/services/asg/asgCameraApi.ts +99 -8
- package/src/services/asg/gallerySyncService.ts +28 -19
- package/src/services/asg/localNetworkTransport.ts +7 -11
- package/src/services/audioPlaybackAssets.ts +8 -0
- package/src/services/glassesClockSync.ts +2 -2
- package/src/services/manifestActions.ts +30 -0
- package/src/services/otaDisplayState.ts +57 -16
- package/src/services/otaInstallPolicy.ts +29 -3
- package/src/services/{asgOtaVersionUrl.ts → otaManifestUrl.ts} +16 -4
- package/src/services/slimStreamStatus.ts +16 -5
- package/src/stores/apps.ts +12 -0
- package/src/stores/bluetoothSettingKeys.ts +2 -0
- package/src/stores/gallerySync.ts +4 -0
- package/src/stores/glasses.ts +3 -0
- package/src/stores/settings.ts +174 -21
- package/src/types/applet.ts +2 -0
- package/src/types/enums.ts +2 -1
- package/src/types/hardware.ts +17 -0
- package/src/utils/configuredDevHost.ts +17 -0
- package/src/utils/devMiniappLaunch.ts +151 -23
- package/src/utils/diagnosticContext.ts +17 -17
- package/src/utils/hardware/hardware.ts +7 -9
- package/src/utils/miniappDiagnostics.ts +226 -0
- package/src/utils/timers/timers.ts +57 -21
- package/build/services/asgOtaVersionUrl.d.ts +0 -2
- package/build/services/asgOtaVersionUrl.d.ts.map +0 -1
- package/build/services/asgOtaVersionUrl.js.map +0 -1
|
@@ -15,11 +15,7 @@ import type {OtaStatus} from "@mentra/bluetooth-sdk/internal"
|
|
|
15
15
|
import GlobalEventEmitter from "../utils/GlobalEventEmitter"
|
|
16
16
|
import {useGlassesStore} from "../stores/glasses"
|
|
17
17
|
import {handleOtaClockSkewFromGlasses} from "./glassesClockSync"
|
|
18
|
-
import {
|
|
19
|
-
legacyOtaProgressFromOtaStatusEvent,
|
|
20
|
-
normalizeOtaStatusEvent,
|
|
21
|
-
otaStatusFromNormalized,
|
|
22
|
-
} from "./otaLegacyMapping"
|
|
18
|
+
import {legacyOtaProgressFromOtaStatusEvent, normalizeOtaStatusEvent, otaStatusFromNormalized} from "./otaLegacyMapping"
|
|
23
19
|
|
|
24
20
|
let subs: Array<{remove: () => void}> = []
|
|
25
21
|
|
|
@@ -40,6 +36,16 @@ export function startOtaService(): void {
|
|
|
40
36
|
}),
|
|
41
37
|
)
|
|
42
38
|
|
|
39
|
+
// The glasses process restarted under a live BLE link (sid changed in
|
|
40
|
+
// glasses_ready/version_info_1). The physical connection never dropped, so this is
|
|
41
|
+
// the ONLY reconnect-edge signal the OTA coordinator gets after an APK install.
|
|
42
|
+
subs.push(
|
|
43
|
+
BluetoothSdk.addListener("glasses_session_changed", (event) => {
|
|
44
|
+
console.log(`[OTA] glasses_session_changed ${event.previous_sid || "<none>"} -> ${event.sid}`)
|
|
45
|
+
GlobalEventEmitter.emit("glasses_session_changed", {previousSid: event.previous_sid, sid: event.sid})
|
|
46
|
+
}),
|
|
47
|
+
)
|
|
48
|
+
|
|
43
49
|
// Install progress / lifecycle. Updates both the new (otaStatus) and the legacy
|
|
44
50
|
// (otaProgress) store shapes; auto-fixes clock skew on the relevant failures.
|
|
45
51
|
subs.push(
|
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
import BluetoothSdk from "@mentra/bluetooth-sdk/internal"
|
|
2
2
|
import type {OtaUpdateInfo} from "@mentra/bluetooth-sdk/internal"
|
|
3
3
|
import {getGlassesSystemTimeMs, isGlassesConnected, useGlassesStore, waitForGlassesState} from "../stores/glasses"
|
|
4
|
+
import {SETTINGS, useSettingsStore} from "../stores/settings"
|
|
4
5
|
import {maybeFixGlassesClockFromVersionInfo} from "./glassesClockSync"
|
|
5
|
-
import {
|
|
6
|
+
import {resolveOtaManifestUrl} from "./otaManifestUrl"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Phone-side mirror of ASG's `DOWNGRADE_FLOOR_VERSION_CODE`. Set to the versionCode of the first
|
|
10
|
+
* release shipping the shared media root (the first downgrade-safe build). A non-positive value
|
|
11
|
+
* means downgrades are disabled, so the phone must NOT offer them — offering a downgrade the
|
|
12
|
+
* glasses' `DowngradeGate` will refuse leaves the version-change session with no install and the
|
|
13
|
+
* glasses reporting a bare "complete", which would otherwise present as false success. Keep this
|
|
14
|
+
* in lockstep with the ASG/recovery constant at release time.
|
|
15
|
+
*/
|
|
16
|
+
export const DOWNGRADE_FLOOR_VERSION_CODE = 0
|
|
6
17
|
|
|
7
18
|
export interface VersionInfo {
|
|
8
19
|
versionCode: number
|
|
@@ -18,11 +29,13 @@ export interface MtkPatch {
|
|
|
18
29
|
start_firmware: string
|
|
19
30
|
end_firmware: string
|
|
20
31
|
url: string
|
|
32
|
+
sha256?: string
|
|
21
33
|
}
|
|
22
34
|
|
|
23
35
|
export interface BesFirmware {
|
|
24
36
|
version: string
|
|
25
37
|
url: string
|
|
38
|
+
sha256?: string
|
|
26
39
|
}
|
|
27
40
|
|
|
28
41
|
export interface VersionJson {
|
|
@@ -46,9 +59,23 @@ export interface OtaCheckResult {
|
|
|
46
59
|
updates: string[]
|
|
47
60
|
mtkPatch: MtkPatch | null
|
|
48
61
|
besVersion: string | null
|
|
62
|
+
/** True when the pending APK step installs an OLDER build than the glasses currently run. */
|
|
63
|
+
isApkDowngrade: boolean
|
|
64
|
+
/**
|
|
65
|
+
* Raw manifest JSON (stringified) this check actually fetched, or null when the
|
|
66
|
+
* fetch failed. Lets change-watchers baseline on exactly what the check saw,
|
|
67
|
+
* instead of racing it with a second fetch of the same URL.
|
|
68
|
+
*/
|
|
69
|
+
manifestBody: string | null
|
|
70
|
+
/**
|
|
71
|
+
* Why the check failed, when it did. "network" is retryable (connection/server
|
|
72
|
+
* trouble); "pin_unavailable" is permanent for this app build (manifest 404/gone,
|
|
73
|
+
* unparseable, or carrying no valid ASG pin) — the remedy is updating the app.
|
|
74
|
+
*/
|
|
75
|
+
checkFailureReason?: "network" | "pin_unavailable"
|
|
49
76
|
}
|
|
50
77
|
|
|
51
|
-
export type OtaCheckSkippedReason = "disconnected" | "missing_build"
|
|
78
|
+
export type OtaCheckSkippedReason = "disconnected" | "missing_build" | "dev_build"
|
|
52
79
|
|
|
53
80
|
export interface OtaCheckCurrentGlassesResult extends OtaCheckResult {
|
|
54
81
|
updateInfo: OtaUpdateInfo | null
|
|
@@ -66,6 +93,8 @@ export interface OtaCheckCurrentGlassesOptions {
|
|
|
66
93
|
waitForMtkVersionMs?: number
|
|
67
94
|
refreshVersionInfo?: boolean
|
|
68
95
|
fixClockBeforeCheck?: boolean
|
|
96
|
+
/** Downgrade floor override (defaults to DOWNGRADE_FLOOR_VERSION_CODE); for tests. */
|
|
97
|
+
floorVersionCode?: number
|
|
69
98
|
}
|
|
70
99
|
|
|
71
100
|
function emptyCheckResult(skippedReason?: OtaCheckSkippedReason): OtaCheckCurrentGlassesResult {
|
|
@@ -76,6 +105,8 @@ function emptyCheckResult(skippedReason?: OtaCheckSkippedReason): OtaCheckCurren
|
|
|
76
105
|
updates: [],
|
|
77
106
|
mtkPatch: null,
|
|
78
107
|
besVersion: null,
|
|
108
|
+
isApkDowngrade: false,
|
|
109
|
+
manifestBody: null,
|
|
79
110
|
updateInfo: null,
|
|
80
111
|
isRequired: true,
|
|
81
112
|
skippedReason,
|
|
@@ -86,46 +117,92 @@ function glassesConnectedNow(): boolean {
|
|
|
86
117
|
return isGlassesConnected(useGlassesStore.getState().connection)
|
|
87
118
|
}
|
|
88
119
|
|
|
89
|
-
|
|
120
|
+
type ManifestFetchResult = {json: VersionJson | null; failureReason?: "network" | "pin_unavailable"}
|
|
121
|
+
|
|
122
|
+
async function fetchVersionInfoDetailed(url: string): Promise<ManifestFetchResult> {
|
|
90
123
|
try {
|
|
91
124
|
const response = await fetch(url)
|
|
92
125
|
if (!response.ok) {
|
|
93
126
|
console.error("Failed to fetch version info:", response.status)
|
|
94
|
-
|
|
127
|
+
// 4xx: the manifest does not exist (or is gone) — permanent for this app
|
|
128
|
+
// build, retrying cannot help. Everything else is transient server/network
|
|
129
|
+
// trouble and retryable.
|
|
130
|
+
return {json: null, failureReason: response.status >= 400 && response.status < 500 ? "pin_unavailable" : "network"}
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
return {json: await response.json()}
|
|
134
|
+
} catch (parseError) {
|
|
135
|
+
console.error("OTA: manifest is not valid JSON:", parseError)
|
|
136
|
+
return {json: null, failureReason: "pin_unavailable"}
|
|
95
137
|
}
|
|
96
|
-
return await response.json()
|
|
97
138
|
} catch (error) {
|
|
98
139
|
console.error("OTA: Error fetching version info:", error)
|
|
99
|
-
return null
|
|
140
|
+
return {json: null, failureReason: "network"}
|
|
100
141
|
}
|
|
101
142
|
}
|
|
102
143
|
|
|
144
|
+
export async function fetchVersionInfo(url: string): Promise<VersionJson | null> {
|
|
145
|
+
return (await fetchVersionInfoDetailed(url)).json
|
|
146
|
+
}
|
|
147
|
+
|
|
103
148
|
export function checkVersionUpdateAvailable(
|
|
104
149
|
currentBuildNumber: string | undefined,
|
|
105
150
|
versionJson: VersionJson | null,
|
|
151
|
+
floorVersionCode: number = DOWNGRADE_FLOOR_VERSION_CODE,
|
|
106
152
|
): boolean {
|
|
153
|
+
return getApkUpdateDirection(currentBuildNumber, versionJson, floorVersionCode) !== null
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Direction of the pending APK change, or null when the glasses already match the manifest.
|
|
158
|
+
* Every apps-shaped manifest the phone drives is an exact pin, so both directions are
|
|
159
|
+
* actionable (mirroring the glasses-side `DowngradeGate`); the ancient top-level manifest
|
|
160
|
+
* shape stays upgrade-only, and zeroed rescue pins are never actionable.
|
|
161
|
+
*/
|
|
162
|
+
export function getApkUpdateDirection(
|
|
163
|
+
currentBuildNumber: string | undefined,
|
|
164
|
+
versionJson: VersionJson | null,
|
|
165
|
+
floorVersionCode: number = DOWNGRADE_FLOOR_VERSION_CODE,
|
|
166
|
+
): "upgrade" | "downgrade" | null {
|
|
107
167
|
if (!currentBuildNumber || !versionJson) {
|
|
108
|
-
return
|
|
168
|
+
return null
|
|
109
169
|
}
|
|
110
170
|
|
|
111
171
|
const currentVersion = parseInt(currentBuildNumber, 10)
|
|
112
172
|
if (isNaN(currentVersion)) {
|
|
113
|
-
return
|
|
173
|
+
return null
|
|
114
174
|
}
|
|
115
175
|
|
|
116
176
|
let serverVersion: number | undefined
|
|
177
|
+
let exactPin = false
|
|
117
178
|
|
|
118
|
-
|
|
119
|
-
|
|
179
|
+
const appEntry = versionJson.apps?.["com.mentra.asg_client"]
|
|
180
|
+
if (appEntry) {
|
|
181
|
+
serverVersion = appEntry.versionCode
|
|
182
|
+
exactPin = true
|
|
120
183
|
} else if (versionJson.versionCode) {
|
|
121
184
|
serverVersion = versionJson.versionCode
|
|
122
185
|
}
|
|
123
186
|
|
|
124
187
|
if (!serverVersion || isNaN(serverVersion)) {
|
|
125
|
-
return
|
|
188
|
+
return null
|
|
126
189
|
}
|
|
127
190
|
|
|
128
|
-
|
|
191
|
+
if (serverVersion > currentVersion) {
|
|
192
|
+
return "upgrade"
|
|
193
|
+
}
|
|
194
|
+
// Downgrades require an exact pin AND a target at/above the enabled floor. A non-positive floor
|
|
195
|
+
// disables downgrades entirely, matching ASG's fail-closed DowngradeGate — so the phone never
|
|
196
|
+
// offers what the glasses would refuse.
|
|
197
|
+
if (
|
|
198
|
+
serverVersion < currentVersion &&
|
|
199
|
+
exactPin &&
|
|
200
|
+
floorVersionCode > 0 &&
|
|
201
|
+
serverVersion >= floorVersionCode
|
|
202
|
+
) {
|
|
203
|
+
return "downgrade"
|
|
204
|
+
}
|
|
205
|
+
return null
|
|
129
206
|
}
|
|
130
207
|
|
|
131
208
|
export function getLatestVersionInfo(versionJson: VersionJson | null): VersionInfo | null {
|
|
@@ -184,7 +261,7 @@ export function checkBesUpdate(besFirmware: BesFirmware | undefined, currentVers
|
|
|
184
261
|
return compareVersions(besFirmware.version, currentVersion) > 0
|
|
185
262
|
}
|
|
186
263
|
|
|
187
|
-
function compareVersions(version1: string, version2: string): number {
|
|
264
|
+
export function compareVersions(version1: string, version2: string): number {
|
|
188
265
|
if (version1.includes(".") && version2.includes(".")) {
|
|
189
266
|
const parts1 = version1.split(".")
|
|
190
267
|
const parts2 = version2.split(".")
|
|
@@ -208,14 +285,62 @@ export async function checkForOtaUpdate(
|
|
|
208
285
|
currentBuildNumber: string,
|
|
209
286
|
currentMtkVersion?: string,
|
|
210
287
|
currentBesVersion?: string,
|
|
288
|
+
floorVersionCode: number = DOWNGRADE_FLOOR_VERSION_CODE,
|
|
211
289
|
): Promise<OtaCheckResult> {
|
|
212
290
|
try {
|
|
213
291
|
console.log("OTA: Checking for OTA update - URL: " + otaVersionUrl + ", current build: " + currentBuildNumber)
|
|
214
|
-
const
|
|
292
|
+
const fetched = await fetchVersionInfoDetailed(otaVersionUrl)
|
|
293
|
+
const versionJson = fetched.json
|
|
294
|
+
if (!versionJson) {
|
|
295
|
+
return {
|
|
296
|
+
hasCheckCompleted: false,
|
|
297
|
+
updateAvailable: false,
|
|
298
|
+
latestVersionInfo: null,
|
|
299
|
+
updates: [],
|
|
300
|
+
mtkPatch: null,
|
|
301
|
+
besVersion: null,
|
|
302
|
+
isApkDowngrade: false,
|
|
303
|
+
manifestBody: null,
|
|
304
|
+
checkFailureReason: fetched.failureReason ?? "network",
|
|
305
|
+
}
|
|
306
|
+
}
|
|
215
307
|
const latestVersionInfo = getLatestVersionInfo(versionJson)
|
|
216
308
|
|
|
217
|
-
|
|
218
|
-
|
|
309
|
+
// A manifest whose ASG pin is missing/zeroed cannot verify anything for modern
|
|
310
|
+
// glasses (zeroed pins are only legitimate in the frozen legacy rescue manifests
|
|
311
|
+
// that pre-39 glasses are checked against). Report the check as FAILED rather
|
|
312
|
+
// than silently completing as "up to date" — an unverifiable state must never
|
|
313
|
+
// present as a verified one.
|
|
314
|
+
const currentVersionNumber = parseInt(currentBuildNumber, 10)
|
|
315
|
+
const pinnedEntry = versionJson?.apps?.["com.mentra.asg_client"]
|
|
316
|
+
const pinnedVersion = pinnedEntry?.versionCode ?? versionJson?.versionCode
|
|
317
|
+
if (
|
|
318
|
+
versionJson &&
|
|
319
|
+
Number.isFinite(currentVersionNumber) &&
|
|
320
|
+
currentVersionNumber >= 39 &&
|
|
321
|
+
!(typeof pinnedVersion === "number" && pinnedVersion > 0)
|
|
322
|
+
) {
|
|
323
|
+
console.error(
|
|
324
|
+
`OTA: manifest at ${otaVersionUrl} has no valid ASG pin (versionCode=${String(pinnedVersion)}) - treating check as failed`,
|
|
325
|
+
)
|
|
326
|
+
return {
|
|
327
|
+
hasCheckCompleted: false,
|
|
328
|
+
updateAvailable: false,
|
|
329
|
+
latestVersionInfo: null,
|
|
330
|
+
updates: [],
|
|
331
|
+
mtkPatch: null,
|
|
332
|
+
besVersion: null,
|
|
333
|
+
isApkDowngrade: false,
|
|
334
|
+
manifestBody: null,
|
|
335
|
+
checkFailureReason: "pin_unavailable",
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const apkDirection = getApkUpdateDirection(currentBuildNumber, versionJson, floorVersionCode)
|
|
340
|
+
const apkUpdateAvailable = apkDirection !== null
|
|
341
|
+
console.log(
|
|
342
|
+
`OTA: APK update available: ${apkUpdateAvailable} (current: ${currentBuildNumber}, direction: ${apkDirection ?? "none"})`,
|
|
343
|
+
)
|
|
219
344
|
|
|
220
345
|
const mtkPatch = findMatchingMtkPatch(versionJson?.mtk_patches, currentMtkVersion)
|
|
221
346
|
const mtkUpdateAvailable = mtkPatch !== null
|
|
@@ -243,6 +368,8 @@ export async function checkForOtaUpdate(
|
|
|
243
368
|
updates,
|
|
244
369
|
mtkPatch,
|
|
245
370
|
besVersion: versionJson?.bes_firmware?.version || null,
|
|
371
|
+
isApkDowngrade: apkDirection === "downgrade",
|
|
372
|
+
manifestBody: versionJson ? JSON.stringify(versionJson) : null,
|
|
246
373
|
}
|
|
247
374
|
} catch (error) {
|
|
248
375
|
console.error("Error checking for OTA update:", error)
|
|
@@ -253,6 +380,9 @@ export async function checkForOtaUpdate(
|
|
|
253
380
|
updates: [],
|
|
254
381
|
mtkPatch: null,
|
|
255
382
|
besVersion: null,
|
|
383
|
+
isApkDowngrade: false,
|
|
384
|
+
manifestBody: null,
|
|
385
|
+
checkFailureReason: "network",
|
|
256
386
|
}
|
|
257
387
|
}
|
|
258
388
|
}
|
|
@@ -266,12 +396,25 @@ export async function checkCurrentGlassesForUpdate(
|
|
|
266
396
|
waitForMtkVersionMs = 2000,
|
|
267
397
|
refreshVersionInfo = true,
|
|
268
398
|
fixClockBeforeCheck = true,
|
|
399
|
+
floorVersionCode = DOWNGRADE_FLOOR_VERSION_CODE,
|
|
269
400
|
} = options
|
|
270
401
|
|
|
271
402
|
if (!glassesConnectedNow()) {
|
|
272
403
|
return emptyCheckResult("disconnected")
|
|
273
404
|
}
|
|
274
405
|
|
|
406
|
+
const embeddedManifestUrl = process.env.EXPO_PUBLIC_ASG_OTA_VERSION_URL?.trim()
|
|
407
|
+
const superMode = useSettingsStore.getState().getSetting(SETTINGS.super_mode.key)
|
|
408
|
+
const overrideUrl = useSettingsStore.getState().getSetting(SETTINGS.ota_version_url.key)
|
|
409
|
+
const overrideActive = superMode && typeof overrideUrl === "string" && overrideUrl.trim() !== ""
|
|
410
|
+
if (!embeddedManifestUrl && !overrideActive) {
|
|
411
|
+
console.log("OTA: check skipped - mobile app has no embedded OTA manifest pin")
|
|
412
|
+
return emptyCheckResult("dev_build")
|
|
413
|
+
}
|
|
414
|
+
if (!embeddedManifestUrl) {
|
|
415
|
+
console.log("OTA: no embedded manifest pin, but super-mode manifest override is active - checking anyway")
|
|
416
|
+
}
|
|
417
|
+
|
|
275
418
|
if (refreshVersionInfo) {
|
|
276
419
|
void BluetoothSdk.requestVersionInfo().catch((error) => {
|
|
277
420
|
console.warn("OTA: Failed to request version_info from glasses:", error)
|
|
@@ -284,7 +427,10 @@ export async function checkCurrentGlassesForUpdate(
|
|
|
284
427
|
buildNumber = useGlassesStore.getState().buildNumber
|
|
285
428
|
}
|
|
286
429
|
|
|
287
|
-
if (!buildNumber) {
|
|
430
|
+
if (!buildNumber || !(parseInt(buildNumber, 10) > 0)) {
|
|
431
|
+
// Covers absent, non-numeric, and zero build numbers: a glasses version we
|
|
432
|
+
// cannot parse means nothing is verifiable, so skip rather than ghost an
|
|
433
|
+
// "up to date" result from a comparison that never really ran.
|
|
288
434
|
return emptyCheckResult("missing_build")
|
|
289
435
|
}
|
|
290
436
|
|
|
@@ -325,8 +471,8 @@ export async function checkCurrentGlassesForUpdate(
|
|
|
325
471
|
})
|
|
326
472
|
}
|
|
327
473
|
|
|
328
|
-
const manifestUrl =
|
|
329
|
-
const result = await checkForOtaUpdate(manifestUrl, buildNumber, mtkFirmwareVersion, besFirmwareVersion)
|
|
474
|
+
const manifestUrl = resolveOtaManifestUrl(useGlassesStore.getState().otaVersionUrl, buildNumber)
|
|
475
|
+
const result = await checkForOtaUpdate(manifestUrl, buildNumber, mtkFirmwareVersion, besFirmwareVersion, floorVersionCode)
|
|
330
476
|
|
|
331
477
|
if (!result.hasCheckCompleted) {
|
|
332
478
|
return {
|
|
@@ -360,6 +506,7 @@ export async function checkCurrentGlassesForUpdate(
|
|
|
360
506
|
)
|
|
361
507
|
}
|
|
362
508
|
const updateAvailable = filteredUpdates.length > 0 && !!result.latestVersionInfo
|
|
509
|
+
const isApkDowngrade = result.isApkDowngrade && filteredUpdates.includes("apk")
|
|
363
510
|
const updateInfo = updateAvailable
|
|
364
511
|
? {
|
|
365
512
|
available: true,
|
|
@@ -367,6 +514,8 @@ export async function checkCurrentGlassesForUpdate(
|
|
|
367
514
|
versionName: result.latestVersionInfo?.versionName || "",
|
|
368
515
|
updates: filteredUpdates,
|
|
369
516
|
totalSize: 0,
|
|
517
|
+
...(filteredUpdates.includes("bes") && result.besVersion ? {besVersion: result.besVersion} : {}),
|
|
518
|
+
isDowngrade: isApkDowngrade,
|
|
370
519
|
}
|
|
371
520
|
: null
|
|
372
521
|
|
|
@@ -376,8 +525,19 @@ export async function checkCurrentGlassesForUpdate(
|
|
|
376
525
|
...result,
|
|
377
526
|
updateAvailable,
|
|
378
527
|
updates: filteredUpdates,
|
|
528
|
+
isApkDowngrade,
|
|
379
529
|
updateInfo,
|
|
380
|
-
|
|
530
|
+
// Required-update policy: an EXPLICIT isRequired in the manifest is the
|
|
531
|
+
// release-engineering escape hatch and wins in both directions. When absent,
|
|
532
|
+
// upgrades keep the historical forced default, but downgrades are always
|
|
533
|
+
// skippable — a version change that resets glasses settings must be
|
|
534
|
+
// declinable unless someone explicitly forced it.
|
|
535
|
+
isRequired:
|
|
536
|
+
result.latestVersionInfo?.isRequired === true
|
|
537
|
+
? true
|
|
538
|
+
: result.latestVersionInfo?.isRequired === false
|
|
539
|
+
? false
|
|
540
|
+
: !isApkDowngrade,
|
|
381
541
|
manifestUrl,
|
|
382
542
|
buildNumber,
|
|
383
543
|
mtkFirmwareVersion,
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import * as Calendar from "expo-calendar"
|
|
2
|
+
import {Platform} from "react-native"
|
|
3
|
+
import {check, PERMISSIONS, RESULTS} from "react-native-permissions"
|
|
2
4
|
|
|
3
5
|
import {MiniappErrorCode, type CalendarListResult} from "@mentra/miniapp"
|
|
4
6
|
|
|
@@ -12,14 +14,35 @@ import {
|
|
|
12
14
|
|
|
13
15
|
export {PhoneCalendarError} from "./PhoneCalendarHelpers"
|
|
14
16
|
|
|
17
|
+
type CalendarPermissionStatus = Awaited<
|
|
18
|
+
ReturnType<typeof Calendar.getCalendarPermissionsAsync>
|
|
19
|
+
>["status"]
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* expo-calendar reports anything below EventKit full access as not granted,
|
|
23
|
+
* so distinguish the states the user can actually fix. "Reopen the miniapp"
|
|
24
|
+
* is only true while the permission is undetermined — once iOS has resolved
|
|
25
|
+
* it (denied or write-only/limited), only Settings can change it.
|
|
26
|
+
*/
|
|
27
|
+
async function calendarPermissionMessage(status: CalendarPermissionStatus): Promise<string> {
|
|
28
|
+
if (status === "undetermined") {
|
|
29
|
+
return "Calendar permission has not been granted yet. Reopen the miniapp and allow calendar access."
|
|
30
|
+
}
|
|
31
|
+
if (Platform.OS === "ios") {
|
|
32
|
+
const writeOnly = await check(PERMISSIONS.IOS.CALENDARS_WRITE_ONLY).catch(() => null)
|
|
33
|
+
if (writeOnly === RESULTS.GRANTED || writeOnly === RESULTS.LIMITED) {
|
|
34
|
+
return 'Calendar access is limited to write-only. Enable "Full Access" for Mentra in Settings › Privacy & Security › Calendars.'
|
|
35
|
+
}
|
|
36
|
+
return "Calendar access is denied. Enable it for Mentra in Settings › Privacy & Security › Calendars."
|
|
37
|
+
}
|
|
38
|
+
return "Calendar access is denied. Enable the Calendar permission for Mentra in your phone's app settings."
|
|
39
|
+
}
|
|
40
|
+
|
|
15
41
|
export async function listPhoneCalendarEvents(request: CalendarListRequest): Promise<CalendarListResult> {
|
|
16
42
|
const {startsAt, endsAt, limit} = validateCalendarListRequest(request)
|
|
17
43
|
const permission = await Calendar.getCalendarPermissionsAsync()
|
|
18
44
|
if (permission.status !== "granted") {
|
|
19
|
-
throw new PhoneCalendarError(
|
|
20
|
-
MiniappErrorCode.PERMISSION_DENIED,
|
|
21
|
-
"Calendar permission is not granted. Reopen the miniapp and grant calendar access.",
|
|
22
|
-
)
|
|
45
|
+
throw new PhoneCalendarError(MiniappErrorCode.PERMISSION_DENIED, await calendarPermissionMessage(permission.status))
|
|
23
46
|
}
|
|
24
47
|
|
|
25
48
|
const calendars = await Calendar.getCalendarsAsync(Calendar.EntityTypes.EVENT)
|
|
@@ -41,6 +41,21 @@ export class PhoneCameraFovCoordinator {
|
|
|
41
41
|
|
|
42
42
|
constructor(private readonly legacyRestartSettleMs = LEGACY_CAMERA_RESTART_SETTLE_MS) {}
|
|
43
43
|
|
|
44
|
+
/** Report-safe FOV ownership snapshot for incident diagnostics. */
|
|
45
|
+
getDiagnosticSnapshot(): Record<string, unknown> {
|
|
46
|
+
return {
|
|
47
|
+
owners: [...this.overrides.entries()]
|
|
48
|
+
.sort(([, a], [, b]) => b.order - a.order)
|
|
49
|
+
.map(([packageName, entry]) => ({
|
|
50
|
+
packageName,
|
|
51
|
+
effective: entry.leaseId === this.effectiveLeaseId,
|
|
52
|
+
fov: entry.fov,
|
|
53
|
+
roiPosition: entry.roiPosition,
|
|
54
|
+
})),
|
|
55
|
+
legacyMode: this.legacyMode,
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
44
59
|
setOverride(packageName: string, request: CameraFovRequest): Promise<CameraFovResult> {
|
|
45
60
|
return this.enqueue(async () => {
|
|
46
61
|
const previous = this.overrides.get(packageName)
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Phone-notifications sync — engine-owned. Pushes the notification-forwarding
|
|
3
|
-
*
|
|
3
|
+
* operational kill switch + config (`android_notification_listener_enabled`
|
|
4
|
+
* and `notifications_blocklist`) to the native
|
|
4
5
|
* NotificationListener via `CrustModule.setNotificationConfig`, so
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* the blocklist reaches the listener for ANY host — not just the Mentra App,
|
|
7
|
+
* where this used to live in MantleManager. The Android service is desired by
|
|
8
|
+
* default now that its cold start is isolated from React Native. Native code
|
|
9
|
+
* keeps the component discoverable in Android's notification-access Settings,
|
|
10
|
+
* but only binds the service after access is granted. The debug setting remains
|
|
11
|
+
* a hidden emergency kill switch; Notify's running state gates presentation,
|
|
12
|
+
* not capture. Android-only; a no-op elsewhere. Started by `engine.start()`.
|
|
8
13
|
*/
|
|
9
14
|
import {shallow} from "zustand/shallow"
|
|
10
15
|
import CrustModule from "@mentra/crust"
|
|
@@ -15,12 +20,9 @@ let unsubscribe: (() => void) | null = null
|
|
|
15
20
|
|
|
16
21
|
function pushConfig(): void {
|
|
17
22
|
const s = useSettingsStore.getState()
|
|
18
|
-
const
|
|
23
|
+
const listenerEnabled = Boolean(s.getSetting(SETTINGS.android_notification_listener_enabled.key))
|
|
19
24
|
const blocklist = s.getSetting(SETTINGS.notifications_blocklist.key)
|
|
20
|
-
|
|
21
|
-
// Android-only, but CrustModule.setNotificationConfig is a safe no-op elsewhere;
|
|
22
|
-
// the Android-only gating lives on the facade's setEnabled/setBlocklist.
|
|
23
|
-
CrustModule.setNotificationConfig(enabled, Array.isArray(blocklist) ? blocklist : []).catch((err: unknown) =>
|
|
25
|
+
CrustModule.setNotificationConfig(listenerEnabled, Array.isArray(blocklist) ? blocklist : []).catch((err: unknown) =>
|
|
24
26
|
console.warn(`PhoneNotificationsSync: setNotificationConfig failed: ${(err as Error)?.message ?? err}`),
|
|
25
27
|
)
|
|
26
28
|
}
|
|
@@ -30,7 +32,7 @@ export function startPhoneNotificationsSync(): void {
|
|
|
30
32
|
pushConfig() // initial sync so the listener has the current config
|
|
31
33
|
unsubscribe = useSettingsStore.subscribe(
|
|
32
34
|
(state) => ({
|
|
33
|
-
|
|
35
|
+
listenerEnabled: state.getSetting(SETTINGS.android_notification_listener_enabled.key),
|
|
34
36
|
blocklist: state.getSetting(SETTINGS.notifications_blocklist.key),
|
|
35
37
|
}),
|
|
36
38
|
() => pushConfig(),
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import BluetoothSdk from "@mentra/bluetooth-sdk/internal"
|
|
22
|
-
import type {PhotoSize} from "@mentra/bluetooth-sdk/internal"
|
|
22
|
+
import type {PhotoSize, PhotoTransferMethod} from "@mentra/bluetooth-sdk/internal"
|
|
23
23
|
import {cloudClientService} from "./CloudClientService"
|
|
24
24
|
import {isGlassesConnected} from "./GlassesReadiness"
|
|
25
25
|
import {useGlassesStore} from "../stores/glasses"
|
|
@@ -49,8 +49,8 @@ export interface PhotoOpts {
|
|
|
49
49
|
/** Legacy cloud size names are normalized before the native take_photo command. */
|
|
50
50
|
size?: "low" | "medium" | "high" | "max" | "small" | "large" | "full"
|
|
51
51
|
mode?: "photo" | "text"
|
|
52
|
-
/**
|
|
53
|
-
transferMethod?:
|
|
52
|
+
/** Select direct-only, phone-relayed BLE, or the default Wi-Fi/BLE fallback policy. */
|
|
53
|
+
transferMethod?: PhotoTransferMethod
|
|
54
54
|
compress?: "none" | "low" | "medium" | "high"
|
|
55
55
|
sound?: boolean
|
|
56
56
|
saveToGallery?: boolean
|
|
@@ -60,8 +60,10 @@ export interface PhotoOpts {
|
|
|
60
60
|
isoCap?: number
|
|
61
61
|
noiseReduction?: boolean
|
|
62
62
|
edgeEnhancement?: boolean
|
|
63
|
-
|
|
63
|
+
/** ZSL preview buffering. */
|
|
64
64
|
zsl?: boolean
|
|
65
|
+
/** MFNR still capture. */
|
|
66
|
+
mfnr?: boolean
|
|
65
67
|
ispDigitalGain?: number
|
|
66
68
|
ispAnalogGain?: string
|
|
67
69
|
}
|
|
@@ -91,6 +93,15 @@ export class PhotoError extends Error {
|
|
|
91
93
|
}
|
|
92
94
|
}
|
|
93
95
|
|
|
96
|
+
function parsePhotoTransferMethod(value: unknown): PhotoTransferMethod | undefined {
|
|
97
|
+
if (value === undefined) return undefined
|
|
98
|
+
if (value === "auto" || value === "direct" || value === "ble") return value
|
|
99
|
+
throw new PhotoError(
|
|
100
|
+
"INVALID_ARGUMENT",
|
|
101
|
+
`Invalid transferMethod ${JSON.stringify(value)}. Expected "auto", "direct", or "ble".`,
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
94
105
|
interface ActiveRequest {
|
|
95
106
|
packageName: string
|
|
96
107
|
abort: AbortController
|
|
@@ -138,7 +149,18 @@ export class PhonePhotoCoordinator {
|
|
|
138
149
|
{requestId: string; durationMs: number; expiryTimer?: ReturnType<typeof setTimeout>}
|
|
139
150
|
>()
|
|
140
151
|
|
|
152
|
+
/** Report-safe camera ownership snapshot for incident diagnostics. */
|
|
153
|
+
getDiagnosticSnapshot(): Record<string, unknown> {
|
|
154
|
+
return {
|
|
155
|
+
captureOwners: [...new Set([...this.activeRequests.values()].map((request) => request.packageName))].sort(),
|
|
156
|
+
warmUpOwners: [...this.activeWarmUps.keys()].sort(),
|
|
157
|
+
activeCaptureCount: this.activeRequests.size,
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
141
161
|
async takePhoto(packageName: string, opts: PhotoOpts): Promise<PhotoTaken> {
|
|
162
|
+
const transferMethod = parsePhotoTransferMethod(opts.transferMethod)
|
|
163
|
+
|
|
142
164
|
// Pre-check: if glasses aren't even connected, the BLE photo command
|
|
143
165
|
// would be sent into the void and we'd wait 30s for the cloud long-poll
|
|
144
166
|
// to time out. Fail fast with a typed error.
|
|
@@ -241,7 +263,7 @@ export class PhonePhotoCoordinator {
|
|
|
241
263
|
mode: opts.mode ?? "photo",
|
|
242
264
|
webhookUrl: uploadUrl,
|
|
243
265
|
authToken: null,
|
|
244
|
-
...(isLoopbackUpload
|
|
266
|
+
...(isLoopbackUpload ? {transferMethod: "ble" as const} : transferMethod ? {transferMethod} : {}),
|
|
245
267
|
compress: toNativeCompression(opts.compress),
|
|
246
268
|
save: opts.saveToGallery ?? false,
|
|
247
269
|
sound: opts.sound ?? true,
|
|
@@ -251,8 +273,8 @@ export class PhonePhotoCoordinator {
|
|
|
251
273
|
isoCap: opts.isoCap,
|
|
252
274
|
noiseReduction: opts.noiseReduction,
|
|
253
275
|
edgeEnhancement: opts.edgeEnhancement,
|
|
254
|
-
|
|
255
|
-
|
|
276
|
+
...(opts.zsl != null ? {zsl: opts.zsl} : {}),
|
|
277
|
+
...(opts.mfnr != null ? {mfnr: opts.mfnr} : {}),
|
|
256
278
|
ispDigitalGain: opts.ispDigitalGain,
|
|
257
279
|
ispAnalogGain: opts.ispAnalogGain,
|
|
258
280
|
}).catch((err) => {
|
|
@@ -322,6 +344,8 @@ export class PhonePhotoCoordinator {
|
|
|
322
344
|
mode?: "photo" | "text"
|
|
323
345
|
exposureTimeNs?: number
|
|
324
346
|
durationMs?: number
|
|
347
|
+
zsl?: boolean
|
|
348
|
+
mfnr?: boolean
|
|
325
349
|
},
|
|
326
350
|
): Promise<void> {
|
|
327
351
|
// Pre-check: if glasses aren't connected, the BLE warm-up command would be
|
|
@@ -347,6 +371,8 @@ export class PhonePhotoCoordinator {
|
|
|
347
371
|
mode: opts.mode ?? "photo",
|
|
348
372
|
exposureTimeNs: opts.exposureTimeNs ?? null,
|
|
349
373
|
durationMs,
|
|
374
|
+
...(opts.zsl != null ? {zsl: opts.zsl} : {}),
|
|
375
|
+
...(opts.mfnr != null ? {mfnr: opts.mfnr} : {}),
|
|
350
376
|
})
|
|
351
377
|
if (this.activeWarmUps.get(packageName) === lease) {
|
|
352
378
|
lease.expiryTimer = setTimeout(() => {
|