@mentra/bluetooth-sdk 0.1.0

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 (278) hide show
  1. package/README.md +278 -0
  2. package/android/build.gradle +167 -0
  3. package/android/lc3Lib/CMakeLists.txt +6 -0
  4. package/android/lc3Lib/build.gradle +109 -0
  5. package/android/lc3Lib/proguard-rules.pro +33 -0
  6. package/android/lc3Lib/src/main/AndroidManifest.xml +2 -0
  7. package/android/lc3Lib/src/main/cpp/CMakeLists.txt +8 -0
  8. package/android/lc3Lib/src/main/cpp/google_opus_stuff/CMakeLists.txt +30 -0
  9. package/android/lc3Lib/src/main/cpp/google_opus_stuff/jni/CMakeLists.txt +7 -0
  10. package/android/lc3Lib/src/main/cpp/google_opus_stuff/jni/ogg_opus_encoder.cc +96 -0
  11. package/android/lc3Lib/src/main/cpp/google_opus_stuff/jni/ogg_opus_encoder.h +53 -0
  12. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/Makefile.am +6 -0
  13. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/config_types.h +26 -0
  14. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/config_types.h.in +26 -0
  15. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/ogg.h +209 -0
  16. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/os_types.h +158 -0
  17. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus.h +981 -0
  18. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_custom.h +342 -0
  19. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_defines.h +799 -0
  20. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_multistream.h +660 -0
  21. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_projection.h +568 -0
  22. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_types.h +166 -0
  23. package/android/lc3Lib/src/main/cpp/google_opus_stuff/ogg_opus_encoder.cc +268 -0
  24. package/android/lc3Lib/src/main/cpp/google_opus_stuff/ogg_opus_encoder.h +115 -0
  25. package/android/lc3Lib/src/main/cpp/google_opus_stuff/opus_tools/opus_header.h +59 -0
  26. package/android/lc3Lib/src/main/cpp/liblc3/CMakeLists.txt +38 -0
  27. package/android/lc3Lib/src/main/cpp/liblc3/include/lc3.h +309 -0
  28. package/android/lc3Lib/src/main/cpp/liblc3/include/lc3_private.h +162 -0
  29. package/android/lc3Lib/src/main/cpp/liblc3/include/rnnoise.h +114 -0
  30. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/CMakeLists.txt +19 -0
  31. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/attdet.c +92 -0
  32. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/attdet.h +44 -0
  33. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/bits.c +375 -0
  34. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/bits.h +315 -0
  35. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/bwdet.c +129 -0
  36. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/bwdet.h +69 -0
  37. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/common.h +148 -0
  38. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/energy.c +70 -0
  39. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/energy.h +43 -0
  40. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/fastmath.h +158 -0
  41. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/lc3.c +702 -0
  42. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/ltpf.c +893 -0
  43. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/ltpf.h +111 -0
  44. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/ltpf_arm.h +506 -0
  45. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/ltpf_neon.h +281 -0
  46. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/makefile.mk +35 -0
  47. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/mdct.c +452 -0
  48. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/mdct.h +57 -0
  49. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/mdct_neon.h +296 -0
  50. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/meson.build +46 -0
  51. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/plc.c +61 -0
  52. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/plc.h +57 -0
  53. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/sns.c +880 -0
  54. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/sns.h +103 -0
  55. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/spec.c +904 -0
  56. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/spec.h +119 -0
  57. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/tables.c +3457 -0
  58. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/tables.h +94 -0
  59. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/tns.c +457 -0
  60. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/tns.h +99 -0
  61. package/android/lc3Lib/src/main/cpp/liblc3/liblc3.cpp +159 -0
  62. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/_kiss_fft_guts.h +182 -0
  63. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/arch.h +261 -0
  64. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/celt_lpc.c +279 -0
  65. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/celt_lpc.h +59 -0
  66. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/common.h +48 -0
  67. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/compile.sh +3 -0
  68. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/denoise.c +646 -0
  69. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/kiss_fft.c +601 -0
  70. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/kiss_fft.h +203 -0
  71. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/opus_types.h +159 -0
  72. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/pitch.c +526 -0
  73. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/pitch.h +149 -0
  74. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn.c +178 -0
  75. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn.h +69 -0
  76. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn_data.c +11051 -0
  77. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn_data.h +34 -0
  78. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn_reader.c +168 -0
  79. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn_train.py +66 -0
  80. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/tansig_table.h +45 -0
  81. package/android/lc3Lib/src/main/java/com/mentra/lc3Lib/Lc3Cpp.java +38 -0
  82. package/android/lc3Lib/third_party/CMakeLists.txt +23 -0
  83. package/android/lc3Lib/third_party/CMakeLists_libogg.txt +18 -0
  84. package/android/lc3Lib/third_party/CMakeLists_libopus.txt +18 -0
  85. package/android/lc3Lib/third_party/CMakeLists_opus-tools.txt +23 -0
  86. package/android/lc3Lib/third_party/opus_tools/src/AUTHORS +5 -0
  87. package/android/lc3Lib/third_party/opus_tools/src/CMakeLists.txt +4 -0
  88. package/android/lc3Lib/third_party/opus_tools/src/COPYING +371 -0
  89. package/android/lc3Lib/third_party/opus_tools/src/ChangeLog +3 -0
  90. package/android/lc3Lib/third_party/opus_tools/src/Makefile.am +42 -0
  91. package/android/lc3Lib/third_party/opus_tools/src/Makefile.unix +23 -0
  92. package/android/lc3Lib/third_party/opus_tools/src/NEWS +0 -0
  93. package/android/lc3Lib/third_party/opus_tools/src/README +0 -0
  94. package/android/lc3Lib/third_party/opus_tools/src/autogen.sh +115 -0
  95. package/android/lc3Lib/third_party/opus_tools/src/configure.ac +293 -0
  96. package/android/lc3Lib/third_party/opus_tools/src/src/arch.h +239 -0
  97. package/android/lc3Lib/third_party/opus_tools/src/src/audio-in.c +1046 -0
  98. package/android/lc3Lib/third_party/opus_tools/src/src/diag_range.c +245 -0
  99. package/android/lc3Lib/third_party/opus_tools/src/src/diag_range.h +28 -0
  100. package/android/lc3Lib/third_party/opus_tools/src/src/info_opus.c +320 -0
  101. package/android/lc3Lib/third_party/opus_tools/src/src/info_opus.h +51 -0
  102. package/android/lc3Lib/third_party/opus_tools/src/src/lpc.c +157 -0
  103. package/android/lc3Lib/third_party/opus_tools/src/src/lpc.h +27 -0
  104. package/android/lc3Lib/third_party/opus_tools/src/src/opus_header.c +286 -0
  105. package/android/lc3Lib/third_party/opus_tools/src/src/opus_header.h +59 -0
  106. package/android/lc3Lib/third_party/opus_tools/src/src/opusdec.c +884 -0
  107. package/android/lc3Lib/third_party/opus_tools/src/src/opusenc.c +1021 -0
  108. package/android/lc3Lib/third_party/opus_tools/src/src/opusenc.h +101 -0
  109. package/android/lc3Lib/third_party/opus_tools/src/src/opusinfo.c +639 -0
  110. package/android/lc3Lib/third_party/opus_tools/src/src/opusinfo.h +51 -0
  111. package/android/lc3Lib/third_party/opus_tools/src/src/os_support.h +167 -0
  112. package/android/lc3Lib/third_party/opus_tools/src/src/resample.c +1137 -0
  113. package/android/lc3Lib/third_party/opus_tools/src/src/speex_resampler.h +344 -0
  114. package/android/lc3Lib/third_party/opus_tools/src/src/stack_alloc.h +115 -0
  115. package/android/lc3Lib/third_party/opus_tools/src/src/wav_io.c +125 -0
  116. package/android/lc3Lib/third_party/opus_tools/src/src/wav_io.h +62 -0
  117. package/android/lc3Lib/third_party/opus_tools/src/src/wave_out.c +223 -0
  118. package/android/lc3Lib/third_party/opus_tools/src/src/wave_out.h +60 -0
  119. package/android/settings.gradle +1 -0
  120. package/android/src/main/AndroidManifest.xml +40 -0
  121. package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +528 -0
  122. package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +610 -0
  123. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +1616 -0
  124. package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +314 -0
  125. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothModels.kt +1710 -0
  126. package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +554 -0
  127. package/android/src/main/java/com/mentra/bluetoothsdk/ObservableStore.kt +68 -0
  128. package/android/src/main/java/com/mentra/bluetoothsdk/controllers/ControllerManager.kt +158 -0
  129. package/android/src/main/java/com/mentra/bluetoothsdk/controllers/R1.kt +934 -0
  130. package/android/src/main/java/com/mentra/bluetoothsdk/services/Foreground.kt +175 -0
  131. package/android/src/main/java/com/mentra/bluetoothsdk/services/PhoneMic.kt +1109 -0
  132. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.java +3974 -0
  133. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +3551 -0
  134. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Mach1.java +1327 -0
  135. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.java +6959 -0
  136. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +1655 -0
  137. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraosBle.java +53675 -0
  138. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +202 -0
  139. package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Simulated.kt +224 -0
  140. package/android/src/main/java/com/mentra/bluetoothsdk/stt/STTTools.kt +340 -0
  141. package/android/src/main/java/com/mentra/bluetoothsdk/stt/SherpaOnnxTranscriber.kt +465 -0
  142. package/android/src/main/java/com/mentra/bluetoothsdk/stt/VadGateSpeechPolicy.kt +221 -0
  143. package/android/src/main/java/com/mentra/bluetoothsdk/utils/AES.java +106 -0
  144. package/android/src/main/java/com/mentra/bluetoothsdk/utils/AudioSessionMonitor.kt +189 -0
  145. package/android/src/main/java/com/mentra/bluetoothsdk/utils/BitmapJavaUtils.java +169 -0
  146. package/android/src/main/java/com/mentra/bluetoothsdk/utils/BlePhotoUploadService.java +179 -0
  147. package/android/src/main/java/com/mentra/bluetoothsdk/utils/Constants.kt +44 -0
  148. package/android/src/main/java/com/mentra/bluetoothsdk/utils/G1Text.kt +436 -0
  149. package/android/src/main/java/com/mentra/bluetoothsdk/utils/IncidentLogBleRelayNaming.java +29 -0
  150. package/android/src/main/java/com/mentra/bluetoothsdk/utils/IncidentLogBleUploadService.java +76 -0
  151. package/android/src/main/java/com/mentra/bluetoothsdk/utils/K900ProtocolUtils.java +791 -0
  152. package/android/src/main/java/com/mentra/bluetoothsdk/utils/MessageChunker.java +186 -0
  153. package/android/src/main/java/com/mentra/bluetoothsdk/utils/NexSGCUtils.kt +589 -0
  154. package/android/src/main/java/com/mentra/bluetoothsdk/utils/PhoneAudioMonitor.kt +303 -0
  155. package/android/src/main/java/com/mentra/bluetoothsdk/utils/SmartGlassesConnectionState.java +9 -0
  156. package/android/src/main/java/com/mentra/bluetoothsdk/utils/audio/ByteUtilAudioPlayer.java +655 -0
  157. package/android/src/main/java/com/mentra/bluetoothsdk/utils/audio/Lc3Player.java +441 -0
  158. package/android/src/main/java/com/mentra/bluetoothsdk/utils/audio/PCMAudioPlayer.java +431 -0
  159. package/android/src/main/res/values/strings.xml +4 -0
  160. package/app.plugin.js +1 -0
  161. package/build/BluetoothSdk.types.d.ts +498 -0
  162. package/build/BluetoothSdk.types.d.ts.map +1 -0
  163. package/build/BluetoothSdk.types.js +23 -0
  164. package/build/BluetoothSdk.types.js.map +1 -0
  165. package/build/BluetoothSdkModule.d.ts +71 -0
  166. package/build/BluetoothSdkModule.d.ts.map +1 -0
  167. package/build/BluetoothSdkModule.js +106 -0
  168. package/build/BluetoothSdkModule.js.map +1 -0
  169. package/build/index.d.ts +3 -0
  170. package/build/index.d.ts.map +1 -0
  171. package/build/index.js +5 -0
  172. package/build/index.js.map +1 -0
  173. package/expo-module.config.json +9 -0
  174. package/ios/BluetoothSdkModule.swift +665 -0
  175. package/ios/MentraBluetoothSDK.podspec +77 -0
  176. package/ios/Packages/CoreObjC/CoreObjC.xcodeproj/project.pbxproj +213 -0
  177. package/ios/Packages/CoreObjC/PcmConverter.h +22 -0
  178. package/ios/Packages/CoreObjC/PcmConverter.m +266 -0
  179. package/ios/Packages/CoreObjC/attdet.c +92 -0
  180. package/ios/Packages/CoreObjC/attdet.h +44 -0
  181. package/ios/Packages/CoreObjC/bits.c +375 -0
  182. package/ios/Packages/CoreObjC/bits.h +315 -0
  183. package/ios/Packages/CoreObjC/bwdet.c +129 -0
  184. package/ios/Packages/CoreObjC/bwdet.h +69 -0
  185. package/ios/Packages/CoreObjC/common.h +151 -0
  186. package/ios/Packages/CoreObjC/energy.c +70 -0
  187. package/ios/Packages/CoreObjC/energy.h +43 -0
  188. package/ios/Packages/CoreObjC/fastmath.h +158 -0
  189. package/ios/Packages/CoreObjC/lc3.c +704 -0
  190. package/ios/Packages/CoreObjC/lc3.h +313 -0
  191. package/ios/Packages/CoreObjC/lc3_cpp.h +283 -0
  192. package/ios/Packages/CoreObjC/lc3_private.h +163 -0
  193. package/ios/Packages/CoreObjC/ltpf.c +905 -0
  194. package/ios/Packages/CoreObjC/ltpf.h +111 -0
  195. package/ios/Packages/CoreObjC/ltpf_arm.h +506 -0
  196. package/ios/Packages/CoreObjC/ltpf_neon.h +281 -0
  197. package/ios/Packages/CoreObjC/makefile.mk +35 -0
  198. package/ios/Packages/CoreObjC/mdct.c +469 -0
  199. package/ios/Packages/CoreObjC/mdct.h +57 -0
  200. package/ios/Packages/CoreObjC/mdct_neon.h +296 -0
  201. package/ios/Packages/CoreObjC/meson.build +61 -0
  202. package/ios/Packages/CoreObjC/plc.c +61 -0
  203. package/ios/Packages/CoreObjC/plc.h +57 -0
  204. package/ios/Packages/CoreObjC/rnnoise.h +114 -0
  205. package/ios/Packages/CoreObjC/sns.c +880 -0
  206. package/ios/Packages/CoreObjC/sns.h +103 -0
  207. package/ios/Packages/CoreObjC/spec.c +907 -0
  208. package/ios/Packages/CoreObjC/spec.h +119 -0
  209. package/ios/Packages/CoreObjC/tables.c +3457 -0
  210. package/ios/Packages/CoreObjC/tables.h +94 -0
  211. package/ios/Packages/CoreObjC/tns.c +457 -0
  212. package/ios/Packages/CoreObjC/tns.h +99 -0
  213. package/ios/Packages/SherpaOnnx/Model/joiner.onnx +0 -0
  214. package/ios/Packages/SherpaOnnx/Model/tokens.txt +502 -0
  215. package/ios/Packages/SherpaOnnx/SherpaOnnx.swift +1659 -0
  216. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Headers/cargs.h +162 -0
  217. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Headers/sherpa-onnx/c-api/c-api.h +1852 -0
  218. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Headers/sherpa-onnx/c-api/cxx-api.h +674 -0
  219. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Info.plist +44 -0
  220. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/libsherpa-onnx.a +0 -0
  221. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/sherpa-onnx.a +0 -0
  222. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/libsherpa-onnx.a +0 -0
  223. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/sherpa-onnx.a +0 -0
  224. package/ios/Packages/VAD/Common/VAD/VADQuality.swift +41 -0
  225. package/ios/Packages/VAD/Common/VAD/VADState.swift +26 -0
  226. package/ios/Packages/VAD/Common/VAD/VADStrategy.swift +29 -0
  227. package/ios/Packages/VAD/Common/VAD/VADType.swift +14 -0
  228. package/ios/Packages/VAD/Data/Configuration.swift +68 -0
  229. package/ios/Packages/VAD/Data/FrameSize.swift +39 -0
  230. package/ios/Packages/VAD/Data/Record.swift +13 -0
  231. package/ios/Packages/VAD/Data/Result.swift +22 -0
  232. package/ios/Packages/VAD/Data/SampleRate.swift +48 -0
  233. package/ios/Packages/VAD/Silero/Model/silero_vad.onnx +0 -0
  234. package/ios/Packages/VAD/Silero/SileroVAD.swift +284 -0
  235. package/ios/Packages/VAD/Silero/SileroVADStrategy.swift +64 -0
  236. package/ios/Packages/libbz2/module.modulemap +5 -0
  237. package/ios/Packages/libbz2/shim.h +1 -0
  238. package/ios/Source/Bridge.swift +353 -0
  239. package/ios/Source/Bridging-Header.h +16 -0
  240. package/ios/Source/DeviceManager.swift +1478 -0
  241. package/ios/Source/DeviceStore.swift +295 -0
  242. package/ios/Source/MentraBluetoothSDK.swift +2439 -0
  243. package/ios/Source/ObservableStore.swift +88 -0
  244. package/ios/Source/PrivacyInfo.xcprivacy +23 -0
  245. package/ios/Source/controllers/ControllerManager.swift +187 -0
  246. package/ios/Source/controllers/R1.swift +827 -0
  247. package/ios/Source/services/PhoneMic.swift +662 -0
  248. package/ios/Source/sgcs/Frame.swift +651 -0
  249. package/ios/Source/sgcs/G1.swift +2505 -0
  250. package/ios/Source/sgcs/G2.swift +3730 -0
  251. package/ios/Source/sgcs/Mach1.swift +543 -0
  252. package/ios/Source/sgcs/MentraLive.swift +4708 -0
  253. package/ios/Source/sgcs/MentraNex.swift +2479 -0
  254. package/ios/Source/sgcs/SGCManager.swift +243 -0
  255. package/ios/Source/sgcs/Simulated.swift +270 -0
  256. package/ios/Source/sgcs/mentraos_ble.pb.swift +3811 -0
  257. package/ios/Source/stt/STTTools.swift +154 -0
  258. package/ios/Source/stt/SherpaOnnxTranscriber.swift +401 -0
  259. package/ios/Source/utils/AudioSessionMonitor.swift +283 -0
  260. package/ios/Source/utils/Constants.swift +74 -0
  261. package/ios/Source/utils/Enums.swift +95 -0
  262. package/ios/Source/utils/G1Text.swift +2067 -0
  263. package/ios/Source/utils/JSCExperiment.swift +241 -0
  264. package/ios/Source/utils/MemoryMonitor.swift +44 -0
  265. package/ios/Source/utils/MessageChunker.swift +164 -0
  266. package/ios/Source/utils/Models.swift +135 -0
  267. package/ios/Source/utils/PhoneAudioMonitor.swift +226 -0
  268. package/ios/Source/utils/TarBz2Extractor.swift +206 -0
  269. package/package.json +78 -0
  270. package/plugin/build/index.d.ts +6 -0
  271. package/plugin/build/index.js +12 -0
  272. package/plugin/build/withAndroid.d.ts +4 -0
  273. package/plugin/build/withAndroid.js +80 -0
  274. package/plugin/build/withIos.d.ts +4 -0
  275. package/plugin/build/withIos.js +64 -0
  276. package/src/BluetoothSdk.types.ts +581 -0
  277. package/src/BluetoothSdkModule.ts +259 -0
  278. package/src/index.ts +4 -0
