@glassly/bluetooth-sdk 0.1.0-dev.26

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 (548) hide show
  1. package/README.md +563 -0
  2. package/android/build.gradle +450 -0
  3. package/android/gradle.properties.example +9 -0
  4. package/android/lc3Lib/CMakeLists.txt +6 -0
  5. package/android/lc3Lib/build.gradle +212 -0
  6. package/android/lc3Lib/proguard-rules.pro +33 -0
  7. package/android/lc3Lib/src/main/AndroidManifest.xml +2 -0
  8. package/android/lc3Lib/src/main/cpp/CMakeLists.txt +8 -0
  9. package/android/lc3Lib/src/main/cpp/google_opus_stuff/CMakeLists.txt +30 -0
  10. package/android/lc3Lib/src/main/cpp/google_opus_stuff/jni/CMakeLists.txt +7 -0
  11. package/android/lc3Lib/src/main/cpp/google_opus_stuff/jni/ogg_opus_encoder.cc +96 -0
  12. package/android/lc3Lib/src/main/cpp/google_opus_stuff/jni/ogg_opus_encoder.h +53 -0
  13. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/Makefile.am +6 -0
  14. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/config_types.h +26 -0
  15. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/config_types.h.in +26 -0
  16. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/ogg.h +209 -0
  17. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/os_types.h +158 -0
  18. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus.h +981 -0
  19. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_custom.h +342 -0
  20. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_defines.h +799 -0
  21. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_multistream.h +660 -0
  22. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_projection.h +568 -0
  23. package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_types.h +166 -0
  24. package/android/lc3Lib/src/main/cpp/google_opus_stuff/ogg_opus_encoder.cc +268 -0
  25. package/android/lc3Lib/src/main/cpp/google_opus_stuff/ogg_opus_encoder.h +115 -0
  26. package/android/lc3Lib/src/main/cpp/google_opus_stuff/opus_tools/opus_header.h +59 -0
  27. package/android/lc3Lib/src/main/cpp/liblc3/CMakeLists.txt +38 -0
  28. package/android/lc3Lib/src/main/cpp/liblc3/include/lc3.h +309 -0
  29. package/android/lc3Lib/src/main/cpp/liblc3/include/lc3_private.h +162 -0
  30. package/android/lc3Lib/src/main/cpp/liblc3/include/rnnoise.h +114 -0
  31. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/CMakeLists.txt +19 -0
  32. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/attdet.c +92 -0
  33. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/attdet.h +44 -0
  34. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/bits.c +375 -0
  35. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/bits.h +315 -0
  36. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/bwdet.c +129 -0
  37. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/bwdet.h +69 -0
  38. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/common.h +148 -0
  39. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/energy.c +70 -0
  40. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/energy.h +43 -0
  41. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/fastmath.h +158 -0
  42. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/lc3.c +702 -0
  43. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/ltpf.c +893 -0
  44. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/ltpf.h +111 -0
  45. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/ltpf_arm.h +506 -0
  46. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/ltpf_neon.h +281 -0
  47. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/makefile.mk +35 -0
  48. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/mdct.c +452 -0
  49. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/mdct.h +57 -0
  50. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/mdct_neon.h +296 -0
  51. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/meson.build +46 -0
  52. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/plc.c +61 -0
  53. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/plc.h +57 -0
  54. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/sns.c +880 -0
  55. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/sns.h +103 -0
  56. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/spec.c +904 -0
  57. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/spec.h +119 -0
  58. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/tables.c +3457 -0
  59. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/tables.h +94 -0
  60. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/tns.c +457 -0
  61. package/android/lc3Lib/src/main/cpp/liblc3/liblc3/tns.h +99 -0
  62. package/android/lc3Lib/src/main/cpp/liblc3/liblc3.cpp +159 -0
  63. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/_kiss_fft_guts.h +182 -0
  64. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/arch.h +261 -0
  65. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/celt_lpc.c +279 -0
  66. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/celt_lpc.h +59 -0
  67. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/common.h +48 -0
  68. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/compile.sh +3 -0
  69. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/denoise.c +646 -0
  70. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/kiss_fft.c +601 -0
  71. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/kiss_fft.h +203 -0
  72. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/opus_types.h +159 -0
  73. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/pitch.c +526 -0
  74. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/pitch.h +149 -0
  75. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn.c +178 -0
  76. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn.h +69 -0
  77. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn_data.c +11051 -0
  78. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn_data.h +34 -0
  79. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn_reader.c +168 -0
  80. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn_train.py +66 -0
  81. package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/tansig_table.h +45 -0
  82. package/android/lc3Lib/src/main/java/com/glassly/lc3Lib/Lc3Cpp.java +38 -0
  83. package/android/lc3Lib/third_party/CMakeLists.txt +23 -0
  84. package/android/lc3Lib/third_party/CMakeLists_libogg.txt +18 -0
  85. package/android/lc3Lib/third_party/CMakeLists_libopus.txt +18 -0
  86. package/android/lc3Lib/third_party/CMakeLists_opus-tools.txt +23 -0
  87. package/android/lc3Lib/third_party/opus_tools/src/AUTHORS +5 -0
  88. package/android/lc3Lib/third_party/opus_tools/src/CMakeLists.txt +4 -0
  89. package/android/lc3Lib/third_party/opus_tools/src/COPYING +371 -0
  90. package/android/lc3Lib/third_party/opus_tools/src/ChangeLog +3 -0
  91. package/android/lc3Lib/third_party/opus_tools/src/Makefile.am +42 -0
  92. package/android/lc3Lib/third_party/opus_tools/src/Makefile.unix +23 -0
  93. package/android/lc3Lib/third_party/opus_tools/src/NEWS +0 -0
  94. package/android/lc3Lib/third_party/opus_tools/src/README +0 -0
  95. package/android/lc3Lib/third_party/opus_tools/src/autogen.sh +115 -0
  96. package/android/lc3Lib/third_party/opus_tools/src/configure.ac +293 -0
  97. package/android/lc3Lib/third_party/opus_tools/src/src/arch.h +239 -0
  98. package/android/lc3Lib/third_party/opus_tools/src/src/audio-in.c +1046 -0
  99. package/android/lc3Lib/third_party/opus_tools/src/src/diag_range.c +245 -0
  100. package/android/lc3Lib/third_party/opus_tools/src/src/diag_range.h +28 -0
  101. package/android/lc3Lib/third_party/opus_tools/src/src/info_opus.c +320 -0
  102. package/android/lc3Lib/third_party/opus_tools/src/src/info_opus.h +51 -0
  103. package/android/lc3Lib/third_party/opus_tools/src/src/lpc.c +157 -0
  104. package/android/lc3Lib/third_party/opus_tools/src/src/lpc.h +27 -0
  105. package/android/lc3Lib/third_party/opus_tools/src/src/opus_header.c +286 -0
  106. package/android/lc3Lib/third_party/opus_tools/src/src/opus_header.h +59 -0
  107. package/android/lc3Lib/third_party/opus_tools/src/src/opusdec.c +884 -0
  108. package/android/lc3Lib/third_party/opus_tools/src/src/opusenc.c +1021 -0
  109. package/android/lc3Lib/third_party/opus_tools/src/src/opusenc.h +101 -0
  110. package/android/lc3Lib/third_party/opus_tools/src/src/opusinfo.c +639 -0
  111. package/android/lc3Lib/third_party/opus_tools/src/src/opusinfo.h +51 -0
  112. package/android/lc3Lib/third_party/opus_tools/src/src/os_support.h +167 -0
  113. package/android/lc3Lib/third_party/opus_tools/src/src/resample.c +1137 -0
  114. package/android/lc3Lib/third_party/opus_tools/src/src/speex_resampler.h +344 -0
  115. package/android/lc3Lib/third_party/opus_tools/src/src/stack_alloc.h +115 -0
  116. package/android/lc3Lib/third_party/opus_tools/src/src/wav_io.c +125 -0
  117. package/android/lc3Lib/third_party/opus_tools/src/src/wav_io.h +62 -0
  118. package/android/lc3Lib/third_party/opus_tools/src/src/wave_out.c +223 -0
  119. package/android/lc3Lib/third_party/opus_tools/src/src/wave_out.h +60 -0
  120. package/android/settings.gradle +4 -0
  121. package/android/silero/LICENSE.md +17 -0
  122. package/android/silero/build.gradle +50 -0
  123. package/android/silero/proguard-rules.pro +24 -0
  124. package/android/silero/src/androidTest/assets/hello.wav +0 -0
  125. package/android/silero/src/androidTest/java/com/konovalov/vad/silero/VadSileroTest.kt +79 -0
  126. package/android/silero/src/main/assets/LICENSE +21 -0
  127. package/android/silero/src/main/assets/silero_vad.onnx +0 -0
  128. package/android/silero/src/main/java/com/konovalov/vad/silero/Vad.kt +160 -0
  129. package/android/silero/src/main/java/com/konovalov/vad/silero/VadSilero.kt +449 -0
  130. package/android/silero/src/main/java/com/konovalov/vad/silero/config/FrameSize.kt +15 -0
  131. package/android/silero/src/main/java/com/konovalov/vad/silero/config/Mode.kt +14 -0
  132. package/android/silero/src/main/java/com/konovalov/vad/silero/config/SampleRate.kt +12 -0
  133. package/android/silero/src/main/java/com/konovalov/vad/silero/utils/AudioUtils.kt +47 -0
  134. package/android/silero/src/main/java/com/konovalov/vad/silero/utils/TensorMap.kt +41 -0
  135. package/android/silero/src/test/java/com/konovalov/vad/silero/utils/AudioUtilsTest.kt +44 -0
  136. package/android/src/main/AndroidManifest.xml +42 -0
  137. package/android/src/main/assets/G2Firmware/README.md +11 -0
  138. package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdk.kt +2325 -0
  139. package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdkAnalytics.kt +216 -0
  140. package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdkDebug.kt +14 -0
  141. package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdkDefaults.kt +7 -0
  142. package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdkModule.kt +1088 -0
  143. package/android/src/main/java/com/glassly/bluetoothsdk/Bridge.kt +953 -0
  144. package/android/src/main/java/com/glassly/bluetoothsdk/DeviceManager.kt +2528 -0
  145. package/android/src/main/java/com/glassly/bluetoothsdk/DeviceStore.kt +360 -0
  146. package/android/src/main/java/com/glassly/bluetoothsdk/GeneratedChangelogCatalog.kt +8 -0
  147. package/android/src/main/java/com/glassly/bluetoothsdk/GeneratedReleaseMetadata.kt +11 -0
  148. package/android/src/main/java/com/glassly/bluetoothsdk/LocalNetworkModule.kt +414 -0
  149. package/android/src/main/java/com/glassly/bluetoothsdk/ObservableStore.kt +88 -0
  150. package/android/src/main/java/com/glassly/bluetoothsdk/OtaManifest.kt +191 -0
  151. package/android/src/main/java/com/glassly/bluetoothsdk/ReleaseChangelog.kt +61 -0
  152. package/android/src/main/java/com/glassly/bluetoothsdk/ScopedNetworkReadiness.kt +17 -0
  153. package/android/src/main/java/com/glassly/bluetoothsdk/audio/AudioModels.kt +144 -0
  154. package/android/src/main/java/com/glassly/bluetoothsdk/camera/CameraModels.kt +486 -0
  155. package/android/src/main/java/com/glassly/bluetoothsdk/connection/ConnectionModels.kt +78 -0
  156. package/android/src/main/java/com/glassly/bluetoothsdk/controllers/ControllerManager.kt +158 -0
  157. package/android/src/main/java/com/glassly/bluetoothsdk/controllers/R1.kt +937 -0
  158. package/android/src/main/java/com/glassly/bluetoothsdk/debug/BleTraceLogger.kt +165 -0
  159. package/android/src/main/java/com/glassly/bluetoothsdk/events/BluetoothEvents.kt +178 -0
  160. package/android/src/main/java/com/glassly/bluetoothsdk/internal/MapParsing.kt +140 -0
  161. package/android/src/main/java/com/glassly/bluetoothsdk/net/LocalIpv4.kt +57 -0
  162. package/android/src/main/java/com/glassly/bluetoothsdk/otaserver/LocalOtaServer.kt +304 -0
  163. package/android/src/main/java/com/glassly/bluetoothsdk/otaserver/OtaServerModule.kt +120 -0
  164. package/android/src/main/java/com/glassly/bluetoothsdk/photoreceiver/LocalPhotoReceiverRegistry.java +108 -0
  165. package/android/src/main/java/com/glassly/bluetoothsdk/photoreceiver/LocalPhotoUploadServer.kt +622 -0
  166. package/android/src/main/java/com/glassly/bluetoothsdk/photoreceiver/PhotoReceiverModule.kt +156 -0
  167. package/android/src/main/java/com/glassly/bluetoothsdk/requests/DisplayRequests.kt +56 -0
  168. package/android/src/main/java/com/glassly/bluetoothsdk/services/Foreground.kt +272 -0
  169. package/android/src/main/java/com/glassly/bluetoothsdk/services/PhoneMic.kt +1195 -0
  170. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/Ar99.java +2653 -0
  171. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/BesOtaHeartbeatGuard.kt +49 -0
  172. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/BesOtaProgressMapper.kt +27 -0
  173. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G1.kt +3878 -0
  174. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G1TextSanitizer.kt +38 -0
  175. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2.kt +6428 -0
  176. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwClassification.kt +49 -0
  177. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwRenderer.kt +1241 -0
  178. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwTextureCache.kt +270 -0
  179. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2FlashProtocol.kt +291 -0
  180. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2Flasher.kt +411 -0
  181. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/GlasslyBle.java +43189 -0
  182. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/Mach1.kt +1280 -0
  183. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraLive.kt +10817 -0
  184. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraLiveL2capChannel.kt +274 -0
  185. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraLivePairingAdvertisement.kt +54 -0
  186. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraNex.kt +2011 -0
  187. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/Nimo.kt +2532 -0
  188. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/SGCManager.kt +470 -0
  189. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/Simulated.kt +228 -0
  190. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/Ar99OtaManager.java +566 -0
  191. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/OtaByteUtils.java +18 -0
  192. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/OtaCommandConstants.java +35 -0
  193. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/OtaCrc32Util.java +13 -0
  194. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/OtaGattTransport.java +11 -0
  195. package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/OtaProtocol.java +92 -0
  196. package/android/src/main/java/com/glassly/bluetoothsdk/speakerid/SpeakerIdEngine.kt +525 -0
  197. package/android/src/main/java/com/glassly/bluetoothsdk/status/DeviceStatus.kt +669 -0
  198. package/android/src/main/java/com/glassly/bluetoothsdk/status/RuntimeState.kt +201 -0
  199. package/android/src/main/java/com/glassly/bluetoothsdk/status/WifiHotspotStatus.kt +189 -0
  200. package/android/src/main/java/com/glassly/bluetoothsdk/streaming/StreamModels.kt +518 -0
  201. package/android/src/main/java/com/glassly/bluetoothsdk/stt/STTTools.kt +391 -0
  202. package/android/src/main/java/com/glassly/bluetoothsdk/stt/SherpaOnnxTranscriber.kt +518 -0
  203. package/android/src/main/java/com/glassly/bluetoothsdk/stt/VadGateSpeechPolicy.kt +229 -0
  204. package/android/src/main/java/com/glassly/bluetoothsdk/tts/TTSTools.kt +248 -0
  205. package/android/src/main/java/com/glassly/bluetoothsdk/types/DeviceModels.kt +96 -0
  206. package/android/src/main/java/com/glassly/bluetoothsdk/utils/AES.java +106 -0
  207. package/android/src/main/java/com/glassly/bluetoothsdk/utils/AudioSessionMonitor.kt +189 -0
  208. package/android/src/main/java/com/glassly/bluetoothsdk/utils/AvifExifStripper.java +573 -0
  209. package/android/src/main/java/com/glassly/bluetoothsdk/utils/BitmapJavaUtils.java +169 -0
  210. package/android/src/main/java/com/glassly/bluetoothsdk/utils/BleJsonCompact.java +496 -0
  211. package/android/src/main/java/com/glassly/bluetoothsdk/utils/BlePhotoUploadService.java +677 -0
  212. package/android/src/main/java/com/glassly/bluetoothsdk/utils/BleWireProtocol.java +108 -0
  213. package/android/src/main/java/com/glassly/bluetoothsdk/utils/Constants.kt +46 -0
  214. package/android/src/main/java/com/glassly/bluetoothsdk/utils/G1Text.kt +436 -0
  215. package/android/src/main/java/com/glassly/bluetoothsdk/utils/HeifExifTagReader.java +199 -0
  216. package/android/src/main/java/com/glassly/bluetoothsdk/utils/IncidentLogBleRelayNaming.java +29 -0
  217. package/android/src/main/java/com/glassly/bluetoothsdk/utils/IncidentLogBleUploadService.java +77 -0
  218. package/android/src/main/java/com/glassly/bluetoothsdk/utils/K900LengthCodec.java +115 -0
  219. package/android/src/main/java/com/glassly/bluetoothsdk/utils/K900ProtocolUtils.java +827 -0
  220. package/android/src/main/java/com/glassly/bluetoothsdk/utils/MessageChunkReassembler.java +228 -0
  221. package/android/src/main/java/com/glassly/bluetoothsdk/utils/MessageChunker.java +325 -0
  222. package/android/src/main/java/com/glassly/bluetoothsdk/utils/NexSGCUtils.kt +698 -0
  223. package/android/src/main/java/com/glassly/bluetoothsdk/utils/PhoneAudioMonitor.kt +348 -0
  224. package/android/src/main/java/com/glassly/bluetoothsdk/utils/SmartGlassesConnectionState.java +9 -0
  225. package/android/src/main/java/com/glassly/bluetoothsdk/utils/audio/Ar99OpusPcmDecoder.java +398 -0
  226. package/android/src/main/java/com/glassly/bluetoothsdk/utils/audio/ByteUtilAudioPlayer.java +655 -0
  227. package/android/src/main/java/com/glassly/bluetoothsdk/utils/audio/Lc3Player.java +441 -0
  228. package/android/src/main/java/com/glassly/bluetoothsdk/utils/audio/PCMAudioPlayer.java +431 -0
  229. package/android/src/main/java/com/glassly/bluetoothsdk/utils/audio/PcmStreamPlayer.kt +328 -0
  230. package/android/src/main/res/values/strings.xml +4 -0
  231. package/android/src/test/java/com/glassly/bluetoothsdk/BluetoothSdkExceptionTest.kt +18 -0
  232. package/android/src/test/java/com/glassly/bluetoothsdk/OtaManifestDowngradeTest.kt +86 -0
  233. package/android/src/test/java/com/glassly/bluetoothsdk/OtaStartResponsePolicyTest.kt +30 -0
  234. package/android/src/test/java/com/glassly/bluetoothsdk/ReleaseChangelogTest.kt +13 -0
  235. package/android/src/test/java/com/glassly/bluetoothsdk/ScopedNetworkReadinessTest.kt +39 -0
  236. package/android/src/test/java/com/glassly/bluetoothsdk/StreamStatusEventTest.kt +29 -0
  237. package/android/src/test/java/com/glassly/bluetoothsdk/camera/PhotoRequestTest.kt +141 -0
  238. package/android/src/test/java/com/glassly/bluetoothsdk/otaserver/LocalOtaServerTest.kt +82 -0
  239. package/android/src/test/java/com/glassly/bluetoothsdk/photoreceiver/LocalPhotoReceiverRegistryTest.java +75 -0
  240. package/android/src/test/java/com/glassly/bluetoothsdk/services/ForegroundServiceTypeTest.kt +69 -0
  241. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/Ar99AdvertisementMatcherTest.java +22 -0
  242. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/BesOtaHeartbeatGuardTest.kt +73 -0
  243. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/BesOtaProgressMapperTest.kt +67 -0
  244. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/EvenRleTest.kt +63 -0
  245. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G1TextSanitizerTest.kt +31 -0
  246. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwClassificationTest.kt +89 -0
  247. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwRendererTest.kt +344 -0
  248. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwTextureCacheTest.kt +200 -0
  249. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2FlashProtocolTest.kt +131 -0
  250. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/MentraLiveGattCharacteristicsTest.kt +39 -0
  251. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/MentraLivePairingAdvertisementParserTest.kt +133 -0
  252. package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/NimoProtocolTest.kt +333 -0
  253. package/android/src/test/java/com/glassly/bluetoothsdk/utils/AvifExifStripperTest.java +46 -0
  254. package/android/src/test/java/com/glassly/bluetoothsdk/utils/BinaryMessageChunkerTest.java +84 -0
  255. package/android/src/test/java/com/glassly/bluetoothsdk/utils/BleJsonCompactTest.java +319 -0
  256. package/android/src/test/java/com/glassly/bluetoothsdk/utils/BlePhotoUploadServiceTest.java +107 -0
  257. package/android/src/test/java/com/glassly/bluetoothsdk/utils/K900ProtocolUtilsEndiannessTest.java +172 -0
  258. package/android/src/test/java/com/glassly/bluetoothsdk/utils/K900ProtocolUtilsTest.java +24 -0
  259. package/android/src/test/java/com/glassly/bluetoothsdk/utils/PhoneAudioMonitorTest.kt +45 -0
  260. package/android/src/test/resources/avif_with_exif.avif +0 -0
  261. package/app.plugin.js +3 -0
  262. package/build/BluetoothSdk.types.d.ts +1354 -0
  263. package/build/BluetoothSdk.types.d.ts.map +1 -0
  264. package/build/BluetoothSdk.types.js +41 -0
  265. package/build/BluetoothSdk.types.js.map +1 -0
  266. package/build/_internal.d.ts +17 -0
  267. package/build/_internal.d.ts.map +1 -0
  268. package/build/_internal.js +16 -0
  269. package/build/_internal.js.map +1 -0
  270. package/build/_private/BluetoothSdkModule.d.ts +172 -0
  271. package/build/_private/BluetoothSdkModule.d.ts.map +1 -0
  272. package/build/_private/BluetoothSdkModule.js +300 -0
  273. package/build/_private/BluetoothSdkModule.js.map +1 -0
  274. package/build/_private/LocalNetworkModule.d.ts +38 -0
  275. package/build/_private/LocalNetworkModule.d.ts.map +1 -0
  276. package/build/_private/LocalNetworkModule.js +3 -0
  277. package/build/_private/LocalNetworkModule.js.map +1 -0
  278. package/build/_private/cameraRequestPayload.d.ts +4 -0
  279. package/build/_private/cameraRequestPayload.d.ts.map +1 -0
  280. package/build/_private/cameraRequestPayload.js +32 -0
  281. package/build/_private/cameraRequestPayload.js.map +1 -0
  282. package/build/_private/photoRequestPayload.d.ts +6 -0
  283. package/build/_private/photoRequestPayload.d.ts.map +1 -0
  284. package/build/_private/photoRequestPayload.js +86 -0
  285. package/build/_private/photoRequestPayload.js.map +1 -0
  286. package/build/_private/sdkOtaManifest.d.ts +11 -0
  287. package/build/_private/sdkOtaManifest.d.ts.map +1 -0
  288. package/build/_private/sdkOtaManifest.js +15 -0
  289. package/build/_private/sdkOtaManifest.js.map +1 -0
  290. package/build/changelogs.d.ts +7 -0
  291. package/build/changelogs.d.ts.map +1 -0
  292. package/build/changelogs.js +46 -0
  293. package/build/changelogs.js.map +1 -0
  294. package/build/debug.d.ts +3 -0
  295. package/build/debug.d.ts.map +1 -0
  296. package/build/debug.js +8 -0
  297. package/build/debug.js.map +1 -0
  298. package/build/generated/changelogCatalog.d.ts +9 -0
  299. package/build/generated/changelogCatalog.d.ts.map +1 -0
  300. package/build/generated/changelogCatalog.js +12 -0
  301. package/build/generated/changelogCatalog.js.map +1 -0
  302. package/build/generated/releaseMetadata.d.ts +12 -0
  303. package/build/generated/releaseMetadata.d.ts.map +1 -0
  304. package/build/generated/releaseMetadata.js +10 -0
  305. package/build/generated/releaseMetadata.js.map +1 -0
  306. package/build/index.d.ts +6 -0
  307. package/build/index.d.ts.map +1 -0
  308. package/build/index.js +171 -0
  309. package/build/index.js.map +1 -0
  310. package/build/ota-server/OtaServer.types.d.ts +14 -0
  311. package/build/ota-server/OtaServer.types.d.ts.map +1 -0
  312. package/build/ota-server/OtaServer.types.js +2 -0
  313. package/build/ota-server/OtaServer.types.js.map +1 -0
  314. package/build/ota-server/OtaServerModule.d.ts +21 -0
  315. package/build/ota-server/OtaServerModule.d.ts.map +1 -0
  316. package/build/ota-server/OtaServerModule.js +3 -0
  317. package/build/ota-server/OtaServerModule.js.map +1 -0
  318. package/build/ota-server/OtaServerModule.web.d.ts +10 -0
  319. package/build/ota-server/OtaServerModule.web.d.ts.map +1 -0
  320. package/build/ota-server/OtaServerModule.web.js +12 -0
  321. package/build/ota-server/OtaServerModule.web.js.map +1 -0
  322. package/build/ota-server/index.d.ts +3 -0
  323. package/build/ota-server/index.d.ts.map +1 -0
  324. package/build/ota-server/index.js +3 -0
  325. package/build/ota-server/index.js.map +1 -0
  326. package/build/ota-transport/index.d.ts +67 -0
  327. package/build/ota-transport/index.d.ts.map +1 -0
  328. package/build/ota-transport/index.js +58 -0
  329. package/build/ota-transport/index.js.map +1 -0
  330. package/build/photo-receiver/PhotoReceiver.types.d.ts +18 -0
  331. package/build/photo-receiver/PhotoReceiver.types.d.ts.map +1 -0
  332. package/build/photo-receiver/PhotoReceiver.types.js +2 -0
  333. package/build/photo-receiver/PhotoReceiver.types.js.map +1 -0
  334. package/build/photo-receiver/PhotoReceiverModule.d.ts +10 -0
  335. package/build/photo-receiver/PhotoReceiverModule.d.ts.map +1 -0
  336. package/build/photo-receiver/PhotoReceiverModule.js +3 -0
  337. package/build/photo-receiver/PhotoReceiverModule.js.map +1 -0
  338. package/build/photo-receiver/PhotoReceiverModule.web.d.ts +10 -0
  339. package/build/photo-receiver/PhotoReceiverModule.web.d.ts.map +1 -0
  340. package/build/photo-receiver/PhotoReceiverModule.web.js +12 -0
  341. package/build/photo-receiver/PhotoReceiverModule.web.js.map +1 -0
  342. package/build/photo-receiver/index.d.ts +3 -0
  343. package/build/photo-receiver/index.d.ts.map +1 -0
  344. package/build/photo-receiver/index.js +3 -0
  345. package/build/photo-receiver/index.js.map +1 -0
  346. package/build/react/index.d.ts +5 -0
  347. package/build/react/index.d.ts.map +1 -0
  348. package/build/react/index.js +4 -0
  349. package/build/react/index.js.map +1 -0
  350. package/build/react/useBluetooth.d.ts +100 -0
  351. package/build/react/useBluetooth.d.ts.map +1 -0
  352. package/build/react/useBluetooth.js +167 -0
  353. package/build/react/useBluetooth.js.map +1 -0
  354. package/build/react/useBluetoothEvent.d.ts +6 -0
  355. package/build/react/useBluetoothEvent.d.ts.map +1 -0
  356. package/build/react/useBluetoothEvent.js +21 -0
  357. package/build/react/useBluetoothEvent.js.map +1 -0
  358. package/build/react/useBluetoothScan.d.ts +22 -0
  359. package/build/react/useBluetoothScan.d.ts.map +1 -0
  360. package/build/react/useBluetoothScan.js +135 -0
  361. package/build/react/useBluetoothScan.js.map +1 -0
  362. package/build/react/useBluetoothStatus.d.ts +16 -0
  363. package/build/react/useBluetoothStatus.d.ts.map +1 -0
  364. package/build/react/useBluetoothStatus.js +137 -0
  365. package/build/react/useBluetoothStatus.js.map +1 -0
  366. package/build/react/useGlassesConnection.d.ts +29 -0
  367. package/build/react/useGlassesConnection.d.ts.map +1 -0
  368. package/build/react/useGlassesConnection.js +153 -0
  369. package/build/react/useGlassesConnection.js.map +1 -0
  370. package/build/types/index.d.ts +3 -0
  371. package/build/types/index.d.ts.map +1 -0
  372. package/build/types/index.js +2 -0
  373. package/build/types/index.js.map +1 -0
  374. package/expo-module.config.json +14 -0
  375. package/ios/BackgroundOtaArtifactDownloader.swift +125 -0
  376. package/ios/BluetoothSdkModule.swift +1034 -0
  377. package/ios/GlasslyBluetoothSDK.podspec +106 -0
  378. package/ios/LocalIPv4.swift +150 -0
  379. package/ios/LocalOtaServer.swift +309 -0
  380. package/ios/LocalPhotoUploadServer.swift +754 -0
  381. package/ios/OtaServerModule.swift +147 -0
  382. package/ios/Packages/CoreObjC/CoreObjC.xcodeproj/project.pbxproj +213 -0
  383. package/ios/Packages/CoreObjC/PcmConverter.h +22 -0
  384. package/ios/Packages/CoreObjC/PcmConverter.m +266 -0
  385. package/ios/Packages/CoreObjC/attdet.c +92 -0
  386. package/ios/Packages/CoreObjC/attdet.h +44 -0
  387. package/ios/Packages/CoreObjC/bits.c +375 -0
  388. package/ios/Packages/CoreObjC/bits.h +315 -0
  389. package/ios/Packages/CoreObjC/bwdet.c +129 -0
  390. package/ios/Packages/CoreObjC/bwdet.h +69 -0
  391. package/ios/Packages/CoreObjC/common.h +151 -0
  392. package/ios/Packages/CoreObjC/energy.c +70 -0
  393. package/ios/Packages/CoreObjC/energy.h +43 -0
  394. package/ios/Packages/CoreObjC/fastmath.h +158 -0
  395. package/ios/Packages/CoreObjC/include/PcmConverter.h +22 -0
  396. package/ios/Packages/CoreObjC/lc3.c +704 -0
  397. package/ios/Packages/CoreObjC/lc3.h +313 -0
  398. package/ios/Packages/CoreObjC/lc3_cpp.h +283 -0
  399. package/ios/Packages/CoreObjC/lc3_private.h +163 -0
  400. package/ios/Packages/CoreObjC/ltpf.c +905 -0
  401. package/ios/Packages/CoreObjC/ltpf.h +111 -0
  402. package/ios/Packages/CoreObjC/ltpf_arm.h +506 -0
  403. package/ios/Packages/CoreObjC/ltpf_neon.h +281 -0
  404. package/ios/Packages/CoreObjC/makefile.mk +35 -0
  405. package/ios/Packages/CoreObjC/mdct.c +469 -0
  406. package/ios/Packages/CoreObjC/mdct.h +57 -0
  407. package/ios/Packages/CoreObjC/mdct_neon.h +296 -0
  408. package/ios/Packages/CoreObjC/meson.build +61 -0
  409. package/ios/Packages/CoreObjC/plc.c +61 -0
  410. package/ios/Packages/CoreObjC/plc.h +57 -0
  411. package/ios/Packages/CoreObjC/rnnoise.h +114 -0
  412. package/ios/Packages/CoreObjC/sns.c +880 -0
  413. package/ios/Packages/CoreObjC/sns.h +103 -0
  414. package/ios/Packages/CoreObjC/spec.c +907 -0
  415. package/ios/Packages/CoreObjC/spec.h +119 -0
  416. package/ios/Packages/CoreObjC/tables.c +3457 -0
  417. package/ios/Packages/CoreObjC/tables.h +94 -0
  418. package/ios/Packages/CoreObjC/tns.c +457 -0
  419. package/ios/Packages/CoreObjC/tns.h +99 -0
  420. package/ios/Packages/SherpaOnnx/Model/joiner.onnx +0 -0
  421. package/ios/Packages/SherpaOnnx/Model/tokens.txt +502 -0
  422. package/ios/Packages/SherpaOnnx/SherpaOnnx.swift +2383 -0
  423. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Info.plist +48 -0
  424. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/Headers/sherpa-onnx/c-api/c-api.h +4689 -0
  425. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/Headers/sherpa-onnx/c-api/cxx-api.h +2036 -0
  426. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/libsherpa-onnx.a +0 -0
  427. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/Headers/sherpa-onnx/c-api/c-api.h +4689 -0
  428. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/Headers/sherpa-onnx/c-api/cxx-api.h +2036 -0
  429. package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/libsherpa-onnx.a +0 -0
  430. package/ios/Packages/libbz2/module.modulemap +5 -0
  431. package/ios/Packages/libbz2/shim.h +1 -0
  432. package/ios/PhotoReceiverModule.swift +129 -0
  433. package/ios/Source/BluetoothSDK.swift +2303 -0
  434. package/ios/Source/BluetoothSDKDebug.swift +12 -0
  435. package/ios/Source/BluetoothSdkDefaults.swift +29 -0
  436. package/ios/Source/Bridge.swift +708 -0
  437. package/ios/Source/Bridging-Header.h +16 -0
  438. package/ios/Source/DeviceManager.swift +2109 -0
  439. package/ios/Source/DeviceStore.swift +336 -0
  440. package/ios/Source/GeneratedChangelogCatalog.swift +7 -0
  441. package/ios/Source/GeneratedReleaseMetadata.swift +11 -0
  442. package/ios/Source/ObservableStore.swift +118 -0
  443. package/ios/Source/OtaManifest.swift +211 -0
  444. package/ios/Source/PrivacyInfo.xcprivacy +23 -0
  445. package/ios/Source/ReleaseChangelog.swift +65 -0
  446. package/ios/Source/Resources/G2Firmware/README.md +10 -0
  447. package/ios/Source/audio/AudioModels.swift +178 -0
  448. package/ios/Source/audio/PcmStreamPlayer.swift +410 -0
  449. package/ios/Source/camera/CameraModels.swift +866 -0
  450. package/ios/Source/connection/ScanSession.swift +27 -0
  451. package/ios/Source/controllers/ControllerManager.swift +184 -0
  452. package/ios/Source/controllers/R1.swift +829 -0
  453. package/ios/Source/errors/BluetoothSdkError.swift +19 -0
  454. package/ios/Source/events/BluetoothEvents.swift +342 -0
  455. package/ios/Source/internal/BleTraceLogger.swift +192 -0
  456. package/ios/Source/internal/BluetoothAvailability.swift +78 -0
  457. package/ios/Source/internal/BluetoothSdkAnalytics.swift +199 -0
  458. package/ios/Source/internal/ValueParsing.swift +99 -0
  459. package/ios/Source/requests/DisplayRequests.swift +82 -0
  460. package/ios/Source/services/PhoneMic.swift +662 -0
  461. package/ios/Source/sgcs/Ar99.swift +2451 -0
  462. package/ios/Source/sgcs/Frame.swift +649 -0
  463. package/ios/Source/sgcs/G1.swift +2554 -0
  464. package/ios/Source/sgcs/G2.swift +6887 -0
  465. package/ios/Source/sgcs/G2CfwClassification.swift +43 -0
  466. package/ios/Source/sgcs/G2CfwRenderer.swift +1279 -0
  467. package/ios/Source/sgcs/G2CfwTextureCache.swift +233 -0
  468. package/ios/Source/sgcs/G2FlashProtocol.swift +312 -0
  469. package/ios/Source/sgcs/G2Flasher.swift +773 -0
  470. package/ios/Source/sgcs/Mach1.swift +541 -0
  471. package/ios/Source/sgcs/MentraLive.swift +7011 -0
  472. package/ios/Source/sgcs/MentraLiveL2capChannel.swift +183 -0
  473. package/ios/Source/sgcs/MentraLivePairingAdvertisement.swift +64 -0
  474. package/ios/Source/sgcs/MentraNex.swift +3221 -0
  475. package/ios/Source/sgcs/Nimo.swift +1886 -0
  476. package/ios/Source/sgcs/SGCManager.swift +608 -0
  477. package/ios/Source/sgcs/Simulated.swift +274 -0
  478. package/ios/Source/sgcs/ar99/ota/Ar99OtaManager.swift +637 -0
  479. package/ios/Source/sgcs/glassly_ble.pb.swift +4354 -0
  480. package/ios/Source/speakerid/SpeakerIdEngine.swift +573 -0
  481. package/ios/Source/status/DeviceStatus.swift +981 -0
  482. package/ios/Source/status/RuntimeState.swift +359 -0
  483. package/ios/Source/status/WifiHotspotStatus.swift +345 -0
  484. package/ios/Source/streaming/StreamModels.swift +693 -0
  485. package/ios/Source/stt/STTTools.swift +191 -0
  486. package/ios/Source/stt/SherpaOnnxTranscriber.swift +466 -0
  487. package/ios/Source/tts/TTSTools.swift +171 -0
  488. package/ios/Source/types/DeviceModels.swift +177 -0
  489. package/ios/Source/utils/AudioSessionMonitor.swift +283 -0
  490. package/ios/Source/utils/BinaryCodec.swift +61 -0
  491. package/ios/Source/utils/BleJsonCompact.swift +405 -0
  492. package/ios/Source/utils/BleWireProtocol.swift +92 -0
  493. package/ios/Source/utils/Constants.swift +78 -0
  494. package/ios/Source/utils/Enums.swift +95 -0
  495. package/ios/Source/utils/G1Text.swift +2096 -0
  496. package/ios/Source/utils/JSCExperiment.swift +241 -0
  497. package/ios/Source/utils/MemoryMonitor.swift +44 -0
  498. package/ios/Source/utils/MessageChunkReassembler.swift +190 -0
  499. package/ios/Source/utils/MessageChunker.swift +294 -0
  500. package/ios/Source/utils/Models.swift +135 -0
  501. package/ios/Source/utils/PhoneAudioMonitor.swift +230 -0
  502. package/ios/Source/utils/TarBz2Extractor.swift +257 -0
  503. package/ios/Tests/BluetoothSDKTests/G2CfwClassificationTests.swift +78 -0
  504. package/ios/Tests/BluetoothSDKTests/G2CfwRendererTests.swift +372 -0
  505. package/ios/Tests/BluetoothSDKTests/G2CfwTextureCacheTests.swift +162 -0
  506. package/ios/Tests/BluetoothSDKTests/G2FlashProtocolTests.swift +182 -0
  507. package/ios/Tests/MentraLivePairingAdvertisementTests.swift +160 -0
  508. package/ios/Tests/ReleaseChangelogTests.swift +13 -0
  509. package/package.json +129 -0
  510. package/plugin/build/index.d.ts +10 -0
  511. package/plugin/build/index.js +12 -0
  512. package/plugin/build/withAndroid.d.ts +3 -0
  513. package/plugin/build/withAndroid.js +190 -0
  514. package/plugin/build/withIos.d.ts +5 -0
  515. package/plugin/build/withIos.js +108 -0
  516. package/scripts/inject-ios-sdk-version.mjs +65 -0
  517. package/scripts/public-ota-api.test.mjs +37 -0
  518. package/scripts/verify-release-package.mjs +45 -0
  519. package/scripts/write-release-metadata.mjs +152 -0
  520. package/scripts/write-release-metadata.test.mjs +37 -0
  521. package/src/BluetoothSdk.types.ts +1623 -0
  522. package/src/_internal.ts +16 -0
  523. package/src/_private/BluetoothSdkModule.ts +684 -0
  524. package/src/_private/LocalNetworkModule.ts +50 -0
  525. package/src/_private/cameraRequestPayload.ts +34 -0
  526. package/src/_private/photoRequestPayload.ts +90 -0
  527. package/src/_private/sdkOtaManifest.ts +18 -0
  528. package/src/changelogs.ts +46 -0
  529. package/src/debug.ts +9 -0
  530. package/src/generated/changelogCatalog.ts +13 -0
  531. package/src/generated/releaseMetadata.ts +20 -0
  532. package/src/index.ts +321 -0
  533. package/src/ota-server/OtaServer.types.ts +10 -0
  534. package/src/ota-server/OtaServerModule.ts +23 -0
  535. package/src/ota-server/OtaServerModule.web.ts +17 -0
  536. package/src/ota-server/index.ts +2 -0
  537. package/src/ota-transport/index.ts +116 -0
  538. package/src/photo-receiver/PhotoReceiver.types.ts +20 -0
  539. package/src/photo-receiver/PhotoReceiverModule.ts +11 -0
  540. package/src/photo-receiver/PhotoReceiverModule.web.ts +17 -0
  541. package/src/photo-receiver/index.ts +2 -0
  542. package/src/react/index.ts +21 -0
  543. package/src/react/useBluetooth.ts +309 -0
  544. package/src/react/useBluetoothEvent.ts +35 -0
  545. package/src/react/useBluetoothScan.ts +173 -0
  546. package/src/react/useBluetoothStatus.ts +180 -0
  547. package/src/react/useGlassesConnection.ts +202 -0
  548. package/src/types/index.ts +12 -0
