@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,1710 @@
1
+ package com.mentra.bluetoothsdk
2
+
3
+ import com.mentra.bluetoothsdk.utils.ControllerTypes
4
+ import com.mentra.bluetoothsdk.utils.ConnTypes
5
+ import com.mentra.bluetoothsdk.utils.DeviceTypes
6
+
7
+ data class MentraBluetoothSdkConfig(
8
+ val deliverCallbacksOnMainThread: Boolean = true,
9
+ )
10
+
11
+ class BluetoothException(
12
+ val code: String,
13
+ message: String,
14
+ cause: Throwable? = null,
15
+ ) : IllegalStateException(message, cause)
16
+
17
+ enum class DeviceModel(val deviceType: String) {
18
+ G1(DeviceTypes.G1),
19
+ G2(DeviceTypes.G2),
20
+ MENTRA_LIVE(DeviceTypes.LIVE),
21
+ MENTRA_NEX(DeviceTypes.NEX),
22
+ MACH1(DeviceTypes.MACH1),
23
+ Z100(DeviceTypes.Z100),
24
+ FRAME(DeviceTypes.FRAME),
25
+ SIMULATED(DeviceTypes.SIMULATED),
26
+ R1(ControllerTypes.R1);
27
+
28
+ companion object {
29
+ @JvmStatic
30
+ fun fromDeviceType(deviceType: String?): DeviceModel =
31
+ values().firstOrNull { it.deviceType == deviceType } ?: MENTRA_LIVE
32
+ }
33
+ }
34
+
35
+ data class Device(
36
+ val model: DeviceModel,
37
+ val name: String,
38
+ val address: String? = null,
39
+ val rssi: Int? = null,
40
+ val id: String = address?.takeIf { it.isNotBlank() } ?: "${model.deviceType}:$name",
41
+ ) {
42
+ internal fun toMap(): Map<String, Any> =
43
+ buildMap {
44
+ put("id", id)
45
+ put("model", model.deviceType)
46
+ put("name", name)
47
+ address?.takeIf { it.isNotBlank() }?.let {
48
+ put("address", it)
49
+ }
50
+ rssi?.let { put("rssi", it) }
51
+ }
52
+
53
+ companion object {
54
+ internal fun fromMap(values: Map<String, Any>): Device? {
55
+ val model = stringValue(values, "model", "deviceModel", "device_model") ?: return null
56
+ val name = stringValue(values, "name", "deviceName", "device_name") ?: return null
57
+ val address = stringValue(values, "address", "deviceAddress", "device_address")?.takeIf { it.isNotBlank() }
58
+ val rssi = numberValue(values, "rssi", "signalStrength", "signal_strength")
59
+ val id = stringValue(values, "id")?.takeIf { it.isNotBlank() } ?: address ?: "${model}:$name"
60
+ return Device(
61
+ model = DeviceModel.fromDeviceType(model),
62
+ name = name,
63
+ address = address,
64
+ rssi = rssi,
65
+ id = id,
66
+ )
67
+ }
68
+ }
69
+ }
70
+
71
+ data class ConnectOptions(
72
+ val saveAsDefault: Boolean = true,
73
+ val cancelExistingConnectionAttempt: Boolean = true,
74
+ )
75
+
76
+ data class WifiScanResult(
77
+ val ssid: String,
78
+ val requiresPassword: Boolean,
79
+ val signalStrength: Int,
80
+ val frequency: Int? = null,
81
+ ) {
82
+ internal fun toMap(): Map<String, Any> {
83
+ val map =
84
+ mutableMapOf<String, Any>(
85
+ "ssid" to ssid,
86
+ "requiresPassword" to requiresPassword,
87
+ "signalStrength" to signalStrength,
88
+ )
89
+ frequency?.let { map["frequency"] = it }
90
+ return map
91
+ }
92
+
93
+ companion object {
94
+ internal fun fromMap(values: Map<String, Any>): WifiScanResult =
95
+ WifiScanResult(
96
+ ssid = stringValue(values, "ssid") ?: "",
97
+ requiresPassword =
98
+ boolValue(values, "requiresPassword", "requires_password", "auth_required") ?: false,
99
+ signalStrength = numberValue(values, "signalStrength", "signal_strength", "rssi") ?: -1,
100
+ frequency = numberValue(values, "frequency"),
101
+ )
102
+ }
103
+ }
104
+
105
+ data class GlassesStatus(
106
+ val fullyBooted: Boolean,
107
+ val connected: Boolean,
108
+ val micEnabled: Boolean,
109
+ val connectionState: GlassesConnectionState,
110
+ val btcConnected: Boolean,
111
+ val signalStrength: Int,
112
+ val signalStrengthUpdatedAt: Long,
113
+ val deviceModel: String,
114
+ val androidVersion: String,
115
+ val firmwareVersion: String,
116
+ val besFirmwareVersion: String,
117
+ val mtkFirmwareVersion: String,
118
+ val btMacAddress: String,
119
+ val leftMacAddress: String,
120
+ val rightMacAddress: String,
121
+ val macAddress: String,
122
+ val buildNumber: String,
123
+ val otaVersionUrl: String,
124
+ val appVersion: String,
125
+ val bluetoothName: String,
126
+ val serialNumber: String,
127
+ val style: String,
128
+ val color: String,
129
+ val wifi: WifiStatus,
130
+ val batteryLevel: Int,
131
+ val charging: Boolean,
132
+ val caseBatteryLevel: Int,
133
+ val caseCharging: Boolean,
134
+ val caseOpen: Boolean,
135
+ val caseRemoved: Boolean,
136
+ val hotspot: HotspotStatus,
137
+ val headUp: Boolean,
138
+ val controllerConnected: Boolean,
139
+ val controllerFullyBooted: Boolean,
140
+ val controllerMacAddress: String,
141
+ val controllerBatteryLevel: Int,
142
+ val controllerSignalStrength: Int,
143
+ val ringSignalStrength: Int,
144
+ ) {
145
+ internal fun toMap(): Map<String, Any> =
146
+ mapOf(
147
+ "connection" to connectionState.toStatusMap(connected, fullyBooted),
148
+ "micEnabled" to micEnabled,
149
+ "btcConnected" to btcConnected,
150
+ "signalStrength" to signalStrength,
151
+ "signalStrengthUpdatedAt" to signalStrengthUpdatedAt,
152
+ "deviceModel" to deviceModel,
153
+ "androidVersion" to androidVersion,
154
+ "fwVersion" to firmwareVersion,
155
+ "besFwVersion" to besFirmwareVersion,
156
+ "mtkFwVersion" to mtkFirmwareVersion,
157
+ "btMacAddress" to btMacAddress,
158
+ "leftMacAddress" to leftMacAddress,
159
+ "rightMacAddress" to rightMacAddress,
160
+ "macAddress" to macAddress,
161
+ "buildNumber" to buildNumber,
162
+ "otaVersionUrl" to otaVersionUrl,
163
+ "appVersion" to appVersion,
164
+ "bluetoothName" to bluetoothName,
165
+ "serialNumber" to serialNumber,
166
+ "style" to style,
167
+ "color" to color,
168
+ "wifi" to wifi.toMap(),
169
+ "batteryLevel" to batteryLevel,
170
+ "charging" to charging,
171
+ "caseBatteryLevel" to caseBatteryLevel,
172
+ "caseCharging" to caseCharging,
173
+ "caseOpen" to caseOpen,
174
+ "caseRemoved" to caseRemoved,
175
+ "hotspot" to hotspot.toMap(),
176
+ "headUp" to headUp,
177
+ "controllerConnected" to controllerConnected,
178
+ "controllerFullyBooted" to controllerFullyBooted,
179
+ "controllerMacAddress" to controllerMacAddress,
180
+ "controllerBatteryLevel" to controllerBatteryLevel,
181
+ "controllerSignalStrength" to controllerSignalStrength,
182
+ "ringSignalStrength" to ringSignalStrength,
183
+ )
184
+
185
+ companion object {
186
+ internal fun fromMap(values: Map<String, Any>): GlassesStatus =
187
+ GlassesStatus(
188
+ fullyBooted = boolValue(values, "fullyBooted") ?: false,
189
+ connected = boolValue(values, "connected") ?: false,
190
+ micEnabled = boolValue(values, "micEnabled") ?: false,
191
+ connectionState = GlassesConnectionState.fromValue(stringValue(values, "connectionState")),
192
+ btcConnected = boolValue(values, "btcConnected") ?: false,
193
+ signalStrength = numberValue(values, "signalStrength") ?: -1,
194
+ signalStrengthUpdatedAt = longValue(values, "signalStrengthUpdatedAt") ?: 0L,
195
+ deviceModel = stringValue(values, "deviceModel") ?: "",
196
+ androidVersion = stringValue(values, "androidVersion") ?: "",
197
+ firmwareVersion = stringValue(values, "firmwareVersion", "fwVersion") ?: "",
198
+ besFirmwareVersion = stringValue(values, "besFwVersion", "besFirmwareVersion") ?: "",
199
+ mtkFirmwareVersion = stringValue(values, "mtkFwVersion", "mtkFirmwareVersion") ?: "",
200
+ btMacAddress = stringValue(values, "btMacAddress") ?: "",
201
+ leftMacAddress = stringValue(values, "leftMacAddress") ?: "",
202
+ rightMacAddress = stringValue(values, "rightMacAddress") ?: "",
203
+ macAddress = stringValue(values, "macAddress") ?: "",
204
+ buildNumber = stringValue(values, "buildNumber") ?: "",
205
+ otaVersionUrl = stringValue(values, "otaVersionUrl") ?: "",
206
+ appVersion = stringValue(values, "appVersion") ?: "",
207
+ bluetoothName = stringValue(values, "bluetoothName") ?: "",
208
+ serialNumber = stringValue(values, "serialNumber") ?: "",
209
+ style = stringValue(values, "style") ?: "",
210
+ color = stringValue(values, "color") ?: "",
211
+ wifi = WifiStatus.fromStoreMap(values) ?: WifiStatus.Disconnected,
212
+ batteryLevel = numberValue(values, "batteryLevel") ?: -1,
213
+ charging = boolValue(values, "charging") ?: false,
214
+ caseBatteryLevel = numberValue(values, "caseBatteryLevel") ?: -1,
215
+ caseCharging = boolValue(values, "caseCharging") ?: false,
216
+ caseOpen = boolValue(values, "caseOpen") ?: true,
217
+ caseRemoved = boolValue(values, "caseRemoved") ?: true,
218
+ hotspot = HotspotStatus.fromStoreMap(values) ?: HotspotStatus.Disabled,
219
+ headUp = boolValue(values, "headUp") ?: false,
220
+ controllerConnected = boolValue(values, "controllerConnected") ?: false,
221
+ controllerFullyBooted = boolValue(values, "controllerFullyBooted") ?: false,
222
+ controllerMacAddress = stringValue(values, "controllerMacAddress") ?: "",
223
+ controllerBatteryLevel = numberValue(values, "controllerBatteryLevel") ?: -1,
224
+ controllerSignalStrength = numberValue(values, "controllerSignalStrength") ?: -1,
225
+ ringSignalStrength = numberValue(values, "ringSignalStrength") ?: -1,
226
+ )
227
+ }
228
+ }
229
+
230
+ data class BluetoothStatus(
231
+ val searching: Boolean,
232
+ val searchingController: Boolean,
233
+ val systemMicUnavailable: Boolean,
234
+ val micEnabled: Boolean,
235
+ val currentMic: String,
236
+ val micRanking: List<String>,
237
+ val searchResults: List<Device>,
238
+ val wifiScanResults: List<WifiScanResult>,
239
+ val lastLog: List<String>,
240
+ val otherBtConnected: Boolean,
241
+ val defaultWearable: String,
242
+ val pendingWearable: String,
243
+ val deviceName: String,
244
+ val deviceAddress: String,
245
+ val defaultController: String,
246
+ val pendingController: String,
247
+ val controllerDeviceName: String,
248
+ val screenDisabled: Boolean,
249
+ val preferredMic: String,
250
+ val sensingEnabled: Boolean,
251
+ val powerSavingMode: Boolean,
252
+ val brightness: Int,
253
+ val autoBrightness: Boolean,
254
+ val dashboardHeight: Int,
255
+ val dashboardDepth: Int,
256
+ val headUpAngle: Int,
257
+ val contextualDashboard: Boolean,
258
+ val galleryModeAuto: Boolean,
259
+ val buttonPhotoSize: ButtonPhotoSize,
260
+ val buttonCameraLed: Boolean,
261
+ val buttonMaxRecordingTime: Int,
262
+ val buttonVideoWidth: Int,
263
+ val buttonVideoHeight: Int,
264
+ val buttonVideoFps: Int,
265
+ val shouldSendPcm: Boolean,
266
+ val shouldSendLc3: Boolean,
267
+ val shouldSendTranscript: Boolean,
268
+ val bypassVad: Boolean,
269
+ val offlineCaptionsRunning: Boolean,
270
+ val localSttFallbackActive: Boolean,
271
+ val shouldSendBootingMessage: Boolean,
272
+ ) {
273
+ val defaultDevice: Device?
274
+ get() =
275
+ defaultWearable.takeIf { it.isNotBlank() }?.let {
276
+ Device(
277
+ model = DeviceModel.fromDeviceType(it),
278
+ name = deviceName,
279
+ address = deviceAddress.takeIf(String::isNotBlank),
280
+ )
281
+ }
282
+
283
+ internal fun toMap(): Map<String, Any> =
284
+ mapOf(
285
+ "searching" to searching,
286
+ "searchingController" to searchingController,
287
+ "systemMicUnavailable" to systemMicUnavailable,
288
+ "micEnabled" to micEnabled,
289
+ "currentMic" to currentMic,
290
+ "micRanking" to micRanking,
291
+ "searchResults" to searchResults.map(Device::toMap),
292
+ "wifiScanResults" to wifiScanResults.map { it.toMap() },
293
+ "lastLog" to lastLog,
294
+ "otherBtConnected" to otherBtConnected,
295
+ "default_wearable" to defaultWearable,
296
+ "pending_wearable" to pendingWearable,
297
+ "device_name" to deviceName,
298
+ "device_address" to deviceAddress,
299
+ "default_controller" to defaultController,
300
+ "pending_controller" to pendingController,
301
+ "controller_device_name" to controllerDeviceName,
302
+ "screen_disabled" to screenDisabled,
303
+ "preferred_mic" to preferredMic,
304
+ "sensing_enabled" to sensingEnabled,
305
+ "power_saving_mode" to powerSavingMode,
306
+ "brightness" to brightness,
307
+ "auto_brightness" to autoBrightness,
308
+ "dashboard_height" to dashboardHeight,
309
+ "dashboard_depth" to dashboardDepth,
310
+ "head_up_angle" to headUpAngle,
311
+ "contextual_dashboard" to contextualDashboard,
312
+ "gallery_mode" to galleryModeAuto,
313
+ "button_photo_size" to buttonPhotoSize.value,
314
+ "button_camera_led" to buttonCameraLed,
315
+ "button_max_recording_time" to buttonMaxRecordingTime,
316
+ "button_video_width" to buttonVideoWidth,
317
+ "button_video_height" to buttonVideoHeight,
318
+ "button_video_fps" to buttonVideoFps,
319
+ "should_send_pcm" to shouldSendPcm,
320
+ "should_send_lc3" to shouldSendLc3,
321
+ "should_send_transcript" to shouldSendTranscript,
322
+ "bypass_vad" to bypassVad,
323
+ "offline_captions_running" to offlineCaptionsRunning,
324
+ "local_stt_fallback_active" to localSttFallbackActive,
325
+ "shouldSendBootingMessage" to shouldSendBootingMessage,
326
+ )
327
+
328
+ companion object {
329
+ internal fun fromMap(values: Map<String, Any>): BluetoothStatus =
330
+ BluetoothStatus(
331
+ searching = boolValue(values, "searching") ?: false,
332
+ searchingController = boolValue(values, "searchingController") ?: false,
333
+ systemMicUnavailable = boolValue(values, "systemMicUnavailable") ?: false,
334
+ micEnabled = boolValue(values, "micEnabled") ?: false,
335
+ currentMic = stringValue(values, "currentMic") ?: "",
336
+ micRanking = stringListValue(values, "micRanking"),
337
+ searchResults =
338
+ mapListValue(values, "searchResults").mapNotNull(Device::fromMap),
339
+ wifiScanResults =
340
+ mapListValue(values, "wifiScanResults").map(WifiScanResult::fromMap),
341
+ lastLog = stringListValue(values, "lastLog"),
342
+ otherBtConnected = boolValue(values, "otherBtConnected") ?: false,
343
+ defaultWearable = stringValue(values, "default_wearable") ?: "",
344
+ pendingWearable = stringValue(values, "pending_wearable") ?: "",
345
+ deviceName = stringValue(values, "device_name") ?: "",
346
+ deviceAddress = stringValue(values, "device_address") ?: "",
347
+ defaultController = stringValue(values, "default_controller") ?: "",
348
+ pendingController = stringValue(values, "pending_controller") ?: "",
349
+ controllerDeviceName = stringValue(values, "controller_device_name") ?: "",
350
+ screenDisabled = boolValue(values, "screen_disabled") ?: false,
351
+ preferredMic = stringValue(values, "preferred_mic") ?: "auto",
352
+ sensingEnabled = boolValue(values, "sensing_enabled") ?: true,
353
+ powerSavingMode = boolValue(values, "power_saving_mode") ?: false,
354
+ brightness = numberValue(values, "brightness") ?: 50,
355
+ autoBrightness = boolValue(values, "auto_brightness") ?: true,
356
+ dashboardHeight = numberValue(values, "dashboard_height") ?: 4,
357
+ dashboardDepth = numberValue(values, "dashboard_depth") ?: 2,
358
+ headUpAngle = numberValue(values, "head_up_angle") ?: 30,
359
+ contextualDashboard = boolValue(values, "contextual_dashboard") ?: true,
360
+ galleryModeAuto = boolValue(values, "gallery_mode") ?: true,
361
+ buttonPhotoSize = ButtonPhotoSize.fromValue(stringValue(values, "button_photo_size")),
362
+ buttonCameraLed = boolValue(values, "button_camera_led") ?: true,
363
+ buttonMaxRecordingTime = numberValue(values, "button_max_recording_time") ?: 10,
364
+ buttonVideoWidth = numberValue(values, "button_video_width") ?: 1280,
365
+ buttonVideoHeight = numberValue(values, "button_video_height") ?: 720,
366
+ buttonVideoFps = numberValue(values, "button_video_fps") ?: 30,
367
+ shouldSendPcm = boolValue(values, "should_send_pcm") ?: false,
368
+ shouldSendLc3 = boolValue(values, "should_send_lc3") ?: false,
369
+ shouldSendTranscript = boolValue(values, "should_send_transcript") ?: false,
370
+ bypassVad = boolValue(values, "bypass_vad") ?: false,
371
+ offlineCaptionsRunning = boolValue(values, "offline_captions_running") ?: false,
372
+ localSttFallbackActive = boolValue(values, "local_stt_fallback_active") ?: false,
373
+ shouldSendBootingMessage = boolValue(values, "shouldSendBootingMessage") ?: true,
374
+ )
375
+ }
376
+ }
377
+
378
+ data class GlassesStatusUpdate(
379
+ val fullyBooted: Boolean? = null,
380
+ val connected: Boolean? = null,
381
+ val micEnabled: Boolean? = null,
382
+ val connectionState: GlassesConnectionState? = null,
383
+ val btcConnected: Boolean? = null,
384
+ val signalStrength: Int? = null,
385
+ val signalStrengthUpdatedAt: Long? = null,
386
+ val deviceModel: String? = null,
387
+ val androidVersion: String? = null,
388
+ val firmwareVersion: String? = null,
389
+ val besFirmwareVersion: String? = null,
390
+ val mtkFirmwareVersion: String? = null,
391
+ val btMacAddress: String? = null,
392
+ val leftMacAddress: String? = null,
393
+ val rightMacAddress: String? = null,
394
+ val macAddress: String? = null,
395
+ val buildNumber: String? = null,
396
+ val otaVersionUrl: String? = null,
397
+ val appVersion: String? = null,
398
+ val bluetoothName: String? = null,
399
+ val serialNumber: String? = null,
400
+ val style: String? = null,
401
+ val color: String? = null,
402
+ val wifi: WifiStatus? = null,
403
+ val batteryLevel: Int? = null,
404
+ val charging: Boolean? = null,
405
+ val caseBatteryLevel: Int? = null,
406
+ val caseCharging: Boolean? = null,
407
+ val caseOpen: Boolean? = null,
408
+ val caseRemoved: Boolean? = null,
409
+ val hotspot: HotspotStatus? = null,
410
+ val headUp: Boolean? = null,
411
+ val controllerConnected: Boolean? = null,
412
+ val controllerFullyBooted: Boolean? = null,
413
+ val controllerMacAddress: String? = null,
414
+ val controllerBatteryLevel: Int? = null,
415
+ val controllerSignalStrength: Int? = null,
416
+ val ringSignalStrength: Int? = null,
417
+ ) {
418
+ internal fun toMap(): Map<String, Any> =
419
+ buildMap {
420
+ if (fullyBooted != null || connected != null || connectionState != null) {
421
+ val state =
422
+ connectionState
423
+ ?: if (connected == true || fullyBooted == true) {
424
+ GlassesConnectionState.CONNECTED
425
+ } else {
426
+ GlassesConnectionState.DISCONNECTED
427
+ }
428
+ put("connection", state.toStatusMap(connected == true, fullyBooted == true))
429
+ }
430
+ putIfNotNull("micEnabled", micEnabled)
431
+ putIfNotNull("btcConnected", btcConnected)
432
+ putIfNotNull("signalStrength", signalStrength)
433
+ putIfNotNull("signalStrengthUpdatedAt", signalStrengthUpdatedAt)
434
+ putIfNotNull("deviceModel", deviceModel)
435
+ putIfNotNull("androidVersion", androidVersion)
436
+ putIfNotNull("fwVersion", firmwareVersion)
437
+ putIfNotNull("besFwVersion", besFirmwareVersion)
438
+ putIfNotNull("mtkFwVersion", mtkFirmwareVersion)
439
+ putIfNotNull("btMacAddress", btMacAddress)
440
+ putIfNotNull("leftMacAddress", leftMacAddress)
441
+ putIfNotNull("rightMacAddress", rightMacAddress)
442
+ putIfNotNull("macAddress", macAddress)
443
+ putIfNotNull("buildNumber", buildNumber)
444
+ putIfNotNull("otaVersionUrl", otaVersionUrl)
445
+ putIfNotNull("appVersion", appVersion)
446
+ putIfNotNull("bluetoothName", bluetoothName)
447
+ putIfNotNull("serialNumber", serialNumber)
448
+ putIfNotNull("style", style)
449
+ putIfNotNull("color", color)
450
+ wifi?.let {
451
+ put("wifi", it.toMap())
452
+ }
453
+ putIfNotNull("batteryLevel", batteryLevel)
454
+ putIfNotNull("charging", charging)
455
+ putIfNotNull("caseBatteryLevel", caseBatteryLevel)
456
+ putIfNotNull("caseCharging", caseCharging)
457
+ putIfNotNull("caseOpen", caseOpen)
458
+ putIfNotNull("caseRemoved", caseRemoved)
459
+ hotspot?.let {
460
+ put("hotspot", it.toMap())
461
+ }
462
+ putIfNotNull("headUp", headUp)
463
+ putIfNotNull("controllerConnected", controllerConnected)
464
+ putIfNotNull("controllerFullyBooted", controllerFullyBooted)
465
+ putIfNotNull("controllerMacAddress", controllerMacAddress)
466
+ putIfNotNull("controllerBatteryLevel", controllerBatteryLevel)
467
+ putIfNotNull("controllerSignalStrength", controllerSignalStrength)
468
+ putIfNotNull("ringSignalStrength", ringSignalStrength)
469
+ }
470
+
471
+ companion object {
472
+ internal fun fromMap(values: Map<String, Any>): GlassesStatusUpdate =
473
+ GlassesStatusUpdate(
474
+ fullyBooted = optionalBoolValue(values, "fullyBooted"),
475
+ connected = optionalBoolValue(values, "connected"),
476
+ micEnabled = optionalBoolValue(values, "micEnabled"),
477
+ connectionState = GlassesConnectionState.optionalFromValue(optionalStringValue(values, "connectionState")),
478
+ btcConnected = optionalBoolValue(values, "btcConnected"),
479
+ signalStrength = optionalNumberValue(values, "signalStrength"),
480
+ signalStrengthUpdatedAt = optionalLongValue(values, "signalStrengthUpdatedAt"),
481
+ deviceModel = optionalStringValue(values, "deviceModel"),
482
+ androidVersion = optionalStringValue(values, "androidVersion"),
483
+ firmwareVersion = optionalStringValue(values, "firmwareVersion", "fwVersion"),
484
+ besFirmwareVersion = optionalStringValue(values, "besFwVersion", "besFirmwareVersion"),
485
+ mtkFirmwareVersion = optionalStringValue(values, "mtkFwVersion", "mtkFirmwareVersion"),
486
+ btMacAddress = optionalStringValue(values, "btMacAddress"),
487
+ leftMacAddress = optionalStringValue(values, "leftMacAddress"),
488
+ rightMacAddress = optionalStringValue(values, "rightMacAddress"),
489
+ macAddress = optionalStringValue(values, "macAddress"),
490
+ buildNumber = optionalStringValue(values, "buildNumber"),
491
+ otaVersionUrl = optionalStringValue(values, "otaVersionUrl"),
492
+ appVersion = optionalStringValue(values, "appVersion"),
493
+ bluetoothName = optionalStringValue(values, "bluetoothName"),
494
+ serialNumber = optionalStringValue(values, "serialNumber"),
495
+ style = optionalStringValue(values, "style"),
496
+ color = optionalStringValue(values, "color"),
497
+ wifi =
498
+ if (hasAnyKey(values, "wifi")) {
499
+ WifiStatus.fromMap(values)
500
+ } else if (hasAnyKey(values, "wifiConnected", "wifiSsid", "wifiLocalIp")) {
501
+ WifiStatus.fromStoreMap(values)
502
+ } else {
503
+ null
504
+ },
505
+ batteryLevel = optionalNumberValue(values, "batteryLevel"),
506
+ charging = optionalBoolValue(values, "charging"),
507
+ caseBatteryLevel = optionalNumberValue(values, "caseBatteryLevel"),
508
+ caseCharging = optionalBoolValue(values, "caseCharging"),
509
+ caseOpen = optionalBoolValue(values, "caseOpen"),
510
+ caseRemoved = optionalBoolValue(values, "caseRemoved"),
511
+ hotspot =
512
+ if (hasAnyKey(values, "hotspot")) {
513
+ HotspotStatus.fromMap(values)
514
+ } else if (hasAnyKey(values, "hotspotEnabled", "hotspotSsid", "hotspotPassword", "hotspotGatewayIp", "hotspotLocalIp")) {
515
+ HotspotStatus.fromStoreMap(values)
516
+ } else {
517
+ null
518
+ },
519
+ headUp = optionalBoolValue(values, "headUp"),
520
+ controllerConnected = optionalBoolValue(values, "controllerConnected"),
521
+ controllerFullyBooted = optionalBoolValue(values, "controllerFullyBooted"),
522
+ controllerMacAddress = optionalStringValue(values, "controllerMacAddress"),
523
+ controllerBatteryLevel = optionalNumberValue(values, "controllerBatteryLevel"),
524
+ controllerSignalStrength = optionalNumberValue(values, "controllerSignalStrength"),
525
+ ringSignalStrength = optionalNumberValue(values, "ringSignalStrength"),
526
+ )
527
+ }
528
+ }
529
+
530
+ data class BluetoothStatusUpdate(
531
+ val searching: Boolean? = null,
532
+ val searchingController: Boolean? = null,
533
+ val systemMicUnavailable: Boolean? = null,
534
+ val micEnabled: Boolean? = null,
535
+ val currentMic: String? = null,
536
+ val micRanking: List<String>? = null,
537
+ val searchResults: List<Device>? = null,
538
+ val wifiScanResults: List<WifiScanResult>? = null,
539
+ val lastLog: List<String>? = null,
540
+ val otherBtConnected: Boolean? = null,
541
+ val defaultWearable: String? = null,
542
+ val pendingWearable: String? = null,
543
+ val deviceName: String? = null,
544
+ val deviceAddress: String? = null,
545
+ val defaultController: String? = null,
546
+ val pendingController: String? = null,
547
+ val controllerDeviceName: String? = null,
548
+ val screenDisabled: Boolean? = null,
549
+ val preferredMic: String? = null,
550
+ val sensingEnabled: Boolean? = null,
551
+ val powerSavingMode: Boolean? = null,
552
+ val brightness: Int? = null,
553
+ val autoBrightness: Boolean? = null,
554
+ val dashboardHeight: Int? = null,
555
+ val dashboardDepth: Int? = null,
556
+ val headUpAngle: Int? = null,
557
+ val contextualDashboard: Boolean? = null,
558
+ val galleryModeAuto: Boolean? = null,
559
+ val buttonPhotoSize: ButtonPhotoSize? = null,
560
+ val buttonCameraLed: Boolean? = null,
561
+ val buttonMaxRecordingTime: Int? = null,
562
+ val buttonVideoWidth: Int? = null,
563
+ val buttonVideoHeight: Int? = null,
564
+ val buttonVideoFps: Int? = null,
565
+ val shouldSendPcm: Boolean? = null,
566
+ val shouldSendLc3: Boolean? = null,
567
+ val shouldSendTranscript: Boolean? = null,
568
+ val bypassVad: Boolean? = null,
569
+ val offlineCaptionsRunning: Boolean? = null,
570
+ val localSttFallbackActive: Boolean? = null,
571
+ val shouldSendBootingMessage: Boolean? = null,
572
+ ) {
573
+ internal fun toMap(): Map<String, Any> =
574
+ buildMap {
575
+ putIfNotNull("searching", searching)
576
+ putIfNotNull("searchingController", searchingController)
577
+ putIfNotNull("systemMicUnavailable", systemMicUnavailable)
578
+ putIfNotNull("micEnabled", micEnabled)
579
+ putIfNotNull("currentMic", currentMic)
580
+ putIfNotNull("micRanking", micRanking)
581
+ searchResults?.let { put("searchResults", it.map(Device::toMap)) }
582
+ wifiScanResults?.let { put("wifiScanResults", it.map(WifiScanResult::toMap)) }
583
+ putIfNotNull("lastLog", lastLog)
584
+ putIfNotNull("otherBtConnected", otherBtConnected)
585
+ putIfNotNull("default_wearable", defaultWearable)
586
+ putIfNotNull("pending_wearable", pendingWearable)
587
+ putIfNotNull("device_name", deviceName)
588
+ putIfNotNull("device_address", deviceAddress)
589
+ putIfNotNull("default_controller", defaultController)
590
+ putIfNotNull("pending_controller", pendingController)
591
+ putIfNotNull("controller_device_name", controllerDeviceName)
592
+ putIfNotNull("screen_disabled", screenDisabled)
593
+ putIfNotNull("preferred_mic", preferredMic)
594
+ putIfNotNull("sensing_enabled", sensingEnabled)
595
+ putIfNotNull("power_saving_mode", powerSavingMode)
596
+ putIfNotNull("brightness", brightness)
597
+ putIfNotNull("auto_brightness", autoBrightness)
598
+ putIfNotNull("dashboard_height", dashboardHeight)
599
+ putIfNotNull("dashboard_depth", dashboardDepth)
600
+ putIfNotNull("head_up_angle", headUpAngle)
601
+ putIfNotNull("contextual_dashboard", contextualDashboard)
602
+ putIfNotNull("gallery_mode", galleryModeAuto)
603
+ buttonPhotoSize?.let { put("button_photo_size", it.value) }
604
+ putIfNotNull("button_camera_led", buttonCameraLed)
605
+ putIfNotNull("button_max_recording_time", buttonMaxRecordingTime)
606
+ putIfNotNull("button_video_width", buttonVideoWidth)
607
+ putIfNotNull("button_video_height", buttonVideoHeight)
608
+ putIfNotNull("button_video_fps", buttonVideoFps)
609
+ putIfNotNull("should_send_pcm", shouldSendPcm)
610
+ putIfNotNull("should_send_lc3", shouldSendLc3)
611
+ putIfNotNull("should_send_transcript", shouldSendTranscript)
612
+ putIfNotNull("bypass_vad", bypassVad)
613
+ putIfNotNull("offline_captions_running", offlineCaptionsRunning)
614
+ putIfNotNull("local_stt_fallback_active", localSttFallbackActive)
615
+ putIfNotNull("shouldSendBootingMessage", shouldSendBootingMessage)
616
+ }
617
+
618
+ companion object {
619
+ internal fun fromMap(values: Map<String, Any>): BluetoothStatusUpdate =
620
+ BluetoothStatusUpdate(
621
+ searching = optionalBoolValue(values, "searching"),
622
+ searchingController = optionalBoolValue(values, "searchingController"),
623
+ systemMicUnavailable = optionalBoolValue(values, "systemMicUnavailable"),
624
+ micEnabled = optionalBoolValue(values, "micEnabled"),
625
+ currentMic = optionalStringValue(values, "currentMic"),
626
+ micRanking = optionalStringListValue(values, "micRanking"),
627
+ searchResults =
628
+ optionalMapListValue(values, "searchResults")
629
+ ?.mapNotNull(Device::fromMap),
630
+ wifiScanResults =
631
+ optionalMapListValue(values, "wifiScanResults")
632
+ ?.map(WifiScanResult::fromMap),
633
+ lastLog = optionalStringListValue(values, "lastLog"),
634
+ otherBtConnected = optionalBoolValue(values, "otherBtConnected"),
635
+ defaultWearable = optionalStringValue(values, "default_wearable"),
636
+ pendingWearable = optionalStringValue(values, "pending_wearable"),
637
+ deviceName = optionalStringValue(values, "device_name"),
638
+ deviceAddress = optionalStringValue(values, "device_address"),
639
+ defaultController = optionalStringValue(values, "default_controller"),
640
+ pendingController = optionalStringValue(values, "pending_controller"),
641
+ controllerDeviceName = optionalStringValue(values, "controller_device_name"),
642
+ screenDisabled = optionalBoolValue(values, "screen_disabled"),
643
+ preferredMic = optionalStringValue(values, "preferred_mic"),
644
+ sensingEnabled = optionalBoolValue(values, "sensing_enabled"),
645
+ powerSavingMode = optionalBoolValue(values, "power_saving_mode"),
646
+ brightness = optionalNumberValue(values, "brightness"),
647
+ autoBrightness = optionalBoolValue(values, "auto_brightness"),
648
+ dashboardHeight = optionalNumberValue(values, "dashboard_height"),
649
+ dashboardDepth = optionalNumberValue(values, "dashboard_depth"),
650
+ headUpAngle = optionalNumberValue(values, "head_up_angle"),
651
+ contextualDashboard = optionalBoolValue(values, "contextual_dashboard"),
652
+ galleryModeAuto = optionalBoolValue(values, "gallery_mode"),
653
+ buttonPhotoSize =
654
+ optionalStringValue(values, "button_photo_size")?.let(ButtonPhotoSize::fromValue),
655
+ buttonCameraLed = optionalBoolValue(values, "button_camera_led"),
656
+ buttonMaxRecordingTime = optionalNumberValue(values, "button_max_recording_time"),
657
+ buttonVideoWidth = optionalNumberValue(values, "button_video_width"),
658
+ buttonVideoHeight = optionalNumberValue(values, "button_video_height"),
659
+ buttonVideoFps = optionalNumberValue(values, "button_video_fps"),
660
+ shouldSendPcm = optionalBoolValue(values, "should_send_pcm"),
661
+ shouldSendLc3 = optionalBoolValue(values, "should_send_lc3"),
662
+ shouldSendTranscript = optionalBoolValue(values, "should_send_transcript"),
663
+ bypassVad = optionalBoolValue(values, "bypass_vad"),
664
+ offlineCaptionsRunning = optionalBoolValue(values, "offline_captions_running"),
665
+ localSttFallbackActive = optionalBoolValue(values, "local_stt_fallback_active"),
666
+ shouldSendBootingMessage = optionalBoolValue(values, "shouldSendBootingMessage"),
667
+ )
668
+ }
669
+ }
670
+
671
+ data class DisplayTextRequest(
672
+ val text: String,
673
+ val x: Int = 0,
674
+ val y: Int = 0,
675
+ val size: Int = 24,
676
+ ) {
677
+ fun toMap(): Map<String, Any> =
678
+ mapOf(
679
+ "text" to text,
680
+ "x" to x,
681
+ "y" to y,
682
+ "size" to size,
683
+ )
684
+ }
685
+
686
+ data class DisplayEventRequest(
687
+ val values: Map<String, Any>,
688
+ ) {
689
+ fun toMap(): Map<String, Any> = values
690
+ }
691
+
692
+ data class DashboardPositionRequest(
693
+ val height: Int,
694
+ val depth: Int,
695
+ )
696
+
697
+ data class DashboardMenuItem(
698
+ val title: String,
699
+ val packageName: String,
700
+ val values: Map<String, Any> = emptyMap(),
701
+ ) {
702
+ fun toMap(): Map<String, Any> =
703
+ values + mapOf(
704
+ "title" to title,
705
+ "packageName" to packageName,
706
+ )
707
+ }
708
+
709
+ enum class GalleryMode {
710
+ AUTO,
711
+ MANUAL,
712
+ }
713
+
714
+ enum class PhotoSize(val value: String) {
715
+ SMALL("small"),
716
+ MEDIUM("medium"),
717
+ LARGE("large"),
718
+ FULL("full");
719
+
720
+ companion object {
721
+ @JvmStatic
722
+ fun fromValue(value: String?): PhotoSize =
723
+ values().firstOrNull { it.value == value } ?: MEDIUM
724
+ }
725
+ }
726
+
727
+ enum class ButtonPhotoSize(val value: String) {
728
+ SMALL("small"),
729
+ MEDIUM("medium"),
730
+ LARGE("large");
731
+
732
+ companion object {
733
+ @JvmStatic
734
+ fun fromValue(value: String?): ButtonPhotoSize =
735
+ values().firstOrNull { it.value == value } ?: MEDIUM
736
+ }
737
+ }
738
+
739
+ enum class PhotoCompression(val value: String) {
740
+ NONE("none"),
741
+ MEDIUM("medium"),
742
+ HEAVY("heavy");
743
+
744
+ companion object {
745
+ @JvmStatic
746
+ fun fromValue(value: String?): PhotoCompression =
747
+ values().firstOrNull { it.value == value } ?: NONE
748
+ }
749
+ }
750
+
751
+ data class ButtonPhotoSettings(
752
+ val size: ButtonPhotoSize,
753
+ )
754
+
755
+ data class ButtonVideoRecordingSettings(
756
+ val width: Int,
757
+ val height: Int,
758
+ val fps: Int,
759
+ )
760
+
761
+ enum class CameraFov(val fov: Int, val roiPosition: Int) {
762
+ STANDARD(118, 0),
763
+ WIDE(118, 0),
764
+ }
765
+
766
+ data class MicConfig(
767
+ val sendPcmData: Boolean,
768
+ val sendTranscript: Boolean,
769
+ val bypassVad: Boolean,
770
+ val sendLc3Data: Boolean = false,
771
+ )
772
+
773
+ enum class MicPreference(val value: String) {
774
+ AUTO("auto"),
775
+ PHONE("phone"),
776
+ GLASSES("glasses"),
777
+ BT_CLASSIC("btclassic"),
778
+ BT("bt"),
779
+ }
780
+
781
+ data class PhotoRequest @JvmOverloads constructor(
782
+ val requestId: String,
783
+ val appId: String,
784
+ val size: PhotoSize,
785
+ val webhookUrl: String,
786
+ val authToken: String? = null,
787
+ val compress: PhotoCompression = PhotoCompression.MEDIUM,
788
+ val flash: Boolean = false,
789
+ val sound: Boolean = true,
790
+ )
791
+
792
+ data class StreamVideoConfig @JvmOverloads constructor(
793
+ val width: Int? = null,
794
+ val height: Int? = null,
795
+ val bitrate: Int? = null,
796
+ val frameRate: Int? = null,
797
+ ) {
798
+ fun toMap(): Map<String, Any> =
799
+ listOfNotNull(
800
+ width?.let { "width" to it },
801
+ height?.let { "height" to it },
802
+ bitrate?.let { "bitrate" to it },
803
+ frameRate?.let { "frameRate" to it },
804
+ ).toMap()
805
+
806
+ companion object {
807
+ @JvmStatic
808
+ fun fromMap(values: Map<*, *>?): StreamVideoConfig? {
809
+ values ?: return null
810
+ return StreamVideoConfig(
811
+ width = numberValue(values, "width", "w"),
812
+ height = numberValue(values, "height", "h"),
813
+ bitrate = numberValue(values, "bitrate", "br"),
814
+ frameRate = numberValue(values, "frameRate", "fr"),
815
+ )
816
+ }
817
+ }
818
+ }
819
+
820
+ data class StreamAudioConfig @JvmOverloads constructor(
821
+ val bitrate: Int? = null,
822
+ val sampleRate: Int? = null,
823
+ val echoCancellation: Boolean? = null,
824
+ val noiseSuppression: Boolean? = null,
825
+ ) {
826
+ fun toMap(): Map<String, Any> =
827
+ listOfNotNull(
828
+ bitrate?.let { "bitrate" to it },
829
+ sampleRate?.let { "sampleRate" to it },
830
+ echoCancellation?.let { "echoCancellation" to it },
831
+ noiseSuppression?.let { "noiseSuppression" to it },
832
+ ).toMap()
833
+
834
+ companion object {
835
+ @JvmStatic
836
+ fun fromMap(values: Map<*, *>?): StreamAudioConfig? {
837
+ values ?: return null
838
+ return StreamAudioConfig(
839
+ bitrate = numberValue(values, "bitrate", "br"),
840
+ sampleRate = numberValue(values, "sampleRate", "sr"),
841
+ echoCancellation = values["echoCancellation"] as? Boolean ?: values["ec"] as? Boolean,
842
+ noiseSuppression = values["noiseSuppression"] as? Boolean ?: values["ns"] as? Boolean,
843
+ )
844
+ }
845
+ }
846
+ }
847
+
848
+ data class StreamRequest @JvmOverloads constructor(
849
+ val streamUrl: String,
850
+ val streamId: String = "",
851
+ val keepAlive: Boolean = true,
852
+ val keepAliveIntervalSeconds: Int = 15,
853
+ val flash: Boolean = true,
854
+ val sound: Boolean = true,
855
+ val video: StreamVideoConfig? = null,
856
+ val audio: StreamAudioConfig? = null,
857
+ val extraValues: Map<String, Any> = emptyMap(),
858
+ ) {
859
+ fun toMap(): Map<String, Any> {
860
+ val values = extraValues.toMutableMap()
861
+ values["type"] = "start_stream"
862
+ values["streamUrl"] = streamUrl
863
+ values["streamId"] = streamId
864
+ values["keepAlive"] = keepAlive
865
+ values["keepAliveIntervalSeconds"] = keepAliveIntervalSeconds
866
+ values["flash"] = flash
867
+ values["sound"] = sound
868
+ video?.toMap()?.takeIf { it.isNotEmpty() }?.let { values["video"] = it }
869
+ audio?.toMap()?.takeIf { it.isNotEmpty() }?.let { values["audio"] = it }
870
+ return values
871
+ }
872
+
873
+ companion object {
874
+ @JvmStatic
875
+ fun fromMap(values: Map<String, Any>): StreamRequest =
876
+ StreamRequest(
877
+ streamUrl =
878
+ (values["streamUrl"] ?: values["rtmpUrl"] ?: values["srtUrl"] ?: values["whipUrl"]) as? String
879
+ ?: "",
880
+ streamId = values["streamId"] as? String ?: "",
881
+ keepAlive = values["keepAlive"] as? Boolean ?: true,
882
+ keepAliveIntervalSeconds = (values["keepAliveIntervalSeconds"] as? Number)?.toInt() ?: 15,
883
+ flash = values["flash"] as? Boolean ?: true,
884
+ sound = values["sound"] as? Boolean ?: true,
885
+ video = StreamVideoConfig.fromMap((values["video"] ?: values["v"]) as? Map<*, *>),
886
+ audio = StreamAudioConfig.fromMap((values["audio"] ?: values["a"]) as? Map<*, *>),
887
+ extraValues = values,
888
+ )
889
+ }
890
+ }
891
+
892
+ data class StreamKeepAliveRequest @JvmOverloads constructor(
893
+ val streamId: String,
894
+ val ackId: String,
895
+ val extraValues: Map<String, Any> = emptyMap(),
896
+ ) {
897
+ fun toMap(): Map<String, Any> {
898
+ val values = extraValues.toMutableMap()
899
+ values["type"] = "keep_stream_alive"
900
+ values["streamId"] = streamId
901
+ values["ackId"] = ackId
902
+ return values
903
+ }
904
+
905
+ companion object {
906
+ @JvmStatic
907
+ fun fromMap(values: Map<String, Any>): StreamKeepAliveRequest =
908
+ StreamKeepAliveRequest(
909
+ streamId = values["streamId"] as? String ?: "",
910
+ ackId = values["ackId"] as? String ?: "",
911
+ extraValues = values,
912
+ )
913
+ }
914
+ }
915
+
916
+ enum class RgbLedAction(val value: String) {
917
+ ON("on"),
918
+ OFF("off");
919
+
920
+ companion object {
921
+ @JvmStatic
922
+ fun fromValue(value: String?): RgbLedAction =
923
+ values().firstOrNull { it.value == value } ?: OFF
924
+ }
925
+ }
926
+
927
+ enum class RgbLedColor(val value: String) {
928
+ RED("red"),
929
+ GREEN("green"),
930
+ BLUE("blue"),
931
+ ORANGE("orange"),
932
+ WHITE("white");
933
+
934
+ companion object {
935
+ @JvmStatic
936
+ fun fromValue(value: String?): RgbLedColor? =
937
+ values().firstOrNull { it.value == value }
938
+ }
939
+ }
940
+
941
+ data class RgbLedRequest @JvmOverloads constructor(
942
+ val requestId: String,
943
+ val packageName: String?,
944
+ val action: RgbLedAction,
945
+ val color: RgbLedColor?,
946
+ val ontime: Int,
947
+ val offtime: Int,
948
+ val count: Int,
949
+ )
950
+
951
+ data class VideoRecordingRequest(
952
+ val requestId: String,
953
+ val save: Boolean,
954
+ val flash: Boolean,
955
+ val sound: Boolean,
956
+ )
957
+
958
+ data class ButtonPressEvent(
959
+ val buttonId: String,
960
+ val pressType: String,
961
+ val timestamp: Long? = null,
962
+ )
963
+
964
+ data class TouchEvent(
965
+ val values: Map<String, Any>,
966
+ ) {
967
+ val deviceModel: String? get() = stringValue(values, "device_model", "deviceModel")
968
+ val gestureName: String? get() = stringValue(values, "gesture_name", "gestureName")
969
+ val timestamp: Long? get() = longValue(values, "timestamp")
970
+ val isSwipe: Boolean get() = gestureName?.contains("swipe", ignoreCase = true) == true
971
+ }
972
+
973
+ data class SwipeEvent(
974
+ val values: Map<String, Any>,
975
+ ) {
976
+ val deviceModel: String? get() = stringValue(values, "device_model", "deviceModel")
977
+ val gestureName: String? get() = stringValue(values, "gesture_name", "gestureName")
978
+ val timestamp: Long? get() = longValue(values, "timestamp")
979
+ }
980
+
981
+ data class BatteryStatusEvent(
982
+ val level: Int?,
983
+ val charging: Boolean?,
984
+ val values: Map<String, Any>,
985
+ )
986
+
987
+ sealed interface WifiStatus {
988
+ val state: String
989
+
990
+ fun toMap(): Map<String, Any> =
991
+ when (this) {
992
+ is Connected -> {
993
+ val values =
994
+ mutableMapOf<String, Any>(
995
+ "state" to state,
996
+ "ssid" to ssid,
997
+ )
998
+ localIp?.let { values["localIp"] = it }
999
+ values
1000
+ }
1001
+ Disconnected -> mapOf("state" to state)
1002
+ }
1003
+
1004
+ fun toEventMap(): Map<String, Any> =
1005
+ toMap() + mapOf("type" to "wifi_status_change")
1006
+
1007
+ object Disconnected : WifiStatus {
1008
+ override val state: String = "disconnected"
1009
+ }
1010
+
1011
+ data class Connected(
1012
+ val ssid: String,
1013
+ val localIp: String?,
1014
+ ) : WifiStatus {
1015
+ override val state: String = "connected"
1016
+ }
1017
+
1018
+ companion object {
1019
+ internal fun fromMap(values: Map<String, Any>): WifiStatus? {
1020
+ val wifiValues = (values["wifi"] as? Map<*, *>)?.stringKeyedMap() ?: values
1021
+ return when (stringValue(wifiValues, "state")?.lowercase()) {
1022
+ "connected" -> connectedFrom(wifiValues)
1023
+ "disconnected" -> Disconnected
1024
+ else -> null
1025
+ }
1026
+ }
1027
+
1028
+ internal fun fromStoreMap(values: Map<String, Any>): WifiStatus? {
1029
+ val connected = boolValue(values, "wifiConnected") ?: return null
1030
+ return fromStoreFields(
1031
+ connected = connected,
1032
+ ssid = stringValue(values, "wifiSsid"),
1033
+ localIp = stringValue(values, "wifiLocalIp"),
1034
+ )
1035
+ }
1036
+
1037
+ internal fun fromStoreFields(connected: Boolean, ssid: String?, localIp: String?): WifiStatus? {
1038
+ if (!connected) return Disconnected
1039
+ val nonEmptySsid = ssid?.trim()?.takeIf { it.isNotEmpty() }
1040
+ val nonEmptyLocalIp = localIp?.trim()?.takeIf { it.isNotEmpty() }
1041
+ return nonEmptySsid?.let { Connected(it, nonEmptyLocalIp) }
1042
+ }
1043
+
1044
+ private fun connectedFrom(values: Map<String, Any>): WifiStatus? =
1045
+ fromStoreFields(
1046
+ connected = true,
1047
+ ssid = stringValue(values, "ssid"),
1048
+ localIp = stringValue(values, "localIp"),
1049
+ )
1050
+ }
1051
+ }
1052
+
1053
+ data class WifiStatusEvent(
1054
+ val status: WifiStatus,
1055
+ ) {
1056
+ internal constructor(values: Map<String, Any>) : this(WifiStatus.fromMap(values) ?: WifiStatus.Disconnected)
1057
+ internal constructor(connected: Boolean, ssid: String?, localIp: String?) : this(
1058
+ WifiStatus.fromStoreFields(connected, ssid, localIp) ?: WifiStatus.Disconnected
1059
+ )
1060
+
1061
+ val values: Map<String, Any> get() = status.toEventMap()
1062
+ }
1063
+
1064
+ sealed interface HotspotStatus {
1065
+ val state: String
1066
+
1067
+ fun toMap(): Map<String, Any> =
1068
+ when (this) {
1069
+ is Enabled ->
1070
+ mapOf(
1071
+ "state" to state,
1072
+ "ssid" to ssid,
1073
+ "password" to password,
1074
+ "localIp" to localIp,
1075
+ )
1076
+ Disabled -> mapOf("state" to state)
1077
+ }
1078
+
1079
+ fun toEventMap(): Map<String, Any> =
1080
+ toMap() + mapOf("type" to "hotspot_status_change")
1081
+
1082
+ object Disabled : HotspotStatus {
1083
+ override val state: String = "disabled"
1084
+ }
1085
+
1086
+ data class Enabled(
1087
+ val ssid: String,
1088
+ val password: String,
1089
+ val localIp: String,
1090
+ ) : HotspotStatus {
1091
+ override val state: String = "enabled"
1092
+ }
1093
+
1094
+ companion object {
1095
+ internal fun fromMap(values: Map<String, Any>): HotspotStatus? {
1096
+ val hotspotValues = (values["hotspot"] as? Map<*, *>)?.stringKeyedMap() ?: values
1097
+ return when (stringValue(hotspotValues, "state")?.lowercase()) {
1098
+ "enabled" -> enabledFrom(hotspotValues)
1099
+ "disabled" -> Disabled
1100
+ else -> null
1101
+ }
1102
+ }
1103
+
1104
+ internal fun fromStoreMap(values: Map<String, Any>): HotspotStatus? {
1105
+ val enabled = boolValue(values, "hotspotEnabled") ?: return null
1106
+ return fromStoreFields(
1107
+ enabled = enabled,
1108
+ ssid = stringValue(values, "hotspotSsid"),
1109
+ password = stringValue(values, "hotspotPassword"),
1110
+ localIp = stringValue(values, "hotspotGatewayIp", "hotspotLocalIp"),
1111
+ )
1112
+ }
1113
+
1114
+ internal fun fromStoreFields(
1115
+ enabled: Boolean,
1116
+ ssid: String?,
1117
+ password: String?,
1118
+ localIp: String?,
1119
+ ): HotspotStatus? {
1120
+ if (!enabled) return Disabled
1121
+ val nonEmptySsid = ssid?.trim()?.takeIf { it.isNotEmpty() }
1122
+ val nonEmptyPassword = password?.trim()?.takeIf { it.isNotEmpty() }
1123
+ val nonEmptyLocalIp = localIp?.trim()?.takeIf { it.isNotEmpty() }
1124
+ return if (nonEmptySsid != null && nonEmptyPassword != null && nonEmptyLocalIp != null) {
1125
+ Enabled(nonEmptySsid, nonEmptyPassword, nonEmptyLocalIp)
1126
+ } else {
1127
+ null
1128
+ }
1129
+ }
1130
+
1131
+ private fun enabledFrom(values: Map<String, Any>): HotspotStatus? =
1132
+ fromStoreFields(
1133
+ enabled = true,
1134
+ ssid = stringValue(values, "ssid"),
1135
+ password = stringValue(values, "password"),
1136
+ localIp = stringValue(values, "localIp"),
1137
+ )
1138
+ }
1139
+ }
1140
+
1141
+ data class HotspotStatusEvent(
1142
+ val status: HotspotStatus,
1143
+ ) {
1144
+ internal constructor(values: Map<String, Any>) : this(HotspotStatus.fromMap(values) ?: HotspotStatus.Disabled)
1145
+ internal constructor(enabled: Boolean, ssid: String?, password: String?, localIp: String?) : this(
1146
+ HotspotStatus.fromStoreFields(enabled, ssid, password, localIp) ?: HotspotStatus.Disabled
1147
+ )
1148
+
1149
+ val values: Map<String, Any> get() = status.toEventMap()
1150
+ }
1151
+
1152
+ data class HotspotErrorEvent(
1153
+ val values: Map<String, Any>,
1154
+ ) {
1155
+ val message: String? get() = stringValue(values, "error_message", "message", "error")
1156
+ val timestamp: Long? get() = longValue(values, "timestamp")
1157
+ }
1158
+
1159
+ data class GalleryStatusEvent(
1160
+ val values: Map<String, Any>,
1161
+ )
1162
+
1163
+ sealed interface PhotoResponse {
1164
+ val state: String
1165
+ val requestId: String
1166
+ val timestamp: Long
1167
+
1168
+ fun toMap(): Map<String, Any> =
1169
+ when (this) {
1170
+ is Success -> mapOf(
1171
+ "state" to state,
1172
+ "requestId" to requestId,
1173
+ "photoUrl" to photoUrl,
1174
+ "timestamp" to timestamp,
1175
+ )
1176
+
1177
+ is Error -> mutableMapOf<String, Any>(
1178
+ "state" to state,
1179
+ "requestId" to requestId,
1180
+ "timestamp" to timestamp,
1181
+ "errorMessage" to errorMessage,
1182
+ ).apply {
1183
+ if (!errorCode.isNullOrBlank()) {
1184
+ this["errorCode"] = errorCode
1185
+ }
1186
+ }
1187
+ }
1188
+
1189
+ fun toEventMap(): Map<String, Any> = toMap() + mapOf("type" to "photo_response")
1190
+
1191
+ data class Success(
1192
+ override val requestId: String,
1193
+ val photoUrl: String,
1194
+ override val timestamp: Long,
1195
+ ) : PhotoResponse {
1196
+ override val state: String = "success"
1197
+ }
1198
+
1199
+ data class Error(
1200
+ override val requestId: String,
1201
+ val errorCode: String?,
1202
+ val errorMessage: String,
1203
+ override val timestamp: Long,
1204
+ ) : PhotoResponse {
1205
+ override val state: String = "error"
1206
+ }
1207
+
1208
+ companion object {
1209
+ fun fromMap(values: Map<String, Any>): PhotoResponse {
1210
+ val requestId = stringValue(values, "requestId", "request_id").orEmpty()
1211
+ val timestamp = longValue(values, "timestamp") ?: System.currentTimeMillis()
1212
+ val state = stringValue(values, "state", "status")?.lowercase()
1213
+ val success = boolValue(values, "success")
1214
+ return if (state == "success" || success == true) {
1215
+ val photoUrl = stringValue(values, "photoUrl", "photo_url").orEmpty()
1216
+ Success(requestId = requestId, photoUrl = photoUrl, timestamp = timestamp)
1217
+ } else {
1218
+ Error(
1219
+ requestId = requestId,
1220
+ errorCode = stringValue(values, "errorCode", "error_code"),
1221
+ errorMessage = stringValue(values, "errorMessage", "error_message", "error")
1222
+ ?: "Unknown photo error",
1223
+ timestamp = timestamp,
1224
+ )
1225
+ }
1226
+ }
1227
+ }
1228
+ }
1229
+
1230
+ data class PhotoResponseEvent(
1231
+ val response: PhotoResponse,
1232
+ ) {
1233
+ constructor(values: Map<String, Any>) : this(PhotoResponse.fromMap(values))
1234
+
1235
+ val requestId: String get() = response.requestId
1236
+ val values: Map<String, Any> get() = response.toEventMap()
1237
+ }
1238
+
1239
+ enum class StreamState(val value: String) {
1240
+ INITIALIZING("initializing"),
1241
+ STREAMING("streaming"),
1242
+ STOPPING("stopping"),
1243
+ STOPPED("stopped"),
1244
+ RECONNECTING("reconnecting"),
1245
+ RECONNECTED("reconnected"),
1246
+ RECONNECT_FAILED("reconnect_failed"),
1247
+ ERROR("error");
1248
+
1249
+ companion object {
1250
+ @JvmStatic
1251
+ fun fromValue(value: String?): StreamState? =
1252
+ when (value?.lowercase()) {
1253
+ "initializing", "starting", "connecting" -> INITIALIZING
1254
+ "streaming", "streaming_started", "active" -> STREAMING
1255
+ "stopping" -> STOPPING
1256
+ "stopped", "not_streaming", "disconnected", "timeout" -> STOPPED
1257
+ "reconnecting" -> RECONNECTING
1258
+ "reconnected" -> RECONNECTED
1259
+ "reconnect_failed" -> RECONNECT_FAILED
1260
+ "error", "error_not_streaming" -> ERROR
1261
+ else -> null
1262
+ }
1263
+ }
1264
+ }
1265
+
1266
+ enum class StreamStatusKind(val value: String) {
1267
+ LIFECYCLE("lifecycle"),
1268
+ RECONNECT("reconnect"),
1269
+ ERROR("error"),
1270
+ SNAPSHOT("snapshot"),
1271
+ }
1272
+
1273
+ sealed interface StreamStatus {
1274
+ val kind: StreamStatusKind
1275
+ val state: StreamState
1276
+ val streamId: String?
1277
+ val timestamp: Long?
1278
+
1279
+ fun toMap(): Map<String, Any> {
1280
+ val values = mutableMapOf<String, Any>(
1281
+ "kind" to kind.value,
1282
+ "status" to state.value,
1283
+ )
1284
+ streamId?.takeIf { it.isNotBlank() }?.let { values["streamId"] = it }
1285
+ timestamp?.let { values["timestamp"] = it }
1286
+
1287
+ when (this) {
1288
+ is Lifecycle -> Unit
1289
+ is Reconnecting -> {
1290
+ values["attempt"] = attempt
1291
+ values["maxAttempts"] = maxAttempts
1292
+ values["reason"] = reason
1293
+ }
1294
+ is Reconnected -> values["attempt"] = attempt
1295
+ is ReconnectFailed -> values["maxAttempts"] = maxAttempts
1296
+ is Error -> values["errorDetails"] = errorDetails
1297
+ is Snapshot -> {
1298
+ values["streaming"] = streaming
1299
+ values["reconnecting"] = reconnecting
1300
+ attempt?.let { values["attempt"] = it }
1301
+ }
1302
+ }
1303
+
1304
+ return values
1305
+ }
1306
+
1307
+ fun toEventMap(): Map<String, Any> = toMap() + mapOf("type" to "stream_status")
1308
+
1309
+ data class Lifecycle(
1310
+ override val state: StreamState,
1311
+ override val streamId: String?,
1312
+ override val timestamp: Long?,
1313
+ ) : StreamStatus {
1314
+ override val kind: StreamStatusKind = StreamStatusKind.LIFECYCLE
1315
+ }
1316
+
1317
+ data class Reconnecting(
1318
+ override val streamId: String?,
1319
+ val attempt: Int,
1320
+ val maxAttempts: Int,
1321
+ val reason: String,
1322
+ override val timestamp: Long?,
1323
+ ) : StreamStatus {
1324
+ override val kind: StreamStatusKind = StreamStatusKind.RECONNECT
1325
+ override val state: StreamState = StreamState.RECONNECTING
1326
+ }
1327
+
1328
+ data class Reconnected(
1329
+ override val streamId: String?,
1330
+ val attempt: Int,
1331
+ override val timestamp: Long?,
1332
+ ) : StreamStatus {
1333
+ override val kind: StreamStatusKind = StreamStatusKind.RECONNECT
1334
+ override val state: StreamState = StreamState.RECONNECTED
1335
+ }
1336
+
1337
+ data class ReconnectFailed(
1338
+ override val streamId: String?,
1339
+ val maxAttempts: Int,
1340
+ override val timestamp: Long?,
1341
+ ) : StreamStatus {
1342
+ override val kind: StreamStatusKind = StreamStatusKind.RECONNECT
1343
+ override val state: StreamState = StreamState.RECONNECT_FAILED
1344
+ }
1345
+
1346
+ data class Error(
1347
+ override val streamId: String?,
1348
+ val errorDetails: String,
1349
+ override val timestamp: Long?,
1350
+ ) : StreamStatus {
1351
+ override val kind: StreamStatusKind = StreamStatusKind.ERROR
1352
+ override val state: StreamState = StreamState.ERROR
1353
+ }
1354
+
1355
+ data class Snapshot(
1356
+ override val state: StreamState,
1357
+ val streaming: Boolean,
1358
+ val reconnecting: Boolean,
1359
+ override val streamId: String?,
1360
+ val attempt: Int?,
1361
+ override val timestamp: Long?,
1362
+ ) : StreamStatus {
1363
+ override val kind: StreamStatusKind = StreamStatusKind.SNAPSHOT
1364
+ }
1365
+
1366
+ companion object {
1367
+ @JvmStatic
1368
+ fun fromMap(values: Map<String, Any>): StreamStatus {
1369
+ val rawState = stringValue(values, "status")
1370
+ val streaming = boolValue(values, "streaming")
1371
+ val reconnecting = boolValue(values, "reconnecting") ?: false
1372
+ val streamId = stringValue(values, "streamId", "stream_id")
1373
+ val timestamp = longValue(values, "timestamp")
1374
+ val attempt = numberValue(values, "attempt")
1375
+ val maxAttempts = numberValue(values, "maxAttempts", "max_attempts") ?: 0
1376
+
1377
+ if (streaming != null || hasAnyKey(values, "reconnecting")) {
1378
+ return Snapshot(
1379
+ state = when {
1380
+ reconnecting -> StreamState.RECONNECTING
1381
+ streaming == true -> StreamState.STREAMING
1382
+ else -> StreamState.STOPPED
1383
+ },
1384
+ streaming = streaming == true,
1385
+ reconnecting = reconnecting,
1386
+ streamId = streamId,
1387
+ attempt = attempt,
1388
+ timestamp = timestamp,
1389
+ )
1390
+ }
1391
+
1392
+ val state = StreamState.fromValue(rawState)
1393
+ ?: return Error(
1394
+ streamId = streamId,
1395
+ errorDetails = rawState?.let { "Unknown stream status: $it" } ?: "Missing stream status",
1396
+ timestamp = timestamp,
1397
+ )
1398
+
1399
+ return when (state) {
1400
+ StreamState.RECONNECTING -> Reconnecting(
1401
+ streamId = streamId,
1402
+ attempt = attempt ?: 0,
1403
+ maxAttempts = maxAttempts,
1404
+ reason = stringValue(values, "reason") ?: "",
1405
+ timestamp = timestamp,
1406
+ )
1407
+ StreamState.RECONNECTED -> Reconnected(
1408
+ streamId = streamId,
1409
+ attempt = attempt ?: 0,
1410
+ timestamp = timestamp,
1411
+ )
1412
+ StreamState.RECONNECT_FAILED -> ReconnectFailed(
1413
+ streamId = streamId,
1414
+ maxAttempts = maxAttempts,
1415
+ timestamp = timestamp,
1416
+ )
1417
+ StreamState.ERROR -> Error(
1418
+ streamId = streamId,
1419
+ errorDetails = stringValue(values, "errorDetails", "error_details", "details", "error", "errorMessage")
1420
+ ?: if (rawState == "error_not_streaming") "not_streaming" else "Unknown stream error",
1421
+ timestamp = timestamp,
1422
+ )
1423
+ else -> Lifecycle(
1424
+ state = state,
1425
+ streamId = streamId,
1426
+ timestamp = timestamp,
1427
+ )
1428
+ }
1429
+ }
1430
+ }
1431
+ }
1432
+
1433
+ data class StreamStatusEvent(
1434
+ val status: StreamStatus,
1435
+ ) {
1436
+ constructor(values: Map<String, Any>) : this(StreamStatus.fromMap(values))
1437
+
1438
+ val state: StreamState get() = status.state
1439
+ val streamId: String? get() = status.streamId
1440
+ val values: Map<String, Any> get() = status.toEventMap()
1441
+ }
1442
+
1443
+ data class KeepAliveAckEvent(
1444
+ val streamId: String,
1445
+ val ackId: String,
1446
+ val timestamp: Long?,
1447
+ ) {
1448
+ constructor(values: Map<String, Any>) : this(
1449
+ streamId = stringValue(values, "streamId", "stream_id").orEmpty(),
1450
+ ackId = stringValue(values, "ackId", "ack_id").orEmpty(),
1451
+ timestamp = longValue(values, "timestamp"),
1452
+ )
1453
+
1454
+ val values: Map<String, Any>
1455
+ get() = buildMap {
1456
+ put("type", "keep_alive_ack")
1457
+ put("streamId", streamId)
1458
+ put("ackId", ackId)
1459
+ timestamp?.let { put("timestamp", it) }
1460
+ }
1461
+ }
1462
+
1463
+ data class LocalTranscriptionEvent(
1464
+ val text: String,
1465
+ val isFinal: Boolean,
1466
+ val values: Map<String, Any>,
1467
+ )
1468
+
1469
+ data class GlassesMediaVolumeGetResult(
1470
+ val volume: Int?,
1471
+ val statusCode: Int?,
1472
+ val values: Map<String, Any>,
1473
+ ) {
1474
+ companion object {
1475
+ fun fromMap(values: Map<String, Any>): GlassesMediaVolumeGetResult =
1476
+ GlassesMediaVolumeGetResult(
1477
+ volume = numberValue(values, "vol", "volume"),
1478
+ statusCode = (values["statusCode"] as? Number)?.toInt(),
1479
+ values = values,
1480
+ )
1481
+ }
1482
+ }
1483
+
1484
+ data class GlassesMediaVolumeSetResult(
1485
+ val statusCode: Int?,
1486
+ val values: Map<String, Any>,
1487
+ ) {
1488
+ companion object {
1489
+ fun fromMap(values: Map<String, Any>): GlassesMediaVolumeSetResult =
1490
+ GlassesMediaVolumeSetResult(
1491
+ statusCode = (values["statusCode"] as? Number)?.toInt(),
1492
+ values = values,
1493
+ )
1494
+ }
1495
+ }
1496
+
1497
+ enum class GlassesConnectionState(val value: String) {
1498
+ DISCONNECTED(ConnTypes.DISCONNECTED),
1499
+ SCANNING(ConnTypes.SCANNING),
1500
+ CONNECTING(ConnTypes.CONNECTING),
1501
+ BONDING(ConnTypes.BONDING),
1502
+ CONNECTED(ConnTypes.CONNECTED);
1503
+
1504
+ val isConnected: Boolean
1505
+ get() = this == CONNECTED
1506
+
1507
+ val isBusy: Boolean
1508
+ get() = this == SCANNING || this == CONNECTING || this == BONDING
1509
+
1510
+ internal fun toStatusMap(
1511
+ connected: Boolean,
1512
+ fullyBooted: Boolean,
1513
+ ): Map<String, Any> =
1514
+ when {
1515
+ this == CONNECTED || connected || fullyBooted ->
1516
+ mapOf("state" to "connected", "fullyBooted" to fullyBooted)
1517
+ this == SCANNING -> mapOf("state" to "scanning")
1518
+ this == CONNECTING -> mapOf("state" to "connecting")
1519
+ this == BONDING -> mapOf("state" to "bonding")
1520
+ else -> mapOf("state" to "disconnected")
1521
+ }
1522
+
1523
+ companion object {
1524
+ @JvmStatic
1525
+ fun fromValue(value: String?): GlassesConnectionState =
1526
+ optionalFromValue(value) ?: DISCONNECTED
1527
+
1528
+ internal fun optionalFromValue(value: String?): GlassesConnectionState? {
1529
+ val normalized = value?.trim()?.takeIf { it.isNotEmpty() } ?: return null
1530
+ return values().firstOrNull { it.value.equals(normalized, ignoreCase = true) }
1531
+ }
1532
+ }
1533
+ }
1534
+
1535
+ data class BluetoothError(
1536
+ val code: String,
1537
+ val message: String,
1538
+ val cause: Throwable? = null,
1539
+ )
1540
+
1541
+ enum class ScanStopReason {
1542
+ COMPLETED,
1543
+ CANCELLED,
1544
+ ERROR,
1545
+ }
1546
+
1547
+ interface MentraBluetoothSdkListener {
1548
+ fun onGlassesStatusChanged(status: GlassesStatusUpdate) {}
1549
+ fun onBluetoothStatusChanged(status: BluetoothStatusUpdate) {}
1550
+ fun onDeviceDiscovered(device: Device) {}
1551
+ fun onScanStopped(reason: ScanStopReason) {}
1552
+ fun onButtonPress(event: ButtonPressEvent) {}
1553
+ fun onTouch(event: TouchEvent) {}
1554
+ fun onSwipe(event: SwipeEvent) {}
1555
+ fun onHeadUpChanged(headUp: Boolean) {}
1556
+ fun onBatteryStatus(event: BatteryStatusEvent) {}
1557
+ fun onWifiStatusChanged(event: WifiStatusEvent) {}
1558
+ fun onHotspotStatusChanged(event: HotspotStatusEvent) {}
1559
+ fun onHotspotError(event: HotspotErrorEvent) {}
1560
+ fun onGalleryStatus(event: GalleryStatusEvent) {}
1561
+ fun onPhotoResponse(event: PhotoResponseEvent) {}
1562
+ fun onStreamStatus(event: StreamStatusEvent) {}
1563
+ fun onKeepAliveAck(event: KeepAliveAckEvent) {}
1564
+ fun onMicPcm(frame: ByteArray) {}
1565
+ fun onMicLc3(frame: ByteArray) {}
1566
+ fun onLocalTranscription(event: LocalTranscriptionEvent) {}
1567
+ fun onDefaultDeviceChanged(device: Device?) {}
1568
+ fun onLog(message: String) {}
1569
+ fun onError(error: BluetoothError) {}
1570
+ fun onRawEvent(eventName: String, values: Map<String, Any>) {}
1571
+ }
1572
+
1573
+ abstract class MentraBluetoothSdkCallback : MentraBluetoothSdkListener
1574
+
1575
+ private fun numberValue(
1576
+ values: Map<*, *>,
1577
+ fullKey: String,
1578
+ compactKey: String,
1579
+ ): Int? = ((values[fullKey] ?: values[compactKey]) as? Number)?.toInt()
1580
+
1581
+ private fun numberValue(
1582
+ values: Map<String, Any>,
1583
+ vararg keys: String,
1584
+ ): Int? =
1585
+ keys.firstNotNullOfOrNull { key ->
1586
+ (values[key] as? Number)?.toInt()
1587
+ }
1588
+
1589
+ private fun stringValue(
1590
+ values: Map<String, Any>,
1591
+ vararg keys: String,
1592
+ ): String? =
1593
+ keys.firstNotNullOfOrNull { key ->
1594
+ values[key]?.let { it as? String }
1595
+ }
1596
+
1597
+ private fun boolValue(
1598
+ values: Map<String, Any>,
1599
+ vararg keys: String,
1600
+ ): Boolean? =
1601
+ keys.firstNotNullOfOrNull { key ->
1602
+ values[key] as? Boolean
1603
+ }
1604
+
1605
+ private fun longValue(
1606
+ values: Map<String, Any>,
1607
+ vararg keys: String,
1608
+ ): Long? =
1609
+ keys.firstNotNullOfOrNull { key ->
1610
+ (values[key] as? Number)?.toLong()
1611
+ }
1612
+
1613
+ private fun hasAnyKey(
1614
+ values: Map<String, Any>,
1615
+ vararg keys: String,
1616
+ ): Boolean = keys.any(values::containsKey)
1617
+
1618
+ private fun Map<*, *>.stringKeyedMap(): Map<String, Any> =
1619
+ entries.mapNotNull { (key, value) ->
1620
+ val stringKey = key as? String ?: return@mapNotNull null
1621
+ val anyValue = value ?: return@mapNotNull null
1622
+ stringKey to anyValue
1623
+ }.toMap()
1624
+
1625
+ private fun optionalNumberValue(
1626
+ values: Map<String, Any>,
1627
+ vararg keys: String,
1628
+ ): Int? =
1629
+ if (hasAnyKey(values, *keys)) {
1630
+ numberValue(values, *keys)
1631
+ } else {
1632
+ null
1633
+ }
1634
+
1635
+ private fun optionalLongValue(
1636
+ values: Map<String, Any>,
1637
+ vararg keys: String,
1638
+ ): Long? =
1639
+ if (hasAnyKey(values, *keys)) {
1640
+ longValue(values, *keys)
1641
+ } else {
1642
+ null
1643
+ }
1644
+
1645
+ private fun optionalStringValue(
1646
+ values: Map<String, Any>,
1647
+ vararg keys: String,
1648
+ ): String? =
1649
+ if (hasAnyKey(values, *keys)) {
1650
+ stringValue(values, *keys) ?: ""
1651
+ } else {
1652
+ null
1653
+ }
1654
+
1655
+ private fun optionalBoolValue(
1656
+ values: Map<String, Any>,
1657
+ vararg keys: String,
1658
+ ): Boolean? =
1659
+ if (hasAnyKey(values, *keys)) {
1660
+ boolValue(values, *keys) ?: false
1661
+ } else {
1662
+ null
1663
+ }
1664
+
1665
+ private fun stringListValue(
1666
+ values: Map<String, Any>,
1667
+ key: String,
1668
+ ): List<String> = (values[key] as? List<*>)?.mapNotNull { it as? String } ?: emptyList()
1669
+
1670
+ private fun optionalStringListValue(
1671
+ values: Map<String, Any>,
1672
+ key: String,
1673
+ ): List<String>? =
1674
+ if (values.containsKey(key)) {
1675
+ stringListValue(values, key)
1676
+ } else {
1677
+ null
1678
+ }
1679
+
1680
+ private fun mapListValue(
1681
+ values: Map<String, Any>,
1682
+ key: String,
1683
+ ): List<Map<String, Any>> =
1684
+ (values[key] as? List<*>)?.mapNotNull(::stringMapValue) ?: emptyList()
1685
+
1686
+ private fun optionalMapListValue(
1687
+ values: Map<String, Any>,
1688
+ key: String,
1689
+ ): List<Map<String, Any>>? =
1690
+ if (values.containsKey(key)) {
1691
+ mapListValue(values, key)
1692
+ } else {
1693
+ null
1694
+ }
1695
+
1696
+ private fun stringMapValue(value: Any?): Map<String, Any>? =
1697
+ (value as? Map<*, *>)?.entries?.mapNotNull { (key, mapValue) ->
1698
+ val stringKey = key as? String ?: return@mapNotNull null
1699
+ val nonNullValue = mapValue ?: return@mapNotNull null
1700
+ stringKey to nonNullValue
1701
+ }?.toMap()
1702
+
1703
+ private fun MutableMap<String, Any>.putIfNotNull(
1704
+ key: String,
1705
+ value: Any?,
1706
+ ) {
1707
+ if (value != null) {
1708
+ put(key, value)
1709
+ }
1710
+ }