@@ -0,0 +1,554 @@
1
+ package com.mentra.bluetoothsdk
2
+
3
+ import android.bluetooth.BluetoothManager
4
+ import android.content.Context
5
+ import android.os.Handler
6
+ import android.os.Looper
7
+ import com.mentra.bluetoothsdk.utils.ConnTypes
8
+ import com.mentra.bluetoothsdk.utils.ControllerTypes
9
+ import com.mentra.bluetoothsdk.utils.PhoneAudioMonitor
10
+ import java.util.Collections
11
+
12
+ class MentraBluetoothSdk private constructor(
13
+ context: Context,
14
+ private val config: MentraBluetoothSdkConfig,
15
+ listener: MentraBluetoothSdkListener,
16
+ ) : AutoCloseable {
17
+ private val appContext = context.applicationContext
18
+ private val mainHandler = Handler(Looper.getMainLooper())
19
+ private val deviceManager: DeviceManager
20
+ private val listeners =
21
+ Collections.synchronizedSet(mutableSetOf<MentraBluetoothSdkListener>())
22
+ private val discoveredDeviceNames = mutableSetOf<String>()
23
+ private val bridgeEventSinkId: String
24
+ private val storeListenerId: String
25
+ private var suppressDefaultDeviceEvents = false
26
+
27
+ init {
28
+ listeners.add(listener)
29
+ Bridge.initialize(appContext)
30
+ deviceManager = DeviceManager.getInstance()
31
+ bridgeEventSinkId = Bridge.addEventSink { eventName, data -> dispatchBridgeEvent(eventName, data) }
32
+ storeListenerId = DeviceStore.store.addListener { category, changes -> dispatchStoreUpdate(category, changes) }
33
+ }
34
+
35
+ companion object {
36
+ private val DEFAULT_DEVICE_KEYS = setOf("default_wearable", "device_name", "device_address")
37
+
38
+ @JvmStatic
39
+ fun create(
40
+ context: Context,
41
+ listener: MentraBluetoothSdkListener,
42
+ ): MentraBluetoothSdk = create(context, MentraBluetoothSdkConfig(), listener)
43
+
44
+ @JvmStatic
45
+ fun create(
46
+ context: Context,
47
+ config: MentraBluetoothSdkConfig,
48
+ listener: MentraBluetoothSdkListener,
49
+ ): MentraBluetoothSdk = MentraBluetoothSdk(context, config, listener)
50
+ }
51
+
52
+ fun addListener(listener: MentraBluetoothSdkListener) {
53
+ listeners.add(listener)
54
+ }
55
+
56
+ fun removeListener(listener: MentraBluetoothSdkListener) {
57
+ listeners.remove(listener)
58
+ }
59
+
60
+ fun getGlassesStatus(): GlassesStatus =
61
+ GlassesStatus.fromMap(DeviceStore.store.getCategory("glasses"))
62
+
63
+ fun getBluetoothStatus(): BluetoothStatus =
64
+ BluetoothStatus.fromMap(DeviceStore.store.getCategory(ObservableStore.BLUETOOTH_CATEGORY))
65
+
66
+ fun getDefaultDevice(): Device? = currentDefaultDevice()
67
+
68
+ fun setDefaultDevice(device: Device?) {
69
+ if (device == null) {
70
+ clearDefaultDevice()
71
+ return
72
+ }
73
+ suppressDefaultDeviceEvents = true
74
+ try {
75
+ DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "default_wearable", device.model.deviceType)
76
+ DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "device_name", device.name)
77
+ DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "device_address", device.address ?: "")
78
+ } finally {
79
+ suppressDefaultDeviceEvents = false
80
+ }
81
+ dispatchDefaultDeviceChanged()
82
+ }
83
+
84
+ fun clearDefaultDevice() {
85
+ suppressDefaultDeviceEvents = true
86
+ try {
87
+ DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "default_wearable", "")
88
+ DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "device_name", "")
89
+ DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "device_address", "")
90
+ } finally {
91
+ suppressDefaultDeviceEvents = false
92
+ }
93
+ dispatchDefaultDeviceChanged()
94
+ }
95
+
96
+ fun startScan(model: DeviceModel) {
97
+ if (model != DeviceModel.SIMULATED) {
98
+ requireBluetoothReady("scan for glasses")
99
+ }
100
+ discoveredDeviceNames.clear()
101
+ DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "searching", true)
102
+ deviceManager.findCompatibleDevices(model.deviceType)
103
+ }
104
+
105
+ fun stopScan() {
106
+ deviceManager.stopScan()
107
+ DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "searching", false)
108
+ dispatchToListeners { it.onScanStopped(ScanStopReason.CANCELLED) }
109
+ }
110
+
111
+ @JvmOverloads
112
+ fun connect(device: Device, options: ConnectOptions = ConnectOptions()) {
113
+ if (device.model != DeviceModel.SIMULATED) {
114
+ requireBluetoothReady("connect to glasses")
115
+ }
116
+ val isController = ControllerTypes.ALL.contains(device.model.deviceType)
117
+ if (options.cancelExistingConnectionAttempt) {
118
+ if (isController) {
119
+ deviceManager.disconnectController()
120
+ } else {
121
+ cancelConnectionAttempt()
122
+ }
123
+ }
124
+ if (options.saveAsDefault && !isController) {
125
+ setDefaultDevice(device)
126
+ }
127
+ DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "pending_wearable", device.model.deviceType)
128
+ deviceManager.connectByName(device.name)
129
+ }
130
+
131
+ @JvmOverloads
132
+ fun connectDefault(options: ConnectOptions = ConnectOptions()) {
133
+ val defaultDevice =
134
+ currentDefaultDevice()
135
+ ?: throw BluetoothException(
136
+ "default_device_missing",
137
+ "Set a default glasses device before calling connectDefault.",
138
+ )
139
+ if (defaultDevice.model != DeviceModel.SIMULATED) {
140
+ requireBluetoothReady("connect to glasses")
141
+ }
142
+ if (options.cancelExistingConnectionAttempt) {
143
+ cancelConnectionAttempt()
144
+ }
145
+ deviceManager.connectDefault()
146
+ }
147
+
148
+ fun cancelConnectionAttempt() {
149
+ deviceManager.disconnect()
150
+ }
151
+
152
+ fun connectSimulated() {
153
+ deviceManager.connectSimulated()
154
+ }
155
+
156
+ fun disconnect() {
157
+ deviceManager.disconnect()
158
+ }
159
+
160
+ fun forget() {
161
+ deviceManager.forget()
162
+ }
163
+
164
+ fun displayText(request: DisplayTextRequest) {
165
+ deviceManager.displayText(request.toMap())
166
+ }
167
+
168
+ fun displayEvent(request: DisplayEventRequest) {
169
+ deviceManager.displayEvent(request.toMap())
170
+ }
171
+
172
+ fun clearDisplay() {
173
+ deviceManager.clearDisplay()
174
+ }
175
+
176
+ fun showDashboard() {
177
+ deviceManager.showDashboard()
178
+ }
179
+
180
+ @JvmOverloads
181
+ fun setBrightness(level: Int, autoMode: Boolean? = null) {
182
+ autoMode?.let { DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "auto_brightness", it) }
183
+ DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "brightness", level)
184
+ }
185
+
186
+ fun setAutoBrightness(enabled: Boolean) {
187
+ DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "auto_brightness", enabled)
188
+ }
189
+
190
+ fun setDashboardPosition(request: DashboardPositionRequest) {
191
+ DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "dashboard_height", request.height)
192
+ DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "dashboard_depth", request.depth)
193
+ }
194
+
195
+ fun setDashboardMenu(items: List<DashboardMenuItem>) {
196
+ DeviceStore.apply(
197
+ ObservableStore.BLUETOOTH_CATEGORY,
198
+ "menu_apps",
199
+ items.map { it.toMap() },
200
+ )
201
+ }
202
+
203
+ fun setHeadUpAngle(angleDegrees: Int) {
204
+ DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "head_up_angle", angleDegrees)
205
+ }
206
+
207
+ fun setScreenDisabled(disabled: Boolean) {
208
+ DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "screen_disabled", disabled)
209
+ }
210
+
211
+ fun setGalleryMode(mode: GalleryMode) {
212
+ DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "gallery_mode", mode == GalleryMode.AUTO)
213
+ }
214
+
215
+ fun setButtonPhotoSettings(settings: ButtonPhotoSettings) {
216
+ DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "button_photo_size", settings.size.value)
217
+ }
218
+
219
+ fun setButtonVideoRecordingSettings(settings: ButtonVideoRecordingSettings) {
220
+ DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "button_video_width", settings.width)
221
+ DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "button_video_height", settings.height)
222
+ DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "button_video_fps", settings.fps)
223
+ }
224
+
225
+ fun setButtonCameraLed(enabled: Boolean) {
226
+ DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "button_camera_led", enabled)
227
+ }
228
+
229
+ fun setButtonMaxRecordingTime(minutes: Int) {
230
+ DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "button_max_recording_time", minutes)
231
+ }
232
+
233
+ fun setCameraFov(fov: CameraFov) {
234
+ DeviceStore.apply(
235
+ ObservableStore.BLUETOOTH_CATEGORY,
236
+ "camera_fov",
237
+ mapOf("fov" to fov.fov, "roi_position" to fov.roiPosition),
238
+ )
239
+ }
240
+
241
+ fun setMicState(config: MicConfig) {
242
+ DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "should_send_pcm", config.sendPcmData)
243
+ DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "should_send_lc3", config.sendLc3Data)
244
+ DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "should_send_transcript", config.sendTranscript)
245
+ DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "bypass_vad", config.bypassVad)
246
+ deviceManager.setMicState()
247
+ }
248
+
249
+ fun setPreferredMic(preferredMic: MicPreference) {
250
+ DeviceStore.apply(ObservableStore.BLUETOOTH_CATEGORY, "preferred_mic", preferredMic.value)
251
+ }
252
+
253
+ fun setOwnAppAudioPlaying(playing: Boolean) {
254
+ PhoneAudioMonitor.getInstance(appContext).setOwnAppAudioPlaying(playing)
255
+ }
256
+
257
+ fun getGlassesMediaVolume(): GlassesMediaVolumeGetResult =
258
+ GlassesMediaVolumeGetResult.fromMap(deviceManager.getGlassesMediaVolumeBlocking())
259
+
260
+ fun setGlassesMediaVolume(level: Int): GlassesMediaVolumeSetResult {
261
+ require(level in 0..15) { "Glasses media volume must be between 0 and 15." }
262
+ return GlassesMediaVolumeSetResult.fromMap(deviceManager.setGlassesMediaVolumeBlocking(level))
263
+ }
264
+
265
+ fun requestWifiScan() {
266
+ deviceManager.requestWifiScan()
267
+ }
268
+
269
+ fun sendWifiCredentials(ssid: String, password: String) {
270
+ deviceManager.sendWifiCredentials(ssid, password)
271
+ }
272
+
273
+ fun forgetWifiNetwork(ssid: String) {
274
+ deviceManager.forgetWifiNetwork(ssid)
275
+ }
276
+
277
+ fun setHotspotState(enabled: Boolean) {
278
+ deviceManager.setHotspotState(enabled)
279
+ }
280
+
281
+ fun requestPhoto(request: PhotoRequest) {
282
+ deviceManager.photoRequest(
283
+ request.requestId,
284
+ request.appId,
285
+ request.size.value,
286
+ request.webhookUrl,
287
+ request.authToken,
288
+ request.compress.value,
289
+ request.flash,
290
+ request.sound,
291
+ )
292
+ }
293
+
294
+ fun queryGalleryStatus() {
295
+ deviceManager.queryGalleryStatus()
296
+ }
297
+
298
+ fun startStream(request: StreamRequest) {
299
+ deviceManager.startStream(request.toMap().toMutableMap())
300
+ }
301
+
302
+ fun keepStreamAlive(request: StreamKeepAliveRequest) {
303
+ deviceManager.keepStreamAlive(request.toMap().toMutableMap())
304
+ }
305
+
306
+ fun rgbLedControl(request: RgbLedRequest) {
307
+ deviceManager.rgbLedControl(
308
+ request.requestId,
309
+ request.packageName,
310
+ request.action.value,
311
+ request.color?.value,
312
+ request.ontime,
313
+ request.offtime,
314
+ request.count,
315
+ )
316
+ }
317
+
318
+ fun stopStream() {
319
+ deviceManager.stopStream()
320
+ }
321
+
322
+ fun startVideoRecording(request: VideoRecordingRequest) {
323
+ deviceManager.startVideoRecording(request.requestId, request.save, request.flash, request.sound)
324
+ }
325
+
326
+ fun stopVideoRecording(requestId: String) {
327
+ deviceManager.stopVideoRecording(requestId)
328
+ }
329
+
330
+ fun requestVersionInfo() {
331
+ deviceManager.requestVersionInfo()
332
+ }
333
+
334
+ fun sendOtaStart() {
335
+ deviceManager.sendOtaStart()
336
+ }
337
+
338
+ fun sendOtaQueryStatus() {
339
+ deviceManager.sendOtaQueryStatus()
340
+ }
341
+
342
+ fun sendShutdown() {
343
+ deviceManager.sendShutdown()
344
+ }
345
+
346
+ fun sendReboot() {
347
+ deviceManager.sendReboot()
348
+ }
349
+
350
+ fun sendIncidentId(incidentId: String, apiBaseUrl: String? = null) {
351
+ deviceManager.sendIncidentId(incidentId, apiBaseUrl)
352
+ }
353
+
354
+ override fun close() {
355
+ Bridge.removeEventSink(bridgeEventSinkId)
356
+ DeviceStore.store.removeListener(storeListenerId)
357
+ listeners.clear()
358
+ }
359
+
360
+ private fun dispatchStoreUpdate(category: String, changes: Map<String, Any>) {
361
+ when (ObservableStore.normalizeCategory(category)) {
362
+ "glasses" ->
363
+ dispatchToListeners {
364
+ it.onGlassesStatusChanged(GlassesStatusUpdate.fromMap(glassesStatusChanges(changes)))
365
+ }
366
+ ObservableStore.BLUETOOTH_CATEGORY -> {
367
+ dispatchToListeners {
368
+ it.onBluetoothStatusChanged(BluetoothStatusUpdate.fromMap(changes))
369
+ }
370
+ if (!suppressDefaultDeviceEvents && changes.keys.any { it in DEFAULT_DEVICE_KEYS }) {
371
+ dispatchDefaultDeviceChanged()
372
+ }
373
+ dispatchDiscoveredDevices(changes["searchResults"])
374
+ }
375
+ }
376
+ }
377
+
378
+ private fun glassesStatusChanges(changes: Map<String, Any>): Map<String, Any> {
379
+ var merged = changes
380
+
381
+ if (changes.keys.any { it in setOf("wifiConnected", "wifiSsid", "wifiLocalIp") }) {
382
+ merged =
383
+ merged +
384
+ mapOf(
385
+ "wifiConnected" to ((DeviceStore.get("glasses", "wifiConnected") as? Boolean) ?: false),
386
+ "wifiSsid" to ((DeviceStore.get("glasses", "wifiSsid") as? String) ?: ""),
387
+ "wifiLocalIp" to ((DeviceStore.get("glasses", "wifiLocalIp") as? String) ?: ""),
388
+ )
389
+ }
390
+
391
+ if (changes.keys.any { it in setOf("connected", "fullyBooted", "connectionState") }) {
392
+ merged =
393
+ merged +
394
+ mapOf(
395
+ "connected" to ((DeviceStore.get("glasses", "connected") as? Boolean) ?: false),
396
+ "fullyBooted" to ((DeviceStore.get("glasses", "fullyBooted") as? Boolean) ?: false),
397
+ "connectionState" to ((DeviceStore.get("glasses", "connectionState") as? String) ?: ConnTypes.DISCONNECTED),
398
+ )
399
+ }
400
+
401
+ if (changes.containsKey("signalStrengthUpdatedAt") && !changes.containsKey("signalStrength")) {
402
+ val signalStrength = (DeviceStore.get("glasses", "signalStrength") as? Number)?.toInt() ?: -1
403
+ merged = merged + ("signalStrength" to signalStrength)
404
+ }
405
+
406
+ return merged
407
+ }
408
+
409
+ private fun dispatchDefaultDeviceChanged() {
410
+ val defaultDevice = currentDefaultDevice()
411
+ dispatchToListeners { it.onDefaultDeviceChanged(defaultDevice) }
412
+ }
413
+
414
+ private fun currentDefaultDevice(): Device? {
415
+ val core = DeviceStore.store.getCategory(ObservableStore.BLUETOOTH_CATEGORY)
416
+ val model = core["default_wearable"] as? String ?: return null
417
+ val name = core["device_name"] as? String ?: return null
418
+ if (model.isBlank() || name.isBlank()) return null
419
+ val address = (core["device_address"] as? String)?.takeIf { it.isNotBlank() }
420
+ return Device(
421
+ model = DeviceModel.fromDeviceType(model),
422
+ name = name,
423
+ address = address,
424
+ )
425
+ }
426
+
427
+ private fun requireBluetoothReady(operation: String) {
428
+ val bluetoothManager = appContext.getSystemService(Context.BLUETOOTH_SERVICE) as? BluetoothManager
429
+ val adapter =
430
+ bluetoothManager?.adapter
431
+ ?: throw BluetoothException(
432
+ "bluetooth_unsupported",
433
+ "This phone does not support Bluetooth.",
434
+ )
435
+ val enabled =
436
+ try {
437
+ adapter.isEnabled
438
+ } catch (error: SecurityException) {
439
+ throw BluetoothException(
440
+ "bluetooth_permission_denied",
441
+ "Allow Bluetooth permission to $operation.",
442
+ error,
443
+ )
444
+ }
445
+ if (!enabled) {
446
+ throw BluetoothException(
447
+ "bluetooth_powered_off",
448
+ "Turn on phone Bluetooth to $operation.",
449
+ )
450
+ }
451
+ }
452
+
453
+ private fun dispatchDiscoveredDevices(rawSearchResults: Any?) {
454
+ val results = rawSearchResults as? List<*> ?: return
455
+ results.forEach { rawResult ->
456
+ val result = rawResult as? Map<*, *> ?: return@forEach
457
+ val name = result["deviceName"] as? String ?: result["name"] as? String ?: return@forEach
458
+ if (!discoveredDeviceNames.add(name)) return@forEach
459
+ val values =
460
+ result.entries.mapNotNull { (key, value) ->
461
+ if (key is String && value != null) key to value else null
462
+ }.toMap()
463
+ val device = Device.fromMap(values) ?: return@forEach
464
+ dispatchToListeners { it.onDeviceDiscovered(device) }
465
+ }
466
+ }
467
+
468
+ private fun dispatchBridgeEvent(eventName: String, data: Map<String, Any>) {
469
+ when (eventName) {
470
+ "log" -> dispatchToListeners { it.onLog(data["message"] as? String ?: data.toString()) }
471
+ "button_press" ->
472
+ dispatchToListeners {
473
+ it.onButtonPress(
474
+ ButtonPressEvent(
475
+ buttonId = data["buttonId"] as? String ?: "",
476
+ pressType = data["pressType"] as? String ?: "",
477
+ timestamp = (data["timestamp"] as? Number)?.toLong(),
478
+ )
479
+ )
480
+ }
481
+ "touch_event" -> {
482
+ val event = TouchEvent(data)
483
+ dispatchToListeners { it.onTouch(event) }
484
+ if (event.isSwipe) {
485
+ dispatchToListeners { it.onSwipe(SwipeEvent(data)) }
486
+ }
487
+ }
488
+ "head_up" -> dispatchToListeners { it.onHeadUpChanged(data["up"] as? Boolean ?: false) }
489
+ "battery_status" ->
490
+ dispatchToListeners {
491
+ it.onBatteryStatus(
492
+ BatteryStatusEvent(
493
+ level = (data["level"] as? Number)?.toInt(),
494
+ charging = data["charging"] as? Boolean,
495
+ values = data,
496
+ )
497
+ )
498
+ }
499
+ "wifi_status_change" -> dispatchToListeners { it.onWifiStatusChanged(WifiStatusEvent(data)) }
500
+ "hotspot_status_change" -> dispatchToListeners { it.onHotspotStatusChanged(HotspotStatusEvent(data)) }
501
+ "hotspot_error" -> dispatchToListeners { it.onHotspotError(HotspotErrorEvent(data)) }
502
+ "gallery_status" -> dispatchToListeners { it.onGalleryStatus(GalleryStatusEvent(data)) }
503
+ "photo_response" -> dispatchToListeners { it.onPhotoResponse(PhotoResponseEvent(data)) }
504
+ "stream_status" -> dispatchToListeners { it.onStreamStatus(StreamStatusEvent(data)) }
505
+ "keep_alive_ack" -> dispatchToListeners { it.onKeepAliveAck(KeepAliveAckEvent(data)) }
506
+ "mic_pcm" -> (data["pcm"] as? ByteArray)?.let { frame ->
507
+ dispatchToListeners { it.onMicPcm(frame) }
508
+ }
509
+ "mic_lc3" -> (data["lc3"] as? ByteArray)?.let { frame ->
510
+ dispatchToListeners { it.onMicLc3(frame) }
511
+ }
512
+ "local_transcription" ->
513
+ dispatchToListeners {
514
+ it.onLocalTranscription(
515
+ LocalTranscriptionEvent(
516
+ text = data["text"] as? String ?: "",
517
+ isFinal = data["isFinal"] as? Boolean ?: false,
518
+ values = data,
519
+ )
520
+ )
521
+ }
522
+ "compatible_glasses_search_stop" ->
523
+ dispatchToListeners { it.onScanStopped(ScanStopReason.COMPLETED) }
524
+ "pair_failure" ->
525
+ dispatchToListeners {
526
+ it.onError(
527
+ BluetoothError(
528
+ code = "pair_failure",
529
+ message = data["error"] as? String ?: data.toString(),
530
+ )
531
+ )
532
+ }
533
+ else -> dispatchToListeners { it.onRawEvent(eventName, data) }
534
+ }
535
+ }
536
+
537
+ private fun dispatchToListeners(callback: (MentraBluetoothSdkListener) -> Unit) {
538
+ val snapshot = synchronized(listeners) { listeners.toList() }
539
+ val deliver = {
540
+ snapshot.forEach { listener ->
541
+ try {
542
+ callback(listener)
543
+ } catch (error: Throwable) {
544
+ // Listener exceptions should not crash Bluetooth event delivery.
545
+ }
546
+ }
547
+ }
548
+ if (config.deliverCallbacksOnMainThread && Looper.myLooper() != Looper.getMainLooper()) {
549
+ mainHandler.post(deliver)
550
+ } else {
551
+ deliver()
552
+ }
553
+ }
554
+ }
@@ -0,0 +1,68 @@
1
+ package com.mentra.bluetoothsdk
2
+
3
+ import java.util.UUID
4
+ import org.json.JSONObject
5
+
6
+ /** Observable state management with immediate event emission */
7
+ class ObservableStore {
8
+ companion object {
9
+ const val BLUETOOTH_CATEGORY = "bluetooth"
10
+ private const val LEGACY_CORE_CATEGORY = "core"
11
+
12
+ fun normalizeCategory(category: String): String =
13
+ if (category == LEGACY_CORE_CATEGORY) BLUETOOTH_CATEGORY else category
14
+ }
15
+
16
+ private val values = mutableMapOf<String, Any>()
17
+ private val emitListeners = linkedMapOf<String, (String, Map<String, Any>) -> Unit>()
18
+
19
+ @Synchronized
20
+ fun configure(onEmit: (String, Map<String, Any>) -> Unit) {
21
+ emitListeners["default"] = onEmit
22
+ }
23
+
24
+ @Synchronized
25
+ fun addListener(onEmit: (String, Map<String, Any>) -> Unit): String {
26
+ val id = UUID.randomUUID().toString()
27
+ emitListeners[id] = onEmit
28
+ return id
29
+ }
30
+
31
+ @Synchronized
32
+ fun removeListener(id: String) {
33
+ emitListeners.remove(id)
34
+ }
35
+
36
+ fun set(category: String, key: String, value: Any) {
37
+ val normalizedCategory: String
38
+ val listeners: List<(String, Map<String, Any>) -> Unit>
39
+
40
+ synchronized(this) {
41
+ normalizedCategory = normalizeCategory(category)
42
+ val fullKey = "$normalizedCategory.$key"
43
+ val oldValue = values[fullKey]
44
+
45
+ // Skip if unchanged
46
+ if (oldValue != null && toJson(oldValue) == toJson(value)) return
47
+
48
+ values[fullKey] = value
49
+ listeners = emitListeners.values.toList()
50
+ }
51
+
52
+ // Emit immediately, outside the store lock so callbacks can safely re-enter the store.
53
+ listeners.forEach { it(normalizedCategory, mapOf(key to value)) }
54
+ }
55
+
56
+ @Synchronized
57
+ fun get(category: String, key: String): Any? = values["${normalizeCategory(category)}.$key"]
58
+
59
+ @Synchronized
60
+ fun getCategory(category: String): Map<String, Any> {
61
+ val prefix = "${normalizeCategory(category)}."
62
+ return values.filterKeys { it.startsWith(prefix) }.mapKeys { it.key.removePrefix(prefix) }
63
+ }
64
+
65
+ private fun toJson(value: Any): String {
66
+ return JSONObject(mapOf("v" to value)).toString()
67
+ }
68
+ }