@@ -0,0 +1,328 @@
1
+ package com.glassly.bluetoothsdk.utils.audio
2
+
3
+ import android.media.AudioAttributes
4
+ import android.media.AudioFormat
5
+ import android.media.AudioTrack
6
+ import android.util.Base64
7
+ import android.util.Log
8
+ import java.util.ArrayDeque
9
+ import java.util.concurrent.locks.ReentrantLock
10
+ import kotlin.concurrent.withLock
11
+ import kotlin.math.max
12
+
13
+ /**
14
+ * Live PCM chunk player for miniapp `speaker.createStream()`.
15
+ *
16
+ * One instance = one stream session. 16-bit LE PCM only. Unlike the SCO-tuned
17
+ * [PCMAudioPlayer], this uses USAGE_MEDIA so the OS routes it like any media
18
+ * playback (A2DP to connected glasses, else phone speaker).
19
+ *
20
+ * Threading: a dedicated feeder thread drains an in-memory chunk queue into a
21
+ * MODE_STREAM AudioTrack (AudioTrack.write blocks there, not on callers).
22
+ * `write` blocks the CALLING thread only while the backlog exceeds
23
+ * [BACKPRESSURE_CEILING_MS] — call it from a background dispatcher. When the
24
+ * track underruns (queue empty), AudioTrack plays silence and playback resumes
25
+ * seamlessly on the next chunk; no stop/start pops.
26
+ */
27
+ class PcmStreamPlayer(
28
+ private val streamId: String,
29
+ private val sampleRate: Int,
30
+ channels: Int,
31
+ volume: Float,
32
+ ) {
33
+ companion object {
34
+ private const val TAG = "PcmStreamPlayer"
35
+
36
+ /** write() blocks while more than this is queued but unplayed. */
37
+ private const val BACKPRESSURE_CEILING_MS = 2_000L
38
+
39
+ /** Hard cap — writes that would exceed this are rejected outright. */
40
+ private const val MAX_BACKLOG_MS = 10_000L
41
+
42
+ /** AudioTrack buffer: max(4x minimum, 500ms) of jitter headroom. */
43
+ private const val TRACK_BUFFER_MS = 500
44
+ }
45
+
46
+ private val bytesPerFrame = 2 * channels
47
+ private val channelMask =
48
+ if (channels >= 2) AudioFormat.CHANNEL_OUT_STEREO else AudioFormat.CHANNEL_OUT_MONO
49
+
50
+ private val lock = ReentrantLock()
51
+ private val queueChanged = lock.newCondition()
52
+
53
+ /** Pending chunks not yet handed to AudioTrack. Guarded by [lock]. */
54
+ private val queue = ArrayDeque<ByteArray>()
55
+ private var queuedBytes = 0L
56
+
57
+ /** Total frames handed to AudioTrack.write. Guarded by [lock]. */
58
+ private var framesWritten = 0L
59
+
60
+ private var closing = false
61
+ private var aborted = false
62
+ private var writeInProgress = false
63
+ private var released = false
64
+ private var finalPlayedFrames: Long? = null
65
+ private var endedError: String? = null
66
+
67
+ private val audioTrack: AudioTrack
68
+ private val feeder: Thread
69
+
70
+ init {
71
+ val minBuf =
72
+ AudioTrack.getMinBufferSize(sampleRate, channelMask, AudioFormat.ENCODING_PCM_16BIT)
73
+ val jitterBuf = sampleRate * bytesPerFrame * TRACK_BUFFER_MS / 1000
74
+ audioTrack =
75
+ AudioTrack.Builder()
76
+ .setAudioAttributes(
77
+ AudioAttributes.Builder()
78
+ .setUsage(AudioAttributes.USAGE_MEDIA)
79
+ .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
80
+ .build()
81
+ )
82
+ .setAudioFormat(
83
+ AudioFormat.Builder()
84
+ .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
85
+ .setSampleRate(sampleRate)
86
+ .setChannelMask(channelMask)
87
+ .build()
88
+ )
89
+ .setBufferSizeInBytes(max(minBuf * 4, jitterBuf))
90
+ .setTransferMode(AudioTrack.MODE_STREAM)
91
+ .build()
92
+ if (audioTrack.state != AudioTrack.STATE_INITIALIZED) {
93
+ audioTrack.release()
94
+ throw IllegalStateException("AudioTrack failed to initialize (rate=$sampleRate)")
95
+ }
96
+ audioTrack.setVolume(volume.coerceIn(0f, 1f))
97
+ audioTrack.play()
98
+
99
+ feeder =
100
+ Thread({ feedLoop() }, "PcmStreamPlayer-$streamId").apply {
101
+ isDaemon = true
102
+ start()
103
+ }
104
+ Log.d(TAG, "[$streamId] opened: rate=$sampleRate ch=$channels vol=$volume")
105
+ }
106
+
107
+ /** Milliseconds of audio accepted but not yet played out. */
108
+ private fun backlogMsLocked(): Long {
109
+ val playedFrames = audioTrack.playbackHeadPosition.toLong() and 0xFFFFFFFFL
110
+ val queuedFrames = queuedBytes / bytesPerFrame
111
+ return max(0L, (framesWritten - playedFrames + queuedFrames)) * 1000L / sampleRate
112
+ }
113
+
114
+ /**
115
+ * Append base64-encoded 16-bit LE PCM. Blocks while the backlog is above
116
+ * the backpressure ceiling. Returns the backlog in ms after this chunk.
117
+ */
118
+ fun write(base64: String): Long {
119
+ val bytes = Base64.decode(base64, Base64.DEFAULT)
120
+ require(bytes.isNotEmpty()) { "PCM chunk is empty" }
121
+ require(bytes.size % bytesPerFrame == 0) {
122
+ "PCM chunk length ${bytes.size} is not aligned to a $bytesPerFrame-byte frame"
123
+ }
124
+ lock.withLock {
125
+ check(!closing && !aborted) { "stream is closed" }
126
+ endedError?.let { throw IllegalStateException(it) }
127
+ check(backlogMsLocked() + bytes.size / bytesPerFrame * 1000L / sampleRate <= MAX_BACKLOG_MS) {
128
+ "backlog exceeds ${MAX_BACKLOG_MS}ms — producer is not throttling"
129
+ }
130
+ queue.add(bytes)
131
+ queuedBytes += bytes.size
132
+ queueChanged.signalAll()
133
+ // Backpressure: hold the caller until the feeder drains below the
134
+ // ceiling. Caller is a background dispatcher, never the UI thread.
135
+ while (backlogMsLocked() > BACKPRESSURE_CEILING_MS && !closing && !aborted && endedError == null) {
136
+ queueChanged.await()
137
+ }
138
+ endedError?.let { throw IllegalStateException(it) }
139
+ return backlogMsLocked()
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Drain everything queued, then stop. Blocks until played out. Returns the
145
+ * total played duration in ms.
146
+ */
147
+ fun close(): Long {
148
+ lock.withLock {
149
+ if (aborted) return playedMsLocked()
150
+ closing = true
151
+ queueChanged.signalAll()
152
+ // Wait for the feeder to hand everything to the track...
153
+ while ((queue.isNotEmpty() || writeInProgress) && endedError == null && !aborted) {
154
+ queueChanged.await()
155
+ }
156
+ }
157
+ // ...then for the track to play it out while AudioTrack is still in
158
+ // PLAYSTATE_PLAYING. Calling stop() first flushes pending MODE_STREAM
159
+ // audio on several Android builds and truncates the final chunk.
160
+ try {
161
+ val deadline = System.currentTimeMillis() + MAX_BACKLOG_MS
162
+ while (System.currentTimeMillis() < deadline) {
163
+ val drained =
164
+ lock.withLock {
165
+ val played = audioTrack.playbackHeadPosition.toLong() and 0xFFFFFFFFL
166
+ played >= framesWritten || endedError != null
167
+ }
168
+ if (drained) break
169
+ Thread.sleep(20)
170
+ }
171
+ audioTrack.stop()
172
+ } catch (e: Exception) {
173
+ Log.w(TAG, "[$streamId] drain-on-close failed", e)
174
+ } finally {
175
+ releaseInternals()
176
+ }
177
+ return playedMsLocked()
178
+ }
179
+
180
+ /** Stop now, dropping the backlog. Idempotent. */
181
+ fun abort() {
182
+ lock.withLock {
183
+ if (aborted) return
184
+ aborted = true
185
+ queue.clear()
186
+ queuedBytes = 0
187
+ queueChanged.signalAll()
188
+ }
189
+ try {
190
+ audioTrack.pause()
191
+ audioTrack.flush()
192
+ } catch (e: Exception) {
193
+ Log.w(TAG, "[$streamId] abort flush failed", e)
194
+ }
195
+ releaseInternals()
196
+ Log.d(TAG, "[$streamId] aborted")
197
+ }
198
+
199
+ /** Played duration so far, in ms. */
200
+ fun playedMs(): Long = lock.withLock { playedMsLocked() }
201
+
202
+ private fun playedMsLocked(): Long {
203
+ val played =
204
+ finalPlayedFrames
205
+ ?: try {
206
+ audioTrack.playbackHeadPosition.toLong() and 0xFFFFFFFFL
207
+ } catch (e: Exception) {
208
+ framesWritten
209
+ }
210
+ return played * 1000L / sampleRate
211
+ }
212
+
213
+ private fun feedLoop() {
214
+ try {
215
+ while (true) {
216
+ val chunk: ByteArray =
217
+ lock.withLock {
218
+ while (queue.isEmpty() && !closing && !aborted) {
219
+ queueChanged.await()
220
+ }
221
+ if (aborted) return
222
+ if (queue.isEmpty() && closing) return
223
+ queue.poll()!!.also { writeInProgress = true }
224
+ }
225
+ // Blocking write OUTSIDE the lock so write()/abort() stay responsive.
226
+ var off = 0
227
+ while (off < chunk.size) {
228
+ val n = audioTrack.write(chunk, off, chunk.size - off)
229
+ if (n < 0) throw IllegalStateException("AudioTrack.write error $n")
230
+ off += n
231
+ }
232
+ lock.withLock {
233
+ queuedBytes -= chunk.size
234
+ framesWritten += chunk.size / bytesPerFrame
235
+ writeInProgress = false
236
+ queueChanged.signalAll()
237
+ }
238
+ }
239
+ } catch (e: Exception) {
240
+ if (!aborted) {
241
+ Log.e(TAG, "[$streamId] feeder failed", e)
242
+ lock.withLock {
243
+ endedError = e.message ?: "feeder failed"
244
+ queue.clear()
245
+ queuedBytes = 0
246
+ writeInProgress = false
247
+ queueChanged.signalAll()
248
+ }
249
+ }
250
+ } finally {
251
+ lock.withLock { queueChanged.signalAll() }
252
+ }
253
+ }
254
+
255
+ private fun releaseInternals() {
256
+ val shouldRelease =
257
+ lock.withLock {
258
+ if (released) {
259
+ false
260
+ } else {
261
+ finalPlayedFrames =
262
+ try {
263
+ audioTrack.playbackHeadPosition.toLong() and 0xFFFFFFFFL
264
+ } catch (_: Exception) {
265
+ framesWritten
266
+ }
267
+ released = true
268
+ true
269
+ }
270
+ }
271
+ if (!shouldRelease) return
272
+ try {
273
+ audioTrack.release()
274
+ } catch (e: Exception) {
275
+ Log.w(TAG, "[$streamId] release failed", e)
276
+ }
277
+ }
278
+ }
279
+
280
+ /**
281
+ * Registry of active [PcmStreamPlayer] sessions, keyed by streamId. All
282
+ * methods are safe to call with unknown ids (write throws, abort no-ops) so
283
+ * races with teardown resolve cleanly.
284
+ */
285
+ object PcmStreamManager {
286
+ private val players = java.util.concurrent.ConcurrentHashMap<String, PcmStreamPlayer>()
287
+
288
+ fun open(streamId: String, sampleRate: Int, channels: Int, volume: Float) {
289
+ require(streamId.isNotBlank()) { "streamId is required" }
290
+ require(sampleRate == 16000 || sampleRate == 24000 || sampleRate == 48000) {
291
+ "unsupported PCM sample rate $sampleRate"
292
+ }
293
+ require(channels == 1) { "only mono PCM is supported" }
294
+ // Replacing an id is a caller bug, but never leak the old track.
295
+ players.remove(streamId)?.abort()
296
+ players[streamId] = PcmStreamPlayer(streamId, sampleRate, channels, volume)
297
+ }
298
+
299
+ fun write(streamId: String, base64: String): Long {
300
+ val player =
301
+ players[streamId] ?: throw IllegalStateException("pcm stream $streamId is not open")
302
+ return player.write(base64)
303
+ }
304
+
305
+ fun close(streamId: String): Long {
306
+ val player =
307
+ players[streamId]
308
+ ?: throw IllegalStateException("pcm stream $streamId is not open")
309
+ return try {
310
+ player.close()
311
+ } finally {
312
+ // Leave a draining player addressable so teardown can still abort
313
+ // it. The conditional remove preserves a replacement with the
314
+ // same id if one was opened while this close was completing.
315
+ players.remove(streamId, player)
316
+ }
317
+ }
318
+
319
+ fun abort(streamId: String) {
320
+ players.remove(streamId)?.abort()
321
+ }
322
+
323
+ fun abortAll() {
324
+ val active = players.values.toList()
325
+ players.clear()
326
+ active.forEach { it.abort() }
327
+ }
328
+ }
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <resources>
3
+ <string name="app_name">Glassly</string>
4
+ </resources>
@@ -0,0 +1,18 @@
1
+ package com.glassly.bluetoothsdk
2
+
3
+ import org.assertj.core.api.Assertions.assertThat
4
+ import org.junit.Test
5
+
6
+ class BluetoothSdkExceptionTest {
7
+ @Test
8
+ fun `exception is usable without the Expo runtime`() {
9
+ val cause = IllegalArgumentException("cause")
10
+ val error = BluetoothSdkException("capture_failed", "Capture failed", cause)
11
+
12
+ assertThat(error.code).isEqualTo("capture_failed")
13
+ assertThat(error.message).isEqualTo("Capture failed")
14
+ assertThat(error.cause).isSameAs(cause)
15
+ assertThat(error).isInstanceOf(IllegalStateException::class.java)
16
+ assertThat(error.javaClass.superclass.name).doesNotStartWith("expo.modules")
17
+ }
18
+ }
@@ -0,0 +1,86 @@
1
+ package com.glassly.bluetoothsdk
2
+
3
+ import org.json.JSONObject
4
+ import org.junit.Assert.assertEquals
5
+ import org.junit.Assert.assertFalse
6
+ import org.junit.Assert.assertTrue
7
+ import org.junit.Assert.fail
8
+ import org.junit.Test
9
+ import org.junit.runner.RunWith
10
+ import org.robolectric.RobolectricTestRunner
11
+ import org.robolectric.annotation.Config
12
+
13
+ @RunWith(RobolectricTestRunner::class)
14
+ @Config(sdk = [33])
15
+ class OtaManifestDowngradeTest {
16
+
17
+ private fun manifest(versionCode: Long): JSONObject =
18
+ JSONObject()
19
+ .put("apps", JSONObject().put("com.mentra.asg_client", JSONObject().put("versionCode", versionCode)))
20
+
21
+ private fun hasUpdate(
22
+ currentBuildNumber: String,
23
+ manifest: JSONObject,
24
+ floor: Long = 0L,
25
+ ): Boolean =
26
+ OtaManifestChecker.hasUpdate(
27
+ currentBuildNumber = currentBuildNumber,
28
+ currentMtkVersion = "",
29
+ currentBesVersion = "",
30
+ manifest = manifest,
31
+ downgradeFloorVersionCode = floor,
32
+ )
33
+
34
+ @Test
35
+ fun upgradeIsAlwaysAnUpdate() {
36
+ assertTrue(hasUpdate("49000000", manifest(49076573L)))
37
+ }
38
+
39
+ @Test
40
+ fun exactPinIsNotAnUpdateEvenWithAnEnabledFloor() {
41
+ assertFalse(hasUpdate("49000000", manifest(49000000L), floor = 49000000L))
42
+ }
43
+
44
+ @Test
45
+ fun downgradeNeedsAnEnabledFloor() {
46
+ // Default floor 0 (fail closed) disables downgrades for native/OEM callers.
47
+ assertFalse(hasUpdate("49076573", manifest(49000000L)))
48
+ // A positive floor at/below the pin enables it.
49
+ assertTrue(hasUpdate("49076573", manifest(49000000L), floor = 49000000L))
50
+ // A pin below the floor is refused.
51
+ assertFalse(hasUpdate("49076573", manifest(48999999L), floor = 49000000L))
52
+ }
53
+
54
+ @Test
55
+ fun exactPinIsNotAnUpdate() {
56
+ assertFalse(hasUpdate("49076573", manifest(49076573L)))
57
+ }
58
+
59
+ private fun legacyTopLevelManifest(versionCode: Long): JSONObject =
60
+ JSONObject().put("versionCode", versionCode)
61
+
62
+ @Test
63
+ fun legacyTopLevelManifestStaysUpgradeOnly() {
64
+ // No apps entry -> not an exact pin -> upgrade-only, never a false downgrade.
65
+ assertFalse(hasUpdate("49076573", legacyTopLevelManifest(49000000L)))
66
+ assertTrue(hasUpdate("49000000", legacyTopLevelManifest(49076573L)))
67
+ }
68
+
69
+ @Test
70
+ fun zeroedPinOnModernGlassesIsAnError() {
71
+ // An unverifiable pin must never present as "no update" on modern glasses.
72
+ try {
73
+ hasUpdate("49076573", manifest(0L))
74
+ fail("expected invalid_ota_manifest")
75
+ } catch (e: BluetoothSdkException) {
76
+ assertEquals("invalid_ota_manifest", e.code)
77
+ }
78
+ }
79
+
80
+ @Test
81
+ fun zeroedRescuePinIsInertForLegacyGlasses() {
82
+ // Pre-39 glasses are legitimately checked against the frozen rescue manifests, whose
83
+ // zeroed ASG pin must read as no APK update (the MTK patch chain is what matters there).
84
+ assertFalse(hasUpdate("38", manifest(0L)))
85
+ }
86
+ }
@@ -0,0 +1,30 @@
1
+ package com.glassly.bluetoothsdk
2
+
3
+ import org.assertj.core.api.Assertions.assertThat
4
+ import org.junit.Test
5
+
6
+ class OtaStartResponsePolicyTest {
7
+ @Test
8
+ fun batteryLowFailureRejectsPendingStart() {
9
+ assertThat(otaStartRejectionErrorCode(status("failed", "battery_low")))
10
+ .isEqualTo("battery_low")
11
+ }
12
+
13
+ @Test
14
+ fun unrelatedFailureDoesNotRejectPendingStart() {
15
+ assertThat(otaStartRejectionErrorCode(status("failed", "download_failed"))).isNull()
16
+ }
17
+
18
+ @Test
19
+ fun nonFailureDoesNotRejectPendingStart() {
20
+ assertThat(otaStartRejectionErrorCode(status("in_progress", "battery_low"))).isNull()
21
+ }
22
+
23
+ private fun status(status: String, errorMessage: String): OtaStatusEvent =
24
+ OtaStatusEvent.fromMap(
25
+ mapOf(
26
+ "status" to status,
27
+ "error_message" to errorMessage,
28
+ )
29
+ )
30
+ }
@@ -0,0 +1,13 @@
1
+ package com.glassly.bluetoothsdk
2
+
3
+ import org.junit.Assert.assertEquals
4
+ import org.junit.Test
5
+
6
+ class ReleaseChangelogTest {
7
+ @Test
8
+ fun includesTargetNotesForTransitionWithinOneReleaseTrain() {
9
+ val changelogs = ReleaseChangelogCatalog.select("3.1.0-dev.2", "3.1.0-beta.8")
10
+
11
+ assertEquals(listOf("3.1.0"), changelogs.map { it.version })
12
+ }
13
+ }
@@ -0,0 +1,39 @@
1
+ package com.glassly.bluetoothsdk
2
+
3
+ import org.junit.Assert.assertFalse
4
+ import org.junit.Assert.assertTrue
5
+ import org.junit.Test
6
+
7
+ class ScopedNetworkReadinessTest {
8
+ @Test
9
+ fun `waits for the available network to become foreground`() {
10
+ val readiness = ScopedNetworkReadiness<String>()
11
+
12
+ readiness.onAvailable("glasses")
13
+
14
+ assertFalse(readiness.isReady("glasses", isForeground = false))
15
+ assertTrue(readiness.isReady("glasses", isForeground = true))
16
+ }
17
+
18
+ @Test
19
+ fun `rejects capabilities from a stale network`() {
20
+ val readiness = ScopedNetworkReadiness<String>()
21
+
22
+ readiness.onAvailable("old")
23
+ readiness.onAvailable("current")
24
+
25
+ assertFalse(readiness.isReady("old", isForeground = true))
26
+ assertTrue(readiness.isReady("current", isForeground = true))
27
+ }
28
+
29
+ @Test
30
+ fun `clears the candidate when the request ends`() {
31
+ val readiness = ScopedNetworkReadiness<String>()
32
+ readiness.onAvailable("glasses")
33
+
34
+ readiness.clear()
35
+
36
+ assertFalse(readiness.contains("glasses"))
37
+ assertFalse(readiness.isReady("glasses", isForeground = true))
38
+ }
39
+ }
@@ -0,0 +1,29 @@
1
+ package com.glassly.bluetoothsdk
2
+
3
+ import org.assertj.core.api.Assertions.assertThat
4
+ import org.junit.Test
5
+
6
+ class StreamStatusEventTest {
7
+ @Test
8
+ fun `stream telemetry survives native status normalization`() {
9
+ val event =
10
+ StreamStatusEvent(
11
+ mapOf(
12
+ "status" to "streaming",
13
+ "streamId" to "stream-1",
14
+ "stats" to
15
+ mapOf(
16
+ "bitrate" to 912_345L,
17
+ "fps" to 19.8,
18
+ "droppedFrames" to 2L,
19
+ "duration" to 31L,
20
+ "temperatureC" to 54.6,
21
+ ),
22
+ )
23
+ )
24
+
25
+ assertThat(event.stats?.bitrate).isEqualTo(912_345L)
26
+ assertThat(event.stats?.temperatureC).isEqualTo(54.6)
27
+ assertThat((event.values["stats"] as Map<*, *>)["duration"]).isEqualTo(31L)
28
+ }
29
+ }
@@ -0,0 +1,141 @@
1
+ package com.glassly.bluetoothsdk.camera
2
+
3
+ import com.glassly.bluetoothsdk.PhotoCompression
4
+ import com.glassly.bluetoothsdk.PhotoMode
5
+ import com.glassly.bluetoothsdk.PhotoRequest
6
+ import com.glassly.bluetoothsdk.PhotoSize
7
+ import org.assertj.core.api.Assertions.assertThat
8
+ import org.assertj.core.api.Assertions.assertThatThrownBy
9
+ import org.junit.Test
10
+
11
+ class PhotoRequestTest {
12
+ @Test
13
+ fun `constructor generates requestId when omitted`() {
14
+ val request =
15
+ PhotoRequest(
16
+ size = PhotoSize.MEDIUM,
17
+ webhookUrl = "https://example.com/upload",
18
+ compress = PhotoCompression.NONE,
19
+ sound = true,
20
+ )
21
+
22
+ assertThat(request.requestId).startsWith("photo-")
23
+ }
24
+
25
+ @Test
26
+ fun `fromMap defaults exposureTimeNs null`() {
27
+ val request =
28
+ PhotoRequest.fromMap(
29
+ mapOf(
30
+ "requestId" to "photo-1",
31
+ "size" to "medium",
32
+ "webhookUrl" to "https://example.com/upload",
33
+ "compress" to "none",
34
+ "sound" to true,
35
+ )
36
+ )
37
+
38
+ assertThat(request.exposureTimeNs).isNull()
39
+ assertThat(request.mode).isEqualTo(PhotoMode.PHOTO)
40
+ assertThat(request.transferMethod).isEqualTo("auto")
41
+ }
42
+
43
+ @Test
44
+ fun `fromMap generates requestId when omitted or blank`() {
45
+ val withoutRequestId =
46
+ PhotoRequest.fromMap(
47
+ mapOf(
48
+ "size" to "medium",
49
+ "webhookUrl" to "https://example.com/upload",
50
+ "compress" to "none",
51
+ "sound" to true,
52
+ )
53
+ )
54
+ val blankRequestId =
55
+ PhotoRequest.fromMap(
56
+ mapOf(
57
+ "requestId" to " ",
58
+ "size" to "medium",
59
+ "webhookUrl" to "https://example.com/upload",
60
+ "compress" to "none",
61
+ "sound" to true,
62
+ )
63
+ )
64
+
65
+ assertThat(withoutRequestId.requestId).startsWith("photo-")
66
+ assertThat(blankRequestId.requestId).startsWith("photo-")
67
+ }
68
+
69
+ @Test
70
+ fun `fromMap preserves explicit requestId`() {
71
+ val request =
72
+ PhotoRequest.fromMap(
73
+ mapOf(
74
+ "requestId" to "photo-1",
75
+ "size" to "medium",
76
+ "webhookUrl" to "https://example.com/upload",
77
+ "compress" to "none",
78
+ "sound" to true,
79
+ )
80
+ )
81
+
82
+ assertThat(request.requestId).isEqualTo("photo-1")
83
+ }
84
+
85
+ @Test
86
+ fun `fromMap preserves text mode`() {
87
+ val request =
88
+ PhotoRequest.fromMap(
89
+ mapOf(
90
+ "size" to "medium",
91
+ "mode" to "text",
92
+ "webhookUrl" to "https://example.com/upload",
93
+ )
94
+ )
95
+
96
+ assertThat(request.mode).isEqualTo(PhotoMode.TEXT)
97
+ }
98
+
99
+ @Test
100
+ fun `fromMap preserves forced BLE transfer`() {
101
+ val request =
102
+ PhotoRequest.fromMap(
103
+ mapOf(
104
+ "size" to "medium",
105
+ "transferMethod" to "ble",
106
+ "webhookUrl" to "https://example.com/upload",
107
+ )
108
+ )
109
+
110
+ assertThat(request.transferMethod).isEqualTo("ble")
111
+ }
112
+
113
+ @Test
114
+ fun `fromMap preserves direct transfer without BLE fallback`() {
115
+ val request =
116
+ PhotoRequest.fromMap(
117
+ mapOf(
118
+ "size" to "medium",
119
+ "transferMethod" to "direct",
120
+ "webhookUrl" to "https://example.com/upload",
121
+ )
122
+ )
123
+
124
+ assertThat(request.transferMethod).isEqualTo("direct")
125
+ }
126
+
127
+ @Test
128
+ fun `fromMap rejects an unknown transfer method`() {
129
+ assertThatThrownBy {
130
+ PhotoRequest.fromMap(
131
+ mapOf(
132
+ "size" to "medium",
133
+ "transferMethod" to "wifi",
134
+ "webhookUrl" to "https://example.com/upload",
135
+ )
136
+ )
137
+ }
138
+ .isInstanceOf(IllegalArgumentException::class.java)
139
+ .hasMessage("Invalid transferMethod \"wifi\". Expected auto, direct, or ble.")
140
+ }
141
+ }