@mentra/bluetooth-sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +278 -0
- package/android/build.gradle +167 -0
- package/android/lc3Lib/CMakeLists.txt +6 -0
- package/android/lc3Lib/build.gradle +109 -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/mentra/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 +1 -0
- package/android/src/main/AndroidManifest.xml +40 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/BluetoothSdkModule.kt +528 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/Bridge.kt +610 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceManager.kt +1616 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/DeviceStore.kt +314 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothModels.kt +1710 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/MentraBluetoothSdk.kt +554 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/ObservableStore.kt +68 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/controllers/ControllerManager.kt +158 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/controllers/R1.kt +934 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/services/Foreground.kt +175 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/services/PhoneMic.kt +1109 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G1.java +3974 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/G2.kt +3551 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Mach1.java +1327 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraLive.java +6959 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraNex.kt +1655 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/MentraosBle.java +53675 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/SGCManager.kt +202 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/sgcs/Simulated.kt +224 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/stt/STTTools.kt +340 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/stt/SherpaOnnxTranscriber.kt +465 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/stt/VadGateSpeechPolicy.kt +221 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/AES.java +106 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/AudioSessionMonitor.kt +189 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/BitmapJavaUtils.java +169 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/BlePhotoUploadService.java +179 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/Constants.kt +44 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/G1Text.kt +436 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/IncidentLogBleRelayNaming.java +29 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/IncidentLogBleUploadService.java +76 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/K900ProtocolUtils.java +791 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/MessageChunker.java +186 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/NexSGCUtils.kt +589 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/PhoneAudioMonitor.kt +303 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/SmartGlassesConnectionState.java +9 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/audio/ByteUtilAudioPlayer.java +655 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/audio/Lc3Player.java +441 -0
- package/android/src/main/java/com/mentra/bluetoothsdk/utils/audio/PCMAudioPlayer.java +431 -0
- package/android/src/main/res/values/strings.xml +4 -0
- package/app.plugin.js +1 -0
- package/build/BluetoothSdk.types.d.ts +498 -0
- package/build/BluetoothSdk.types.d.ts.map +1 -0
- package/build/BluetoothSdk.types.js +23 -0
- package/build/BluetoothSdk.types.js.map +1 -0
- package/build/BluetoothSdkModule.d.ts +71 -0
- package/build/BluetoothSdkModule.d.ts.map +1 -0
- package/build/BluetoothSdkModule.js +106 -0
- package/build/BluetoothSdkModule.js.map +1 -0
- package/build/index.d.ts +3 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +5 -0
- package/build/index.js.map +1 -0
- package/expo-module.config.json +9 -0
- package/ios/BluetoothSdkModule.swift +665 -0
- package/ios/MentraBluetoothSDK.podspec +77 -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/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 +1659 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Headers/cargs.h +162 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Headers/sherpa-onnx/c-api/c-api.h +1852 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Headers/sherpa-onnx/c-api/cxx-api.h +674 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Info.plist +44 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/libsherpa-onnx.a +0 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/sherpa-onnx.a +0 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/libsherpa-onnx.a +0 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/sherpa-onnx.a +0 -0
- package/ios/Packages/VAD/Common/VAD/VADQuality.swift +41 -0
- package/ios/Packages/VAD/Common/VAD/VADState.swift +26 -0
- package/ios/Packages/VAD/Common/VAD/VADStrategy.swift +29 -0
- package/ios/Packages/VAD/Common/VAD/VADType.swift +14 -0
- package/ios/Packages/VAD/Data/Configuration.swift +68 -0
- package/ios/Packages/VAD/Data/FrameSize.swift +39 -0
- package/ios/Packages/VAD/Data/Record.swift +13 -0
- package/ios/Packages/VAD/Data/Result.swift +22 -0
- package/ios/Packages/VAD/Data/SampleRate.swift +48 -0
- package/ios/Packages/VAD/Silero/Model/silero_vad.onnx +0 -0
- package/ios/Packages/VAD/Silero/SileroVAD.swift +284 -0
- package/ios/Packages/VAD/Silero/SileroVADStrategy.swift +64 -0
- package/ios/Packages/libbz2/module.modulemap +5 -0
- package/ios/Packages/libbz2/shim.h +1 -0
- package/ios/Source/Bridge.swift +353 -0
- package/ios/Source/Bridging-Header.h +16 -0
- package/ios/Source/DeviceManager.swift +1478 -0
- package/ios/Source/DeviceStore.swift +295 -0
- package/ios/Source/MentraBluetoothSDK.swift +2439 -0
- package/ios/Source/ObservableStore.swift +88 -0
- package/ios/Source/PrivacyInfo.xcprivacy +23 -0
- package/ios/Source/controllers/ControllerManager.swift +187 -0
- package/ios/Source/controllers/R1.swift +827 -0
- package/ios/Source/services/PhoneMic.swift +662 -0
- package/ios/Source/sgcs/Frame.swift +651 -0
- package/ios/Source/sgcs/G1.swift +2505 -0
- package/ios/Source/sgcs/G2.swift +3730 -0
- package/ios/Source/sgcs/Mach1.swift +543 -0
- package/ios/Source/sgcs/MentraLive.swift +4708 -0
- package/ios/Source/sgcs/MentraNex.swift +2479 -0
- package/ios/Source/sgcs/SGCManager.swift +243 -0
- package/ios/Source/sgcs/Simulated.swift +270 -0
- package/ios/Source/sgcs/mentraos_ble.pb.swift +3811 -0
- package/ios/Source/stt/STTTools.swift +154 -0
- package/ios/Source/stt/SherpaOnnxTranscriber.swift +401 -0
- package/ios/Source/utils/AudioSessionMonitor.swift +283 -0
- package/ios/Source/utils/Constants.swift +74 -0
- package/ios/Source/utils/Enums.swift +95 -0
- package/ios/Source/utils/G1Text.swift +2067 -0
- package/ios/Source/utils/JSCExperiment.swift +241 -0
- package/ios/Source/utils/MemoryMonitor.swift +44 -0
- package/ios/Source/utils/MessageChunker.swift +164 -0
- package/ios/Source/utils/Models.swift +135 -0
- package/ios/Source/utils/PhoneAudioMonitor.swift +226 -0
- package/ios/Source/utils/TarBz2Extractor.swift +206 -0
- package/package.json +78 -0
- package/plugin/build/index.d.ts +6 -0
- package/plugin/build/index.js +12 -0
- package/plugin/build/withAndroid.d.ts +4 -0
- package/plugin/build/withAndroid.js +80 -0
- package/plugin/build/withIos.d.ts +4 -0
- package/plugin/build/withIos.js +64 -0
- package/src/BluetoothSdk.types.ts +581 -0
- package/src/BluetoothSdkModule.ts +259 -0
- package/src/index.ts +4 -0
|
@@ -0,0 +1,934 @@
|
|
|
1
|
+
package com.mentra.bluetoothsdk.controllers
|
|
2
|
+
|
|
3
|
+
import android.Manifest
|
|
4
|
+
import android.bluetooth.BluetoothAdapter
|
|
5
|
+
import android.bluetooth.BluetoothDevice
|
|
6
|
+
import android.bluetooth.BluetoothGatt
|
|
7
|
+
import android.bluetooth.BluetoothGattCallback
|
|
8
|
+
import android.bluetooth.BluetoothGattCharacteristic
|
|
9
|
+
import android.bluetooth.BluetoothGattDescriptor
|
|
10
|
+
import android.bluetooth.le.BluetoothLeScanner
|
|
11
|
+
import android.bluetooth.le.ScanCallback
|
|
12
|
+
import android.bluetooth.le.ScanResult
|
|
13
|
+
import android.bluetooth.le.ScanSettings
|
|
14
|
+
import android.content.Context
|
|
15
|
+
import android.content.pm.PackageManager
|
|
16
|
+
import android.os.Build
|
|
17
|
+
import android.os.Handler
|
|
18
|
+
import android.os.Looper
|
|
19
|
+
import androidx.core.content.ContextCompat
|
|
20
|
+
import com.mentra.bluetoothsdk.Bridge
|
|
21
|
+
import com.mentra.bluetoothsdk.DeviceManager
|
|
22
|
+
import com.mentra.bluetoothsdk.DeviceStore
|
|
23
|
+
import com.mentra.bluetoothsdk.utils.ControllerTypes
|
|
24
|
+
import java.util.UUID
|
|
25
|
+
import kotlinx.coroutines.CoroutineScope
|
|
26
|
+
import kotlinx.coroutines.Dispatchers
|
|
27
|
+
import kotlinx.coroutines.Job
|
|
28
|
+
import kotlinx.coroutines.delay
|
|
29
|
+
import kotlinx.coroutines.isActive
|
|
30
|
+
import kotlinx.coroutines.launch
|
|
31
|
+
|
|
32
|
+
// MARK: - R1 BLE Constants
|
|
33
|
+
|
|
34
|
+
private object R1BLE {
|
|
35
|
+
val SERVICE_UUID: UUID = UUID.fromString("BAE80001-4F05-4503-8E65-3AF1F7329D1F")
|
|
36
|
+
val WRITE_CHAR_1: UUID = UUID.fromString("BAE80010-4F05-4503-8E65-3AF1F7329D1F")
|
|
37
|
+
val NOTIFY_CHAR_1: UUID = UUID.fromString("BAE80011-4F05-4503-8E65-3AF1F7329D1F")
|
|
38
|
+
val WRITE_CHAR_2: UUID = UUID.fromString("BAE80012-4F05-4503-8E65-3AF1F7329D1F")
|
|
39
|
+
val NOTIFY_CHAR_2: UUID = UUID.fromString("BAE80013-4F05-4503-8E65-3AF1F7329D1F")
|
|
40
|
+
val BATTERY_SERVICE: UUID = UUID.fromString("0000180f-0000-1000-8000-00805f9b34fb")
|
|
41
|
+
val BATTERY_LEVEL_CHAR: UUID = UUID.fromString("00002a19-0000-1000-8000-00805f9b34fb")
|
|
42
|
+
val CCCD_UUID: UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
|
|
43
|
+
|
|
44
|
+
val NAME_FILTERS = arrayOf("EVEN R1", "BCL60")
|
|
45
|
+
|
|
46
|
+
val CONFIG_FC = byteArrayOf(0xFC.toByte())
|
|
47
|
+
val CONFIG_11 = byteArrayOf(0x11.toByte())
|
|
48
|
+
|
|
49
|
+
// BleRing1 command header (cmd, module, subCmd) for advStart.
|
|
50
|
+
// From RE: BleRing1Cmd_system=0, BleRing1Module_system=0, BleRing1SubCmd_advStart=9
|
|
51
|
+
const val CMD_SYSTEM: Byte = 0x00
|
|
52
|
+
const val MODULE_SYSTEM: Byte = 0x00
|
|
53
|
+
const val SUBCMD_ADV_START: Byte = 0x09
|
|
54
|
+
|
|
55
|
+
const val GESTURE_MARKER: Byte = 0xFF.toByte()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// MARK: - R1 Gesture Types
|
|
59
|
+
|
|
60
|
+
private enum class R1Gesture(val rawValue: String) {
|
|
61
|
+
HOLD("hold"),
|
|
62
|
+
SINGLE_TAP("single_tap"),
|
|
63
|
+
DOUBLE_TAP("double_tap"),
|
|
64
|
+
SWIPE_UP("swipe_up"),
|
|
65
|
+
SWIPE_DOWN("swipe_down");
|
|
66
|
+
|
|
67
|
+
companion object {
|
|
68
|
+
/** Parse gesture from notification data: [0xFF, type, param] */
|
|
69
|
+
fun parse(data: ByteArray): R1Gesture? {
|
|
70
|
+
if (data.size < 3 || data[0] != R1BLE.GESTURE_MARKER) return null
|
|
71
|
+
return when (data[1]) {
|
|
72
|
+
0x03.toByte() -> HOLD
|
|
73
|
+
0x04.toByte() -> when (data[2]) {
|
|
74
|
+
0x01.toByte() -> SINGLE_TAP
|
|
75
|
+
0x02.toByte() -> DOUBLE_TAP
|
|
76
|
+
else -> null
|
|
77
|
+
}
|
|
78
|
+
0x05.toByte() -> if ((data[2].toInt() and 0xFF) < 0x80) SWIPE_UP else SWIPE_DOWN
|
|
79
|
+
else -> null
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// MARK: - R1 Controller
|
|
86
|
+
|
|
87
|
+
class R1 : ControllerManager() {
|
|
88
|
+
|
|
89
|
+
// Connection state
|
|
90
|
+
private val appContext: Context = Bridge.getContext()
|
|
91
|
+
private val prefs = appContext.getSharedPreferences("r1_prefs", Context.MODE_PRIVATE)
|
|
92
|
+
|
|
93
|
+
private var bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter()
|
|
94
|
+
private var scanner: BluetoothLeScanner? = null
|
|
95
|
+
private var scanCallback: ScanCallback? = null
|
|
96
|
+
|
|
97
|
+
private var ringGatt: BluetoothGatt? = null
|
|
98
|
+
private var isDisconnecting = false
|
|
99
|
+
|
|
100
|
+
// BLE characteristics
|
|
101
|
+
private var writeChar1: BluetoothGattCharacteristic? = null
|
|
102
|
+
private var notifyChar1: BluetoothGattCharacteristic? = null
|
|
103
|
+
private var writeChar2: BluetoothGattCharacteristic? = null
|
|
104
|
+
private var notifyChar2: BluetoothGattCharacteristic? = null
|
|
105
|
+
private var batteryLevelChar: BluetoothGattCharacteristic? = null
|
|
106
|
+
private var notifySubscriptionCount = 0
|
|
107
|
+
private var initSequenceRun = false
|
|
108
|
+
|
|
109
|
+
// Android serializes CCCD writes and reads — queue and drain in callbacks
|
|
110
|
+
private val pendingDescriptorWrites = ArrayDeque<BluetoothGattDescriptor>()
|
|
111
|
+
private val pendingReads = ArrayDeque<BluetoothGattCharacteristic>()
|
|
112
|
+
private var descriptorWriteInFlight = false
|
|
113
|
+
private var readInFlight = false
|
|
114
|
+
|
|
115
|
+
// Device search
|
|
116
|
+
private var deviceSearchId: String = "NOT_SET"
|
|
117
|
+
|
|
118
|
+
// BLE handle used by Android for reconnection (BluetoothDevice.address — may be a
|
|
119
|
+
// resolvable/random address depending on bonding state; not the ring's public MAC).
|
|
120
|
+
private var ringBleAddress: String?
|
|
121
|
+
get() = prefs.getString("r1_ringBleAddress", null)
|
|
122
|
+
set(value) {
|
|
123
|
+
prefs.edit().apply {
|
|
124
|
+
if (value == null) remove("r1_ringBleAddress") else putString("r1_ringBleAddress", value)
|
|
125
|
+
apply()
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Public ring MAC parsed from advertisement manufacturer data (last 6 bytes), formatted
|
|
130
|
+
// as "AA:BB:CC:DD:EE:FF". This is what gets published to DeviceStore/controllerMacAddress.
|
|
131
|
+
private var ringMacAddress: String?
|
|
132
|
+
get() = prefs.getString("r1_ringMacAddress", null)
|
|
133
|
+
set(value) {
|
|
134
|
+
prefs.edit().apply {
|
|
135
|
+
if (value == null) remove("r1_ringMacAddress") else putString("r1_ringMacAddress", value)
|
|
136
|
+
apply()
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// peripheral name -> 6-byte ring MAC (hex string), populated from mfgData on every scan.
|
|
141
|
+
// Mirrors iOS ringMacAddressMap so reconnects re-validate freshness instead of trusting
|
|
142
|
+
// a stale stored MAC.
|
|
143
|
+
private fun loadRingMacAddressMap(): MutableMap<String, String> {
|
|
144
|
+
val raw = prefs.getString("r1_ringMacAddressMap", null) ?: return mutableMapOf()
|
|
145
|
+
val out = mutableMapOf<String, String>()
|
|
146
|
+
for (entry in raw.split(';')) {
|
|
147
|
+
if (entry.isEmpty()) continue
|
|
148
|
+
val idx = entry.indexOf('=')
|
|
149
|
+
if (idx <= 0) continue
|
|
150
|
+
out[entry.substring(0, idx)] = entry.substring(idx + 1)
|
|
151
|
+
}
|
|
152
|
+
return out
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private fun saveRingMacAddressMap(map: Map<String, String>) {
|
|
156
|
+
val raw = map.entries.joinToString(";") { "${it.key}=${it.value}" }
|
|
157
|
+
prefs.edit().putString("r1_ringMacAddressMap", raw).apply()
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private fun putRingMacInMap(name: String, mac: String) {
|
|
161
|
+
val map = loadRingMacAddressMap()
|
|
162
|
+
map[name] = mac
|
|
163
|
+
saveRingMacAddressMap(map)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private fun getRingMacFromMap(name: String): String? = loadRingMacAddressMap()[name]
|
|
167
|
+
|
|
168
|
+
// Reconnection (defined but currently unwired — matches iOS which leaves it commented)
|
|
169
|
+
private val reconnectionManager = R1ReconnectionManager()
|
|
170
|
+
|
|
171
|
+
// Battery
|
|
172
|
+
private var _batteryLevel: Int = -1
|
|
173
|
+
set(value) {
|
|
174
|
+
val old = field
|
|
175
|
+
field = value
|
|
176
|
+
if (value != old && value >= 0) {
|
|
177
|
+
DeviceStore.apply("glasses", "controllerBatteryLevel", value)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Heartbeat
|
|
182
|
+
private val mainHandler = Handler(Looper.getMainLooper())
|
|
183
|
+
private var heartbeatRunnable: Runnable? = null
|
|
184
|
+
|
|
185
|
+
init {
|
|
186
|
+
type = ControllerTypes.R1
|
|
187
|
+
hasMic = false
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// MARK: - Permissions
|
|
191
|
+
|
|
192
|
+
private fun hasScanPermission(): Boolean {
|
|
193
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return true
|
|
194
|
+
return ContextCompat.checkSelfPermission(
|
|
195
|
+
appContext, Manifest.permission.BLUETOOTH_SCAN
|
|
196
|
+
) == PackageManager.PERMISSION_GRANTED
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private fun hasConnectPermission(): Boolean {
|
|
200
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return true
|
|
201
|
+
return ContextCompat.checkSelfPermission(
|
|
202
|
+
appContext, Manifest.permission.BLUETOOTH_CONNECT
|
|
203
|
+
) == PackageManager.PERMISSION_GRANTED
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// MARK: - BLE Scanning
|
|
207
|
+
|
|
208
|
+
private fun startScan(): Boolean {
|
|
209
|
+
Bridge.log("R1: startScan()")
|
|
210
|
+
val adapter = bluetoothAdapter ?: BluetoothAdapter.getDefaultAdapter().also { bluetoothAdapter = it }
|
|
211
|
+
if (adapter == null) {
|
|
212
|
+
Bridge.log("R1: No Bluetooth adapter")
|
|
213
|
+
return false
|
|
214
|
+
}
|
|
215
|
+
if (!adapter.isEnabled) {
|
|
216
|
+
Bridge.log("R1: Bluetooth not powered on")
|
|
217
|
+
return false
|
|
218
|
+
}
|
|
219
|
+
if (!hasScanPermission() || !hasConnectPermission()) {
|
|
220
|
+
Bridge.log("R1: Missing Bluetooth permissions")
|
|
221
|
+
return false
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Already connected — don't start a new scan
|
|
225
|
+
if (ringGatt != null) {
|
|
226
|
+
Bridge.log("R1: Already connected, skipping scan")
|
|
227
|
+
return true
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
isDisconnecting = false
|
|
231
|
+
|
|
232
|
+
// Stop any prior scan before starting a new one (avoids leaking ScanCallback)
|
|
233
|
+
stopScan()
|
|
234
|
+
|
|
235
|
+
// Try address-based reconnection first
|
|
236
|
+
if (connectByBleAddress()) {
|
|
237
|
+
return true
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
val s = adapter.bluetoothLeScanner
|
|
241
|
+
if (s == null) {
|
|
242
|
+
Bridge.log("R1: BluetoothLeScanner not available")
|
|
243
|
+
return false
|
|
244
|
+
}
|
|
245
|
+
scanner = s
|
|
246
|
+
|
|
247
|
+
val cb = object : ScanCallback() {
|
|
248
|
+
override fun onScanResult(callbackType: Int, result: ScanResult?) {
|
|
249
|
+
if (result == null) return
|
|
250
|
+
handleScanResult(result)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
override fun onScanFailed(errorCode: Int) {
|
|
254
|
+
Bridge.log("R1: Scan failed: $errorCode")
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
scanCallback = cb
|
|
258
|
+
val settings = ScanSettings.Builder()
|
|
259
|
+
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
|
|
260
|
+
.build()
|
|
261
|
+
try {
|
|
262
|
+
s.startScan(null, settings, cb)
|
|
263
|
+
} catch (e: SecurityException) {
|
|
264
|
+
Bridge.log("R1: startScan SecurityException: ${e.message}")
|
|
265
|
+
return false
|
|
266
|
+
}
|
|
267
|
+
return true
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
override fun stopScan() {
|
|
271
|
+
val s = scanner ?: return
|
|
272
|
+
val cb = scanCallback ?: return
|
|
273
|
+
try {
|
|
274
|
+
s.stopScan(cb)
|
|
275
|
+
} catch (e: SecurityException) {
|
|
276
|
+
Bridge.log("R1: stopScan SecurityException: ${e.message}")
|
|
277
|
+
}
|
|
278
|
+
scanCallback = null
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
private fun connectByBleAddress(): Boolean {
|
|
282
|
+
if (deviceSearchId == "NOT_SET" || deviceSearchId.isEmpty()) {
|
|
283
|
+
Bridge.log("R1: No deviceSearchId set, skipping connect by address")
|
|
284
|
+
return false
|
|
285
|
+
}
|
|
286
|
+
val address = ringBleAddress ?: return false
|
|
287
|
+
val adapter = bluetoothAdapter ?: return false
|
|
288
|
+
if (ringGatt != null) {
|
|
289
|
+
Bridge.log("R1: connectByBleAddress skipped — already connected")
|
|
290
|
+
return true
|
|
291
|
+
}
|
|
292
|
+
val device = try {
|
|
293
|
+
adapter.getRemoteDevice(address)
|
|
294
|
+
} catch (e: IllegalArgumentException) {
|
|
295
|
+
Bridge.log("R1: Invalid stored BLE address: $address")
|
|
296
|
+
return false
|
|
297
|
+
}
|
|
298
|
+
try {
|
|
299
|
+
ringGatt = device.connectGatt(appContext, false, gattCallback, BluetoothDevice.TRANSPORT_LE)
|
|
300
|
+
Bridge.log("R1: Reconnecting by address to ${device.name ?: address}")
|
|
301
|
+
} catch (e: SecurityException) {
|
|
302
|
+
Bridge.log("R1: connectGatt SecurityException: ${e.message}")
|
|
303
|
+
return false
|
|
304
|
+
}
|
|
305
|
+
return true
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
private fun matchesNameFilter(name: String?): Boolean {
|
|
309
|
+
if (name == null) return false
|
|
310
|
+
return R1BLE.NAME_FILTERS.any { name.contains(it) }
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** Extract a device identifier from the ring name (e.g. "EVEN R1_CEC5BA" -> "CEC5BA") */
|
|
314
|
+
private fun extractRingId(name: String): String? {
|
|
315
|
+
val idx = name.indexOf("R1_")
|
|
316
|
+
if (idx < 0) return null
|
|
317
|
+
val id = name.substring(idx + 3)
|
|
318
|
+
return if (id.isEmpty()) null else id
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
private fun handleScanResult(result: ScanResult) {
|
|
322
|
+
val device = result.device ?: return
|
|
323
|
+
val advertisedName = result.scanRecord?.deviceName
|
|
324
|
+
val deviceName: String? = try {
|
|
325
|
+
device.name ?: advertisedName
|
|
326
|
+
} catch (e: SecurityException) {
|
|
327
|
+
advertisedName
|
|
328
|
+
}
|
|
329
|
+
mainHandler.post {
|
|
330
|
+
// Already connected — ignore further scan results
|
|
331
|
+
if (ringGatt != null) {
|
|
332
|
+
stopScan()
|
|
333
|
+
return@post
|
|
334
|
+
}
|
|
335
|
+
if (!matchesNameFilter(deviceName)) return@post
|
|
336
|
+
|
|
337
|
+
val mfgMap = result.scanRecord?.manufacturerSpecificData
|
|
338
|
+
val mfgBytes: ByteArray? = if (mfgMap != null && mfgMap.size() > 0) mfgMap.valueAt(0) else null
|
|
339
|
+
val mfgHex = mfgBytes?.joinToString(" ") { String.format("%02X", it) } ?: "none"
|
|
340
|
+
Bridge.log("R1: Discovered: ${deviceName ?: "?"} (RSSI: ${result.rssi}) mfgData: $mfgHex")
|
|
341
|
+
|
|
342
|
+
// Extract ring MAC from manufacturer data (last 6 bytes) and store name->MAC map.
|
|
343
|
+
// Android's manufacturerSpecificData strips the 2-byte company ID; iOS keeps it. The
|
|
344
|
+
// resulting "last 6 bytes" land in reversed byte order between platforms (verified in
|
|
345
|
+
// the field), so reverse here to match iOS's stored string ("1B:08:26:8E:0E:E6").
|
|
346
|
+
if (deviceName != null && mfgBytes != null && mfgBytes.size >= 6) {
|
|
347
|
+
val tail = mfgBytes.copyOfRange(mfgBytes.size - 6, mfgBytes.size)
|
|
348
|
+
val macStr = tail.joinToString(":") { String.format("%02X", it) }
|
|
349
|
+
putRingMacInMap(deviceName, macStr)
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Emit discovered device
|
|
353
|
+
val id = deviceName?.let { extractRingId(it) }
|
|
354
|
+
if (id != null) {
|
|
355
|
+
Bridge.sendDiscoveredDevice(ControllerTypes.R1, id)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Scan-only mode: don't auto-connect
|
|
359
|
+
if (deviceSearchId == "NOT_SET") return@post
|
|
360
|
+
|
|
361
|
+
// If search ID is specific, check it matches
|
|
362
|
+
val name = deviceName ?: return@post
|
|
363
|
+
val parsedId = extractRingId(name)
|
|
364
|
+
if (parsedId != deviceSearchId && !name.contains(deviceSearchId)) return@post
|
|
365
|
+
|
|
366
|
+
if (ringGatt == null) {
|
|
367
|
+
ringBleAddress = device.address
|
|
368
|
+
try {
|
|
369
|
+
ringGatt = device.connectGatt(appContext, false, gattCallback, BluetoothDevice.TRANSPORT_LE)
|
|
370
|
+
} catch (e: SecurityException) {
|
|
371
|
+
Bridge.log("R1: connectGatt SecurityException: ${e.message}")
|
|
372
|
+
return@post
|
|
373
|
+
}
|
|
374
|
+
stopScan()
|
|
375
|
+
Bridge.log("R1: Connecting to ${name}")
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// MARK: - Init Sequence
|
|
381
|
+
|
|
382
|
+
private fun runInitSequence() {
|
|
383
|
+
if (initSequenceRun) return
|
|
384
|
+
initSequenceRun = true
|
|
385
|
+
|
|
386
|
+
Bridge.log("R1: Running init sequence")
|
|
387
|
+
|
|
388
|
+
val writeChars = listOfNotNull(writeChar1, writeChar2)
|
|
389
|
+
if (writeChars.isEmpty()) {
|
|
390
|
+
Bridge.log("R1: No write characteristics found, skipping init")
|
|
391
|
+
markConnected()
|
|
392
|
+
return
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
writeChars.forEach { writeNoResponse(it, R1BLE.CONFIG_FC) }
|
|
396
|
+
|
|
397
|
+
mainHandler.postDelayed({
|
|
398
|
+
writeChars.forEach { writeNoResponse(it, R1BLE.CONFIG_11) }
|
|
399
|
+
markConnected()
|
|
400
|
+
}, 200)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
private fun writeNoResponse(char: BluetoothGattCharacteristic, data: ByteArray) {
|
|
404
|
+
char.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
|
|
405
|
+
char.value = data
|
|
406
|
+
try {
|
|
407
|
+
ringGatt?.writeCharacteristic(char)
|
|
408
|
+
} catch (e: SecurityException) {
|
|
409
|
+
Bridge.log("R1: writeCharacteristic SecurityException: ${e.message}")
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
private fun markConnected() {
|
|
414
|
+
reconnectionManager.stop()
|
|
415
|
+
Bridge.log("R1: Ring connected")
|
|
416
|
+
|
|
417
|
+
val gatt = ringGatt
|
|
418
|
+
val connectedName = try { gatt?.device?.name } catch (e: SecurityException) { null }
|
|
419
|
+
if (connectedName != null) {
|
|
420
|
+
extractRingId(connectedName)?.let {
|
|
421
|
+
DeviceStore.apply("bluetooth", "controller_device_name", it)
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
val mac = ringMacAddress
|
|
426
|
+
if (mac == null) {
|
|
427
|
+
Bridge.log("R1: No ring MAC address found")
|
|
428
|
+
return
|
|
429
|
+
}
|
|
430
|
+
DeviceStore.apply("glasses", "controllerMacAddress", mac)
|
|
431
|
+
DeviceStore.apply("glasses", "controllerConnected", true)
|
|
432
|
+
// DeviceStore.apply("glasses", "controllerFullyBooted", true)
|
|
433
|
+
|
|
434
|
+
// tell the ring to connect to the glasses if we have its mac address:
|
|
435
|
+
connectToGlasses()
|
|
436
|
+
|
|
437
|
+
// after a second, connect the glasses to the controller if needed:
|
|
438
|
+
CoroutineScope(Dispatchers.Main).launch {
|
|
439
|
+
delay(1000)
|
|
440
|
+
DeviceManager.getInstance().sgc?.connectController()
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
startHeartbeat()
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Tells the ring to start advertising / connect to the glasses.
|
|
448
|
+
* Sends BleRing1 advStart (cmd=0, module=0, subCmd=9) with the 6-byte glasses MAC as payload
|
|
449
|
+
* to WRITE_CHAR_2 (BAE80012-…). Reverse-engineered from the Even Realities mobile app
|
|
450
|
+
* (BleRing1CmdProto::advStart -> BleRing1CmdPublicExt.sendCmd).
|
|
451
|
+
*
|
|
452
|
+
* TODO: BleRing1CmdPublicExt.sendCmd may add additional outer framing (length/seq/CRC) around
|
|
453
|
+
* the 3-byte header + MAC. If the ring rejects this raw payload, decode the wrapper.
|
|
454
|
+
*/
|
|
455
|
+
private fun connectToGlasses() {
|
|
456
|
+
// Try DeviceStore first; fall back to cached value in SharedPreferences.
|
|
457
|
+
val glassesMac = (DeviceStore.get("glasses", "btMacAddress") as? String)
|
|
458
|
+
?: prefs.getString("glasses_btMacAddress", null)
|
|
459
|
+
if (glassesMac == null) {
|
|
460
|
+
Bridge.log("R1: connectToGlasses: no glasses MAC")
|
|
461
|
+
return
|
|
462
|
+
}
|
|
463
|
+
// Cache so we can reconnect even before the glasses are scanned.
|
|
464
|
+
prefs.edit().putString("glasses_btMacAddress", glassesMac).apply()
|
|
465
|
+
|
|
466
|
+
val macBytes = parseMac(glassesMac)
|
|
467
|
+
if (macBytes == null) {
|
|
468
|
+
Bridge.log("R1: connectToGlasses: could not parse glasses MAC")
|
|
469
|
+
return
|
|
470
|
+
}
|
|
471
|
+
val wc = writeChar2 ?: writeChar1
|
|
472
|
+
if (wc == null) {
|
|
473
|
+
Bridge.log("R1: connectToGlasses: no write characteristic")
|
|
474
|
+
return
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
val payload = byteArrayOf(R1BLE.CMD_SYSTEM, R1BLE.MODULE_SYSTEM, R1BLE.SUBCMD_ADV_START) + macBytes
|
|
478
|
+
Bridge.log("R1: advStart sent")
|
|
479
|
+
|
|
480
|
+
wc.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
|
|
481
|
+
wc.value = payload
|
|
482
|
+
try {
|
|
483
|
+
ringGatt?.writeCharacteristic(wc)
|
|
484
|
+
} catch (e: SecurityException) {
|
|
485
|
+
Bridge.log("R1: connectToGlasses writeCharacteristic SecurityException: ${e.message}")
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/** Parse "AA:BB:CC:DD:EE:FF" or "AABBCCDDEEFF" into 6 raw bytes. */
|
|
490
|
+
private fun parseMac(s: String): ByteArray? {
|
|
491
|
+
val cleaned = s.replace(":", "").replace("-", "")
|
|
492
|
+
if (cleaned.length != 12) return null
|
|
493
|
+
val out = ByteArray(6)
|
|
494
|
+
for (i in 0 until 6) {
|
|
495
|
+
val byte = cleaned.substring(i * 2, i * 2 + 2).toIntOrNull(16) ?: return null
|
|
496
|
+
out[i] = byte.toByte()
|
|
497
|
+
}
|
|
498
|
+
return out
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// MARK: - Heartbeat
|
|
502
|
+
|
|
503
|
+
private fun startHeartbeat() {
|
|
504
|
+
stopHeartbeat()
|
|
505
|
+
val r = object : Runnable {
|
|
506
|
+
override fun run() {
|
|
507
|
+
mainHandler.postDelayed(this, 30_000L)
|
|
508
|
+
val isConnected = DeviceStore.get("glasses", "controllerConnected") as? Boolean ?: false
|
|
509
|
+
if (!isConnected) return
|
|
510
|
+
val char = batteryLevelChar
|
|
511
|
+
if (char != null) {
|
|
512
|
+
try {
|
|
513
|
+
ringGatt?.readCharacteristic(char)
|
|
514
|
+
} catch (e: SecurityException) {
|
|
515
|
+
Bridge.log("R1: heartbeat read SecurityException: ${e.message}")
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
heartbeatRunnable = r
|
|
521
|
+
mainHandler.postDelayed(r, 30_000L)
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
private fun stopHeartbeat() {
|
|
525
|
+
heartbeatRunnable?.let { mainHandler.removeCallbacks(it) }
|
|
526
|
+
heartbeatRunnable = null
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// MARK: - Incoming Data Handling
|
|
530
|
+
|
|
531
|
+
private fun handleNotification(characteristic: BluetoothGattCharacteristic, data: ByteArray) {
|
|
532
|
+
val hex = data.joinToString(" ") { String.format("%02X", it) }
|
|
533
|
+
val shortUuid = characteristic.uuid.toString().takeLast(4)
|
|
534
|
+
Bridge.log("R1: $shortUuid -> $hex")
|
|
535
|
+
|
|
536
|
+
val timestamp = System.currentTimeMillis()
|
|
537
|
+
|
|
538
|
+
// Gesture: [0xFF, type, param]
|
|
539
|
+
if (data.size >= 3 && data[0] == R1BLE.GESTURE_MARKER) {
|
|
540
|
+
val gesture = R1Gesture.parse(data)
|
|
541
|
+
if (gesture != null) {
|
|
542
|
+
Bridge.log("R1: Gesture: ${gesture.rawValue}")
|
|
543
|
+
Bridge.sendTouchEvent(ControllerTypes.R1, gesture.rawValue, timestamp)
|
|
544
|
+
} else {
|
|
545
|
+
Bridge.log(
|
|
546
|
+
"R1: Unknown gesture type=0x%02X param=0x%02X".format(data[1], data[2])
|
|
547
|
+
)
|
|
548
|
+
}
|
|
549
|
+
return
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Battery: 2 bytes, first byte is percentage
|
|
553
|
+
if (data.size == 2 && (data[0].toInt() and 0xFF) <= 100) {
|
|
554
|
+
_batteryLevel = data[0].toInt() and 0xFF
|
|
555
|
+
Bridge.log("R1: Battery: ${_batteryLevel}%")
|
|
556
|
+
return
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// State: single byte (0x01=ready, 0x00=menu)
|
|
560
|
+
if (data.size == 1) {
|
|
561
|
+
val state = when (data[0]) {
|
|
562
|
+
0x01.toByte() -> "ready"
|
|
563
|
+
0x00.toByte() -> "menu"
|
|
564
|
+
else -> "unknown(${data[0].toInt() and 0xFF})"
|
|
565
|
+
}
|
|
566
|
+
Bridge.log("R1: State: $state")
|
|
567
|
+
return
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// Longer data: check for embedded gesture marker
|
|
571
|
+
if (data.size > 3) {
|
|
572
|
+
val ffIndex = data.indexOfFirst { it == R1BLE.GESTURE_MARKER }
|
|
573
|
+
if (ffIndex >= 0 && ffIndex + 2 < data.size) {
|
|
574
|
+
val slice = byteArrayOf(data[ffIndex], data[ffIndex + 1], data[ffIndex + 2])
|
|
575
|
+
val gesture = R1Gesture.parse(slice)
|
|
576
|
+
if (gesture != null) {
|
|
577
|
+
Bridge.log("R1: Embedded gesture: ${gesture.rawValue}")
|
|
578
|
+
Bridge.sendTouchEvent(ControllerTypes.R1, gesture.rawValue, timestamp)
|
|
579
|
+
return
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// MARK: - Connection State Reset
|
|
586
|
+
|
|
587
|
+
private fun resetConnectionState() {
|
|
588
|
+
ringGatt = null
|
|
589
|
+
writeChar1 = null
|
|
590
|
+
notifyChar1 = null
|
|
591
|
+
writeChar2 = null
|
|
592
|
+
notifyChar2 = null
|
|
593
|
+
batteryLevelChar = null
|
|
594
|
+
notifySubscriptionCount = 0
|
|
595
|
+
initSequenceRun = false
|
|
596
|
+
pendingDescriptorWrites.clear()
|
|
597
|
+
pendingReads.clear()
|
|
598
|
+
descriptorWriteInFlight = false
|
|
599
|
+
readInFlight = false
|
|
600
|
+
ringMacAddress = null
|
|
601
|
+
ringBleAddress = null
|
|
602
|
+
DeviceStore.apply("glasses", "controllerConnected", false)
|
|
603
|
+
DeviceStore.apply("glasses", "controllerFullyBooted", false)
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// MARK: - GATT Callback
|
|
607
|
+
|
|
608
|
+
private val gattCallback = object : BluetoothGattCallback() {
|
|
609
|
+
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
|
|
610
|
+
when (newState) {
|
|
611
|
+
BluetoothGatt.STATE_CONNECTED -> {
|
|
612
|
+
val name = try { gatt.device.name } catch (e: SecurityException) { null }
|
|
613
|
+
Bridge.log("R1: Connected to ${name ?: "ring"}")
|
|
614
|
+
|
|
615
|
+
// Validate against the name->MAC map populated during scan. If the connected
|
|
616
|
+
// peripheral isn't in the map we don't have its true public MAC — drop the
|
|
617
|
+
// connection and rescan rather than publishing a stale/wrong MAC. Mirrors
|
|
618
|
+
// iOS R1.swift didConnect.
|
|
619
|
+
val mappedMac = name?.let { getRingMacFromMap(it) }
|
|
620
|
+
if (mappedMac == null) {
|
|
621
|
+
Bridge.log("R1: No MAC stored in map found for ${name ?: "ring"}")
|
|
622
|
+
mainHandler.post {
|
|
623
|
+
disconnect()
|
|
624
|
+
ringBleAddress = null
|
|
625
|
+
DeviceStore.apply("glasses", "controllerConnected", false)
|
|
626
|
+
DeviceStore.apply("glasses", "controllerFullyBooted", false)
|
|
627
|
+
DeviceStore.apply("glasses", "controllerSearching", true)
|
|
628
|
+
mainHandler.postDelayed({ startScan() }, 1000)
|
|
629
|
+
}
|
|
630
|
+
return
|
|
631
|
+
}
|
|
632
|
+
ringMacAddress = mappedMac
|
|
633
|
+
|
|
634
|
+
try {
|
|
635
|
+
gatt.discoverServices()
|
|
636
|
+
} catch (e: SecurityException) {
|
|
637
|
+
Bridge.log("R1: discoverServices SecurityException: ${e.message}")
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
BluetoothGatt.STATE_DISCONNECTED -> {
|
|
641
|
+
Bridge.log("R1: Disconnected (status=$status)")
|
|
642
|
+
mainHandler.post {
|
|
643
|
+
stopHeartbeat()
|
|
644
|
+
try { gatt.close() } catch (e: SecurityException) {}
|
|
645
|
+
if (isDisconnecting) return@post
|
|
646
|
+
resetConnectionState()
|
|
647
|
+
// iOS leaves reconnection timer commented; match that behavior
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
|
|
654
|
+
if (status != BluetoothGatt.GATT_SUCCESS) {
|
|
655
|
+
Bridge.log("R1: onServicesDiscovered status=$status")
|
|
656
|
+
return
|
|
657
|
+
}
|
|
658
|
+
mainHandler.post {
|
|
659
|
+
val services = gatt.services ?: return@post
|
|
660
|
+
for (service in services) {
|
|
661
|
+
for (char in service.characteristics) {
|
|
662
|
+
Bridge.log("R1: char discovered: ${char.uuid}")
|
|
663
|
+
val props = char.properties
|
|
664
|
+
val propStr = buildList {
|
|
665
|
+
if (props and BluetoothGattCharacteristic.PROPERTY_READ != 0) add("read")
|
|
666
|
+
if (props and BluetoothGattCharacteristic.PROPERTY_WRITE != 0) add("write")
|
|
667
|
+
if (props and BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE != 0) add("writeNoResp")
|
|
668
|
+
if (props and BluetoothGattCharacteristic.PROPERTY_NOTIFY != 0) add("notify")
|
|
669
|
+
if (props and BluetoothGattCharacteristic.PROPERTY_INDICATE != 0) add("indicate")
|
|
670
|
+
}.joinToString(",")
|
|
671
|
+
Bridge.log("R1: char ${char.uuid} props=[$propStr]")
|
|
672
|
+
|
|
673
|
+
// Store known characteristics
|
|
674
|
+
when (char.uuid) {
|
|
675
|
+
R1BLE.WRITE_CHAR_1 -> writeChar1 = char
|
|
676
|
+
R1BLE.NOTIFY_CHAR_1 -> notifyChar1 = char
|
|
677
|
+
R1BLE.WRITE_CHAR_2 -> writeChar2 = char
|
|
678
|
+
R1BLE.NOTIFY_CHAR_2 -> notifyChar2 = char
|
|
679
|
+
R1BLE.BATTERY_LEVEL_CHAR -> batteryLevelChar = char
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// Subscribe to any notify/indicate characteristic
|
|
683
|
+
val notifyEligible = props and BluetoothGattCharacteristic.PROPERTY_NOTIFY != 0
|
|
684
|
+
val indicateEligible = props and BluetoothGattCharacteristic.PROPERTY_INDICATE != 0
|
|
685
|
+
if (notifyEligible || indicateEligible) {
|
|
686
|
+
try {
|
|
687
|
+
gatt.setCharacteristicNotification(char, true)
|
|
688
|
+
} catch (e: SecurityException) {
|
|
689
|
+
Bridge.log("R1: setCharacteristicNotification SecurityException: ${e.message}")
|
|
690
|
+
}
|
|
691
|
+
val descriptor = char.getDescriptor(R1BLE.CCCD_UUID)
|
|
692
|
+
if (descriptor != null) {
|
|
693
|
+
descriptor.value = if (notifyEligible) {
|
|
694
|
+
BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
|
|
695
|
+
} else {
|
|
696
|
+
BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
|
|
697
|
+
}
|
|
698
|
+
pendingDescriptorWrites.addLast(descriptor)
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// Queue reads (run after CCCD queue drains)
|
|
703
|
+
if (props and BluetoothGattCharacteristic.PROPERTY_READ != 0) {
|
|
704
|
+
pendingReads.addLast(char)
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
drainDescriptorQueue(gatt)
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
override fun onDescriptorWrite(
|
|
713
|
+
gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int
|
|
714
|
+
) {
|
|
715
|
+
mainHandler.post {
|
|
716
|
+
descriptorWriteInFlight = false
|
|
717
|
+
if (status != BluetoothGatt.GATT_SUCCESS) {
|
|
718
|
+
Bridge.log("R1: Descriptor write failed: $status on ${descriptor.characteristic.uuid}")
|
|
719
|
+
} else {
|
|
720
|
+
Bridge.log("R1: Notify enabled on ${descriptor.characteristic.uuid}")
|
|
721
|
+
notifySubscriptionCount += 1
|
|
722
|
+
if (notifySubscriptionCount >= 2 && !initSequenceRun) {
|
|
723
|
+
mainHandler.postDelayed({ runInitSequence() }, 300)
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
drainDescriptorQueue(gatt)
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
override fun onCharacteristicChanged(
|
|
731
|
+
gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic
|
|
732
|
+
) {
|
|
733
|
+
val data = characteristic.value ?: return
|
|
734
|
+
if (data.isEmpty()) return
|
|
735
|
+
mainHandler.post { handleNotification(characteristic, data) }
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
override fun onCharacteristicRead(
|
|
739
|
+
gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int
|
|
740
|
+
) {
|
|
741
|
+
val data = characteristic.value
|
|
742
|
+
mainHandler.post {
|
|
743
|
+
readInFlight = false
|
|
744
|
+
if (status == BluetoothGatt.GATT_SUCCESS && data != null && data.isNotEmpty()) {
|
|
745
|
+
if (characteristic.uuid == R1BLE.BATTERY_LEVEL_CHAR) {
|
|
746
|
+
_batteryLevel = data[0].toInt() and 0xFF
|
|
747
|
+
Bridge.log("R1: Battery (std): ${_batteryLevel}%")
|
|
748
|
+
} else {
|
|
749
|
+
handleNotification(characteristic, data)
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
drainReadQueue(gatt)
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
override fun onCharacteristicWrite(
|
|
757
|
+
gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int
|
|
758
|
+
) {
|
|
759
|
+
if (status != BluetoothGatt.GATT_SUCCESS) {
|
|
760
|
+
Bridge.log("R1: Write error on ${characteristic.uuid}: $status")
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
private fun drainDescriptorQueue(gatt: BluetoothGatt) {
|
|
766
|
+
if (descriptorWriteInFlight) return
|
|
767
|
+
val next = pendingDescriptorWrites.removeFirstOrNull()
|
|
768
|
+
if (next == null) {
|
|
769
|
+
// Once CCCD queue drained, start reads
|
|
770
|
+
drainReadQueue(gatt)
|
|
771
|
+
return
|
|
772
|
+
}
|
|
773
|
+
descriptorWriteInFlight = true
|
|
774
|
+
try {
|
|
775
|
+
gatt.writeDescriptor(next)
|
|
776
|
+
} catch (e: SecurityException) {
|
|
777
|
+
descriptorWriteInFlight = false
|
|
778
|
+
Bridge.log("R1: writeDescriptor SecurityException: ${e.message}")
|
|
779
|
+
drainDescriptorQueue(gatt)
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
private fun drainReadQueue(gatt: BluetoothGatt) {
|
|
784
|
+
if (readInFlight) return
|
|
785
|
+
val next = pendingReads.removeFirstOrNull() ?: return
|
|
786
|
+
readInFlight = true
|
|
787
|
+
try {
|
|
788
|
+
gatt.readCharacteristic(next)
|
|
789
|
+
} catch (e: SecurityException) {
|
|
790
|
+
readInFlight = false
|
|
791
|
+
Bridge.log("R1: readCharacteristic SecurityException: ${e.message}")
|
|
792
|
+
drainReadQueue(gatt)
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// MARK: - ControllerManager overrides
|
|
797
|
+
|
|
798
|
+
override fun findCompatibleDevices() {
|
|
799
|
+
Bridge.log("R1: findCompatibleDevices()")
|
|
800
|
+
deviceSearchId = "NOT_SET"
|
|
801
|
+
startScan()
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
override fun connectById(id: String) {
|
|
805
|
+
Bridge.log("R1: connectById($id)")
|
|
806
|
+
deviceSearchId = id
|
|
807
|
+
startScan()
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
override fun disconnect() {
|
|
811
|
+
Bridge.log("R1: disconnect()")
|
|
812
|
+
isDisconnecting = true
|
|
813
|
+
stopHeartbeat()
|
|
814
|
+
reconnectionManager.stop()
|
|
815
|
+
try {
|
|
816
|
+
ringGatt?.disconnect()
|
|
817
|
+
ringGatt?.close()
|
|
818
|
+
} catch (e: SecurityException) {
|
|
819
|
+
Bridge.log("R1: disconnect SecurityException: ${e.message}")
|
|
820
|
+
}
|
|
821
|
+
resetConnectionState()
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
override fun forget() {
|
|
825
|
+
disconnect()
|
|
826
|
+
ringMacAddress = null
|
|
827
|
+
ringBleAddress = null
|
|
828
|
+
prefs.edit().remove("r1_ringMacAddressMap").apply()
|
|
829
|
+
deviceSearchId = "NOT_SET"
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
override fun cleanup() {
|
|
833
|
+
disconnect()
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
override fun getConnectedBluetoothName(): String? {
|
|
837
|
+
return try { ringGatt?.device?.name } catch (e: SecurityException) { null }
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
override fun ping() {
|
|
841
|
+
val char = batteryLevelChar ?: return
|
|
842
|
+
try {
|
|
843
|
+
ringGatt?.readCharacteristic(char)
|
|
844
|
+
} catch (e: SecurityException) {
|
|
845
|
+
Bridge.log("R1: ping SecurityException: ${e.message}")
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
override fun getBatteryStatus() {
|
|
850
|
+
val char = batteryLevelChar ?: return
|
|
851
|
+
try {
|
|
852
|
+
ringGatt?.readCharacteristic(char)
|
|
853
|
+
} catch (e: SecurityException) {
|
|
854
|
+
Bridge.log("R1: getBatteryStatus SecurityException: ${e.message}")
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// MARK: - No-op implementations (ring has no display/camera/wifi/mic)
|
|
859
|
+
|
|
860
|
+
override fun sendIncidentId(incidentId: String) {}
|
|
861
|
+
override fun setMicEnabled(enabled: Boolean) {}
|
|
862
|
+
override fun sortMicRanking(list: MutableList<String>): MutableList<String> = list
|
|
863
|
+
override fun sendJson(jsonOriginal: Map<String, Any>, wakeUp: Boolean, requireAck: Boolean) {}
|
|
864
|
+
override fun requestPhoto(
|
|
865
|
+
requestId: String, appId: String, size: String?, webhookUrl: String?,
|
|
866
|
+
authToken: String?, compress: String?, flash: Boolean, sound: Boolean
|
|
867
|
+
) {}
|
|
868
|
+
override fun startVideoRecording(requestId: String, save: Boolean, flash: Boolean, sound: Boolean) {}
|
|
869
|
+
override fun stopVideoRecording(requestId: String) {}
|
|
870
|
+
override fun startStream(message: Map<String, Any>) {}
|
|
871
|
+
override fun stopStream() {}
|
|
872
|
+
override fun sendStreamKeepAlive(message: Map<String, Any>) {}
|
|
873
|
+
override fun sendButtonPhotoSettings() {}
|
|
874
|
+
override fun sendButtonVideoRecordingSettings() {}
|
|
875
|
+
override fun sendButtonMaxRecordingTime() {}
|
|
876
|
+
override fun sendButtonCameraLedSetting() {}
|
|
877
|
+
override fun setBrightness(level: Int, autoMode: Boolean) {}
|
|
878
|
+
override fun clearDisplay() {}
|
|
879
|
+
override fun sendTextWall(text: String) {}
|
|
880
|
+
override fun sendDoubleTextWall(top: String, bottom: String) {}
|
|
881
|
+
override fun displayBitmap(base64ImageData: String): Boolean = false
|
|
882
|
+
override fun showDashboard() {}
|
|
883
|
+
override fun setDashboardPosition(height: Int, depth: Int) {}
|
|
884
|
+
override fun setHeadUpAngle(angle: Int) {}
|
|
885
|
+
override fun setSilentMode(enabled: Boolean) {}
|
|
886
|
+
override fun exit() {}
|
|
887
|
+
override fun sendShutdown() { disconnect() }
|
|
888
|
+
override fun sendReboot() {}
|
|
889
|
+
override fun sendRgbLedControl(
|
|
890
|
+
requestId: String, packageName: String?, action: String, color: String?,
|
|
891
|
+
ontime: Int, offtime: Int, count: Int
|
|
892
|
+
) {}
|
|
893
|
+
override fun requestWifiScan() {}
|
|
894
|
+
override fun sendWifiCredentials(ssid: String, password: String) {}
|
|
895
|
+
override fun forgetWifiNetwork(ssid: String) {}
|
|
896
|
+
override fun sendHotspotState(enabled: Boolean) {}
|
|
897
|
+
override fun sendOtaStart() {}
|
|
898
|
+
override fun sendUserEmailToGlasses(email: String) {}
|
|
899
|
+
override fun queryGalleryStatus() {}
|
|
900
|
+
override fun sendGalleryMode() {}
|
|
901
|
+
override fun requestVersionInfo() {}
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// MARK: - R1 Reconnection Manager
|
|
905
|
+
// Coroutine-based port of Swift's R1ReconnectionManager actor.
|
|
906
|
+
// Currently unwired — matches iOS which leaves the reconnection timer commented out.
|
|
907
|
+
|
|
908
|
+
private class R1ReconnectionManager(
|
|
909
|
+
private val intervalSeconds: Long = 30L,
|
|
910
|
+
private val maxAttempts: Int = -1
|
|
911
|
+
) {
|
|
912
|
+
private var job: Job? = null
|
|
913
|
+
private var attempts = 0
|
|
914
|
+
|
|
915
|
+
fun start(onAttempt: suspend () -> Boolean) {
|
|
916
|
+
stop()
|
|
917
|
+
attempts = 0
|
|
918
|
+
job = CoroutineScope(Dispatchers.Default).launch {
|
|
919
|
+
while (isActive) {
|
|
920
|
+
delay(intervalSeconds * 1_000L)
|
|
921
|
+
if (!isActive) break
|
|
922
|
+
attempts += 1
|
|
923
|
+
if (maxAttempts > 0 && attempts > maxAttempts) break
|
|
924
|
+
if (onAttempt()) break
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
fun stop() {
|
|
930
|
+
job?.cancel()
|
|
931
|
+
job = null
|
|
932
|
+
attempts = 0
|
|
933
|
+
}
|
|
934
|
+
}
|