@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.
- package/README.md +563 -0
- package/android/build.gradle +450 -0
- package/android/gradle.properties.example +9 -0
- package/android/lc3Lib/CMakeLists.txt +6 -0
- package/android/lc3Lib/build.gradle +212 -0
- package/android/lc3Lib/proguard-rules.pro +33 -0
- package/android/lc3Lib/src/main/AndroidManifest.xml +2 -0
- package/android/lc3Lib/src/main/cpp/CMakeLists.txt +8 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/CMakeLists.txt +30 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/jni/CMakeLists.txt +7 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/jni/ogg_opus_encoder.cc +96 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/jni/ogg_opus_encoder.h +53 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/Makefile.am +6 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/config_types.h +26 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/config_types.h.in +26 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/ogg.h +209 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/os_types.h +158 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus.h +981 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_custom.h +342 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_defines.h +799 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_multistream.h +660 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_projection.h +568 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_types.h +166 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/ogg_opus_encoder.cc +268 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/ogg_opus_encoder.h +115 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/opus_tools/opus_header.h +59 -0
- package/android/lc3Lib/src/main/cpp/liblc3/CMakeLists.txt +38 -0
- package/android/lc3Lib/src/main/cpp/liblc3/include/lc3.h +309 -0
- package/android/lc3Lib/src/main/cpp/liblc3/include/lc3_private.h +162 -0
- package/android/lc3Lib/src/main/cpp/liblc3/include/rnnoise.h +114 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/CMakeLists.txt +19 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/attdet.c +92 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/attdet.h +44 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/bits.c +375 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/bits.h +315 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/bwdet.c +129 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/bwdet.h +69 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/common.h +148 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/energy.c +70 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/energy.h +43 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/fastmath.h +158 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/lc3.c +702 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/ltpf.c +893 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/ltpf.h +111 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/ltpf_arm.h +506 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/ltpf_neon.h +281 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/makefile.mk +35 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/mdct.c +452 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/mdct.h +57 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/mdct_neon.h +296 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/meson.build +46 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/plc.c +61 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/plc.h +57 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/sns.c +880 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/sns.h +103 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/spec.c +904 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/spec.h +119 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/tables.c +3457 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/tables.h +94 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/tns.c +457 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/tns.h +99 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3.cpp +159 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/_kiss_fft_guts.h +182 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/arch.h +261 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/celt_lpc.c +279 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/celt_lpc.h +59 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/common.h +48 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/compile.sh +3 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/denoise.c +646 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/kiss_fft.c +601 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/kiss_fft.h +203 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/opus_types.h +159 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/pitch.c +526 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/pitch.h +149 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn.c +178 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn.h +69 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn_data.c +11051 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn_data.h +34 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn_reader.c +168 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn_train.py +66 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/tansig_table.h +45 -0
- package/android/lc3Lib/src/main/java/com/glassly/lc3Lib/Lc3Cpp.java +38 -0
- package/android/lc3Lib/third_party/CMakeLists.txt +23 -0
- package/android/lc3Lib/third_party/CMakeLists_libogg.txt +18 -0
- package/android/lc3Lib/third_party/CMakeLists_libopus.txt +18 -0
- package/android/lc3Lib/third_party/CMakeLists_opus-tools.txt +23 -0
- package/android/lc3Lib/third_party/opus_tools/src/AUTHORS +5 -0
- package/android/lc3Lib/third_party/opus_tools/src/CMakeLists.txt +4 -0
- package/android/lc3Lib/third_party/opus_tools/src/COPYING +371 -0
- package/android/lc3Lib/third_party/opus_tools/src/ChangeLog +3 -0
- package/android/lc3Lib/third_party/opus_tools/src/Makefile.am +42 -0
- package/android/lc3Lib/third_party/opus_tools/src/Makefile.unix +23 -0
- package/android/lc3Lib/third_party/opus_tools/src/NEWS +0 -0
- package/android/lc3Lib/third_party/opus_tools/src/README +0 -0
- package/android/lc3Lib/third_party/opus_tools/src/autogen.sh +115 -0
- package/android/lc3Lib/third_party/opus_tools/src/configure.ac +293 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/arch.h +239 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/audio-in.c +1046 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/diag_range.c +245 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/diag_range.h +28 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/info_opus.c +320 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/info_opus.h +51 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/lpc.c +157 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/lpc.h +27 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opus_header.c +286 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opus_header.h +59 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opusdec.c +884 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opusenc.c +1021 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opusenc.h +101 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opusinfo.c +639 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opusinfo.h +51 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/os_support.h +167 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/resample.c +1137 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/speex_resampler.h +344 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/stack_alloc.h +115 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/wav_io.c +125 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/wav_io.h +62 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/wave_out.c +223 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/wave_out.h +60 -0
- package/android/settings.gradle +4 -0
- package/android/silero/LICENSE.md +17 -0
- package/android/silero/build.gradle +50 -0
- package/android/silero/proguard-rules.pro +24 -0
- package/android/silero/src/androidTest/assets/hello.wav +0 -0
- package/android/silero/src/androidTest/java/com/konovalov/vad/silero/VadSileroTest.kt +79 -0
- package/android/silero/src/main/assets/LICENSE +21 -0
- package/android/silero/src/main/assets/silero_vad.onnx +0 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/Vad.kt +160 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/VadSilero.kt +449 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/config/FrameSize.kt +15 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/config/Mode.kt +14 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/config/SampleRate.kt +12 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/utils/AudioUtils.kt +47 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/utils/TensorMap.kt +41 -0
- package/android/silero/src/test/java/com/konovalov/vad/silero/utils/AudioUtilsTest.kt +44 -0
- package/android/src/main/AndroidManifest.xml +42 -0
- package/android/src/main/assets/G2Firmware/README.md +11 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdk.kt +2325 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdkAnalytics.kt +216 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdkDebug.kt +14 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdkDefaults.kt +7 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdkModule.kt +1088 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/Bridge.kt +953 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/DeviceManager.kt +2528 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/DeviceStore.kt +360 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/GeneratedChangelogCatalog.kt +8 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/GeneratedReleaseMetadata.kt +11 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/LocalNetworkModule.kt +414 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/ObservableStore.kt +88 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/OtaManifest.kt +191 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/ReleaseChangelog.kt +61 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/ScopedNetworkReadiness.kt +17 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/audio/AudioModels.kt +144 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/camera/CameraModels.kt +486 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/connection/ConnectionModels.kt +78 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/controllers/ControllerManager.kt +158 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/controllers/R1.kt +937 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/debug/BleTraceLogger.kt +165 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/events/BluetoothEvents.kt +178 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/internal/MapParsing.kt +140 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/net/LocalIpv4.kt +57 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/otaserver/LocalOtaServer.kt +304 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/otaserver/OtaServerModule.kt +120 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/photoreceiver/LocalPhotoReceiverRegistry.java +108 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/photoreceiver/LocalPhotoUploadServer.kt +622 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/photoreceiver/PhotoReceiverModule.kt +156 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/requests/DisplayRequests.kt +56 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/services/Foreground.kt +272 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/services/PhoneMic.kt +1195 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/Ar99.java +2653 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/BesOtaHeartbeatGuard.kt +49 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/BesOtaProgressMapper.kt +27 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G1.kt +3878 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G1TextSanitizer.kt +38 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2.kt +6428 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwClassification.kt +49 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwRenderer.kt +1241 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwTextureCache.kt +270 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2FlashProtocol.kt +291 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2Flasher.kt +411 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/GlasslyBle.java +43189 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/Mach1.kt +1280 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraLive.kt +10817 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraLiveL2capChannel.kt +274 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraLivePairingAdvertisement.kt +54 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraNex.kt +2011 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/Nimo.kt +2532 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/SGCManager.kt +470 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/Simulated.kt +228 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/Ar99OtaManager.java +566 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/OtaByteUtils.java +18 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/OtaCommandConstants.java +35 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/OtaCrc32Util.java +13 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/OtaGattTransport.java +11 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/OtaProtocol.java +92 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/speakerid/SpeakerIdEngine.kt +525 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/status/DeviceStatus.kt +669 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/status/RuntimeState.kt +201 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/status/WifiHotspotStatus.kt +189 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/streaming/StreamModels.kt +518 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/stt/STTTools.kt +391 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/stt/SherpaOnnxTranscriber.kt +518 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/stt/VadGateSpeechPolicy.kt +229 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/tts/TTSTools.kt +248 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/types/DeviceModels.kt +96 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/AES.java +106 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/AudioSessionMonitor.kt +189 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/AvifExifStripper.java +573 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/BitmapJavaUtils.java +169 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/BleJsonCompact.java +496 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/BlePhotoUploadService.java +677 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/BleWireProtocol.java +108 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/Constants.kt +46 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/G1Text.kt +436 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/HeifExifTagReader.java +199 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/IncidentLogBleRelayNaming.java +29 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/IncidentLogBleUploadService.java +77 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/K900LengthCodec.java +115 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/K900ProtocolUtils.java +827 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/MessageChunkReassembler.java +228 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/MessageChunker.java +325 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/NexSGCUtils.kt +698 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/PhoneAudioMonitor.kt +348 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/SmartGlassesConnectionState.java +9 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/audio/Ar99OpusPcmDecoder.java +398 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/audio/ByteUtilAudioPlayer.java +655 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/audio/Lc3Player.java +441 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/audio/PCMAudioPlayer.java +431 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/audio/PcmStreamPlayer.kt +328 -0
- package/android/src/main/res/values/strings.xml +4 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/BluetoothSdkExceptionTest.kt +18 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/OtaManifestDowngradeTest.kt +86 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/OtaStartResponsePolicyTest.kt +30 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/ReleaseChangelogTest.kt +13 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/ScopedNetworkReadinessTest.kt +39 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/StreamStatusEventTest.kt +29 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/camera/PhotoRequestTest.kt +141 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/otaserver/LocalOtaServerTest.kt +82 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/photoreceiver/LocalPhotoReceiverRegistryTest.java +75 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/services/ForegroundServiceTypeTest.kt +69 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/Ar99AdvertisementMatcherTest.java +22 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/BesOtaHeartbeatGuardTest.kt +73 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/BesOtaProgressMapperTest.kt +67 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/EvenRleTest.kt +63 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G1TextSanitizerTest.kt +31 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwClassificationTest.kt +89 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwRendererTest.kt +344 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwTextureCacheTest.kt +200 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2FlashProtocolTest.kt +131 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/MentraLiveGattCharacteristicsTest.kt +39 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/MentraLivePairingAdvertisementParserTest.kt +133 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/NimoProtocolTest.kt +333 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/AvifExifStripperTest.java +46 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/BinaryMessageChunkerTest.java +84 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/BleJsonCompactTest.java +319 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/BlePhotoUploadServiceTest.java +107 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/K900ProtocolUtilsEndiannessTest.java +172 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/K900ProtocolUtilsTest.java +24 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/PhoneAudioMonitorTest.kt +45 -0
- package/android/src/test/resources/avif_with_exif.avif +0 -0
- package/app.plugin.js +3 -0
- package/build/BluetoothSdk.types.d.ts +1354 -0
- package/build/BluetoothSdk.types.d.ts.map +1 -0
- package/build/BluetoothSdk.types.js +41 -0
- package/build/BluetoothSdk.types.js.map +1 -0
- package/build/_internal.d.ts +17 -0
- package/build/_internal.d.ts.map +1 -0
- package/build/_internal.js +16 -0
- package/build/_internal.js.map +1 -0
- package/build/_private/BluetoothSdkModule.d.ts +172 -0
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -0
- package/build/_private/BluetoothSdkModule.js +300 -0
- package/build/_private/BluetoothSdkModule.js.map +1 -0
- package/build/_private/LocalNetworkModule.d.ts +38 -0
- package/build/_private/LocalNetworkModule.d.ts.map +1 -0
- package/build/_private/LocalNetworkModule.js +3 -0
- package/build/_private/LocalNetworkModule.js.map +1 -0
- package/build/_private/cameraRequestPayload.d.ts +4 -0
- package/build/_private/cameraRequestPayload.d.ts.map +1 -0
- package/build/_private/cameraRequestPayload.js +32 -0
- package/build/_private/cameraRequestPayload.js.map +1 -0
- package/build/_private/photoRequestPayload.d.ts +6 -0
- package/build/_private/photoRequestPayload.d.ts.map +1 -0
- package/build/_private/photoRequestPayload.js +86 -0
- package/build/_private/photoRequestPayload.js.map +1 -0
- package/build/_private/sdkOtaManifest.d.ts +11 -0
- package/build/_private/sdkOtaManifest.d.ts.map +1 -0
- package/build/_private/sdkOtaManifest.js +15 -0
- package/build/_private/sdkOtaManifest.js.map +1 -0
- package/build/changelogs.d.ts +7 -0
- package/build/changelogs.d.ts.map +1 -0
- package/build/changelogs.js +46 -0
- package/build/changelogs.js.map +1 -0
- package/build/debug.d.ts +3 -0
- package/build/debug.d.ts.map +1 -0
- package/build/debug.js +8 -0
- package/build/debug.js.map +1 -0
- package/build/generated/changelogCatalog.d.ts +9 -0
- package/build/generated/changelogCatalog.d.ts.map +1 -0
- package/build/generated/changelogCatalog.js +12 -0
- package/build/generated/changelogCatalog.js.map +1 -0
- package/build/generated/releaseMetadata.d.ts +12 -0
- package/build/generated/releaseMetadata.d.ts.map +1 -0
- package/build/generated/releaseMetadata.js +10 -0
- package/build/generated/releaseMetadata.js.map +1 -0
- package/build/index.d.ts +6 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +171 -0
- package/build/index.js.map +1 -0
- package/build/ota-server/OtaServer.types.d.ts +14 -0
- package/build/ota-server/OtaServer.types.d.ts.map +1 -0
- package/build/ota-server/OtaServer.types.js +2 -0
- package/build/ota-server/OtaServer.types.js.map +1 -0
- package/build/ota-server/OtaServerModule.d.ts +21 -0
- package/build/ota-server/OtaServerModule.d.ts.map +1 -0
- package/build/ota-server/OtaServerModule.js +3 -0
- package/build/ota-server/OtaServerModule.js.map +1 -0
- package/build/ota-server/OtaServerModule.web.d.ts +10 -0
- package/build/ota-server/OtaServerModule.web.d.ts.map +1 -0
- package/build/ota-server/OtaServerModule.web.js +12 -0
- package/build/ota-server/OtaServerModule.web.js.map +1 -0
- package/build/ota-server/index.d.ts +3 -0
- package/build/ota-server/index.d.ts.map +1 -0
- package/build/ota-server/index.js +3 -0
- package/build/ota-server/index.js.map +1 -0
- package/build/ota-transport/index.d.ts +67 -0
- package/build/ota-transport/index.d.ts.map +1 -0
- package/build/ota-transport/index.js +58 -0
- package/build/ota-transport/index.js.map +1 -0
- package/build/photo-receiver/PhotoReceiver.types.d.ts +18 -0
- package/build/photo-receiver/PhotoReceiver.types.d.ts.map +1 -0
- package/build/photo-receiver/PhotoReceiver.types.js +2 -0
- package/build/photo-receiver/PhotoReceiver.types.js.map +1 -0
- package/build/photo-receiver/PhotoReceiverModule.d.ts +10 -0
- package/build/photo-receiver/PhotoReceiverModule.d.ts.map +1 -0
- package/build/photo-receiver/PhotoReceiverModule.js +3 -0
- package/build/photo-receiver/PhotoReceiverModule.js.map +1 -0
- package/build/photo-receiver/PhotoReceiverModule.web.d.ts +10 -0
- package/build/photo-receiver/PhotoReceiverModule.web.d.ts.map +1 -0
- package/build/photo-receiver/PhotoReceiverModule.web.js +12 -0
- package/build/photo-receiver/PhotoReceiverModule.web.js.map +1 -0
- package/build/photo-receiver/index.d.ts +3 -0
- package/build/photo-receiver/index.d.ts.map +1 -0
- package/build/photo-receiver/index.js +3 -0
- package/build/photo-receiver/index.js.map +1 -0
- package/build/react/index.d.ts +5 -0
- package/build/react/index.d.ts.map +1 -0
- package/build/react/index.js +4 -0
- package/build/react/index.js.map +1 -0
- package/build/react/useBluetooth.d.ts +100 -0
- package/build/react/useBluetooth.d.ts.map +1 -0
- package/build/react/useBluetooth.js +167 -0
- package/build/react/useBluetooth.js.map +1 -0
- package/build/react/useBluetoothEvent.d.ts +6 -0
- package/build/react/useBluetoothEvent.d.ts.map +1 -0
- package/build/react/useBluetoothEvent.js +21 -0
- package/build/react/useBluetoothEvent.js.map +1 -0
- package/build/react/useBluetoothScan.d.ts +22 -0
- package/build/react/useBluetoothScan.d.ts.map +1 -0
- package/build/react/useBluetoothScan.js +135 -0
- package/build/react/useBluetoothScan.js.map +1 -0
- package/build/react/useBluetoothStatus.d.ts +16 -0
- package/build/react/useBluetoothStatus.d.ts.map +1 -0
- package/build/react/useBluetoothStatus.js +137 -0
- package/build/react/useBluetoothStatus.js.map +1 -0
- package/build/react/useGlassesConnection.d.ts +29 -0
- package/build/react/useGlassesConnection.d.ts.map +1 -0
- package/build/react/useGlassesConnection.js +153 -0
- package/build/react/useGlassesConnection.js.map +1 -0
- package/build/types/index.d.ts +3 -0
- package/build/types/index.d.ts.map +1 -0
- package/build/types/index.js +2 -0
- package/build/types/index.js.map +1 -0
- package/expo-module.config.json +14 -0
- package/ios/BackgroundOtaArtifactDownloader.swift +125 -0
- package/ios/BluetoothSdkModule.swift +1034 -0
- package/ios/GlasslyBluetoothSDK.podspec +106 -0
- package/ios/LocalIPv4.swift +150 -0
- package/ios/LocalOtaServer.swift +309 -0
- package/ios/LocalPhotoUploadServer.swift +754 -0
- package/ios/OtaServerModule.swift +147 -0
- package/ios/Packages/CoreObjC/CoreObjC.xcodeproj/project.pbxproj +213 -0
- package/ios/Packages/CoreObjC/PcmConverter.h +22 -0
- package/ios/Packages/CoreObjC/PcmConverter.m +266 -0
- package/ios/Packages/CoreObjC/attdet.c +92 -0
- package/ios/Packages/CoreObjC/attdet.h +44 -0
- package/ios/Packages/CoreObjC/bits.c +375 -0
- package/ios/Packages/CoreObjC/bits.h +315 -0
- package/ios/Packages/CoreObjC/bwdet.c +129 -0
- package/ios/Packages/CoreObjC/bwdet.h +69 -0
- package/ios/Packages/CoreObjC/common.h +151 -0
- package/ios/Packages/CoreObjC/energy.c +70 -0
- package/ios/Packages/CoreObjC/energy.h +43 -0
- package/ios/Packages/CoreObjC/fastmath.h +158 -0
- package/ios/Packages/CoreObjC/include/PcmConverter.h +22 -0
- package/ios/Packages/CoreObjC/lc3.c +704 -0
- package/ios/Packages/CoreObjC/lc3.h +313 -0
- package/ios/Packages/CoreObjC/lc3_cpp.h +283 -0
- package/ios/Packages/CoreObjC/lc3_private.h +163 -0
- package/ios/Packages/CoreObjC/ltpf.c +905 -0
- package/ios/Packages/CoreObjC/ltpf.h +111 -0
- package/ios/Packages/CoreObjC/ltpf_arm.h +506 -0
- package/ios/Packages/CoreObjC/ltpf_neon.h +281 -0
- package/ios/Packages/CoreObjC/makefile.mk +35 -0
- package/ios/Packages/CoreObjC/mdct.c +469 -0
- package/ios/Packages/CoreObjC/mdct.h +57 -0
- package/ios/Packages/CoreObjC/mdct_neon.h +296 -0
- package/ios/Packages/CoreObjC/meson.build +61 -0
- package/ios/Packages/CoreObjC/plc.c +61 -0
- package/ios/Packages/CoreObjC/plc.h +57 -0
- package/ios/Packages/CoreObjC/rnnoise.h +114 -0
- package/ios/Packages/CoreObjC/sns.c +880 -0
- package/ios/Packages/CoreObjC/sns.h +103 -0
- package/ios/Packages/CoreObjC/spec.c +907 -0
- package/ios/Packages/CoreObjC/spec.h +119 -0
- package/ios/Packages/CoreObjC/tables.c +3457 -0
- package/ios/Packages/CoreObjC/tables.h +94 -0
- package/ios/Packages/CoreObjC/tns.c +457 -0
- package/ios/Packages/CoreObjC/tns.h +99 -0
- package/ios/Packages/SherpaOnnx/Model/joiner.onnx +0 -0
- package/ios/Packages/SherpaOnnx/Model/tokens.txt +502 -0
- package/ios/Packages/SherpaOnnx/SherpaOnnx.swift +2383 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Info.plist +48 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/Headers/sherpa-onnx/c-api/c-api.h +4689 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/Headers/sherpa-onnx/c-api/cxx-api.h +2036 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/libsherpa-onnx.a +0 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/Headers/sherpa-onnx/c-api/c-api.h +4689 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/Headers/sherpa-onnx/c-api/cxx-api.h +2036 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/libsherpa-onnx.a +0 -0
- package/ios/Packages/libbz2/module.modulemap +5 -0
- package/ios/Packages/libbz2/shim.h +1 -0
- package/ios/PhotoReceiverModule.swift +129 -0
- package/ios/Source/BluetoothSDK.swift +2303 -0
- package/ios/Source/BluetoothSDKDebug.swift +12 -0
- package/ios/Source/BluetoothSdkDefaults.swift +29 -0
- package/ios/Source/Bridge.swift +708 -0
- package/ios/Source/Bridging-Header.h +16 -0
- package/ios/Source/DeviceManager.swift +2109 -0
- package/ios/Source/DeviceStore.swift +336 -0
- package/ios/Source/GeneratedChangelogCatalog.swift +7 -0
- package/ios/Source/GeneratedReleaseMetadata.swift +11 -0
- package/ios/Source/ObservableStore.swift +118 -0
- package/ios/Source/OtaManifest.swift +211 -0
- package/ios/Source/PrivacyInfo.xcprivacy +23 -0
- package/ios/Source/ReleaseChangelog.swift +65 -0
- package/ios/Source/Resources/G2Firmware/README.md +10 -0
- package/ios/Source/audio/AudioModels.swift +178 -0
- package/ios/Source/audio/PcmStreamPlayer.swift +410 -0
- package/ios/Source/camera/CameraModels.swift +866 -0
- package/ios/Source/connection/ScanSession.swift +27 -0
- package/ios/Source/controllers/ControllerManager.swift +184 -0
- package/ios/Source/controllers/R1.swift +829 -0
- package/ios/Source/errors/BluetoothSdkError.swift +19 -0
- package/ios/Source/events/BluetoothEvents.swift +342 -0
- package/ios/Source/internal/BleTraceLogger.swift +192 -0
- package/ios/Source/internal/BluetoothAvailability.swift +78 -0
- package/ios/Source/internal/BluetoothSdkAnalytics.swift +199 -0
- package/ios/Source/internal/ValueParsing.swift +99 -0
- package/ios/Source/requests/DisplayRequests.swift +82 -0
- package/ios/Source/services/PhoneMic.swift +662 -0
- package/ios/Source/sgcs/Ar99.swift +2451 -0
- package/ios/Source/sgcs/Frame.swift +649 -0
- package/ios/Source/sgcs/G1.swift +2554 -0
- package/ios/Source/sgcs/G2.swift +6887 -0
- package/ios/Source/sgcs/G2CfwClassification.swift +43 -0
- package/ios/Source/sgcs/G2CfwRenderer.swift +1279 -0
- package/ios/Source/sgcs/G2CfwTextureCache.swift +233 -0
- package/ios/Source/sgcs/G2FlashProtocol.swift +312 -0
- package/ios/Source/sgcs/G2Flasher.swift +773 -0
- package/ios/Source/sgcs/Mach1.swift +541 -0
- package/ios/Source/sgcs/MentraLive.swift +7011 -0
- package/ios/Source/sgcs/MentraLiveL2capChannel.swift +183 -0
- package/ios/Source/sgcs/MentraLivePairingAdvertisement.swift +64 -0
- package/ios/Source/sgcs/MentraNex.swift +3221 -0
- package/ios/Source/sgcs/Nimo.swift +1886 -0
- package/ios/Source/sgcs/SGCManager.swift +608 -0
- package/ios/Source/sgcs/Simulated.swift +274 -0
- package/ios/Source/sgcs/ar99/ota/Ar99OtaManager.swift +637 -0
- package/ios/Source/sgcs/glassly_ble.pb.swift +4354 -0
- package/ios/Source/speakerid/SpeakerIdEngine.swift +573 -0
- package/ios/Source/status/DeviceStatus.swift +981 -0
- package/ios/Source/status/RuntimeState.swift +359 -0
- package/ios/Source/status/WifiHotspotStatus.swift +345 -0
- package/ios/Source/streaming/StreamModels.swift +693 -0
- package/ios/Source/stt/STTTools.swift +191 -0
- package/ios/Source/stt/SherpaOnnxTranscriber.swift +466 -0
- package/ios/Source/tts/TTSTools.swift +171 -0
- package/ios/Source/types/DeviceModels.swift +177 -0
- package/ios/Source/utils/AudioSessionMonitor.swift +283 -0
- package/ios/Source/utils/BinaryCodec.swift +61 -0
- package/ios/Source/utils/BleJsonCompact.swift +405 -0
- package/ios/Source/utils/BleWireProtocol.swift +92 -0
- package/ios/Source/utils/Constants.swift +78 -0
- package/ios/Source/utils/Enums.swift +95 -0
- package/ios/Source/utils/G1Text.swift +2096 -0
- package/ios/Source/utils/JSCExperiment.swift +241 -0
- package/ios/Source/utils/MemoryMonitor.swift +44 -0
- package/ios/Source/utils/MessageChunkReassembler.swift +190 -0
- package/ios/Source/utils/MessageChunker.swift +294 -0
- package/ios/Source/utils/Models.swift +135 -0
- package/ios/Source/utils/PhoneAudioMonitor.swift +230 -0
- package/ios/Source/utils/TarBz2Extractor.swift +257 -0
- package/ios/Tests/BluetoothSDKTests/G2CfwClassificationTests.swift +78 -0
- package/ios/Tests/BluetoothSDKTests/G2CfwRendererTests.swift +372 -0
- package/ios/Tests/BluetoothSDKTests/G2CfwTextureCacheTests.swift +162 -0
- package/ios/Tests/BluetoothSDKTests/G2FlashProtocolTests.swift +182 -0
- package/ios/Tests/MentraLivePairingAdvertisementTests.swift +160 -0
- package/ios/Tests/ReleaseChangelogTests.swift +13 -0
- package/package.json +129 -0
- package/plugin/build/index.d.ts +10 -0
- package/plugin/build/index.js +12 -0
- package/plugin/build/withAndroid.d.ts +3 -0
- package/plugin/build/withAndroid.js +190 -0
- package/plugin/build/withIos.d.ts +5 -0
- package/plugin/build/withIos.js +108 -0
- package/scripts/inject-ios-sdk-version.mjs +65 -0
- package/scripts/public-ota-api.test.mjs +37 -0
- package/scripts/verify-release-package.mjs +45 -0
- package/scripts/write-release-metadata.mjs +152 -0
- package/scripts/write-release-metadata.test.mjs +37 -0
- package/src/BluetoothSdk.types.ts +1623 -0
- package/src/_internal.ts +16 -0
- package/src/_private/BluetoothSdkModule.ts +684 -0
- package/src/_private/LocalNetworkModule.ts +50 -0
- package/src/_private/cameraRequestPayload.ts +34 -0
- package/src/_private/photoRequestPayload.ts +90 -0
- package/src/_private/sdkOtaManifest.ts +18 -0
- package/src/changelogs.ts +46 -0
- package/src/debug.ts +9 -0
- package/src/generated/changelogCatalog.ts +13 -0
- package/src/generated/releaseMetadata.ts +20 -0
- package/src/index.ts +321 -0
- package/src/ota-server/OtaServer.types.ts +10 -0
- package/src/ota-server/OtaServerModule.ts +23 -0
- package/src/ota-server/OtaServerModule.web.ts +17 -0
- package/src/ota-server/index.ts +2 -0
- package/src/ota-transport/index.ts +116 -0
- package/src/photo-receiver/PhotoReceiver.types.ts +20 -0
- package/src/photo-receiver/PhotoReceiverModule.ts +11 -0
- package/src/photo-receiver/PhotoReceiverModule.web.ts +17 -0
- package/src/photo-receiver/index.ts +2 -0
- package/src/react/index.ts +21 -0
- package/src/react/useBluetooth.ts +309 -0
- package/src/react/useBluetoothEvent.ts +35 -0
- package/src/react/useBluetoothScan.ts +173 -0
- package/src/react/useBluetoothStatus.ts +180 -0
- package/src/react/useGlassesConnection.ts +202 -0
- 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,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
|
+
}
|