@glassly/bluetooth-sdk 0.1.0-dev.100
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/assets/G2Firmware/g2-glassly-cfw-2.2.9.22.evenota.bin +0 -0
- package/android/src/main/assets/G2Firmware/g2-stock-2.2.9.22.evenota.bin +0 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdk.kt +2321 -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 +1087 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/Bridge.kt +953 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/DeviceManager.kt +2472 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/DeviceStore.kt +359 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/GeneratedChangelogCatalog.kt +9 -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 +1057 -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 +3837 -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 +6457 -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 +1253 -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 +1267 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraLive.kt +10742 -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 +2008 -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 +227 -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 +1390 -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 +171 -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 +12 -0
- package/build/generated/changelogCatalog.d.ts.map +1 -0
- package/build/generated/changelogCatalog.js +16 -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 +170 -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 +1028 -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 +2299 -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 +2054 -0
- package/ios/Source/DeviceStore.swift +350 -0
- package/ios/Source/GeneratedChangelogCatalog.swift +8 -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/Resources/G2Firmware/g2-glassly-cfw-2.2.9.22.evenota.bin +0 -0
- package/ios/Source/Resources/G2Firmware/g2-stock-2.2.9.22.evenota.bin +0 -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 +925 -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/G1.swift +2554 -0
- package/ios/Source/sgcs/G2.swift +7142 -0
- package/ios/Source/sgcs/G2CfwClassification.swift +43 -0
- package/ios/Source/sgcs/G2CfwRenderer.swift +1295 -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 +3220 -0
- package/ios/Source/sgcs/Nimo.swift +1886 -0
- package/ios/Source/sgcs/SGCManager.swift +621 -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 +167 -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/G2NotifyFile.swift +116 -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/BluetoothSDKTests/G2NotifyFileTests.swift +91 -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 +46 -0
- package/scripts/write-release-metadata.mjs +154 -0
- package/scripts/write-release-metadata.test.mjs +37 -0
- package/src/BluetoothSdk.types.ts +1659 -0
- package/src/_internal.ts +16 -0
- package/src/_private/BluetoothSdkModule.ts +683 -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 +17 -0
- package/src/generated/releaseMetadata.ts +20 -0
- package/src/index.ts +320 -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,1057 @@
|
|
|
1
|
+
package com.glassly.bluetoothsdk.controllers
|
|
2
|
+
|
|
3
|
+
import com.glassly.bluetoothsdk.PhotoRequest
|
|
4
|
+
import android.Manifest
|
|
5
|
+
import android.bluetooth.BluetoothAdapter
|
|
6
|
+
import android.bluetooth.BluetoothDevice
|
|
7
|
+
import android.bluetooth.BluetoothGatt
|
|
8
|
+
import android.bluetooth.BluetoothGattCallback
|
|
9
|
+
import android.bluetooth.BluetoothGattCharacteristic
|
|
10
|
+
import android.bluetooth.BluetoothGattDescriptor
|
|
11
|
+
import android.bluetooth.le.BluetoothLeScanner
|
|
12
|
+
import android.bluetooth.le.ScanCallback
|
|
13
|
+
import android.bluetooth.le.ScanResult
|
|
14
|
+
import android.bluetooth.le.ScanSettings
|
|
15
|
+
import android.content.Context
|
|
16
|
+
import android.content.pm.PackageManager
|
|
17
|
+
import android.os.Build
|
|
18
|
+
import android.os.Handler
|
|
19
|
+
import android.os.Looper
|
|
20
|
+
import androidx.core.content.ContextCompat
|
|
21
|
+
import com.glassly.bluetoothsdk.Bridge
|
|
22
|
+
import com.glassly.bluetoothsdk.DeviceManager
|
|
23
|
+
import com.glassly.bluetoothsdk.DeviceStore
|
|
24
|
+
import com.glassly.bluetoothsdk.utils.ControllerTypes
|
|
25
|
+
import java.util.UUID
|
|
26
|
+
import kotlinx.coroutines.CoroutineScope
|
|
27
|
+
import kotlinx.coroutines.Dispatchers
|
|
28
|
+
import kotlinx.coroutines.Job
|
|
29
|
+
import kotlinx.coroutines.delay
|
|
30
|
+
import kotlinx.coroutines.isActive
|
|
31
|
+
import kotlinx.coroutines.launch
|
|
32
|
+
|
|
33
|
+
// MARK: - R1 BLE Constants
|
|
34
|
+
|
|
35
|
+
private object R1BLE {
|
|
36
|
+
val SERVICE_UUID: UUID = UUID.fromString("BAE80001-4F05-4503-8E65-3AF1F7329D1F")
|
|
37
|
+
val WRITE_CHAR_1: UUID = UUID.fromString("BAE80010-4F05-4503-8E65-3AF1F7329D1F")
|
|
38
|
+
val NOTIFY_CHAR_1: UUID = UUID.fromString("BAE80011-4F05-4503-8E65-3AF1F7329D1F")
|
|
39
|
+
val WRITE_CHAR_2: UUID = UUID.fromString("BAE80012-4F05-4503-8E65-3AF1F7329D1F")
|
|
40
|
+
val NOTIFY_CHAR_2: UUID = UUID.fromString("BAE80013-4F05-4503-8E65-3AF1F7329D1F")
|
|
41
|
+
val BATTERY_SERVICE: UUID = UUID.fromString("0000180f-0000-1000-8000-00805f9b34fb")
|
|
42
|
+
val BATTERY_LEVEL_CHAR: UUID = UUID.fromString("00002a19-0000-1000-8000-00805f9b34fb")
|
|
43
|
+
val CCCD_UUID: UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
|
|
44
|
+
|
|
45
|
+
val NAME_FILTERS = arrayOf("EVEN R1", "BCL60")
|
|
46
|
+
|
|
47
|
+
const val GESTURE_MARKER: Byte = 0xFF.toByte()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// MARK: - EUS transport encoder (phone → ring, channel 2)
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Recovered firmware contract (openCFW r1/ research): every phone→ring packet
|
|
54
|
+
* is a 12-byte "model" header
|
|
55
|
+
* [version, module, moduleVersion, serial LE16, status, command, subcommand,
|
|
56
|
+
* totalLength LE16, crc16 LE16]
|
|
57
|
+
* followed by the payload, where the phone-direction CRC-16 (CCITT rounds,
|
|
58
|
+
* init 0xFFFF) runs over the compact bytes [0,1,2,3,5,6,7,8,0x00] and then
|
|
59
|
+
* continues over the payload bytes. The model is carried on channel 2 inside a
|
|
60
|
+
* fragment train [sequence, crc32 LE32, ≤239 bytes]; the CRC-32 (poly
|
|
61
|
+
* 0x1EDC6F41, MSB-first, zero seed, no final xor) covers the whole model and
|
|
62
|
+
* sequence 0 terminates the train — all our packets fit a single fragment.
|
|
63
|
+
* Mirrors iOS R1.swift's R1Eus.
|
|
64
|
+
*/
|
|
65
|
+
private object R1Eus {
|
|
66
|
+
const val VERSION = 100
|
|
67
|
+
const val MODULE_SYSTEM = 0x01
|
|
68
|
+
const val MODULE_VERSION = 100
|
|
69
|
+
/** status bit 0x02 marks a SET; advStart requires it */
|
|
70
|
+
const val STATUS_SET = 0x02
|
|
71
|
+
|
|
72
|
+
/** system subcommand 0x08: payload [0x01] claims the phone role; the ring
|
|
73
|
+
* requests link encryption/bonding in response */
|
|
74
|
+
const val SUBCMD_PAIR_AUTH = 0x08
|
|
75
|
+
/** system subcommand 0x0A: 12-byte payload = two 6-byte glasses arm MACs;
|
|
76
|
+
* the ring stores both targets and fast-advertises for them */
|
|
77
|
+
const val SUBCMD_ADV_START = 0x0A
|
|
78
|
+
|
|
79
|
+
private fun crc16Step(crc: Int, byte: Int): Int {
|
|
80
|
+
var c = ((crc shr 8) or (crc shl 8)) and 0xFFFF
|
|
81
|
+
c = c xor (byte and 0xFF)
|
|
82
|
+
c = c xor ((c and 0x00FF) shr 4)
|
|
83
|
+
c = (c xor (c shl 12)) and 0xFFFF
|
|
84
|
+
c = (c xor ((c and 0x00FF) shl 5)) and 0xFFFF
|
|
85
|
+
return c
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private fun phoneCrc16(header: ByteArray, payload: ByteArray): Int {
|
|
89
|
+
var crc = 0xFFFF
|
|
90
|
+
// compact header: bytes 0-3 and 5-8, then a zero placeholder
|
|
91
|
+
for (b in intArrayOf(
|
|
92
|
+
header[0].toInt(), header[1].toInt(), header[2].toInt(), header[3].toInt(),
|
|
93
|
+
header[5].toInt(), header[6].toInt(), header[7].toInt(), header[8].toInt(), 0
|
|
94
|
+
)) {
|
|
95
|
+
crc = crc16Step(crc, b)
|
|
96
|
+
}
|
|
97
|
+
for (b in payload) {
|
|
98
|
+
crc = crc16Step(crc, b.toInt())
|
|
99
|
+
}
|
|
100
|
+
return crc
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private fun crc32(data: ByteArray): Long {
|
|
104
|
+
var crc = 0L
|
|
105
|
+
for (byte in data) {
|
|
106
|
+
crc = crc xor ((byte.toLong() and 0xFF) shl 24)
|
|
107
|
+
repeat(8) {
|
|
108
|
+
crc = if ((crc and 0x8000_0000L) != 0L) {
|
|
109
|
+
((crc shl 1) xor 0x1EDC6F41L) and 0xFFFF_FFFFL
|
|
110
|
+
} else {
|
|
111
|
+
(crc shl 1) and 0xFFFF_FFFFL
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return crc
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Build one single-fragment system-module command packet. */
|
|
119
|
+
fun frame(subcommand: Int, status: Int, serial: Int, payload: ByteArray): ByteArray {
|
|
120
|
+
val length = 12 + payload.size
|
|
121
|
+
val header = byteArrayOf(
|
|
122
|
+
VERSION.toByte(), MODULE_SYSTEM.toByte(), MODULE_VERSION.toByte(),
|
|
123
|
+
(serial and 0xFF).toByte(), ((serial shr 8) and 0xFF).toByte(),
|
|
124
|
+
status.toByte(), 0x00, subcommand.toByte(),
|
|
125
|
+
(length and 0xFF).toByte(), ((length shr 8) and 0xFF).toByte(),
|
|
126
|
+
0, 0,
|
|
127
|
+
)
|
|
128
|
+
val crc16 = phoneCrc16(header, payload)
|
|
129
|
+
header[10] = (crc16 and 0xFF).toByte()
|
|
130
|
+
header[11] = ((crc16 shr 8) and 0xFF).toByte()
|
|
131
|
+
val model = header + payload
|
|
132
|
+
|
|
133
|
+
val outer = crc32(model)
|
|
134
|
+
return byteArrayOf(
|
|
135
|
+
0x00, // sequence 0: single-fragment train
|
|
136
|
+
(outer and 0xFF).toByte(),
|
|
137
|
+
((outer shr 8) and 0xFF).toByte(),
|
|
138
|
+
((outer shr 16) and 0xFF).toByte(),
|
|
139
|
+
((outer shr 24) and 0xFF).toByte(),
|
|
140
|
+
) + model
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// MARK: - R1 Gesture Types
|
|
145
|
+
|
|
146
|
+
private enum class R1Gesture(val rawValue: String) {
|
|
147
|
+
HOLD("hold"),
|
|
148
|
+
SINGLE_TAP("single_tap"),
|
|
149
|
+
DOUBLE_TAP("double_tap"),
|
|
150
|
+
SWIPE_UP("swipe_up"),
|
|
151
|
+
SWIPE_DOWN("swipe_down");
|
|
152
|
+
|
|
153
|
+
companion object {
|
|
154
|
+
/** Parse gesture from notification data: [0xFF, type, param] */
|
|
155
|
+
fun parse(data: ByteArray): R1Gesture? {
|
|
156
|
+
if (data.size < 3 || data[0] != R1BLE.GESTURE_MARKER) return null
|
|
157
|
+
return when (data[1]) {
|
|
158
|
+
0x03.toByte() -> HOLD
|
|
159
|
+
0x04.toByte() -> when (data[2]) {
|
|
160
|
+
0x01.toByte() -> SINGLE_TAP
|
|
161
|
+
0x02.toByte() -> DOUBLE_TAP
|
|
162
|
+
else -> null
|
|
163
|
+
}
|
|
164
|
+
0x05.toByte() -> if ((data[2].toInt() and 0xFF) < 0x80) SWIPE_UP else SWIPE_DOWN
|
|
165
|
+
else -> null
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// MARK: - R1 Controller
|
|
172
|
+
|
|
173
|
+
class R1 : ControllerManager() {
|
|
174
|
+
|
|
175
|
+
// Connection state
|
|
176
|
+
private val appContext: Context = Bridge.getContext()
|
|
177
|
+
private val prefs = appContext.getSharedPreferences("r1_prefs", Context.MODE_PRIVATE)
|
|
178
|
+
|
|
179
|
+
private var bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter()
|
|
180
|
+
private var scanner: BluetoothLeScanner? = null
|
|
181
|
+
private var scanCallback: ScanCallback? = null
|
|
182
|
+
|
|
183
|
+
private var ringGatt: BluetoothGatt? = null
|
|
184
|
+
private var isDisconnecting = false
|
|
185
|
+
|
|
186
|
+
// BLE characteristics
|
|
187
|
+
private var writeChar1: BluetoothGattCharacteristic? = null
|
|
188
|
+
private var notifyChar1: BluetoothGattCharacteristic? = null
|
|
189
|
+
private var writeChar2: BluetoothGattCharacteristic? = null
|
|
190
|
+
private var notifyChar2: BluetoothGattCharacteristic? = null
|
|
191
|
+
private var batteryLevelChar: BluetoothGattCharacteristic? = null
|
|
192
|
+
private var notifySubscriptionCount = 0
|
|
193
|
+
private var initSequenceRun = false
|
|
194
|
+
|
|
195
|
+
// Android serializes CCCD writes and reads — queue and drain in callbacks
|
|
196
|
+
private val pendingDescriptorWrites = ArrayDeque<BluetoothGattDescriptor>()
|
|
197
|
+
private val pendingReads = ArrayDeque<BluetoothGattCharacteristic>()
|
|
198
|
+
private var descriptorWriteInFlight = false
|
|
199
|
+
private var readInFlight = false
|
|
200
|
+
|
|
201
|
+
// Device search
|
|
202
|
+
private var deviceSearchId: String = "NOT_SET"
|
|
203
|
+
|
|
204
|
+
// BLE handle used by Android for reconnection (BluetoothDevice.address — may be a
|
|
205
|
+
// resolvable/random address depending on bonding state; not the ring's public MAC).
|
|
206
|
+
private var ringBleAddress: String?
|
|
207
|
+
get() = prefs.getString("r1_ringBleAddress", null)
|
|
208
|
+
set(value) {
|
|
209
|
+
prefs.edit().apply {
|
|
210
|
+
if (value == null) remove("r1_ringBleAddress") else putString("r1_ringBleAddress", value)
|
|
211
|
+
apply()
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Public ring MAC parsed from advertisement manufacturer data (last 6 bytes), formatted
|
|
216
|
+
// as "AA:BB:CC:DD:EE:FF". This is what gets published to DeviceStore/controllerMacAddress.
|
|
217
|
+
private var ringMacAddress: String?
|
|
218
|
+
get() = prefs.getString("r1_ringMacAddress", null)
|
|
219
|
+
set(value) {
|
|
220
|
+
prefs.edit().apply {
|
|
221
|
+
if (value == null) remove("r1_ringMacAddress") else putString("r1_ringMacAddress", value)
|
|
222
|
+
apply()
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// peripheral name -> 6-byte ring MAC (hex string), populated from mfgData on every scan.
|
|
227
|
+
// Mirrors iOS ringMacAddressMap so reconnects re-validate freshness instead of trusting
|
|
228
|
+
// a stale stored MAC.
|
|
229
|
+
private fun loadRingMacAddressMap(): MutableMap<String, String> {
|
|
230
|
+
val raw = prefs.getString("r1_ringMacAddressMap", null) ?: return mutableMapOf()
|
|
231
|
+
val out = mutableMapOf<String, String>()
|
|
232
|
+
for (entry in raw.split(';')) {
|
|
233
|
+
if (entry.isEmpty()) continue
|
|
234
|
+
val idx = entry.indexOf('=')
|
|
235
|
+
if (idx <= 0) continue
|
|
236
|
+
out[entry.substring(0, idx)] = entry.substring(idx + 1)
|
|
237
|
+
}
|
|
238
|
+
return out
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private fun saveRingMacAddressMap(map: Map<String, String>) {
|
|
242
|
+
val raw = map.entries.joinToString(";") { "${it.key}=${it.value}" }
|
|
243
|
+
prefs.edit().putString("r1_ringMacAddressMap", raw).apply()
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private fun putRingMacInMap(name: String, mac: String) {
|
|
247
|
+
val map = loadRingMacAddressMap()
|
|
248
|
+
map[name] = mac
|
|
249
|
+
saveRingMacAddressMap(map)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
private fun getRingMacFromMap(name: String): String? = loadRingMacAddressMap()[name]
|
|
253
|
+
|
|
254
|
+
// Reconnection
|
|
255
|
+
private val reconnectionManager = R1ReconnectionManager()
|
|
256
|
+
|
|
257
|
+
private fun startReconnectionTimer() {
|
|
258
|
+
reconnectionManager.start {
|
|
259
|
+
val connected = DeviceStore.get("glasses", "controllerConnected") as? Boolean ?: false
|
|
260
|
+
if (connected) {
|
|
261
|
+
true // already connected — stop retrying
|
|
262
|
+
} else {
|
|
263
|
+
Bridge.log("R1: Attempting reconnection...")
|
|
264
|
+
mainHandler.post { startScan() }
|
|
265
|
+
false // keep trying
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Battery
|
|
271
|
+
private var _batteryLevel: Int = -1
|
|
272
|
+
set(value) {
|
|
273
|
+
val old = field
|
|
274
|
+
field = value
|
|
275
|
+
if (value != old && value >= 0) {
|
|
276
|
+
DeviceStore.apply("glasses", "controllerBatteryLevel", value)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Heartbeat
|
|
281
|
+
private val mainHandler = Handler(Looper.getMainLooper())
|
|
282
|
+
private var heartbeatRunnable: Runnable? = null
|
|
283
|
+
|
|
284
|
+
init {
|
|
285
|
+
type = ControllerTypes.R1
|
|
286
|
+
hasMic = false
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// MARK: - Permissions
|
|
290
|
+
|
|
291
|
+
private fun hasScanPermission(): Boolean {
|
|
292
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return true
|
|
293
|
+
return ContextCompat.checkSelfPermission(
|
|
294
|
+
appContext, Manifest.permission.BLUETOOTH_SCAN
|
|
295
|
+
) == PackageManager.PERMISSION_GRANTED
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
private fun hasConnectPermission(): Boolean {
|
|
299
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return true
|
|
300
|
+
return ContextCompat.checkSelfPermission(
|
|
301
|
+
appContext, Manifest.permission.BLUETOOTH_CONNECT
|
|
302
|
+
) == PackageManager.PERMISSION_GRANTED
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// MARK: - BLE Scanning
|
|
306
|
+
|
|
307
|
+
private fun startScan(): Boolean {
|
|
308
|
+
Bridge.log("R1: startScan()")
|
|
309
|
+
val adapter = bluetoothAdapter ?: BluetoothAdapter.getDefaultAdapter().also { bluetoothAdapter = it }
|
|
310
|
+
if (adapter == null) {
|
|
311
|
+
Bridge.log("R1: No Bluetooth adapter")
|
|
312
|
+
return false
|
|
313
|
+
}
|
|
314
|
+
if (!adapter.isEnabled) {
|
|
315
|
+
Bridge.log("R1: Bluetooth not powered on")
|
|
316
|
+
return false
|
|
317
|
+
}
|
|
318
|
+
if (!hasScanPermission() || !hasConnectPermission()) {
|
|
319
|
+
Bridge.log("R1: Missing Bluetooth permissions")
|
|
320
|
+
return false
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Already connected — don't start a new scan
|
|
324
|
+
if (ringGatt != null) {
|
|
325
|
+
Bridge.log("R1: Already connected, skipping scan")
|
|
326
|
+
return true
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
isDisconnecting = false
|
|
330
|
+
|
|
331
|
+
// Stop any prior scan before starting a new one (avoids leaking ScanCallback)
|
|
332
|
+
stopScan()
|
|
333
|
+
|
|
334
|
+
// Try address-based reconnection first
|
|
335
|
+
if (connectByBleAddress()) {
|
|
336
|
+
return true
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
val s = adapter.bluetoothLeScanner
|
|
340
|
+
if (s == null) {
|
|
341
|
+
Bridge.log("R1: BluetoothLeScanner not available")
|
|
342
|
+
return false
|
|
343
|
+
}
|
|
344
|
+
scanner = s
|
|
345
|
+
|
|
346
|
+
val cb = object : ScanCallback() {
|
|
347
|
+
override fun onScanResult(callbackType: Int, result: ScanResult?) {
|
|
348
|
+
if (result == null) return
|
|
349
|
+
handleScanResult(result)
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
override fun onScanFailed(errorCode: Int) {
|
|
353
|
+
Bridge.log("R1: Scan failed: $errorCode")
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
scanCallback = cb
|
|
357
|
+
val settings = ScanSettings.Builder()
|
|
358
|
+
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
|
|
359
|
+
.build()
|
|
360
|
+
try {
|
|
361
|
+
s.startScan(null, settings, cb)
|
|
362
|
+
} catch (e: SecurityException) {
|
|
363
|
+
Bridge.log("R1: startScan SecurityException: ${e.message}")
|
|
364
|
+
return false
|
|
365
|
+
}
|
|
366
|
+
return true
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
override fun stopScan() {
|
|
370
|
+
val s = scanner ?: return
|
|
371
|
+
val cb = scanCallback ?: return
|
|
372
|
+
try {
|
|
373
|
+
s.stopScan(cb)
|
|
374
|
+
} catch (e: SecurityException) {
|
|
375
|
+
Bridge.log("R1: stopScan SecurityException: ${e.message}")
|
|
376
|
+
}
|
|
377
|
+
scanCallback = null
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
private fun connectByBleAddress(): Boolean {
|
|
381
|
+
if (deviceSearchId == "NOT_SET" || deviceSearchId.isEmpty()) {
|
|
382
|
+
Bridge.log("R1: No deviceSearchId set, skipping connect by address")
|
|
383
|
+
return false
|
|
384
|
+
}
|
|
385
|
+
val address = ringBleAddress ?: return false
|
|
386
|
+
val adapter = bluetoothAdapter ?: return false
|
|
387
|
+
if (ringGatt != null) {
|
|
388
|
+
Bridge.log("R1: connectByBleAddress skipped — already connected")
|
|
389
|
+
return true
|
|
390
|
+
}
|
|
391
|
+
val device = try {
|
|
392
|
+
adapter.getRemoteDevice(address)
|
|
393
|
+
} catch (e: IllegalArgumentException) {
|
|
394
|
+
Bridge.log("R1: Invalid stored BLE address: $address")
|
|
395
|
+
return false
|
|
396
|
+
}
|
|
397
|
+
try {
|
|
398
|
+
ringGatt = device.connectGatt(appContext, false, gattCallback, BluetoothDevice.TRANSPORT_LE)
|
|
399
|
+
Bridge.log("R1: Reconnecting by address to ${device.name ?: address}")
|
|
400
|
+
} catch (e: SecurityException) {
|
|
401
|
+
Bridge.log("R1: connectGatt SecurityException: ${e.message}")
|
|
402
|
+
return false
|
|
403
|
+
}
|
|
404
|
+
return true
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
private fun matchesNameFilter(name: String?): Boolean {
|
|
408
|
+
if (name == null) return false
|
|
409
|
+
return R1BLE.NAME_FILTERS.any { name.contains(it) }
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/** Extract a device identifier from the ring name (e.g. "EVEN R1_CEC5BA" -> "CEC5BA") */
|
|
413
|
+
private fun extractRingId(name: String): String? {
|
|
414
|
+
val idx = name.indexOf("R1_")
|
|
415
|
+
if (idx < 0) return null
|
|
416
|
+
val id = name.substring(idx + 3)
|
|
417
|
+
return if (id.isEmpty()) null else id
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
private fun handleScanResult(result: ScanResult) {
|
|
421
|
+
val device = result.device ?: return
|
|
422
|
+
val advertisedName = result.scanRecord?.deviceName
|
|
423
|
+
val deviceName: String? = try {
|
|
424
|
+
device.name ?: advertisedName
|
|
425
|
+
} catch (e: SecurityException) {
|
|
426
|
+
advertisedName
|
|
427
|
+
}
|
|
428
|
+
mainHandler.post {
|
|
429
|
+
// Already connected — ignore further scan results
|
|
430
|
+
if (ringGatt != null) {
|
|
431
|
+
stopScan()
|
|
432
|
+
return@post
|
|
433
|
+
}
|
|
434
|
+
if (!matchesNameFilter(deviceName)) return@post
|
|
435
|
+
|
|
436
|
+
val mfgMap = result.scanRecord?.manufacturerSpecificData
|
|
437
|
+
val mfgBytes: ByteArray? = if (mfgMap != null && mfgMap.size() > 0) mfgMap.valueAt(0) else null
|
|
438
|
+
val mfgHex = mfgBytes?.joinToString(" ") { String.format("%02X", it) } ?: "none"
|
|
439
|
+
Bridge.log("R1: Discovered: ${deviceName ?: "?"} (RSSI: ${result.rssi}) mfgData: $mfgHex")
|
|
440
|
+
|
|
441
|
+
// Extract ring MAC from manufacturer data (last 6 bytes) and store name->MAC map.
|
|
442
|
+
// Android's manufacturerSpecificData strips the 2-byte company ID; iOS keeps it. The
|
|
443
|
+
// resulting "last 6 bytes" land in reversed byte order between platforms (verified in
|
|
444
|
+
// the field), so reverse here to match iOS's stored string ("1B:08:26:8E:0E:E6").
|
|
445
|
+
if (deviceName != null && mfgBytes != null && mfgBytes.size >= 6) {
|
|
446
|
+
val tail = mfgBytes.copyOfRange(mfgBytes.size - 6, mfgBytes.size)
|
|
447
|
+
val macStr = tail.joinToString(":") { String.format("%02X", it) }
|
|
448
|
+
putRingMacInMap(deviceName, macStr)
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Emit discovered device
|
|
452
|
+
val id = deviceName?.let { extractRingId(it) }
|
|
453
|
+
if (id != null) {
|
|
454
|
+
Bridge.sendDiscoveredDevice(ControllerTypes.R1, id)
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Scan-only mode: don't auto-connect
|
|
458
|
+
if (deviceSearchId == "NOT_SET") return@post
|
|
459
|
+
|
|
460
|
+
// If search ID is specific, check it matches
|
|
461
|
+
val name = deviceName ?: return@post
|
|
462
|
+
val parsedId = extractRingId(name)
|
|
463
|
+
if (parsedId != deviceSearchId && !name.contains(deviceSearchId)) return@post
|
|
464
|
+
|
|
465
|
+
if (ringGatt == null) {
|
|
466
|
+
ringBleAddress = device.address
|
|
467
|
+
try {
|
|
468
|
+
ringGatt = device.connectGatt(appContext, false, gattCallback, BluetoothDevice.TRANSPORT_LE)
|
|
469
|
+
} catch (e: SecurityException) {
|
|
470
|
+
Bridge.log("R1: connectGatt SecurityException: ${e.message}")
|
|
471
|
+
return@post
|
|
472
|
+
}
|
|
473
|
+
stopScan()
|
|
474
|
+
Bridge.log("R1: Connecting to ${name}")
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// MARK: - EUS commands
|
|
480
|
+
|
|
481
|
+
private var eusSerial = 0
|
|
482
|
+
|
|
483
|
+
private fun nextEusSerial(): Int {
|
|
484
|
+
eusSerial = (eusSerial + 1) and 0xFFFF
|
|
485
|
+
if (eusSerial == 0) eusSerial = 1
|
|
486
|
+
return eusSerial
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* EUS system commands go to WRITE_CHAR_2 only — channel 2 is the framed
|
|
491
|
+
* transport; channel 1 speaks the separate legacy protocol.
|
|
492
|
+
*/
|
|
493
|
+
private fun sendEusCommand(subcommand: Int, payload: ByteArray, log: String) {
|
|
494
|
+
val wc = writeChar2
|
|
495
|
+
if (wc == null) {
|
|
496
|
+
Bridge.log("R1: $log: no channel-2 write characteristic")
|
|
497
|
+
return
|
|
498
|
+
}
|
|
499
|
+
val packet = R1Eus.frame(subcommand, R1Eus.STATUS_SET, nextEusSerial(), payload)
|
|
500
|
+
writeNoResponse(wc, packet)
|
|
501
|
+
Bridge.log("R1: $log sent (${packet.size} bytes)")
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// MARK: - Init Sequence
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Runs after notify subscriptions are confirmed: claim the phone role,
|
|
508
|
+
* then mark the phone link ready.
|
|
509
|
+
*/
|
|
510
|
+
private fun runInitSequence() {
|
|
511
|
+
if (initSequenceRun) return
|
|
512
|
+
initSequenceRun = true
|
|
513
|
+
|
|
514
|
+
Bridge.log("R1: Running init sequence")
|
|
515
|
+
|
|
516
|
+
if (writeChar2 == null) {
|
|
517
|
+
Bridge.log("R1: No channel-2 write characteristic, skipping init")
|
|
518
|
+
markConnected()
|
|
519
|
+
return
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
sendEusCommand(R1Eus.SUBCMD_PAIR_AUTH, byteArrayOf(0x01), "pairAuth")
|
|
523
|
+
|
|
524
|
+
mainHandler.postDelayed({ markConnected() }, 300)
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
private fun writeNoResponse(char: BluetoothGattCharacteristic, data: ByteArray) {
|
|
528
|
+
char.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
|
|
529
|
+
char.value = data
|
|
530
|
+
try {
|
|
531
|
+
ringGatt?.writeCharacteristic(char)
|
|
532
|
+
} catch (e: SecurityException) {
|
|
533
|
+
Bridge.log("R1: writeCharacteristic SecurityException: ${e.message}")
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
private fun markConnected() {
|
|
538
|
+
reconnectionManager.stop()
|
|
539
|
+
Bridge.log("R1: Ring connected")
|
|
540
|
+
|
|
541
|
+
val gatt = ringGatt
|
|
542
|
+
val connectedName = try { gatt?.device?.name } catch (e: SecurityException) { null }
|
|
543
|
+
if (connectedName != null) {
|
|
544
|
+
extractRingId(connectedName)?.let {
|
|
545
|
+
DeviceStore.apply("bluetooth", "controller_device_name", it)
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
val mac = ringMacAddress
|
|
550
|
+
if (mac == null) {
|
|
551
|
+
Bridge.log("R1: No ring MAC address found")
|
|
552
|
+
return
|
|
553
|
+
}
|
|
554
|
+
DeviceStore.apply("glasses", "controllerMacAddress", mac)
|
|
555
|
+
DeviceStore.apply("glasses", "controllerConnected", true)
|
|
556
|
+
// The phone link owns these flags: a ready phone link IS pairing
|
|
557
|
+
// success (fires handleControllerReady). The glasses link is layered
|
|
558
|
+
// on afterwards via advStart + RING_CONNECT_INFO.
|
|
559
|
+
DeviceStore.apply("glasses", "controllerFullyBooted", true)
|
|
560
|
+
|
|
561
|
+
// tell the ring to connect to the glasses if we have its mac address:
|
|
562
|
+
connectToGlasses()
|
|
563
|
+
|
|
564
|
+
// after a second, connect the glasses to the controller if needed:
|
|
565
|
+
CoroutineScope(Dispatchers.Main).launch {
|
|
566
|
+
delay(1000)
|
|
567
|
+
DeviceManager.getInstance().sgc?.connectController()
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
startHeartbeat()
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Tells the ring which glasses may connect to it: EUS advStart whose
|
|
575
|
+
* 12-byte payload is the two glasses arm MACs. The ring stores both
|
|
576
|
+
* targets, fast-advertises while a glasses role is open, and accepts a
|
|
577
|
+
* connection from either arm. With only one arm MAC known it is sent for
|
|
578
|
+
* both targets — the ring matches against either slot.
|
|
579
|
+
*/
|
|
580
|
+
private fun connectToGlasses() {
|
|
581
|
+
fun stored(key: String): String? =
|
|
582
|
+
(DeviceStore.get("glasses", key) as? String)?.takeIf { it.isNotEmpty() }
|
|
583
|
+
|
|
584
|
+
val left = stored("leftMacAddress")
|
|
585
|
+
?: stored("bluetoothMacAddress")
|
|
586
|
+
?: prefs.getString("glasses_btMacAddress", null)
|
|
587
|
+
val right = stored("rightMacAddress")
|
|
588
|
+
?: prefs.getString("glasses_btMacAddressRight", null)
|
|
589
|
+
|
|
590
|
+
if (left == null) {
|
|
591
|
+
Bridge.log("R1: connectToGlasses: no glasses MAC")
|
|
592
|
+
return
|
|
593
|
+
}
|
|
594
|
+
val leftBytes = parseMac(left)
|
|
595
|
+
if (leftBytes == null) {
|
|
596
|
+
Bridge.log("R1: connectToGlasses: could not parse glasses MAC")
|
|
597
|
+
return
|
|
598
|
+
}
|
|
599
|
+
val rightBytes = right?.let { parseMac(it) } ?: leftBytes
|
|
600
|
+
|
|
601
|
+
// Cache so we can re-send even before the glasses are next scanned.
|
|
602
|
+
prefs.edit().apply {
|
|
603
|
+
putString("glasses_btMacAddress", left)
|
|
604
|
+
if (right != null) putString("glasses_btMacAddressRight", right)
|
|
605
|
+
apply()
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
sendEusCommand(R1Eus.SUBCMD_ADV_START, leftBytes + rightBytes, "advStart")
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/** Parse "AA:BB:CC:DD:EE:FF" or "AABBCCDDEEFF" into 6 raw bytes. */
|
|
612
|
+
private fun parseMac(s: String): ByteArray? {
|
|
613
|
+
val cleaned = s.replace(":", "").replace("-", "")
|
|
614
|
+
if (cleaned.length != 12) return null
|
|
615
|
+
val out = ByteArray(6)
|
|
616
|
+
for (i in 0 until 6) {
|
|
617
|
+
val byte = cleaned.substring(i * 2, i * 2 + 2).toIntOrNull(16) ?: return null
|
|
618
|
+
out[i] = byte.toByte()
|
|
619
|
+
}
|
|
620
|
+
return out
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// MARK: - Heartbeat
|
|
624
|
+
|
|
625
|
+
private fun startHeartbeat() {
|
|
626
|
+
stopHeartbeat()
|
|
627
|
+
val r = object : Runnable {
|
|
628
|
+
override fun run() {
|
|
629
|
+
mainHandler.postDelayed(this, 30_000L)
|
|
630
|
+
val isConnected = DeviceStore.get("glasses", "controllerConnected") as? Boolean ?: false
|
|
631
|
+
if (!isConnected) return
|
|
632
|
+
val char = batteryLevelChar
|
|
633
|
+
if (char != null) {
|
|
634
|
+
try {
|
|
635
|
+
ringGatt?.readCharacteristic(char)
|
|
636
|
+
} catch (e: SecurityException) {
|
|
637
|
+
Bridge.log("R1: heartbeat read SecurityException: ${e.message}")
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
heartbeatRunnable = r
|
|
643
|
+
mainHandler.postDelayed(r, 30_000L)
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
private fun stopHeartbeat() {
|
|
647
|
+
heartbeatRunnable?.let { mainHandler.removeCallbacks(it) }
|
|
648
|
+
heartbeatRunnable = null
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// MARK: - Incoming Data Handling
|
|
652
|
+
|
|
653
|
+
private fun handleNotification(characteristic: BluetoothGattCharacteristic, data: ByteArray) {
|
|
654
|
+
val hex = data.joinToString(" ") { String.format("%02X", it) }
|
|
655
|
+
val shortUuid = characteristic.uuid.toString().takeLast(4)
|
|
656
|
+
Bridge.log("R1: $shortUuid -> $hex")
|
|
657
|
+
|
|
658
|
+
val timestamp = System.currentTimeMillis()
|
|
659
|
+
|
|
660
|
+
// Gesture: [0xFF, type, param]
|
|
661
|
+
if (data.size >= 3 && data[0] == R1BLE.GESTURE_MARKER) {
|
|
662
|
+
val gesture = R1Gesture.parse(data)
|
|
663
|
+
if (gesture != null) {
|
|
664
|
+
Bridge.log("R1: Gesture: ${gesture.rawValue}")
|
|
665
|
+
Bridge.sendTouchEvent(ControllerTypes.R1, gesture.rawValue, timestamp)
|
|
666
|
+
} else {
|
|
667
|
+
Bridge.log(
|
|
668
|
+
"R1: Unknown gesture type=0x%02X param=0x%02X".format(data[1], data[2])
|
|
669
|
+
)
|
|
670
|
+
}
|
|
671
|
+
return
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// Battery: 2 bytes, first byte is percentage
|
|
675
|
+
if (data.size == 2 && (data[0].toInt() and 0xFF) <= 100) {
|
|
676
|
+
_batteryLevel = data[0].toInt() and 0xFF
|
|
677
|
+
Bridge.log("R1: Battery: ${_batteryLevel}%")
|
|
678
|
+
return
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// State: single byte (0x01=ready, 0x00=menu)
|
|
682
|
+
if (data.size == 1) {
|
|
683
|
+
val state = when (data[0]) {
|
|
684
|
+
0x01.toByte() -> "ready"
|
|
685
|
+
0x00.toByte() -> "menu"
|
|
686
|
+
else -> "unknown(${data[0].toInt() and 0xFF})"
|
|
687
|
+
}
|
|
688
|
+
Bridge.log("R1: State: $state")
|
|
689
|
+
return
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// Longer data: check for embedded gesture marker
|
|
693
|
+
if (data.size > 3) {
|
|
694
|
+
val ffIndex = data.indexOfFirst { it == R1BLE.GESTURE_MARKER }
|
|
695
|
+
if (ffIndex >= 0 && ffIndex + 2 < data.size) {
|
|
696
|
+
val slice = byteArrayOf(data[ffIndex], data[ffIndex + 1], data[ffIndex + 2])
|
|
697
|
+
val gesture = R1Gesture.parse(slice)
|
|
698
|
+
if (gesture != null) {
|
|
699
|
+
Bridge.log("R1: Embedded gesture: ${gesture.rawValue}")
|
|
700
|
+
Bridge.sendTouchEvent(ControllerTypes.R1, gesture.rawValue, timestamp)
|
|
701
|
+
return
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// MARK: - Connection State Reset
|
|
708
|
+
|
|
709
|
+
private fun resetConnectionState() {
|
|
710
|
+
ringGatt = null
|
|
711
|
+
writeChar1 = null
|
|
712
|
+
notifyChar1 = null
|
|
713
|
+
writeChar2 = null
|
|
714
|
+
notifyChar2 = null
|
|
715
|
+
batteryLevelChar = null
|
|
716
|
+
notifySubscriptionCount = 0
|
|
717
|
+
initSequenceRun = false
|
|
718
|
+
pendingDescriptorWrites.clear()
|
|
719
|
+
pendingReads.clear()
|
|
720
|
+
descriptorWriteInFlight = false
|
|
721
|
+
readInFlight = false
|
|
722
|
+
// ringMacAddress/ringBleAddress persist across disconnects so
|
|
723
|
+
// reconnection can find the ring again; only forget() clears them.
|
|
724
|
+
DeviceStore.apply("glasses", "controllerConnected", false)
|
|
725
|
+
DeviceStore.apply("glasses", "controllerFullyBooted", false)
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// MARK: - GATT Callback
|
|
729
|
+
|
|
730
|
+
private val gattCallback = object : BluetoothGattCallback() {
|
|
731
|
+
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
|
|
732
|
+
when (newState) {
|
|
733
|
+
BluetoothGatt.STATE_CONNECTED -> {
|
|
734
|
+
val name = try { gatt.device.name } catch (e: SecurityException) { null }
|
|
735
|
+
Bridge.log("R1: Connected to ${name ?: "ring"}")
|
|
736
|
+
|
|
737
|
+
// Validate against the name->MAC map populated during scan. If the connected
|
|
738
|
+
// peripheral isn't in the map we don't have its true public MAC — drop the
|
|
739
|
+
// connection and rescan rather than publishing a stale/wrong MAC. Mirrors
|
|
740
|
+
// iOS R1.swift didConnect.
|
|
741
|
+
val mappedMac = name?.let { getRingMacFromMap(it) }
|
|
742
|
+
if (mappedMac == null) {
|
|
743
|
+
Bridge.log("R1: No MAC stored in map found for ${name ?: "ring"}")
|
|
744
|
+
mainHandler.post {
|
|
745
|
+
disconnect()
|
|
746
|
+
ringBleAddress = null
|
|
747
|
+
DeviceStore.apply("glasses", "controllerConnected", false)
|
|
748
|
+
DeviceStore.apply("glasses", "controllerFullyBooted", false)
|
|
749
|
+
DeviceStore.apply("glasses", "controllerSearching", true)
|
|
750
|
+
mainHandler.postDelayed({ startScan() }, 1000)
|
|
751
|
+
}
|
|
752
|
+
return
|
|
753
|
+
}
|
|
754
|
+
ringMacAddress = mappedMac
|
|
755
|
+
|
|
756
|
+
try {
|
|
757
|
+
gatt.discoverServices()
|
|
758
|
+
} catch (e: SecurityException) {
|
|
759
|
+
Bridge.log("R1: discoverServices SecurityException: ${e.message}")
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
BluetoothGatt.STATE_DISCONNECTED -> {
|
|
763
|
+
Bridge.log("R1: Disconnected (status=$status)")
|
|
764
|
+
mainHandler.post {
|
|
765
|
+
stopHeartbeat()
|
|
766
|
+
try { gatt.close() } catch (e: SecurityException) {}
|
|
767
|
+
if (isDisconnecting) return@post
|
|
768
|
+
resetConnectionState()
|
|
769
|
+
startReconnectionTimer()
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
|
|
776
|
+
if (status != BluetoothGatt.GATT_SUCCESS) {
|
|
777
|
+
Bridge.log("R1: onServicesDiscovered status=$status")
|
|
778
|
+
return
|
|
779
|
+
}
|
|
780
|
+
mainHandler.post {
|
|
781
|
+
val services = gatt.services ?: return@post
|
|
782
|
+
for (service in services) {
|
|
783
|
+
for (char in service.characteristics) {
|
|
784
|
+
Bridge.log("R1: char discovered: ${char.uuid}")
|
|
785
|
+
val props = char.properties
|
|
786
|
+
val propStr = buildList {
|
|
787
|
+
if (props and BluetoothGattCharacteristic.PROPERTY_READ != 0) add("read")
|
|
788
|
+
if (props and BluetoothGattCharacteristic.PROPERTY_WRITE != 0) add("write")
|
|
789
|
+
if (props and BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE != 0) add("writeNoResp")
|
|
790
|
+
if (props and BluetoothGattCharacteristic.PROPERTY_NOTIFY != 0) add("notify")
|
|
791
|
+
if (props and BluetoothGattCharacteristic.PROPERTY_INDICATE != 0) add("indicate")
|
|
792
|
+
}.joinToString(",")
|
|
793
|
+
Bridge.log("R1: char ${char.uuid} props=[$propStr]")
|
|
794
|
+
|
|
795
|
+
// Store known characteristics
|
|
796
|
+
when (char.uuid) {
|
|
797
|
+
R1BLE.WRITE_CHAR_1 -> writeChar1 = char
|
|
798
|
+
R1BLE.NOTIFY_CHAR_1 -> notifyChar1 = char
|
|
799
|
+
R1BLE.WRITE_CHAR_2 -> writeChar2 = char
|
|
800
|
+
R1BLE.NOTIFY_CHAR_2 -> notifyChar2 = char
|
|
801
|
+
R1BLE.BATTERY_LEVEL_CHAR -> batteryLevelChar = char
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// Subscribe to any notify/indicate characteristic
|
|
805
|
+
val notifyEligible = props and BluetoothGattCharacteristic.PROPERTY_NOTIFY != 0
|
|
806
|
+
val indicateEligible = props and BluetoothGattCharacteristic.PROPERTY_INDICATE != 0
|
|
807
|
+
if (notifyEligible || indicateEligible) {
|
|
808
|
+
try {
|
|
809
|
+
gatt.setCharacteristicNotification(char, true)
|
|
810
|
+
} catch (e: SecurityException) {
|
|
811
|
+
Bridge.log("R1: setCharacteristicNotification SecurityException: ${e.message}")
|
|
812
|
+
}
|
|
813
|
+
val descriptor = char.getDescriptor(R1BLE.CCCD_UUID)
|
|
814
|
+
if (descriptor != null) {
|
|
815
|
+
descriptor.value = if (notifyEligible) {
|
|
816
|
+
BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
|
|
817
|
+
} else {
|
|
818
|
+
BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
|
|
819
|
+
}
|
|
820
|
+
pendingDescriptorWrites.addLast(descriptor)
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// Queue reads (run after CCCD queue drains)
|
|
825
|
+
if (props and BluetoothGattCharacteristic.PROPERTY_READ != 0) {
|
|
826
|
+
pendingReads.addLast(char)
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
drainDescriptorQueue(gatt)
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
override fun onDescriptorWrite(
|
|
835
|
+
gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int
|
|
836
|
+
) {
|
|
837
|
+
mainHandler.post {
|
|
838
|
+
descriptorWriteInFlight = false
|
|
839
|
+
if (status != BluetoothGatt.GATT_SUCCESS) {
|
|
840
|
+
Bridge.log("R1: Descriptor write failed: $status on ${descriptor.characteristic.uuid}")
|
|
841
|
+
} else {
|
|
842
|
+
Bridge.log("R1: Notify enabled on ${descriptor.characteristic.uuid}")
|
|
843
|
+
notifySubscriptionCount += 1
|
|
844
|
+
if (notifySubscriptionCount >= 2 && !initSequenceRun) {
|
|
845
|
+
mainHandler.postDelayed({ runInitSequence() }, 300)
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
drainDescriptorQueue(gatt)
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
override fun onCharacteristicChanged(
|
|
853
|
+
gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic
|
|
854
|
+
) {
|
|
855
|
+
val data = characteristic.value ?: return
|
|
856
|
+
if (data.isEmpty()) return
|
|
857
|
+
mainHandler.post { handleNotification(characteristic, data) }
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
override fun onCharacteristicRead(
|
|
861
|
+
gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int
|
|
862
|
+
) {
|
|
863
|
+
val data = characteristic.value
|
|
864
|
+
mainHandler.post {
|
|
865
|
+
readInFlight = false
|
|
866
|
+
if (status == BluetoothGatt.GATT_SUCCESS && data != null && data.isNotEmpty()) {
|
|
867
|
+
if (characteristic.uuid == R1BLE.BATTERY_LEVEL_CHAR) {
|
|
868
|
+
_batteryLevel = data[0].toInt() and 0xFF
|
|
869
|
+
Bridge.log("R1: Battery (std): ${_batteryLevel}%")
|
|
870
|
+
} else {
|
|
871
|
+
handleNotification(characteristic, data)
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
drainReadQueue(gatt)
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
override fun onCharacteristicWrite(
|
|
879
|
+
gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int
|
|
880
|
+
) {
|
|
881
|
+
if (status != BluetoothGatt.GATT_SUCCESS) {
|
|
882
|
+
Bridge.log("R1: Write error on ${characteristic.uuid}: $status")
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
private fun drainDescriptorQueue(gatt: BluetoothGatt) {
|
|
888
|
+
if (descriptorWriteInFlight) return
|
|
889
|
+
val next = pendingDescriptorWrites.removeFirstOrNull()
|
|
890
|
+
if (next == null) {
|
|
891
|
+
// Once CCCD queue drained, start reads
|
|
892
|
+
drainReadQueue(gatt)
|
|
893
|
+
return
|
|
894
|
+
}
|
|
895
|
+
descriptorWriteInFlight = true
|
|
896
|
+
try {
|
|
897
|
+
gatt.writeDescriptor(next)
|
|
898
|
+
} catch (e: SecurityException) {
|
|
899
|
+
descriptorWriteInFlight = false
|
|
900
|
+
Bridge.log("R1: writeDescriptor SecurityException: ${e.message}")
|
|
901
|
+
drainDescriptorQueue(gatt)
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
private fun drainReadQueue(gatt: BluetoothGatt) {
|
|
906
|
+
if (readInFlight) return
|
|
907
|
+
val next = pendingReads.removeFirstOrNull() ?: return
|
|
908
|
+
readInFlight = true
|
|
909
|
+
try {
|
|
910
|
+
gatt.readCharacteristic(next)
|
|
911
|
+
} catch (e: SecurityException) {
|
|
912
|
+
readInFlight = false
|
|
913
|
+
Bridge.log("R1: readCharacteristic SecurityException: ${e.message}")
|
|
914
|
+
drainReadQueue(gatt)
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// MARK: - ControllerManager overrides
|
|
919
|
+
|
|
920
|
+
override fun findCompatibleDevices() {
|
|
921
|
+
Bridge.log("R1: findCompatibleDevices()")
|
|
922
|
+
deviceSearchId = "NOT_SET"
|
|
923
|
+
startScan()
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
override fun connectById(id: String) {
|
|
927
|
+
Bridge.log("R1: connectById($id)")
|
|
928
|
+
deviceSearchId = id
|
|
929
|
+
startScan()
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
override fun disconnect() {
|
|
933
|
+
Bridge.log("R1: disconnect()")
|
|
934
|
+
isDisconnecting = true
|
|
935
|
+
stopHeartbeat()
|
|
936
|
+
reconnectionManager.stop()
|
|
937
|
+
try {
|
|
938
|
+
ringGatt?.disconnect()
|
|
939
|
+
ringGatt?.close()
|
|
940
|
+
} catch (e: SecurityException) {
|
|
941
|
+
Bridge.log("R1: disconnect SecurityException: ${e.message}")
|
|
942
|
+
}
|
|
943
|
+
resetConnectionState()
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
override fun forget() {
|
|
947
|
+
disconnect()
|
|
948
|
+
ringMacAddress = null
|
|
949
|
+
ringBleAddress = null
|
|
950
|
+
prefs.edit().remove("r1_ringMacAddressMap").apply()
|
|
951
|
+
deviceSearchId = "NOT_SET"
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
override fun cleanup() {
|
|
955
|
+
disconnect()
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
override fun getConnectedBluetoothName(): String? {
|
|
959
|
+
return try { ringGatt?.device?.name } catch (e: SecurityException) { null }
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
override fun ping() {
|
|
963
|
+
val char = batteryLevelChar ?: return
|
|
964
|
+
try {
|
|
965
|
+
ringGatt?.readCharacteristic(char)
|
|
966
|
+
} catch (e: SecurityException) {
|
|
967
|
+
Bridge.log("R1: ping SecurityException: ${e.message}")
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
override fun getBatteryStatus() {
|
|
972
|
+
val char = batteryLevelChar ?: return
|
|
973
|
+
try {
|
|
974
|
+
ringGatt?.readCharacteristic(char)
|
|
975
|
+
} catch (e: SecurityException) {
|
|
976
|
+
Bridge.log("R1: getBatteryStatus SecurityException: ${e.message}")
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// MARK: - No-op implementations (ring has no display/camera/wifi/mic)
|
|
981
|
+
|
|
982
|
+
override fun sendIncidentId(incidentId: String) {}
|
|
983
|
+
override fun setMicEnabled(enabled: Boolean) {}
|
|
984
|
+
override fun sortMicRanking(list: MutableList<String>): MutableList<String> = list
|
|
985
|
+
override fun sendJson(jsonOriginal: Map<String, Any>, wakeUp: Boolean, requireAck: Boolean) {}
|
|
986
|
+
override fun requestPhoto(request: PhotoRequest) {}
|
|
987
|
+
override fun startVideoRecording(requestId: String, save: Boolean, sound: Boolean) {}
|
|
988
|
+
override fun stopVideoRecording(requestId: String) {}
|
|
989
|
+
override fun startStream(message: Map<String, Any>) {}
|
|
990
|
+
override fun stopStream() {}
|
|
991
|
+
override fun sendStreamKeepAlive(message: Map<String, Any>) {}
|
|
992
|
+
override fun sendButtonPhotoSettings() {}
|
|
993
|
+
override fun sendButtonVideoRecordingSettings() {}
|
|
994
|
+
override fun sendButtonMaxRecordingTime() {}
|
|
995
|
+
override fun setBrightness(level: Int, autoMode: Boolean) {}
|
|
996
|
+
override fun clearDisplay() {}
|
|
997
|
+
override fun sendTextWall(text: String) {}
|
|
998
|
+
override fun sendDoubleTextWall(top: String, bottom: String) {}
|
|
999
|
+
override fun displayBitmap(
|
|
1000
|
+
base64ImageData: String,
|
|
1001
|
+
x: Int?,
|
|
1002
|
+
y: Int?,
|
|
1003
|
+
width: Int?,
|
|
1004
|
+
height: Int?
|
|
1005
|
+
): Boolean = false
|
|
1006
|
+
override fun showDashboard() {}
|
|
1007
|
+
override fun setDashboardPosition(height: Int, depth: Int) {}
|
|
1008
|
+
override fun setHeadUpAngle(angle: Int) {}
|
|
1009
|
+
override fun setSilentMode(enabled: Boolean) {}
|
|
1010
|
+
override fun exit() {}
|
|
1011
|
+
override fun sendShutdown() { disconnect() }
|
|
1012
|
+
override fun sendReboot() {}
|
|
1013
|
+
override fun sendRgbLedControl(
|
|
1014
|
+
requestId: String, packageName: String?, action: String, color: String?,
|
|
1015
|
+
onDurationMs: Int, offDurationMs: Int, count: Int
|
|
1016
|
+
) {}
|
|
1017
|
+
override fun requestWifiScan() {}
|
|
1018
|
+
override fun sendWifiCredentials(ssid: String, password: String) {}
|
|
1019
|
+
override fun forgetWifiNetwork(ssid: String) {}
|
|
1020
|
+
override fun sendHotspotState(enabled: Boolean) {}
|
|
1021
|
+
override fun sendOtaStart(otaVersionUrl: String?) {}
|
|
1022
|
+
override fun sendUserEmailToGlasses(email: String) {}
|
|
1023
|
+
override fun queryGalleryStatus() {}
|
|
1024
|
+
override fun sendGalleryMode() {}
|
|
1025
|
+
override fun requestVersionInfo() {}
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
// MARK: - R1 Reconnection Manager
|
|
1029
|
+
// Coroutine-based port of Swift's R1ReconnectionManager actor.
|
|
1030
|
+
|
|
1031
|
+
private class R1ReconnectionManager(
|
|
1032
|
+
private val intervalSeconds: Long = 30L,
|
|
1033
|
+
private val maxAttempts: Int = -1
|
|
1034
|
+
) {
|
|
1035
|
+
private var job: Job? = null
|
|
1036
|
+
private var attempts = 0
|
|
1037
|
+
|
|
1038
|
+
fun start(onAttempt: suspend () -> Boolean) {
|
|
1039
|
+
stop()
|
|
1040
|
+
attempts = 0
|
|
1041
|
+
job = CoroutineScope(Dispatchers.Default).launch {
|
|
1042
|
+
while (isActive) {
|
|
1043
|
+
delay(intervalSeconds * 1_000L)
|
|
1044
|
+
if (!isActive) break
|
|
1045
|
+
attempts += 1
|
|
1046
|
+
if (maxAttempts > 0 && attempts > maxAttempts) break
|
|
1047
|
+
if (onAttempt()) break
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
fun stop() {
|
|
1053
|
+
job?.cancel()
|
|
1054
|
+
job = null
|
|
1055
|
+
attempts = 0
|
|
1056
|
+
}
|
|
1057
|
+
}
|