@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,1195 @@
1
+ package com.glassly.bluetoothsdk.services
2
+
3
+ import android.Manifest
4
+ import android.bluetooth.BluetoothAdapter
5
+ import android.bluetooth.BluetoothDevice
6
+ import android.bluetooth.BluetoothHeadset
7
+ import android.content.BroadcastReceiver
8
+ import android.content.Context
9
+ import android.content.Intent
10
+ import android.content.IntentFilter
11
+ import android.content.pm.PackageManager
12
+ import android.media.*
13
+ import android.os.Build
14
+ import android.os.Handler
15
+ import android.os.Looper
16
+ import android.telephony.PhoneStateListener
17
+ import android.telephony.TelephonyManager
18
+ import androidx.core.app.ActivityCompat
19
+ import androidx.core.content.ContextCompat
20
+ import com.glassly.bluetoothsdk.Bridge
21
+ import com.glassly.bluetoothsdk.DeviceManager
22
+ import com.glassly.bluetoothsdk.utils.MicTypes
23
+ import java.nio.ByteBuffer
24
+ import java.nio.ByteOrder
25
+ import java.util.concurrent.atomic.AtomicBoolean
26
+ import kotlinx.coroutines.*
27
+
28
+ class PhoneMic private constructor(private val context: Context) {
29
+
30
+ companion object {
31
+ @Volatile private var instance: PhoneMic? = null
32
+
33
+ fun getInstance(): PhoneMic {
34
+ return instance
35
+ ?: synchronized(this) {
36
+ instance ?: PhoneMic(Bridge.getContext()).also { instance = it }
37
+ }
38
+ }
39
+
40
+ // Audio configuration constants
41
+ private const val SAMPLE_RATE = 16000
42
+ private const val CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO
43
+ private const val AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT
44
+ private const val BUFFER_SIZE_MULTIPLIER = 2
45
+
46
+ // Debouncing and retry constants
47
+ private const val MODE_CHANGE_DEBOUNCE_MS = 500L
48
+ private const val MAX_SCO_RETRIES = 3
49
+ private const val FOCUS_REGAIN_DELAY_MS = 500L
50
+ private const val SAMSUNG_MIC_TEST_DELAY_MS = 500L
51
+ private const val MIC_SWITCH_DELAY_MS = 300L // Time for DeviceManager to switch mics
52
+ private const val AUDIO_SOURCE_HOTWORD = 1999
53
+ }
54
+
55
+ // Audio recording components
56
+ private var audioRecord: AudioRecord? = null
57
+ private var recordingThread: Thread? = null
58
+ public val isRecording = AtomicBoolean(false)
59
+ private var preferScoMode = true // Try SCO first, fallback to normal if needed
60
+
61
+ // Audio manager and routing
62
+ private val audioManager: AudioManager =
63
+ context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
64
+ private var bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter()
65
+ private var bluetoothHeadset: BluetoothHeadset? = null
66
+
67
+ // Broadcast receivers
68
+ private var audioRouteReceiver: BroadcastReceiver? = null
69
+ private var bluetoothReceiver: BroadcastReceiver? = null
70
+
71
+ // Phone call detection
72
+ private var telephonyManager: TelephonyManager? = null
73
+ private var phoneStateListener: PhoneStateListener? = null
74
+ private var isPhoneCallActive = false
75
+
76
+ // Audio focus management
77
+ private var audioFocusListener: AudioManager.OnAudioFocusChangeListener? = null
78
+ private var hasAudioFocus = false
79
+ private var audioFocusRequest: AudioFocusRequest? = null // For Android 8.0+
80
+
81
+ // Audio recording conflict detection (API 24+)
82
+ private var audioRecordingCallback: AudioManager.AudioRecordingCallback? = null
83
+ private val ourAudioSessionIds = mutableListOf<Int>()
84
+ private var isExternalAudioActive = false
85
+
86
+ // State tracking
87
+ private var lastModeChangeTime = 0L
88
+ private var scoRetries = 0
89
+ private var pendingRecordingRequest = false
90
+ private var currentMicMode: String = ""
91
+
92
+ // Handler for main thread operations
93
+ private val mainHandler = Handler(Looper.getMainLooper())
94
+
95
+ // Coroutine scope for async operations
96
+ private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
97
+
98
+ init {
99
+ setupPhoneCallDetection()
100
+ setupAudioFocusListener()
101
+ setupAudioRecordingDetection()
102
+ setupAudioRouteListener()
103
+ setupBluetoothListener()
104
+ }
105
+
106
+ // MARK: - Public Methods (Simplified Interface)
107
+
108
+ /**
109
+ * Start recording from the phone microphone Will automatically handle SCO mode, conflicts, and
110
+ * fallbacks
111
+ */
112
+ fun startRecording(): Boolean {
113
+ // Ensure we're on main thread for consistency
114
+ if (Looper.myLooper() != Looper.getMainLooper()) {
115
+ var result = false
116
+ runBlocking { withContext(Dispatchers.Main) { result = startRecording() } }
117
+ return result
118
+ }
119
+
120
+ // Check if already recording
121
+ if (isRecording.get()) {
122
+ Bridge.log("MIC: Already recording")
123
+ return true
124
+ }
125
+
126
+ // Check permissions
127
+ if (!checkPermissions()) {
128
+ Bridge.log("MIC: Microphone permissions not granted")
129
+ notifyDeviceManager("permission_denied", emptyList())
130
+ return false
131
+ }
132
+
133
+ // Smart debouncing
134
+ val now = System.currentTimeMillis()
135
+ if (now - lastModeChangeTime < MODE_CHANGE_DEBOUNCE_MS) {
136
+ Bridge.log("MIC: Debouncing rapid recording request")
137
+ pendingRecordingRequest = true
138
+ mainHandler.postDelayed(
139
+ {
140
+ if (pendingRecordingRequest && !isRecording.get()) {
141
+ startRecordingInternal()
142
+ }
143
+ pendingRecordingRequest = false
144
+ },
145
+ MODE_CHANGE_DEBOUNCE_MS
146
+ )
147
+ return false
148
+ }
149
+
150
+ return startRecordingInternal()
151
+ }
152
+
153
+ /** Stop recording from the phone microphone */
154
+ fun stopRecording() {
155
+ // Ensure we're on main thread for consistency
156
+ if (Looper.myLooper() != Looper.getMainLooper()) {
157
+ runBlocking { withContext(Dispatchers.Main) { stopRecording() } }
158
+ return
159
+ }
160
+
161
+ if (!isRecording.get()) {
162
+ return
163
+ }
164
+
165
+ Bridge.log("MIC: Stopping recording")
166
+
167
+ // Clean up recording
168
+ cleanUpRecording()
169
+
170
+ // Abandon audio focus
171
+ abandonAudioFocus()
172
+
173
+ // Reset Bluetooth SCO
174
+ if (audioManager.isBluetoothScoOn) {
175
+ audioManager.stopBluetoothSco()
176
+ audioManager.isBluetoothScoOn = false
177
+ }
178
+
179
+ // Reset audio mode
180
+ audioManager.mode = AudioManager.MODE_NORMAL
181
+
182
+ // Notify DeviceManager
183
+ notifyDeviceManager("recording_stopped", getAvailableInputDevices().values.toList())
184
+
185
+ Bridge.log("MIC: Recording stopped")
186
+ }
187
+
188
+ fun isRecordingWithMode(mode: String): Boolean {
189
+ if (!isRecording.get()) {
190
+ return false
191
+ }
192
+
193
+ val record = audioRecord
194
+ val activelyRecording =
195
+ try {
196
+ record?.recordingState == AudioRecord.RECORDSTATE_RECORDING
197
+ } catch (e: Exception) {
198
+ Bridge.log("MIC: Failed to read AudioRecord state: ${e.message}")
199
+ false
200
+ }
201
+
202
+ if (!activelyRecording) {
203
+ Bridge.log("MIC: Stale recording state detected, cleaning up")
204
+ cleanUpRecording()
205
+ abandonAudioFocus()
206
+ currentMicMode = ""
207
+ return false
208
+ }
209
+
210
+ return currentMicMode == mode
211
+ }
212
+
213
+ fun hasBlockingMicInterruption(): Boolean {
214
+ val callActive =
215
+ isPhoneCallActive ||
216
+ try {
217
+ (telephonyManager?.callState ?: TelephonyManager.CALL_STATE_IDLE) !=
218
+ TelephonyManager.CALL_STATE_IDLE
219
+ } catch (e: Exception) {
220
+ Bridge.log("MIC: Failed to read telephony call state: ${e.message}")
221
+ true
222
+ }
223
+
224
+ return callActive || isExternalAudioActive
225
+ }
226
+
227
+ /**
228
+ * Start recording from a specific microphone type
229
+ * @param mode One of MicTypes constants (PHONE_INTERNAL, BLUETOOTH_CLASSIC, BLUETOOTH)
230
+ * @return true if successfully started recording, false otherwise
231
+ */
232
+ fun startMode(mode: String): Boolean {
233
+ // Ensure we're on main thread for consistency
234
+ if (Looper.myLooper() != Looper.getMainLooper()) {
235
+ var result = false
236
+ runBlocking { withContext(Dispatchers.Main) { result = startMode(mode) } }
237
+ return result
238
+ }
239
+
240
+ if (isRecordingWithMode(mode)) {
241
+ return true
242
+ }
243
+
244
+ // recording with a different mode, so stop recording and start recording with the new mode:
245
+ if (isRecording.get()) {
246
+ Bridge.log(
247
+ "MIC: Already recording with different mode ($currentMicMode), stopping first"
248
+ )
249
+ stopRecording()
250
+ }
251
+
252
+ // Check permissions
253
+ if (!checkPermissions()) {
254
+ Bridge.log("MIC: Microphone permissions not granted")
255
+ notifyDeviceManager("permission_denied", emptyList())
256
+ return false
257
+ }
258
+
259
+ // Smart debouncing
260
+ val now = System.currentTimeMillis()
261
+ if (now - lastModeChangeTime < MODE_CHANGE_DEBOUNCE_MS) {
262
+ Bridge.log("MIC: Debouncing rapid recording request")
263
+ return false
264
+ }
265
+
266
+ lastModeChangeTime = System.currentTimeMillis()
267
+
268
+ // Check for conflicts
269
+ if (isPhoneCallActive) {
270
+ Bridge.log("MIC: Cannot start recording - phone call active")
271
+ notifyDeviceManager("phone_call_active", emptyList())
272
+ return false
273
+ }
274
+
275
+ // Request audio focus for all modes (needed to detect Chrome STT)
276
+ if (!requestAudioFocus()) {
277
+ Bridge.log("MIC: Failed to get audio focus")
278
+ if (isSamsungDevice()) {
279
+ testMicrophoneAvailabilityOnSamsung()
280
+ } else {
281
+ notifyDeviceManager("audio_focus_denied", emptyList())
282
+ }
283
+ return false
284
+ }
285
+
286
+ // Start recording based on mode
287
+ return when (mode) {
288
+ MicTypes.PHONE_INTERNAL -> {
289
+ Bridge.log("MIC: Starting phone internal mic")
290
+ return startRecordingPhoneInternal()
291
+ }
292
+ MicTypes.BLUETOOTH_CLASSIC -> {
293
+ Bridge.log("MIC: Starting Bluetooth Classic (SCO)")
294
+ if (!audioManager.isBluetoothScoAvailableOffCall) {
295
+ Bridge.log("MIC: Bluetooth SCO not available")
296
+ notifyDeviceManager("bt_classic_unavailable", emptyList())
297
+ return false
298
+ }
299
+ return startRecordingBtClassic()
300
+ }
301
+ MicTypes.BLUETOOTH -> {
302
+ Bridge.log("MIC: Starting high-quality Bluetooth mic")
303
+ if (!isHighQualityBluetoothAvailable()) {
304
+ Bridge.log("MIC: High-quality Bluetooth not available")
305
+ notifyDeviceManager("bt_hq_unavailable", emptyList())
306
+ return false
307
+ }
308
+ return startRecordingBtHighQuality()
309
+ }
310
+ else -> {
311
+ Bridge.log("MIC: Unknown mic type: $mode")
312
+ return false
313
+ }
314
+ }
315
+ }
316
+
317
+
318
+ fun stopMode(mode: String): Boolean {
319
+ if (isRecordingWithMode(mode)) {
320
+ stopRecording()
321
+ return true
322
+ }
323
+ return false
324
+ }
325
+
326
+ private fun startRecordingPhoneInternal(): Boolean {
327
+ try {
328
+ Bridge.log("MIC: startRecordingPhoneInternal() - Setting up phone mic ONLY")
329
+
330
+ // Use MODE_NORMAL to avoid any Bluetooth routing
331
+ audioManager.mode = AudioManager.MODE_NORMAL
332
+ Bridge.log("MIC: Set audio mode to MODE_NORMAL (${AudioManager.MODE_NORMAL})")
333
+
334
+ // Ensure Bluetooth SCO is off
335
+ if (audioManager.isBluetoothScoOn) {
336
+ Bridge.log("MIC: Bluetooth SCO was on, turning it off")
337
+ audioManager.stopBluetoothSco()
338
+ audioManager.isBluetoothScoOn = false
339
+ }
340
+
341
+ // Disable speaker phone to ensure proper routing
342
+ if (audioManager.isSpeakerphoneOn) {
343
+ Bridge.log("MIC: Speakerphone was on, turning it off")
344
+ audioManager.isSpeakerphoneOn = false
345
+ }
346
+
347
+ // Log current audio routing state
348
+ Bridge.log("MIC: Audio state before recording - Mode: ${audioManager.mode}, " +
349
+ "BT SCO: ${audioManager.isBluetoothScoOn}, " +
350
+ "Speakerphone: ${audioManager.isSpeakerphoneOn}")
351
+
352
+ // EXPERIMENTAL: Skip audio focus for phone internal mic to avoid media routing issues
353
+ // This allows YouTube/Spotify to play normally through speakers
354
+ Bridge.log("MIC: Skipping audio focus request for phone internal mic")
355
+
356
+ val success = createAndStartAudioRecord(MediaRecorder.AudioSource.MIC)
357
+ if (success) {
358
+ currentMicMode = MicTypes.PHONE_INTERNAL
359
+ Bridge.log("MIC: Phone internal mic started successfully")
360
+ } else {
361
+ Bridge.log("MIC: Failed to start phone internal mic")
362
+ }
363
+ return success
364
+ } catch (e: Exception) {
365
+ Bridge.log("MIC: Phone internal recording failed: ${e.message}")
366
+ e.printStackTrace()
367
+ return false
368
+ }
369
+ }
370
+
371
+ private fun startRecordingBtClassic(): Boolean {
372
+ try {
373
+ // Use MODE_IN_COMMUNICATION for SCO
374
+ audioManager.mode = AudioManager.MODE_IN_COMMUNICATION
375
+
376
+ // Start Bluetooth SCO
377
+ audioManager.startBluetoothSco()
378
+ audioManager.isBluetoothScoOn = true
379
+
380
+ // Wait briefly for SCO to connect
381
+ Thread.sleep(100)
382
+
383
+ val success = createAndStartAudioRecord(MediaRecorder.AudioSource.VOICE_COMMUNICATION)
384
+ if (success) {
385
+ currentMicMode = MicTypes.BLUETOOTH_CLASSIC
386
+ }
387
+ return success
388
+ } catch (e: Exception) {
389
+ Bridge.log("MIC: BT Classic recording failed: ${e.message}")
390
+
391
+ // Clean up SCO
392
+ audioManager.stopBluetoothSco()
393
+ audioManager.isBluetoothScoOn = false
394
+ audioManager.mode = AudioManager.MODE_NORMAL
395
+
396
+ return false
397
+ }
398
+ }
399
+
400
+ private fun startRecordingBtHighQuality(): Boolean {
401
+ try {
402
+ // For high-quality BT devices like AirPods, we want to avoid SCO mode
403
+ // and use the standard microphone source which will route to BT automatically
404
+ audioManager.mode = AudioManager.MODE_NORMAL
405
+
406
+ // Ensure SCO is off - we want standard BT audio profile
407
+ if (audioManager.isBluetoothScoOn) {
408
+ audioManager.stopBluetoothSco()
409
+ audioManager.isBluetoothScoOn = false
410
+ }
411
+
412
+ // Use UNPROCESSED if available for highest quality, otherwise MIC
413
+ val audioSource =
414
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
415
+ MediaRecorder.AudioSource.UNPROCESSED
416
+ } else {
417
+ MediaRecorder.AudioSource.MIC
418
+ }
419
+
420
+ val success = createAndStartAudioRecord(audioSource)
421
+ if (success) {
422
+ currentMicMode = MicTypes.BLUETOOTH
423
+ }
424
+ return success
425
+ } catch (e: Exception) {
426
+ Bridge.log("MIC: BT high-quality recording failed: ${e.message}")
427
+ return false
428
+ }
429
+ }
430
+
431
+ private fun isHighQualityBluetoothAvailable(): Boolean {
432
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
433
+ val devices = audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)
434
+ // Look for Bluetooth A2DP or LE Audio devices (not just SCO)
435
+ return devices.any { device ->
436
+ device.type == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP ||
437
+ (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
438
+ device.type == AudioDeviceInfo.TYPE_BLE_HEADSET)
439
+ }
440
+ }
441
+ // Fallback: if any Bluetooth audio device is connected
442
+ return bluetoothAdapter?.let { adapter ->
443
+ if (ActivityCompat.checkSelfPermission(
444
+ context,
445
+ Manifest.permission.BLUETOOTH_CONNECT
446
+ ) == PackageManager.PERMISSION_GRANTED
447
+ ) {
448
+ adapter.bondedDevices.any { device ->
449
+ device.bluetoothClass?.majorDeviceClass ==
450
+ android.bluetooth.BluetoothClass.Device.Major.AUDIO_VIDEO
451
+ }
452
+ } else {
453
+ false
454
+ }
455
+ }
456
+ ?: false
457
+ }
458
+
459
+ // MARK: - Private Methods
460
+
461
+ private fun startRecordingInternal(): Boolean {
462
+ lastModeChangeTime = System.currentTimeMillis()
463
+
464
+ // Check for conflicts
465
+ if (isPhoneCallActive) {
466
+ Bridge.log("MIC: Cannot start recording - phone call active")
467
+ notifyDeviceManager("phone_call_active", emptyList())
468
+ return false
469
+ }
470
+
471
+ // Request audio focus
472
+ if (!requestAudioFocus()) {
473
+ Bridge.log("MIC: Failed to get audio focus")
474
+ // On Samsung, test if another app actually needs the mic
475
+ if (isSamsungDevice()) {
476
+ testMicrophoneAvailabilityOnSamsung()
477
+ } else {
478
+ notifyDeviceManager("audio_focus_denied", emptyList())
479
+ }
480
+ return false
481
+ }
482
+
483
+ // Try SCO mode first (if preferred)
484
+ if (preferScoMode && audioManager.isBluetoothScoAvailableOffCall) {
485
+ Bridge.log("MIC: Attempting to start with Bluetooth SCO")
486
+ if (startRecordingWithSco()) {
487
+ return true
488
+ }
489
+ Bridge.log("MIC: SCO failed, falling back to normal mode")
490
+ }
491
+
492
+ // Fallback to normal mode
493
+ return startRecordingNormal()
494
+ }
495
+
496
+ private fun startRecordingWithSco(): Boolean {
497
+ try {
498
+ // Use MODE_IN_COMMUNICATION instead of MODE_IN_CALL
499
+ // This allows media playback to coexist with microphone recording
500
+ audioManager.mode = AudioManager.MODE_IN_COMMUNICATION
501
+
502
+ // Start Bluetooth SCO
503
+ audioManager.startBluetoothSco()
504
+ audioManager.isBluetoothScoOn = true
505
+
506
+ // Wait briefly for SCO to connect
507
+ Thread.sleep(100)
508
+
509
+ return createAndStartAudioRecord(MediaRecorder.AudioSource.VOICE_COMMUNICATION)
510
+ } catch (e: Exception) {
511
+ Bridge.log("MIC: SCO recording failed: ${e.message}")
512
+
513
+ // Clean up SCO
514
+ audioManager.stopBluetoothSco()
515
+ audioManager.isBluetoothScoOn = false
516
+ audioManager.mode = AudioManager.MODE_NORMAL
517
+
518
+ // Retry logic
519
+ if (scoRetries < MAX_SCO_RETRIES) {
520
+ scoRetries++
521
+ Bridge.log("MIC: Retrying SCO (attempt $scoRetries)")
522
+ return startRecordingWithSco()
523
+ }
524
+
525
+ return false
526
+ }
527
+ }
528
+
529
+ private fun startRecordingNormal(): Boolean {
530
+ try {
531
+ // Set appropriate audio mode for Samsung devices
532
+ if (isSamsungDevice()) {
533
+ audioManager.mode = AudioManager.MODE_IN_COMMUNICATION
534
+ }
535
+
536
+ return createAndStartAudioRecord(MediaRecorder.AudioSource.MIC)
537
+ } catch (e: Exception) {
538
+ Bridge.log("MIC: Normal recording failed: ${e.message}")
539
+ return false
540
+ }
541
+ }
542
+
543
+ private fun createAndStartAudioRecord(audioSource: Int): Boolean {
544
+ // Calculate buffer size
545
+ val minBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT)
546
+
547
+ if (minBufferSize == AudioRecord.ERROR || minBufferSize == AudioRecord.ERROR_BAD_VALUE) {
548
+ Bridge.log("MIC: Failed to get min buffer size")
549
+ return false
550
+ }
551
+
552
+ val bufferSize = minBufferSize * BUFFER_SIZE_MULTIPLIER
553
+
554
+ // Create AudioRecord
555
+ audioRecord =
556
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
557
+ AudioRecord.Builder()
558
+ .setAudioSource(audioSource)
559
+ .setAudioFormat(
560
+ AudioFormat.Builder()
561
+ .setSampleRate(SAMPLE_RATE)
562
+ .setChannelMask(CHANNEL_CONFIG)
563
+ .setEncoding(AUDIO_FORMAT)
564
+ .build()
565
+ )
566
+ .setBufferSizeInBytes(bufferSize)
567
+ .build()
568
+ } else {
569
+ AudioRecord(audioSource, SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT, bufferSize)
570
+ }
571
+
572
+ if (audioRecord?.state != AudioRecord.STATE_INITIALIZED) {
573
+ Bridge.log("MIC: AudioRecord failed to initialize")
574
+ audioRecord?.release()
575
+ audioRecord = null
576
+ return false
577
+ }
578
+
579
+ // Register this AudioRecord's session ID
580
+ audioRecord?.let { ourAudioSessionIds.add(it.audioSessionId) }
581
+
582
+ // Start recording
583
+ val record = audioRecord ?: return false
584
+ try {
585
+ record.startRecording()
586
+ } catch (e: Exception) {
587
+ Bridge.log("MIC: AudioRecord.startRecording() failed: ${e.message}")
588
+ audioRecord?.release()
589
+ audioRecord = null
590
+ return false
591
+ }
592
+
593
+ if (record.recordingState != AudioRecord.RECORDSTATE_RECORDING) {
594
+ Bridge.log("MIC: AudioRecord did not enter recording state")
595
+ record.release()
596
+ audioRecord = null
597
+ return false
598
+ }
599
+
600
+ isRecording.set(true)
601
+
602
+ // Start recording thread
603
+ startRecordingThread(bufferSize)
604
+
605
+ // Notify DeviceManager
606
+ val activeDevice = getActiveInputDevice() ?: "Unknown"
607
+ Bridge.log("MIC: Started recording from: $activeDevice")
608
+ Bridge.log("MIC: Current audio mode: ${audioManager.mode} (NORMAL=${AudioManager.MODE_NORMAL}, IN_COMM=${AudioManager.MODE_IN_COMMUNICATION}, IN_CALL=${AudioManager.MODE_IN_CALL})")
609
+
610
+ // Log detailed routing info for debugging
611
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
612
+ audioRecord?.routedDevice?.let { device ->
613
+ Bridge.log("MIC: Routed to device - Type: ${device.type}, Name: ${device.productName}")
614
+ }
615
+ }
616
+
617
+ notifyDeviceManager("recording_started", listOf(activeDevice))
618
+
619
+ // Reset retry counter on success
620
+ scoRetries = 0
621
+
622
+ return true
623
+ }
624
+
625
+ private fun startRecordingThread(bufferSize: Int) {
626
+ recordingThread =
627
+ Thread {
628
+ android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO)
629
+
630
+ val audioBuffer = ShortArray(bufferSize / 2)
631
+
632
+ var unexpectedStopReason: String? = null
633
+
634
+ while (isRecording.get()) {
635
+ // Capture local reference to avoid use-after-free if cleanUpRecording()
636
+ // releases the AudioRecord while we're in read()
637
+ val record = audioRecord
638
+ if (record == null) {
639
+ unexpectedStopReason = "AudioRecord missing"
640
+ break
641
+ }
642
+
643
+ val readResult = try {
644
+ record.read(audioBuffer, 0, audioBuffer.size)
645
+ } catch (e: Exception) {
646
+ Bridge.log("MIC: AudioRecord.read() exception: ${e.message}")
647
+ unexpectedStopReason = "AudioRecord.read() exception: ${e.message}"
648
+ break
649
+ }
650
+
651
+ if (readResult > 0) {
652
+ // Convert short array to byte array (16-bit PCM)
653
+ val pcmData = ByteArray(readResult * 2)
654
+ val byteBuffer = ByteBuffer.wrap(pcmData).order(ByteOrder.LITTLE_ENDIAN)
655
+
656
+ for (i in 0 until readResult) {
657
+ byteBuffer.putShort(audioBuffer[i])
658
+ }
659
+
660
+ // Send PCM data to DeviceManager
661
+ DeviceManager.getInstance().handlePcm(pcmData)
662
+ } else if (readResult < 0) {
663
+ // AudioRecord error (ERROR, ERROR_INVALID_OPERATION, ERROR_BAD_VALUE, ERROR_DEAD_OBJECT)
664
+ Bridge.log("MIC: AudioRecord.read() returned error: $readResult")
665
+ unexpectedStopReason = "AudioRecord.read() error: $readResult"
666
+ break
667
+ }
668
+ }
669
+
670
+ unexpectedStopReason?.let { reason ->
671
+ if (isRecording.get()) {
672
+ mainHandler.post { handleUnexpectedRecordingStop(reason) }
673
+ }
674
+ }
675
+ }
676
+ .apply {
677
+ name = "AudioRecordingThread"
678
+ start()
679
+ }
680
+ }
681
+
682
+ private fun handleUnexpectedRecordingStop(reason: String) {
683
+ if (!isRecording.compareAndSet(true, false)) {
684
+ return
685
+ }
686
+
687
+ Bridge.log("MIC: Recording stopped unexpectedly: $reason")
688
+ cleanUpRecording()
689
+ abandonAudioFocus()
690
+
691
+ if (audioManager.isBluetoothScoOn) {
692
+ audioManager.stopBluetoothSco()
693
+ audioManager.isBluetoothScoOn = false
694
+ }
695
+
696
+ audioManager.mode = AudioManager.MODE_NORMAL
697
+ currentMicMode = ""
698
+ notifyDeviceManager("recording_stopped", getAvailableInputDevices().values.toList())
699
+ }
700
+
701
+ private fun cleanUpRecording() {
702
+ isRecording.set(false)
703
+
704
+ // Wait for recording thread to finish BEFORE touching audioRecord
705
+ // This prevents use-after-free where read() races with release()
706
+ recordingThread?.let { thread ->
707
+ thread.interrupt()
708
+ try {
709
+ thread.join(1000) // Wait up to 1 second for thread to finish
710
+ } catch (e: InterruptedException) {
711
+ Bridge.log("MIC: Interrupted while waiting for recording thread to finish")
712
+ Thread.currentThread().interrupt()
713
+ }
714
+ }
715
+ recordingThread = null
716
+
717
+ // NOW safe to stop and release - recording thread is no longer reading
718
+ audioRecord?.let { record ->
719
+ try {
720
+ // Unregister session ID
721
+ ourAudioSessionIds.remove(record.audioSessionId)
722
+
723
+ if (record.state == AudioRecord.STATE_INITIALIZED) {
724
+ record.stop()
725
+ }
726
+ record.release()
727
+ } catch (e: Exception) {
728
+ Bridge.log("MIC: Error cleaning up AudioRecord: ${e.message}")
729
+ }
730
+ }
731
+ audioRecord = null
732
+ }
733
+
734
+ private fun setupPhoneCallDetection() {
735
+ // Check for phone state permission
736
+ if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) !=
737
+ PackageManager.PERMISSION_GRANTED
738
+ ) {
739
+ Bridge.log("MIC: READ_PHONE_STATE permission not granted, skipping call detection")
740
+ return
741
+ }
742
+
743
+ telephonyManager = context.getSystemService(Context.TELEPHONY_SERVICE) as? TelephonyManager
744
+
745
+ phoneStateListener =
746
+ object : PhoneStateListener() {
747
+ override fun onCallStateChanged(state: Int, phoneNumber: String?) {
748
+ val wasCallActive = isPhoneCallActive
749
+ isPhoneCallActive = (state != TelephonyManager.CALL_STATE_IDLE)
750
+
751
+ if (wasCallActive != isPhoneCallActive) {
752
+ if (isPhoneCallActive) {
753
+ Bridge.log("MIC: Phone call started - stopping recording")
754
+ if (isRecording.get()) {
755
+ // Notify DeviceManager BEFORE stopping - allows switch to glasses
756
+ // mic
757
+ notifyDeviceManager("phone_call_interruption", emptyList())
758
+ // Give DeviceManager time to switch to glasses mic
759
+ mainHandler.postDelayed(
760
+ { stopRecording() },
761
+ MIC_SWITCH_DELAY_MS
762
+ )
763
+ } else {
764
+ // Not currently recording, but still notify about
765
+ // unavailability
766
+ notifyDeviceManager("phone_call_interruption", emptyList())
767
+ }
768
+ } else {
769
+ Bridge.log("MIC: Phone call ended")
770
+ notifyDeviceManager(
771
+ "phone_call_ended",
772
+ getAvailableInputDevices().values.toList()
773
+ )
774
+ }
775
+ }
776
+ }
777
+ }
778
+
779
+ telephonyManager?.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE)
780
+ }
781
+
782
+ private fun setupAudioFocusListener() {
783
+ audioFocusListener =
784
+ AudioManager.OnAudioFocusChangeListener { focusChange ->
785
+ when (focusChange) {
786
+ AudioManager.AUDIOFOCUS_LOSS -> {
787
+ Bridge.log("MIC: Permanent audio focus loss - mode: $currentMicMode")
788
+ hasAudioFocus = false
789
+
790
+ // For phone internal mic, ignore AUDIOFOCUS_LOSS to prevent routing changes
791
+ // This allows media apps (YouTube, Spotify) to play normally
792
+ if (currentMicMode == MicTypes.PHONE_INTERNAL) {
793
+ Bridge.log("MIC: Ignoring AUDIOFOCUS_LOSS for phone internal mic (allows media playback)")
794
+ } else {
795
+ // For other modes (BT, etc), respect audio focus loss
796
+ if (isRecording.get()) {
797
+ notifyDeviceManager("audio_focus_lost", emptyList())
798
+ stopRecording()
799
+ }
800
+ }
801
+ }
802
+ AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
803
+ Bridge.log("MIC: Transient audio focus loss - mode: $currentMicMode, isExternalAudioActive: $isExternalAudioActive")
804
+ hasAudioFocus = false
805
+
806
+ // For phone internal mic, only stop if we know an external app is actually recording
807
+ // AUDIOFOCUS_LOSS_TRANSIENT fires for many reasons (app switching, system UI, etc)
808
+ // AudioRecordingCallback is more reliable for detecting actual mic conflicts
809
+ if (currentMicMode == MicTypes.PHONE_INTERNAL && isRecording.get()) {
810
+ if (isExternalAudioActive) {
811
+ // External app is already recording (detected by AudioRecordingCallback)
812
+ Bridge.log("MIC: AUDIOFOCUS_LOSS_TRANSIENT with external app recording - stopping")
813
+ notifyDeviceManager("external_app_recording", emptyList())
814
+ stopRecording()
815
+ } else {
816
+ // AudioRecordingCallback might fire shortly after AUDIOFOCUS_LOSS_TRANSIENT
817
+ // Wait briefly to see if an external app actually starts recording
818
+ Bridge.log("MIC: AUDIOFOCUS_LOSS_TRANSIENT - waiting 150ms to check for external app")
819
+ mainHandler.postDelayed({
820
+ if (isRecording.get() && isExternalAudioActive) {
821
+ Bridge.log("MIC: External app detected after delay - stopping")
822
+ notifyDeviceManager("external_app_recording", emptyList())
823
+ stopRecording()
824
+ } else if (isRecording.get()) {
825
+ Bridge.log("MIC: No external app after delay - continuing recording")
826
+ }
827
+ }, 150)
828
+ }
829
+ } else if (isSamsungDevice() && isRecording.get()) {
830
+ // Samsung non-phone modes need special handling
831
+ testMicrophoneAvailabilityOnSamsung()
832
+ }
833
+ }
834
+ AudioManager.AUDIOFOCUS_GAIN -> {
835
+ Bridge.log("MIC: Regained audio focus")
836
+ hasAudioFocus = true
837
+
838
+ if (isSamsungDevice()) {
839
+ isExternalAudioActive = false
840
+ }
841
+
842
+ // Notify that focus is available again
843
+ if (!isRecording.get()) {
844
+ notifyDeviceManager(
845
+ "audio_focus_available",
846
+ getAvailableInputDevices().values.toList()
847
+ )
848
+ }
849
+ }
850
+ }
851
+ }
852
+ }
853
+
854
+ private fun setupAudioRecordingDetection() {
855
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
856
+ audioRecordingCallback =
857
+ object : AudioManager.AudioRecordingCallback() {
858
+ override fun onRecordingConfigChanged(
859
+ configs: MutableList<AudioRecordingConfiguration>?
860
+ ) {
861
+ configs ?: return
862
+
863
+ // Filter out our own recordings
864
+ val otherAppRecordings =
865
+ configs.filter { config ->
866
+ !ourAudioSessionIds.contains(config.clientAudioSessionId) &&
867
+ config.clientAudioSource != AUDIO_SOURCE_HOTWORD
868
+ }
869
+
870
+ val wasExternalActive = isExternalAudioActive
871
+ isExternalAudioActive = otherAppRecordings.isNotEmpty()
872
+
873
+ if (wasExternalActive != isExternalAudioActive) {
874
+ if (isExternalAudioActive) {
875
+ Bridge.log("MIC: External app started recording - isRecording: ${isRecording.get()}")
876
+ if (isRecording.get()) {
877
+ // Notify DeviceManager BEFORE stopping - allows switch to glasses mic
878
+ notifyDeviceManager("external_app_recording", emptyList())
879
+ // Stop IMMEDIATELY to prevent audio corruption with Gboard
880
+ stopRecording()
881
+ } else {
882
+ // Not currently recording, but still notify about
883
+ // unavailability
884
+ notifyDeviceManager("external_app_recording", emptyList())
885
+ }
886
+ } else {
887
+ Bridge.log("MIC: External app stopped recording")
888
+ // Notify DeviceManager that phone mic is available again
889
+ notifyDeviceManager(
890
+ "external_app_stopped",
891
+ getAvailableInputDevices().values.toList()
892
+ )
893
+ }
894
+ }
895
+ }
896
+ }
897
+
898
+ if (audioRecordingCallback != null) {
899
+ audioManager.registerAudioRecordingCallback(audioRecordingCallback!!, mainHandler)
900
+ }
901
+ }
902
+ }
903
+
904
+ private fun setupAudioRouteListener() {
905
+ audioRouteReceiver =
906
+ object : BroadcastReceiver() {
907
+ override fun onReceive(context: Context?, intent: Intent?) {
908
+ when (intent?.action) {
909
+ AudioManager.ACTION_AUDIO_BECOMING_NOISY -> {
910
+ Bridge.log("MIC: Audio becoming noisy")
911
+ handleAudioRouteChange()
912
+ }
913
+ AudioManager.ACTION_HEADSET_PLUG -> {
914
+ val state = intent.getIntExtra("state", -1)
915
+ if (state == 1) {
916
+ Bridge.log("MIC: Headset connected")
917
+ } else if (state == 0) {
918
+ Bridge.log("MIC: Headset disconnected")
919
+ }
920
+ handleAudioRouteChange()
921
+ }
922
+ AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED -> {
923
+ val state =
924
+ intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -1)
925
+ when (state) {
926
+ AudioManager.SCO_AUDIO_STATE_CONNECTED -> {
927
+ Bridge.log("MIC: Bluetooth SCO connected")
928
+ handleAudioRouteChange()
929
+ }
930
+ AudioManager.SCO_AUDIO_STATE_DISCONNECTED -> {
931
+ Bridge.log("MIC: Bluetooth SCO disconnected")
932
+ handleAudioRouteChange()
933
+ }
934
+ }
935
+ }
936
+ }
937
+ }
938
+ }
939
+
940
+ val filter =
941
+ IntentFilter().apply {
942
+ addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY)
943
+ addAction(AudioManager.ACTION_HEADSET_PLUG)
944
+ addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED)
945
+ }
946
+
947
+ context.registerReceiver(audioRouteReceiver, filter)
948
+ }
949
+
950
+ private fun setupBluetoothListener() {
951
+ bluetoothReceiver =
952
+ object : BroadcastReceiver() {
953
+ override fun onReceive(context: Context?, intent: Intent?) {
954
+ when (intent?.action) {
955
+ BluetoothDevice.ACTION_ACL_CONNECTED -> {
956
+ val device =
957
+ intent.getParcelableExtra<BluetoothDevice>(
958
+ BluetoothDevice.EXTRA_DEVICE
959
+ )
960
+ Bridge.log(
961
+ "MIC: Bluetooth device connected: ${device?.name ?: "Unknown"}"
962
+ )
963
+ handleAudioRouteChange()
964
+ }
965
+ BluetoothDevice.ACTION_ACL_DISCONNECTED -> {
966
+ val device =
967
+ intent.getParcelableExtra<BluetoothDevice>(
968
+ BluetoothDevice.EXTRA_DEVICE
969
+ )
970
+ Bridge.log(
971
+ "MIC: Bluetooth device disconnected: ${device?.name ?: "Unknown"}"
972
+ )
973
+ handleAudioRouteChange()
974
+ }
975
+ }
976
+ }
977
+ }
978
+
979
+ val filter =
980
+ IntentFilter().apply {
981
+ addAction(BluetoothDevice.ACTION_ACL_CONNECTED)
982
+ addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED)
983
+ }
984
+
985
+ context.registerReceiver(bluetoothReceiver, filter)
986
+ }
987
+
988
+ private fun handleAudioRouteChange() {
989
+ val availableInputs = getAvailableInputDevices().values.toList()
990
+ notifyDeviceManager("audio_route_changed", availableInputs)
991
+ }
992
+
993
+ private fun requestAudioFocus(): Boolean {
994
+ Bridge.log("MIC: Requesting audio focus with USAGE_VOICE_COMMUNICATION")
995
+ val result =
996
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
997
+ val audioAttributes =
998
+ AudioAttributes.Builder()
999
+ .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
1000
+ .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
1001
+ .build()
1002
+
1003
+ audioFocusRequest =
1004
+ AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
1005
+ .setAudioAttributes(audioAttributes)
1006
+ .setOnAudioFocusChangeListener(
1007
+ audioFocusListener!!,
1008
+ mainHandler
1009
+ )
1010
+ .setAcceptsDelayedFocusGain(false)
1011
+ .build()
1012
+
1013
+ audioManager.requestAudioFocus(audioFocusRequest!!)
1014
+ } else {
1015
+ audioManager.requestAudioFocus(
1016
+ audioFocusListener,
1017
+ AudioManager.STREAM_VOICE_CALL,
1018
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT
1019
+ )
1020
+ }
1021
+
1022
+ hasAudioFocus = (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED)
1023
+ Bridge.log("MIC: Audio focus ${if (hasAudioFocus) "GRANTED" else "DENIED"}")
1024
+ return hasAudioFocus
1025
+ }
1026
+
1027
+ private fun abandonAudioFocus() {
1028
+ if (!hasAudioFocus) return
1029
+
1030
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && audioFocusRequest != null) {
1031
+ audioManager.abandonAudioFocusRequest(audioFocusRequest!!)
1032
+ audioFocusRequest = null
1033
+ } else {
1034
+ audioManager.abandonAudioFocus(audioFocusListener)
1035
+ }
1036
+
1037
+ hasAudioFocus = false
1038
+ }
1039
+
1040
+ private fun testMicrophoneAvailabilityOnSamsung() {
1041
+ Bridge.log("MIC: Samsung - testing mic availability")
1042
+
1043
+ val currentRecord = audioRecord
1044
+
1045
+ // Temporarily stop recording
1046
+ cleanUpRecording()
1047
+
1048
+ // Wait and try to recreate
1049
+ mainHandler.postDelayed(
1050
+ {
1051
+ if (tryCreateTestAudioRecord()) {
1052
+ Bridge.log("MIC: Samsung - mic available, just playback app")
1053
+ // Restart recording
1054
+ startRecordingInternal()
1055
+ } else {
1056
+ Bridge.log("MIC: Samsung - mic taken by another app")
1057
+ isExternalAudioActive = true
1058
+ notifyDeviceManager("samsung_mic_conflict", emptyList())
1059
+ }
1060
+ },
1061
+ SAMSUNG_MIC_TEST_DELAY_MS
1062
+ )
1063
+ }
1064
+
1065
+ private fun tryCreateTestAudioRecord(): Boolean {
1066
+ var testRecorder: AudioRecord? = null
1067
+ try {
1068
+ val minBufferSize =
1069
+ AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT)
1070
+
1071
+ testRecorder =
1072
+ AudioRecord(
1073
+ MediaRecorder.AudioSource.MIC,
1074
+ SAMPLE_RATE,
1075
+ CHANNEL_CONFIG,
1076
+ AUDIO_FORMAT,
1077
+ minBufferSize
1078
+ )
1079
+
1080
+ return testRecorder.state == AudioRecord.STATE_INITIALIZED
1081
+ } catch (e: Exception) {
1082
+ return false
1083
+ } finally {
1084
+ testRecorder?.release()
1085
+ }
1086
+ }
1087
+
1088
+ private fun isSamsungDevice(): Boolean {
1089
+ return Build.MANUFACTURER.equals("samsung", ignoreCase = true)
1090
+ }
1091
+
1092
+ private fun checkPermissions(): Boolean {
1093
+ return ActivityCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO) ==
1094
+ PackageManager.PERMISSION_GRANTED
1095
+ }
1096
+
1097
+ private fun getAvailableInputDevices(): Map<String, String> {
1098
+ val deviceInfo = mutableMapOf<String, String>()
1099
+
1100
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
1101
+ val devices = audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)
1102
+ for (device in devices) {
1103
+ val name =
1104
+ when (device.type) {
1105
+ AudioDeviceInfo.TYPE_BUILTIN_MIC -> "Built-in Microphone"
1106
+ AudioDeviceInfo.TYPE_BLUETOOTH_SCO -> "Bluetooth Headset"
1107
+ AudioDeviceInfo.TYPE_WIRED_HEADSET -> "Wired Headset"
1108
+ AudioDeviceInfo.TYPE_USB_HEADSET -> "USB Headset"
1109
+ else -> device.productName.toString()
1110
+ }
1111
+ deviceInfo[device.id.toString()] = name
1112
+ }
1113
+ } else {
1114
+ deviceInfo["default"] = "Default Microphone"
1115
+ if (audioManager.isBluetoothScoAvailableOffCall) {
1116
+ deviceInfo["bluetooth"] = "Bluetooth Headset"
1117
+ }
1118
+ if (audioManager.isWiredHeadsetOn) {
1119
+ deviceInfo["wired"] = "Wired Headset"
1120
+ }
1121
+ }
1122
+
1123
+ return deviceInfo
1124
+ }
1125
+
1126
+ private fun getActiveInputDevice(): String? {
1127
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
1128
+ audioRecord?.routedDevice?.let { device ->
1129
+ when (device.type) {
1130
+ AudioDeviceInfo.TYPE_BUILTIN_MIC -> "Built-in Microphone"
1131
+ AudioDeviceInfo.TYPE_BLUETOOTH_SCO -> "Bluetooth Headset"
1132
+ AudioDeviceInfo.TYPE_WIRED_HEADSET -> "Wired Headset"
1133
+ AudioDeviceInfo.TYPE_USB_HEADSET -> "USB Headset"
1134
+ else -> device.productName.toString()
1135
+ }
1136
+ }
1137
+ } else {
1138
+ when {
1139
+ audioManager.isBluetoothScoOn -> "Bluetooth Headset"
1140
+ audioManager.isWiredHeadsetOn -> "Wired Headset"
1141
+ else -> "Built-in Microphone"
1142
+ }
1143
+ }
1144
+ }
1145
+
1146
+ private fun notifyDeviceManager(reason: String, availableInputs: List<String>) {
1147
+ mainHandler.post {
1148
+ DeviceManager.getInstance()
1149
+ .onRouteChange(reason = reason, availableInputs = availableInputs)
1150
+ }
1151
+ }
1152
+
1153
+ fun cleanup() {
1154
+ stopRecording()
1155
+
1156
+ // CRITICAL: Force reset audio mode to prevent system-wide Bluetooth audio breakage
1157
+ // This ensures that even if stopRecording() failed, we restore normal audio routing
1158
+ try {
1159
+ if (audioManager.isBluetoothScoOn) {
1160
+ Bridge.log("MIC: Force stopping Bluetooth SCO in cleanup")
1161
+ audioManager.stopBluetoothSco()
1162
+ audioManager.isBluetoothScoOn = false
1163
+ }
1164
+
1165
+ if (audioManager.mode != AudioManager.MODE_NORMAL) {
1166
+ Bridge.log("MIC: Force resetting audio mode to NORMAL in cleanup")
1167
+ audioManager.mode = AudioManager.MODE_NORMAL
1168
+ }
1169
+ } catch (e: Exception) {
1170
+ Bridge.log("MIC: Error during audio mode cleanup: ${e.message}")
1171
+ }
1172
+
1173
+ // Unregister listeners
1174
+ phoneStateListener?.let { telephonyManager?.listen(it, PhoneStateListener.LISTEN_NONE) }
1175
+
1176
+ audioRecordingCallback?.let {
1177
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
1178
+ audioManager.unregisterAudioRecordingCallback(it)
1179
+ }
1180
+ }
1181
+
1182
+ // Unregister receivers
1183
+ try {
1184
+ audioRouteReceiver?.let { context.unregisterReceiver(it) }
1185
+ bluetoothReceiver?.let { context.unregisterReceiver(it) }
1186
+ } catch (e: Exception) {
1187
+ Bridge.log("Error unregistering receivers: ${e.message}")
1188
+ }
1189
+
1190
+ // Cancel coroutines
1191
+ scope.cancel()
1192
+
1193
+ Bridge.log("MIC: Cleaned up")
1194
+ }
1195
+ }