@glassly/bluetooth-sdk 0.1.0-dev.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +563 -0
- package/android/build.gradle +450 -0
- package/android/gradle.properties.example +9 -0
- package/android/lc3Lib/CMakeLists.txt +6 -0
- package/android/lc3Lib/build.gradle +212 -0
- package/android/lc3Lib/proguard-rules.pro +33 -0
- package/android/lc3Lib/src/main/AndroidManifest.xml +2 -0
- package/android/lc3Lib/src/main/cpp/CMakeLists.txt +8 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/CMakeLists.txt +30 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/jni/CMakeLists.txt +7 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/jni/ogg_opus_encoder.cc +96 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/jni/ogg_opus_encoder.h +53 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/Makefile.am +6 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/config_types.h +26 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/config_types.h.in +26 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/ogg.h +209 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libogg/os_types.h +158 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus.h +981 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_custom.h +342 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_defines.h +799 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_multistream.h +660 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_projection.h +568 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/libopus/opus_types.h +166 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/ogg_opus_encoder.cc +268 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/ogg_opus_encoder.h +115 -0
- package/android/lc3Lib/src/main/cpp/google_opus_stuff/opus_tools/opus_header.h +59 -0
- package/android/lc3Lib/src/main/cpp/liblc3/CMakeLists.txt +38 -0
- package/android/lc3Lib/src/main/cpp/liblc3/include/lc3.h +309 -0
- package/android/lc3Lib/src/main/cpp/liblc3/include/lc3_private.h +162 -0
- package/android/lc3Lib/src/main/cpp/liblc3/include/rnnoise.h +114 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/CMakeLists.txt +19 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/attdet.c +92 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/attdet.h +44 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/bits.c +375 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/bits.h +315 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/bwdet.c +129 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/bwdet.h +69 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/common.h +148 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/energy.c +70 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/energy.h +43 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/fastmath.h +158 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/lc3.c +702 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/ltpf.c +893 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/ltpf.h +111 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/ltpf_arm.h +506 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/ltpf_neon.h +281 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/makefile.mk +35 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/mdct.c +452 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/mdct.h +57 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/mdct_neon.h +296 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/meson.build +46 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/plc.c +61 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/plc.h +57 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/sns.c +880 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/sns.h +103 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/spec.c +904 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/spec.h +119 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/tables.c +3457 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/tables.h +94 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/tns.c +457 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3/tns.h +99 -0
- package/android/lc3Lib/src/main/cpp/liblc3/liblc3.cpp +159 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/_kiss_fft_guts.h +182 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/arch.h +261 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/celt_lpc.c +279 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/celt_lpc.h +59 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/common.h +48 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/compile.sh +3 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/denoise.c +646 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/kiss_fft.c +601 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/kiss_fft.h +203 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/opus_types.h +159 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/pitch.c +526 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/pitch.h +149 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn.c +178 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn.h +69 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn_data.c +11051 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn_data.h +34 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn_reader.c +168 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/rnn_train.py +66 -0
- package/android/lc3Lib/src/main/cpp/liblc3/rnnoise/tansig_table.h +45 -0
- package/android/lc3Lib/src/main/java/com/glassly/lc3Lib/Lc3Cpp.java +38 -0
- package/android/lc3Lib/third_party/CMakeLists.txt +23 -0
- package/android/lc3Lib/third_party/CMakeLists_libogg.txt +18 -0
- package/android/lc3Lib/third_party/CMakeLists_libopus.txt +18 -0
- package/android/lc3Lib/third_party/CMakeLists_opus-tools.txt +23 -0
- package/android/lc3Lib/third_party/opus_tools/src/AUTHORS +5 -0
- package/android/lc3Lib/third_party/opus_tools/src/CMakeLists.txt +4 -0
- package/android/lc3Lib/third_party/opus_tools/src/COPYING +371 -0
- package/android/lc3Lib/third_party/opus_tools/src/ChangeLog +3 -0
- package/android/lc3Lib/third_party/opus_tools/src/Makefile.am +42 -0
- package/android/lc3Lib/third_party/opus_tools/src/Makefile.unix +23 -0
- package/android/lc3Lib/third_party/opus_tools/src/NEWS +0 -0
- package/android/lc3Lib/third_party/opus_tools/src/README +0 -0
- package/android/lc3Lib/third_party/opus_tools/src/autogen.sh +115 -0
- package/android/lc3Lib/third_party/opus_tools/src/configure.ac +293 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/arch.h +239 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/audio-in.c +1046 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/diag_range.c +245 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/diag_range.h +28 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/info_opus.c +320 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/info_opus.h +51 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/lpc.c +157 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/lpc.h +27 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opus_header.c +286 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opus_header.h +59 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opusdec.c +884 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opusenc.c +1021 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opusenc.h +101 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opusinfo.c +639 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/opusinfo.h +51 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/os_support.h +167 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/resample.c +1137 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/speex_resampler.h +344 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/stack_alloc.h +115 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/wav_io.c +125 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/wav_io.h +62 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/wave_out.c +223 -0
- package/android/lc3Lib/third_party/opus_tools/src/src/wave_out.h +60 -0
- package/android/settings.gradle +4 -0
- package/android/silero/LICENSE.md +17 -0
- package/android/silero/build.gradle +50 -0
- package/android/silero/proguard-rules.pro +24 -0
- package/android/silero/src/androidTest/assets/hello.wav +0 -0
- package/android/silero/src/androidTest/java/com/konovalov/vad/silero/VadSileroTest.kt +79 -0
- package/android/silero/src/main/assets/LICENSE +21 -0
- package/android/silero/src/main/assets/silero_vad.onnx +0 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/Vad.kt +160 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/VadSilero.kt +449 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/config/FrameSize.kt +15 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/config/Mode.kt +14 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/config/SampleRate.kt +12 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/utils/AudioUtils.kt +47 -0
- package/android/silero/src/main/java/com/konovalov/vad/silero/utils/TensorMap.kt +41 -0
- package/android/silero/src/test/java/com/konovalov/vad/silero/utils/AudioUtilsTest.kt +44 -0
- package/android/src/main/AndroidManifest.xml +42 -0
- package/android/src/main/assets/G2Firmware/README.md +11 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdk.kt +2325 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdkAnalytics.kt +216 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdkDebug.kt +14 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdkDefaults.kt +7 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdkModule.kt +1088 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/Bridge.kt +953 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/DeviceManager.kt +2528 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/DeviceStore.kt +360 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/GeneratedChangelogCatalog.kt +8 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/GeneratedReleaseMetadata.kt +11 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/LocalNetworkModule.kt +414 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/ObservableStore.kt +88 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/OtaManifest.kt +191 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/ReleaseChangelog.kt +61 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/ScopedNetworkReadiness.kt +17 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/audio/AudioModels.kt +144 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/camera/CameraModels.kt +486 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/connection/ConnectionModels.kt +78 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/controllers/ControllerManager.kt +158 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/controllers/R1.kt +937 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/debug/BleTraceLogger.kt +165 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/events/BluetoothEvents.kt +178 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/internal/MapParsing.kt +140 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/net/LocalIpv4.kt +57 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/otaserver/LocalOtaServer.kt +304 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/otaserver/OtaServerModule.kt +120 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/photoreceiver/LocalPhotoReceiverRegistry.java +108 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/photoreceiver/LocalPhotoUploadServer.kt +622 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/photoreceiver/PhotoReceiverModule.kt +156 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/requests/DisplayRequests.kt +56 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/services/Foreground.kt +272 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/services/PhoneMic.kt +1195 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/Ar99.java +2653 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/BesOtaHeartbeatGuard.kt +49 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/BesOtaProgressMapper.kt +27 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G1.kt +3878 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G1TextSanitizer.kt +38 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2.kt +6428 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwClassification.kt +49 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwRenderer.kt +1241 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwTextureCache.kt +270 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2FlashProtocol.kt +291 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2Flasher.kt +411 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/GlasslyBle.java +43189 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/Mach1.kt +1280 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraLive.kt +10817 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraLiveL2capChannel.kt +274 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraLivePairingAdvertisement.kt +54 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraNex.kt +2011 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/Nimo.kt +2532 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/SGCManager.kt +470 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/Simulated.kt +228 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/Ar99OtaManager.java +566 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/OtaByteUtils.java +18 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/OtaCommandConstants.java +35 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/OtaCrc32Util.java +13 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/OtaGattTransport.java +11 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/ar99/ota/OtaProtocol.java +92 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/speakerid/SpeakerIdEngine.kt +525 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/status/DeviceStatus.kt +669 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/status/RuntimeState.kt +201 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/status/WifiHotspotStatus.kt +189 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/streaming/StreamModels.kt +518 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/stt/STTTools.kt +391 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/stt/SherpaOnnxTranscriber.kt +518 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/stt/VadGateSpeechPolicy.kt +229 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/tts/TTSTools.kt +248 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/types/DeviceModels.kt +96 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/AES.java +106 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/AudioSessionMonitor.kt +189 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/AvifExifStripper.java +573 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/BitmapJavaUtils.java +169 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/BleJsonCompact.java +496 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/BlePhotoUploadService.java +677 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/BleWireProtocol.java +108 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/Constants.kt +46 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/G1Text.kt +436 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/HeifExifTagReader.java +199 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/IncidentLogBleRelayNaming.java +29 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/IncidentLogBleUploadService.java +77 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/K900LengthCodec.java +115 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/K900ProtocolUtils.java +827 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/MessageChunkReassembler.java +228 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/MessageChunker.java +325 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/NexSGCUtils.kt +698 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/PhoneAudioMonitor.kt +348 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/SmartGlassesConnectionState.java +9 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/audio/Ar99OpusPcmDecoder.java +398 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/audio/ByteUtilAudioPlayer.java +655 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/audio/Lc3Player.java +441 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/audio/PCMAudioPlayer.java +431 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/audio/PcmStreamPlayer.kt +328 -0
- package/android/src/main/res/values/strings.xml +4 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/BluetoothSdkExceptionTest.kt +18 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/OtaManifestDowngradeTest.kt +86 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/OtaStartResponsePolicyTest.kt +30 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/ReleaseChangelogTest.kt +13 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/ScopedNetworkReadinessTest.kt +39 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/StreamStatusEventTest.kt +29 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/camera/PhotoRequestTest.kt +141 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/otaserver/LocalOtaServerTest.kt +82 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/photoreceiver/LocalPhotoReceiverRegistryTest.java +75 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/services/ForegroundServiceTypeTest.kt +69 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/Ar99AdvertisementMatcherTest.java +22 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/BesOtaHeartbeatGuardTest.kt +73 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/BesOtaProgressMapperTest.kt +67 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/EvenRleTest.kt +63 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G1TextSanitizerTest.kt +31 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwClassificationTest.kt +89 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwRendererTest.kt +344 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwTextureCacheTest.kt +200 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2FlashProtocolTest.kt +131 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/MentraLiveGattCharacteristicsTest.kt +39 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/MentraLivePairingAdvertisementParserTest.kt +133 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/NimoProtocolTest.kt +333 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/AvifExifStripperTest.java +46 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/BinaryMessageChunkerTest.java +84 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/BleJsonCompactTest.java +319 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/BlePhotoUploadServiceTest.java +107 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/K900ProtocolUtilsEndiannessTest.java +172 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/K900ProtocolUtilsTest.java +24 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/utils/PhoneAudioMonitorTest.kt +45 -0
- package/android/src/test/resources/avif_with_exif.avif +0 -0
- package/app.plugin.js +3 -0
- package/build/BluetoothSdk.types.d.ts +1354 -0
- package/build/BluetoothSdk.types.d.ts.map +1 -0
- package/build/BluetoothSdk.types.js +41 -0
- package/build/BluetoothSdk.types.js.map +1 -0
- package/build/_internal.d.ts +17 -0
- package/build/_internal.d.ts.map +1 -0
- package/build/_internal.js +16 -0
- package/build/_internal.js.map +1 -0
- package/build/_private/BluetoothSdkModule.d.ts +172 -0
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -0
- package/build/_private/BluetoothSdkModule.js +300 -0
- package/build/_private/BluetoothSdkModule.js.map +1 -0
- package/build/_private/LocalNetworkModule.d.ts +38 -0
- package/build/_private/LocalNetworkModule.d.ts.map +1 -0
- package/build/_private/LocalNetworkModule.js +3 -0
- package/build/_private/LocalNetworkModule.js.map +1 -0
- package/build/_private/cameraRequestPayload.d.ts +4 -0
- package/build/_private/cameraRequestPayload.d.ts.map +1 -0
- package/build/_private/cameraRequestPayload.js +32 -0
- package/build/_private/cameraRequestPayload.js.map +1 -0
- package/build/_private/photoRequestPayload.d.ts +6 -0
- package/build/_private/photoRequestPayload.d.ts.map +1 -0
- package/build/_private/photoRequestPayload.js +86 -0
- package/build/_private/photoRequestPayload.js.map +1 -0
- package/build/_private/sdkOtaManifest.d.ts +11 -0
- package/build/_private/sdkOtaManifest.d.ts.map +1 -0
- package/build/_private/sdkOtaManifest.js +15 -0
- package/build/_private/sdkOtaManifest.js.map +1 -0
- package/build/changelogs.d.ts +7 -0
- package/build/changelogs.d.ts.map +1 -0
- package/build/changelogs.js +46 -0
- package/build/changelogs.js.map +1 -0
- package/build/debug.d.ts +3 -0
- package/build/debug.d.ts.map +1 -0
- package/build/debug.js +8 -0
- package/build/debug.js.map +1 -0
- package/build/generated/changelogCatalog.d.ts +9 -0
- package/build/generated/changelogCatalog.d.ts.map +1 -0
- package/build/generated/changelogCatalog.js +12 -0
- package/build/generated/changelogCatalog.js.map +1 -0
- package/build/generated/releaseMetadata.d.ts +12 -0
- package/build/generated/releaseMetadata.d.ts.map +1 -0
- package/build/generated/releaseMetadata.js +10 -0
- package/build/generated/releaseMetadata.js.map +1 -0
- package/build/index.d.ts +6 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +171 -0
- package/build/index.js.map +1 -0
- package/build/ota-server/OtaServer.types.d.ts +14 -0
- package/build/ota-server/OtaServer.types.d.ts.map +1 -0
- package/build/ota-server/OtaServer.types.js +2 -0
- package/build/ota-server/OtaServer.types.js.map +1 -0
- package/build/ota-server/OtaServerModule.d.ts +21 -0
- package/build/ota-server/OtaServerModule.d.ts.map +1 -0
- package/build/ota-server/OtaServerModule.js +3 -0
- package/build/ota-server/OtaServerModule.js.map +1 -0
- package/build/ota-server/OtaServerModule.web.d.ts +10 -0
- package/build/ota-server/OtaServerModule.web.d.ts.map +1 -0
- package/build/ota-server/OtaServerModule.web.js +12 -0
- package/build/ota-server/OtaServerModule.web.js.map +1 -0
- package/build/ota-server/index.d.ts +3 -0
- package/build/ota-server/index.d.ts.map +1 -0
- package/build/ota-server/index.js +3 -0
- package/build/ota-server/index.js.map +1 -0
- package/build/ota-transport/index.d.ts +67 -0
- package/build/ota-transport/index.d.ts.map +1 -0
- package/build/ota-transport/index.js +58 -0
- package/build/ota-transport/index.js.map +1 -0
- package/build/photo-receiver/PhotoReceiver.types.d.ts +18 -0
- package/build/photo-receiver/PhotoReceiver.types.d.ts.map +1 -0
- package/build/photo-receiver/PhotoReceiver.types.js +2 -0
- package/build/photo-receiver/PhotoReceiver.types.js.map +1 -0
- package/build/photo-receiver/PhotoReceiverModule.d.ts +10 -0
- package/build/photo-receiver/PhotoReceiverModule.d.ts.map +1 -0
- package/build/photo-receiver/PhotoReceiverModule.js +3 -0
- package/build/photo-receiver/PhotoReceiverModule.js.map +1 -0
- package/build/photo-receiver/PhotoReceiverModule.web.d.ts +10 -0
- package/build/photo-receiver/PhotoReceiverModule.web.d.ts.map +1 -0
- package/build/photo-receiver/PhotoReceiverModule.web.js +12 -0
- package/build/photo-receiver/PhotoReceiverModule.web.js.map +1 -0
- package/build/photo-receiver/index.d.ts +3 -0
- package/build/photo-receiver/index.d.ts.map +1 -0
- package/build/photo-receiver/index.js +3 -0
- package/build/photo-receiver/index.js.map +1 -0
- package/build/react/index.d.ts +5 -0
- package/build/react/index.d.ts.map +1 -0
- package/build/react/index.js +4 -0
- package/build/react/index.js.map +1 -0
- package/build/react/useBluetooth.d.ts +100 -0
- package/build/react/useBluetooth.d.ts.map +1 -0
- package/build/react/useBluetooth.js +167 -0
- package/build/react/useBluetooth.js.map +1 -0
- package/build/react/useBluetoothEvent.d.ts +6 -0
- package/build/react/useBluetoothEvent.d.ts.map +1 -0
- package/build/react/useBluetoothEvent.js +21 -0
- package/build/react/useBluetoothEvent.js.map +1 -0
- package/build/react/useBluetoothScan.d.ts +22 -0
- package/build/react/useBluetoothScan.d.ts.map +1 -0
- package/build/react/useBluetoothScan.js +135 -0
- package/build/react/useBluetoothScan.js.map +1 -0
- package/build/react/useBluetoothStatus.d.ts +16 -0
- package/build/react/useBluetoothStatus.d.ts.map +1 -0
- package/build/react/useBluetoothStatus.js +137 -0
- package/build/react/useBluetoothStatus.js.map +1 -0
- package/build/react/useGlassesConnection.d.ts +29 -0
- package/build/react/useGlassesConnection.d.ts.map +1 -0
- package/build/react/useGlassesConnection.js +153 -0
- package/build/react/useGlassesConnection.js.map +1 -0
- package/build/types/index.d.ts +3 -0
- package/build/types/index.d.ts.map +1 -0
- package/build/types/index.js +2 -0
- package/build/types/index.js.map +1 -0
- package/expo-module.config.json +14 -0
- package/ios/BackgroundOtaArtifactDownloader.swift +125 -0
- package/ios/BluetoothSdkModule.swift +1034 -0
- package/ios/GlasslyBluetoothSDK.podspec +106 -0
- package/ios/LocalIPv4.swift +150 -0
- package/ios/LocalOtaServer.swift +309 -0
- package/ios/LocalPhotoUploadServer.swift +754 -0
- package/ios/OtaServerModule.swift +147 -0
- package/ios/Packages/CoreObjC/CoreObjC.xcodeproj/project.pbxproj +213 -0
- package/ios/Packages/CoreObjC/PcmConverter.h +22 -0
- package/ios/Packages/CoreObjC/PcmConverter.m +266 -0
- package/ios/Packages/CoreObjC/attdet.c +92 -0
- package/ios/Packages/CoreObjC/attdet.h +44 -0
- package/ios/Packages/CoreObjC/bits.c +375 -0
- package/ios/Packages/CoreObjC/bits.h +315 -0
- package/ios/Packages/CoreObjC/bwdet.c +129 -0
- package/ios/Packages/CoreObjC/bwdet.h +69 -0
- package/ios/Packages/CoreObjC/common.h +151 -0
- package/ios/Packages/CoreObjC/energy.c +70 -0
- package/ios/Packages/CoreObjC/energy.h +43 -0
- package/ios/Packages/CoreObjC/fastmath.h +158 -0
- package/ios/Packages/CoreObjC/include/PcmConverter.h +22 -0
- package/ios/Packages/CoreObjC/lc3.c +704 -0
- package/ios/Packages/CoreObjC/lc3.h +313 -0
- package/ios/Packages/CoreObjC/lc3_cpp.h +283 -0
- package/ios/Packages/CoreObjC/lc3_private.h +163 -0
- package/ios/Packages/CoreObjC/ltpf.c +905 -0
- package/ios/Packages/CoreObjC/ltpf.h +111 -0
- package/ios/Packages/CoreObjC/ltpf_arm.h +506 -0
- package/ios/Packages/CoreObjC/ltpf_neon.h +281 -0
- package/ios/Packages/CoreObjC/makefile.mk +35 -0
- package/ios/Packages/CoreObjC/mdct.c +469 -0
- package/ios/Packages/CoreObjC/mdct.h +57 -0
- package/ios/Packages/CoreObjC/mdct_neon.h +296 -0
- package/ios/Packages/CoreObjC/meson.build +61 -0
- package/ios/Packages/CoreObjC/plc.c +61 -0
- package/ios/Packages/CoreObjC/plc.h +57 -0
- package/ios/Packages/CoreObjC/rnnoise.h +114 -0
- package/ios/Packages/CoreObjC/sns.c +880 -0
- package/ios/Packages/CoreObjC/sns.h +103 -0
- package/ios/Packages/CoreObjC/spec.c +907 -0
- package/ios/Packages/CoreObjC/spec.h +119 -0
- package/ios/Packages/CoreObjC/tables.c +3457 -0
- package/ios/Packages/CoreObjC/tables.h +94 -0
- package/ios/Packages/CoreObjC/tns.c +457 -0
- package/ios/Packages/CoreObjC/tns.h +99 -0
- package/ios/Packages/SherpaOnnx/Model/joiner.onnx +0 -0
- package/ios/Packages/SherpaOnnx/Model/tokens.txt +502 -0
- package/ios/Packages/SherpaOnnx/SherpaOnnx.swift +2383 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/Info.plist +48 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/Headers/sherpa-onnx/c-api/c-api.h +4689 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/Headers/sherpa-onnx/c-api/cxx-api.h +2036 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64/libsherpa-onnx.a +0 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/Headers/sherpa-onnx/c-api/c-api.h +4689 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/Headers/sherpa-onnx/c-api/cxx-api.h +2036 -0
- package/ios/Packages/SherpaOnnx/sherpa-onnx.xcframework/ios-arm64_x86_64-simulator/libsherpa-onnx.a +0 -0
- package/ios/Packages/libbz2/module.modulemap +5 -0
- package/ios/Packages/libbz2/shim.h +1 -0
- package/ios/PhotoReceiverModule.swift +129 -0
- package/ios/Source/BluetoothSDK.swift +2303 -0
- package/ios/Source/BluetoothSDKDebug.swift +12 -0
- package/ios/Source/BluetoothSdkDefaults.swift +29 -0
- package/ios/Source/Bridge.swift +708 -0
- package/ios/Source/Bridging-Header.h +16 -0
- package/ios/Source/DeviceManager.swift +2109 -0
- package/ios/Source/DeviceStore.swift +336 -0
- package/ios/Source/GeneratedChangelogCatalog.swift +7 -0
- package/ios/Source/GeneratedReleaseMetadata.swift +11 -0
- package/ios/Source/ObservableStore.swift +118 -0
- package/ios/Source/OtaManifest.swift +211 -0
- package/ios/Source/PrivacyInfo.xcprivacy +23 -0
- package/ios/Source/ReleaseChangelog.swift +65 -0
- package/ios/Source/Resources/G2Firmware/README.md +10 -0
- package/ios/Source/audio/AudioModels.swift +178 -0
- package/ios/Source/audio/PcmStreamPlayer.swift +410 -0
- package/ios/Source/camera/CameraModels.swift +866 -0
- package/ios/Source/connection/ScanSession.swift +27 -0
- package/ios/Source/controllers/ControllerManager.swift +184 -0
- package/ios/Source/controllers/R1.swift +829 -0
- package/ios/Source/errors/BluetoothSdkError.swift +19 -0
- package/ios/Source/events/BluetoothEvents.swift +342 -0
- package/ios/Source/internal/BleTraceLogger.swift +192 -0
- package/ios/Source/internal/BluetoothAvailability.swift +78 -0
- package/ios/Source/internal/BluetoothSdkAnalytics.swift +199 -0
- package/ios/Source/internal/ValueParsing.swift +99 -0
- package/ios/Source/requests/DisplayRequests.swift +82 -0
- package/ios/Source/services/PhoneMic.swift +662 -0
- package/ios/Source/sgcs/Ar99.swift +2451 -0
- package/ios/Source/sgcs/Frame.swift +649 -0
- package/ios/Source/sgcs/G1.swift +2554 -0
- package/ios/Source/sgcs/G2.swift +6887 -0
- package/ios/Source/sgcs/G2CfwClassification.swift +43 -0
- package/ios/Source/sgcs/G2CfwRenderer.swift +1279 -0
- package/ios/Source/sgcs/G2CfwTextureCache.swift +233 -0
- package/ios/Source/sgcs/G2FlashProtocol.swift +312 -0
- package/ios/Source/sgcs/G2Flasher.swift +773 -0
- package/ios/Source/sgcs/Mach1.swift +541 -0
- package/ios/Source/sgcs/MentraLive.swift +7011 -0
- package/ios/Source/sgcs/MentraLiveL2capChannel.swift +183 -0
- package/ios/Source/sgcs/MentraLivePairingAdvertisement.swift +64 -0
- package/ios/Source/sgcs/MentraNex.swift +3221 -0
- package/ios/Source/sgcs/Nimo.swift +1886 -0
- package/ios/Source/sgcs/SGCManager.swift +608 -0
- package/ios/Source/sgcs/Simulated.swift +274 -0
- package/ios/Source/sgcs/ar99/ota/Ar99OtaManager.swift +637 -0
- package/ios/Source/sgcs/glassly_ble.pb.swift +4354 -0
- package/ios/Source/speakerid/SpeakerIdEngine.swift +573 -0
- package/ios/Source/status/DeviceStatus.swift +981 -0
- package/ios/Source/status/RuntimeState.swift +359 -0
- package/ios/Source/status/WifiHotspotStatus.swift +345 -0
- package/ios/Source/streaming/StreamModels.swift +693 -0
- package/ios/Source/stt/STTTools.swift +191 -0
- package/ios/Source/stt/SherpaOnnxTranscriber.swift +466 -0
- package/ios/Source/tts/TTSTools.swift +171 -0
- package/ios/Source/types/DeviceModels.swift +177 -0
- package/ios/Source/utils/AudioSessionMonitor.swift +283 -0
- package/ios/Source/utils/BinaryCodec.swift +61 -0
- package/ios/Source/utils/BleJsonCompact.swift +405 -0
- package/ios/Source/utils/BleWireProtocol.swift +92 -0
- package/ios/Source/utils/Constants.swift +78 -0
- package/ios/Source/utils/Enums.swift +95 -0
- package/ios/Source/utils/G1Text.swift +2096 -0
- package/ios/Source/utils/JSCExperiment.swift +241 -0
- package/ios/Source/utils/MemoryMonitor.swift +44 -0
- package/ios/Source/utils/MessageChunkReassembler.swift +190 -0
- package/ios/Source/utils/MessageChunker.swift +294 -0
- package/ios/Source/utils/Models.swift +135 -0
- package/ios/Source/utils/PhoneAudioMonitor.swift +230 -0
- package/ios/Source/utils/TarBz2Extractor.swift +257 -0
- package/ios/Tests/BluetoothSDKTests/G2CfwClassificationTests.swift +78 -0
- package/ios/Tests/BluetoothSDKTests/G2CfwRendererTests.swift +372 -0
- package/ios/Tests/BluetoothSDKTests/G2CfwTextureCacheTests.swift +162 -0
- package/ios/Tests/BluetoothSDKTests/G2FlashProtocolTests.swift +182 -0
- package/ios/Tests/MentraLivePairingAdvertisementTests.swift +160 -0
- package/ios/Tests/ReleaseChangelogTests.swift +13 -0
- package/package.json +129 -0
- package/plugin/build/index.d.ts +10 -0
- package/plugin/build/index.js +12 -0
- package/plugin/build/withAndroid.d.ts +3 -0
- package/plugin/build/withAndroid.js +190 -0
- package/plugin/build/withIos.d.ts +5 -0
- package/plugin/build/withIos.js +108 -0
- package/scripts/inject-ios-sdk-version.mjs +65 -0
- package/scripts/public-ota-api.test.mjs +37 -0
- package/scripts/verify-release-package.mjs +45 -0
- package/scripts/write-release-metadata.mjs +152 -0
- package/scripts/write-release-metadata.test.mjs +37 -0
- package/src/BluetoothSdk.types.ts +1623 -0
- package/src/_internal.ts +16 -0
- package/src/_private/BluetoothSdkModule.ts +684 -0
- package/src/_private/LocalNetworkModule.ts +50 -0
- package/src/_private/cameraRequestPayload.ts +34 -0
- package/src/_private/photoRequestPayload.ts +90 -0
- package/src/_private/sdkOtaManifest.ts +18 -0
- package/src/changelogs.ts +46 -0
- package/src/debug.ts +9 -0
- package/src/generated/changelogCatalog.ts +13 -0
- package/src/generated/releaseMetadata.ts +20 -0
- package/src/index.ts +321 -0
- package/src/ota-server/OtaServer.types.ts +10 -0
- package/src/ota-server/OtaServerModule.ts +23 -0
- package/src/ota-server/OtaServerModule.web.ts +17 -0
- package/src/ota-server/index.ts +2 -0
- package/src/ota-transport/index.ts +116 -0
- package/src/photo-receiver/PhotoReceiver.types.ts +20 -0
- package/src/photo-receiver/PhotoReceiverModule.ts +11 -0
- package/src/photo-receiver/PhotoReceiverModule.web.ts +17 -0
- package/src/photo-receiver/index.ts +2 -0
- package/src/react/index.ts +21 -0
- package/src/react/useBluetooth.ts +309 -0
- package/src/react/useBluetoothEvent.ts +35 -0
- package/src/react/useBluetoothScan.ts +173 -0
- package/src/react/useBluetoothStatus.ts +180 -0
- package/src/react/useGlassesConnection.ts +202 -0
- package/src/types/index.ts +12 -0
|
@@ -0,0 +1,2653 @@
|
|
|
1
|
+
package com.glassly.bluetoothsdk.sgcs;
|
|
2
|
+
|
|
3
|
+
import android.bluetooth.BluetoothAdapter;
|
|
4
|
+
import android.bluetooth.BluetoothDevice;
|
|
5
|
+
import android.bluetooth.BluetoothGatt;
|
|
6
|
+
import android.bluetooth.BluetoothGattCallback;
|
|
7
|
+
import android.bluetooth.BluetoothGattCharacteristic;
|
|
8
|
+
import android.bluetooth.BluetoothGattDescriptor;
|
|
9
|
+
import android.bluetooth.BluetoothGattService;
|
|
10
|
+
import android.bluetooth.BluetoothProfile;
|
|
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.os.Build;
|
|
17
|
+
import android.os.Handler;
|
|
18
|
+
import android.os.Looper;
|
|
19
|
+
import android.text.format.DateFormat;
|
|
20
|
+
import android.util.Log;
|
|
21
|
+
import com.google.protobuf.CodedInputStream;
|
|
22
|
+
import com.google.protobuf.CodedOutputStream;
|
|
23
|
+
import com.google.protobuf.WireFormat;
|
|
24
|
+
import com.glassly.bluetoothsdk.Bridge;
|
|
25
|
+
import com.glassly.bluetoothsdk.DeviceManager;
|
|
26
|
+
import com.glassly.bluetoothsdk.DeviceStore;
|
|
27
|
+
import com.glassly.bluetoothsdk.PhotoRequest;
|
|
28
|
+
import com.glassly.bluetoothsdk.sgcs.ar99.ota.Ar99OtaManager;
|
|
29
|
+
import com.glassly.bluetoothsdk.sgcs.ar99.ota.OtaGattTransport;
|
|
30
|
+
import com.glassly.bluetoothsdk.utils.ConnTypes;
|
|
31
|
+
import com.glassly.bluetoothsdk.utils.DeviceTypes;
|
|
32
|
+
import com.glassly.bluetoothsdk.utils.audio.Ar99OpusPcmDecoder;
|
|
33
|
+
import java.io.ByteArrayOutputStream;
|
|
34
|
+
import java.io.File;
|
|
35
|
+
import java.io.IOException;
|
|
36
|
+
import java.util.ArrayDeque;
|
|
37
|
+
import java.util.Arrays;
|
|
38
|
+
import java.util.Collections;
|
|
39
|
+
import java.util.Deque;
|
|
40
|
+
import java.util.HashMap;
|
|
41
|
+
import java.util.LinkedHashSet;
|
|
42
|
+
import java.util.TimeZone;
|
|
43
|
+
import java.util.UUID;
|
|
44
|
+
import java.util.concurrent.atomic.AtomicInteger;
|
|
45
|
+
|
|
46
|
+
public class Ar99 extends SGCManager {
|
|
47
|
+
private static final String TAG = "Ar99";
|
|
48
|
+
|
|
49
|
+
/** Use: {@code adb logcat -s Ar99Opus}. */
|
|
50
|
+
private static final String LOG_TAG_OPUS = "Ar99Opus";
|
|
51
|
+
|
|
52
|
+
// NOTE: UUIDs are taken from the reference implementation.
|
|
53
|
+
private static final UUID CTRL_SERVICE_UUID =
|
|
54
|
+
UUID.fromString("A72EC9A8-CF72-8544-AD96-D1481A02CE98");
|
|
55
|
+
private static final UUID CTRL_HOST_TO_SLAVE =
|
|
56
|
+
UUID.fromString("A72EC9A9-CF72-8544-AD96-D1481A02CE98");
|
|
57
|
+
private static final UUID CTRL_SLAVE_TO_HOST =
|
|
58
|
+
UUID.fromString("A72EC9AA-CF72-8544-AD96-D1481A02CE98");
|
|
59
|
+
|
|
60
|
+
private static final UUID OPUS_SERVICE_UUID =
|
|
61
|
+
UUID.fromString("F6B26AAD-41A5-93F5-F640-AC3BA3C73597");
|
|
62
|
+
private static final UUID OPUS_SLAVE_TO_HOST =
|
|
63
|
+
UUID.fromString("F6B26AAF-41A5-93F5-F640-AC3BA3C73597");
|
|
64
|
+
|
|
65
|
+
private static final UUID OTA_SERVICE_UUID =
|
|
66
|
+
UUID.fromString("e49a25f8-f69a-11e8-8eb2-f2801f1b9fd1");
|
|
67
|
+
private static final UUID OTA_WRITE_UUID =
|
|
68
|
+
UUID.fromString("e49a25e0-f69a-11e8-8eb2-f2801f1b9fd1");
|
|
69
|
+
private static final UUID OTA_NOTIFY_UUID =
|
|
70
|
+
UUID.fromString("e49a28e1-f69a-11e8-8eb2-f2801f1b9fd1");
|
|
71
|
+
|
|
72
|
+
private static final UUID CLIENT_CHARACTERISTIC_CONFIG_UUID =
|
|
73
|
+
UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
|
|
74
|
+
|
|
75
|
+
private static final int MAGIC_NUMBER = 0x1A2B3C4D;
|
|
76
|
+
|
|
77
|
+
private static final int FUNC_COMM = 1;
|
|
78
|
+
private static final int FUNC_TRANS = 3;
|
|
79
|
+
private static final int FUNC_COMM_DISPLAY = 7;
|
|
80
|
+
|
|
81
|
+
private static final int CMD_TRANS_DISPLAY_START = 100;
|
|
82
|
+
private static final int CMD_TRANS_DISPLAY_STOP = 101;
|
|
83
|
+
private static final int CMD_TRANS_DISPLAY_SET_CONTENT = 102;
|
|
84
|
+
|
|
85
|
+
private static final int CMD_DISPLAY_GET_DEVICEINFO = 100;
|
|
86
|
+
private static final int CMD_DISPLAY_SET_LANGUAGE = 131;
|
|
87
|
+
private static final int CMD_DISPLAY_GET_BRIGHT = 102;
|
|
88
|
+
private static final int CMD_DISPLAY_GET_BATTERY = 104;
|
|
89
|
+
private static final int CMD_COMM_SET_TIME = 31;
|
|
90
|
+
private static final int CMD_DISPLAY_SET_BRIGHT = 132;
|
|
91
|
+
private static final int CMD_DISPLAY_SET_AUTO_ADJUST_LIGHT = 138;
|
|
92
|
+
|
|
93
|
+
private static final int CMD_DISPLAY_CTRL_AUDIO_RECORD = 161;
|
|
94
|
+
private static final int CMD_DISPLAY_CTRL_FACTORY_RESET = 162;
|
|
95
|
+
private static final int CMD_DISPLAY_CTRL_SYS_FUNCTION = 164;
|
|
96
|
+
private static final int CMD_DISPLAY_CTRL_MINIMAL_TEXT_START = 181;
|
|
97
|
+
private static final int CMD_DISPLAY_CTRL_MINIMAL_TEXT_SET = 182;
|
|
98
|
+
private static final int CMD_DISPLAY_CTRL_MINIMAL_TEXT_CLEAR = 183;
|
|
99
|
+
private static final int CMD_DISPLAY_CTRL_MINIMAL_TEXT_STOP = 184;
|
|
100
|
+
|
|
101
|
+
private static final int TEXT_STREAM_STATE_BEGIN = 0;
|
|
102
|
+
private static final int TEXT_STREAM_STATE_UPDATE = 1;
|
|
103
|
+
private static final int TEXT_STREAM_STATE_END = 2;
|
|
104
|
+
|
|
105
|
+
private static final int TRANS_DISPLAY_SOURCE_LANGUAGE = 0;
|
|
106
|
+
private static final int TRANS_DISPLAY_TARGET_LANGUAGE = 1;
|
|
107
|
+
private static final int TRANS_DISPLAY_MIC_SOURCE_PHONE = 0;
|
|
108
|
+
private static final int TRANS_DISPLAY_FONT_SIZE = 24;
|
|
109
|
+
private static final int DISPLAY_FUNCTION_TYPE_UI_MODE = 2;
|
|
110
|
+
private static final int DISPLAY_UI_MODE_MINIMAL = 1;
|
|
111
|
+
private static final int DISPLAY_LANGUAGE_ENGLISH = 1;
|
|
112
|
+
private static final int FACTORY_RESET_CONFIRM_CODE = 0xAA;
|
|
113
|
+
|
|
114
|
+
private static final int TARGET_MOBILE_APP = 0;
|
|
115
|
+
private static final int TARGET_GLASS_ANDROID = 1;
|
|
116
|
+
private static final int TARGET_GLASS_MCU_DISPLAY = 3;
|
|
117
|
+
|
|
118
|
+
private static final int CTRL_ACTION_RESPONSE = 0x40;
|
|
119
|
+
|
|
120
|
+
private static final long SCAN_DURATION_MS = 15_000L;
|
|
121
|
+
private static final long INITIAL_BATTERY_RETRY_DELAY_MS = 1_000L;
|
|
122
|
+
private static final int MAX_INITIAL_BATTERY_RETRIES = 5;
|
|
123
|
+
private static final long BATTERY_POLL_INTERVAL_MS = 60_000L;
|
|
124
|
+
private static final int BLE_WRITE_CHUNK_SIZE = 180;
|
|
125
|
+
private static final long BLE_WRITE_CHUNK_DELAY_MS = 10L;
|
|
126
|
+
private static final long READY_MINIMAL_MODE_DELAY_MS = 40L;
|
|
127
|
+
private static final long READY_LANGUAGE_DELAY_MS = 90L;
|
|
128
|
+
private static final long READY_DEVICE_INFO_DELAY_MS = 140L;
|
|
129
|
+
private static final long READY_BRIGHTNESS_DELAY_MS = 280L;
|
|
130
|
+
private static final long READY_BATTERY_DELAY_MS = 420L;
|
|
131
|
+
private static final long DISPLAY_SESSION_IDLE_RESTART_MS = 8_000L;
|
|
132
|
+
static final String[] SUPPORTED_PROJECT_NAMES = new String[] {"AR99"};
|
|
133
|
+
|
|
134
|
+
private static final int CTRL_SENDER_MASK = 0x03;
|
|
135
|
+
|
|
136
|
+
private final Context context;
|
|
137
|
+
private final BluetoothAdapter bluetoothAdapter;
|
|
138
|
+
private final Handler handler;
|
|
139
|
+
private final Handler batteryQueryHandler;
|
|
140
|
+
private final AtomicInteger sequence = new AtomicInteger(1);
|
|
141
|
+
|
|
142
|
+
private final LinkedHashSet<String> discoveredNames = new LinkedHashSet<>();
|
|
143
|
+
private final Deque<byte[]> writeQueue = new ArrayDeque<>();
|
|
144
|
+
private final Deque<byte[]> otaWriteQueue = new ArrayDeque<>();
|
|
145
|
+
private final Deque<BluetoothGattCharacteristic> notifyQueue = new ArrayDeque<>();
|
|
146
|
+
private byte[] controlNotifyBuffer = new byte[0];
|
|
147
|
+
|
|
148
|
+
private BluetoothLeScanner scanner;
|
|
149
|
+
private ScanCallback scanCallback;
|
|
150
|
+
private Runnable scanTimeoutRunnable;
|
|
151
|
+
private String targetIdentifier;
|
|
152
|
+
private String currentProjectName;
|
|
153
|
+
private String currentBroadcastMacAddress;
|
|
154
|
+
|
|
155
|
+
private BluetoothGatt controlGatt;
|
|
156
|
+
private BluetoothGattCharacteristic controlWriteCharacteristic;
|
|
157
|
+
private BluetoothGattCharacteristic controlNotifyCharacteristic;
|
|
158
|
+
private BluetoothGattCharacteristic opusNotifyCharacteristic;
|
|
159
|
+
private BluetoothGattCharacteristic otaWriteCharacteristic;
|
|
160
|
+
private BluetoothGattCharacteristic otaNotifyCharacteristic;
|
|
161
|
+
|
|
162
|
+
/** Lazily created; decodes 48-byte framed Opus from {@link #OPUS_SLAVE_TO_HOST} notifies. */
|
|
163
|
+
private Ar99OpusPcmDecoder opusPcmDecoder;
|
|
164
|
+
|
|
165
|
+
/** Reset in {@link #cleanupGatt}; first notify each session logs INFO with hex preview. */
|
|
166
|
+
private boolean opusLoggedFirstInSession;
|
|
167
|
+
|
|
168
|
+
private boolean isScanning = false;
|
|
169
|
+
@SuppressWarnings("unused")
|
|
170
|
+
private boolean connecting = false;
|
|
171
|
+
private boolean isWriteInFlight = false;
|
|
172
|
+
private boolean isOtaWriteInFlight = false;
|
|
173
|
+
private boolean otaWriteUsesNoResponseMode = false;
|
|
174
|
+
private boolean isOtaNotificationEnabled = false;
|
|
175
|
+
private boolean isNotifyWriteInFlight = false;
|
|
176
|
+
private boolean minimalModeReady = false;
|
|
177
|
+
private boolean displaySessionStarted = false;
|
|
178
|
+
private boolean awaitingMicDisableAck = false;
|
|
179
|
+
private long lastDisplayContentAtMs = 0L;
|
|
180
|
+
private boolean hasPendingDisplayUpdate = false;
|
|
181
|
+
private String pendingDisplayText = "";
|
|
182
|
+
private String lastDisplayText = "";
|
|
183
|
+
private boolean hasReceivedBatteryForCurrentConnection = false;
|
|
184
|
+
private int pendingBatteryRetries = 0;
|
|
185
|
+
private final Runnable writeDrainRunnable =
|
|
186
|
+
new Runnable() {
|
|
187
|
+
@Override
|
|
188
|
+
public void run() {
|
|
189
|
+
isWriteInFlight = false;
|
|
190
|
+
processNextWrite();
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
private final Runnable initialBatteryRetryRunnable =
|
|
195
|
+
new Runnable() {
|
|
196
|
+
@Override
|
|
197
|
+
public void run() {
|
|
198
|
+
if (controlGatt == null || !getConnected()) return;
|
|
199
|
+
if (hasReceivedBatteryForCurrentConnection
|
|
200
|
+
|| pendingBatteryRetries >= MAX_INITIAL_BATTERY_RETRIES) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
pendingBatteryRetries++;
|
|
205
|
+
Bridge.log(TAG + ": retrying battery query attempt " + pendingBatteryRetries);
|
|
206
|
+
getBatteryStatus();
|
|
207
|
+
batteryQueryHandler.postDelayed(this, INITIAL_BATTERY_RETRY_DELAY_MS);
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
private final Runnable periodicBatteryPollRunnable =
|
|
212
|
+
new Runnable() {
|
|
213
|
+
@Override
|
|
214
|
+
public void run() {
|
|
215
|
+
if (controlGatt == null || !getConnected()) return;
|
|
216
|
+
getBatteryStatus();
|
|
217
|
+
batteryQueryHandler.postDelayed(this, BATTERY_POLL_INTERVAL_MS);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
private final Runnable readyDeviceInfoRunnable =
|
|
222
|
+
new Runnable() {
|
|
223
|
+
@Override
|
|
224
|
+
public void run() {
|
|
225
|
+
if (controlGatt == null || !getConnected()) return;
|
|
226
|
+
requestDeviceInfo();
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
private final Runnable readyMinimalModeRunnable =
|
|
231
|
+
new Runnable() {
|
|
232
|
+
@Override
|
|
233
|
+
public void run() {
|
|
234
|
+
if (controlGatt == null || !getConnected()) return;
|
|
235
|
+
setMinimalDisplayMode();
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
private final Runnable readyLanguageRunnable =
|
|
240
|
+
new Runnable() {
|
|
241
|
+
@Override
|
|
242
|
+
public void run() {
|
|
243
|
+
if (controlGatt == null || !getConnected()) return;
|
|
244
|
+
setDisplayLanguage(DISPLAY_LANGUAGE_ENGLISH);
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
private final Runnable readyBrightnessRunnable =
|
|
249
|
+
new Runnable() {
|
|
250
|
+
@Override
|
|
251
|
+
public void run() {
|
|
252
|
+
if (controlGatt == null || !getConnected()) return;
|
|
253
|
+
requestBrightness();
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
private final Runnable readyBatteryRunnable =
|
|
258
|
+
new Runnable() {
|
|
259
|
+
@Override
|
|
260
|
+
public void run() {
|
|
261
|
+
if (controlGatt == null || !getConnected()) return;
|
|
262
|
+
getBatteryStatus();
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
private final BluetoothGattCallback gattCallback =
|
|
267
|
+
new BluetoothGattCallback() {
|
|
268
|
+
@Override
|
|
269
|
+
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
|
|
270
|
+
if (newState == BluetoothProfile.STATE_CONNECTED) {
|
|
271
|
+
Bridge.log(TAG + ": connected to " + (gatt.getDevice() != null ? gatt.getDevice().getName() : ""));
|
|
272
|
+
controlGatt = gatt;
|
|
273
|
+
gatt.discoverServices();
|
|
274
|
+
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
|
|
275
|
+
Bridge.log(TAG + ": disconnected status=" + status);
|
|
276
|
+
if (Ar99OtaManager.getInstance().isOTAInProgress()) {
|
|
277
|
+
Ar99OtaManager.getInstance().onBleDisconnected();
|
|
278
|
+
}
|
|
279
|
+
cleanupGatt();
|
|
280
|
+
updateConnectionState(ConnTypes.DISCONNECTED);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
@Override
|
|
285
|
+
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
|
|
286
|
+
if (status != BluetoothGatt.GATT_SUCCESS) {
|
|
287
|
+
Bridge.log(TAG + ": service discovery failed status=" + status);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
BluetoothGattService controlService = gatt.getService(CTRL_SERVICE_UUID);
|
|
292
|
+
controlWriteCharacteristic = controlService != null ? controlService.getCharacteristic(CTRL_HOST_TO_SLAVE) : null;
|
|
293
|
+
controlNotifyCharacteristic = controlService != null ? controlService.getCharacteristic(CTRL_SLAVE_TO_HOST) : null;
|
|
294
|
+
|
|
295
|
+
BluetoothGattService opusService = gatt.getService(OPUS_SERVICE_UUID);
|
|
296
|
+
opusNotifyCharacteristic = opusService != null ? opusService.getCharacteristic(OPUS_SLAVE_TO_HOST) : null;
|
|
297
|
+
|
|
298
|
+
BluetoothGattService otaService = gatt.getService(OTA_SERVICE_UUID);
|
|
299
|
+
otaWriteCharacteristic = otaService != null ? otaService.getCharacteristic(OTA_WRITE_UUID) : null;
|
|
300
|
+
otaNotifyCharacteristic = otaService != null ? otaService.getCharacteristic(OTA_NOTIFY_UUID) : null;
|
|
301
|
+
isOtaNotificationEnabled = false;
|
|
302
|
+
|
|
303
|
+
notifyQueue.clear();
|
|
304
|
+
if (controlNotifyCharacteristic != null) notifyQueue.add(controlNotifyCharacteristic);
|
|
305
|
+
if (opusNotifyCharacteristic != null) notifyQueue.add(opusNotifyCharacteristic);
|
|
306
|
+
|
|
307
|
+
processNextNotifyDescriptor();
|
|
308
|
+
|
|
309
|
+
if (notifyQueue.isEmpty()) {
|
|
310
|
+
markReady();
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
@Override
|
|
315
|
+
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
|
|
316
|
+
isNotifyWriteInFlight = false;
|
|
317
|
+
if (status != BluetoothGatt.GATT_SUCCESS) {
|
|
318
|
+
Bridge.log(TAG + ": descriptor write failed status=" + status);
|
|
319
|
+
}
|
|
320
|
+
if (otaNotifyCharacteristic != null
|
|
321
|
+
&& descriptor.getCharacteristic() != null
|
|
322
|
+
&& OTA_NOTIFY_UUID.equals(descriptor.getCharacteristic().getUuid())) {
|
|
323
|
+
if (status == BluetoothGatt.GATT_SUCCESS) {
|
|
324
|
+
isOtaNotificationEnabled = true;
|
|
325
|
+
Ar99OtaManager.getInstance().onOtaNotifyEnabled();
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
processNextNotifyDescriptor();
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
@Override
|
|
332
|
+
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
|
|
333
|
+
if (characteristic != null && OTA_WRITE_UUID.equals(characteristic.getUuid())) {
|
|
334
|
+
synchronized (otaWriteQueue) {
|
|
335
|
+
if (otaWriteUsesNoResponseMode) {
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
isOtaWriteInFlight = false;
|
|
339
|
+
}
|
|
340
|
+
if (status != BluetoothGatt.GATT_SUCCESS) {
|
|
341
|
+
Bridge.log(TAG + ": OTA characteristic write failed status=" + status);
|
|
342
|
+
}
|
|
343
|
+
processNextOtaWrite();
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
handler.removeCallbacks(writeDrainRunnable);
|
|
348
|
+
isWriteInFlight = false;
|
|
349
|
+
if (status != BluetoothGatt.GATT_SUCCESS) {
|
|
350
|
+
Bridge.log(TAG + ": characteristic write failed status=" + status);
|
|
351
|
+
}
|
|
352
|
+
processNextWrite();
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
@Override
|
|
356
|
+
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
|
|
357
|
+
byte[] data = characteristic.getValue();
|
|
358
|
+
if (data == null) return;
|
|
359
|
+
|
|
360
|
+
UUID uuid = characteristic.getUuid();
|
|
361
|
+
if (OTA_NOTIFY_UUID.equals(uuid)) {
|
|
362
|
+
Ar99OtaManager.getInstance().handleOTAResponse(data);
|
|
363
|
+
} else if (CTRL_SLAVE_TO_HOST.equals(uuid)) {
|
|
364
|
+
handleControlNotifyChunk(data);
|
|
365
|
+
} else if (OPUS_SLAVE_TO_HOST.equals(uuid)) {
|
|
366
|
+
handleOpusData(data);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
@Override
|
|
371
|
+
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
|
|
372
|
+
if (Ar99OtaManager.getInstance().handleGattMtuChangedForOta(status)) {
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
super.onMtuChanged(gatt, mtu, status);
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
public Ar99() {
|
|
380
|
+
context = Bridge.getContext();
|
|
381
|
+
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
|
|
382
|
+
handler = new Handler(Looper.getMainLooper());
|
|
383
|
+
batteryQueryHandler = new Handler(Looper.getMainLooper());
|
|
384
|
+
|
|
385
|
+
this.type = DeviceTypes.AR99;
|
|
386
|
+
this.hasMic = true;
|
|
387
|
+
|
|
388
|
+
DeviceStore.INSTANCE.apply("glasses", "micEnabled", false);
|
|
389
|
+
Ar99OtaManager.getInstance()
|
|
390
|
+
.setTransport(
|
|
391
|
+
new OtaGattTransport() {
|
|
392
|
+
@Override
|
|
393
|
+
public boolean enableOtaNotification() {
|
|
394
|
+
return Ar99.this.enableOtaNotification();
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
@Override
|
|
398
|
+
public void sendOtaData(byte[] data) {
|
|
399
|
+
Ar99.this.sendOtaData(data);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
@Override
|
|
403
|
+
public void requestMtu(int mtu) {
|
|
404
|
+
BluetoothGatt gatt = controlGatt;
|
|
405
|
+
if (gatt != null) {
|
|
406
|
+
gatt.requestMtu(mtu);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
@Override
|
|
411
|
+
public boolean isBleConnected() {
|
|
412
|
+
return controlGatt != null
|
|
413
|
+
&& controlWriteCharacteristic != null
|
|
414
|
+
&& otaWriteCharacteristic != null
|
|
415
|
+
&& otaNotifyCharacteristic != null;
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
@Override
|
|
421
|
+
public void setMicEnabled(boolean enabled) {
|
|
422
|
+
if (enabled) {
|
|
423
|
+
awaitingMicDisableAck = false;
|
|
424
|
+
DeviceStore.INSTANCE.apply("glasses", "micEnabled", true);
|
|
425
|
+
} else {
|
|
426
|
+
awaitingMicDisableAck = true;
|
|
427
|
+
}
|
|
428
|
+
sendControlDisplayAudioRecord(enabled);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
@Override
|
|
432
|
+
public java.util.List<String> sortMicRanking(java.util.List<String> list) {
|
|
433
|
+
return list;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
@Override
|
|
437
|
+
public void requestPhoto(PhotoRequest request) {
|
|
438
|
+
Bridge.log(TAG + ": requestPhoto not implemented for AR99 yet");
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
@Override
|
|
442
|
+
public void startStream(java.util.Map<String, Object> message) {}
|
|
443
|
+
|
|
444
|
+
@Override
|
|
445
|
+
public void stopStream() {}
|
|
446
|
+
|
|
447
|
+
@Override
|
|
448
|
+
public void sendStreamKeepAlive(java.util.Map<String, Object> message) {}
|
|
449
|
+
|
|
450
|
+
@Override
|
|
451
|
+
public void startVideoRecording(String requestId, boolean save, boolean sound) {}
|
|
452
|
+
|
|
453
|
+
@Override
|
|
454
|
+
public void stopVideoRecording(String requestId) {}
|
|
455
|
+
|
|
456
|
+
@Override
|
|
457
|
+
public void sendButtonPhotoSettings() {}
|
|
458
|
+
|
|
459
|
+
public void sendButtonModeSetting() {}
|
|
460
|
+
|
|
461
|
+
@Override
|
|
462
|
+
public void sendButtonVideoRecordingSettings() {}
|
|
463
|
+
|
|
464
|
+
@Override
|
|
465
|
+
public void sendButtonMaxRecordingTime() {}
|
|
466
|
+
|
|
467
|
+
public void sendButtonCameraLedSetting() {}
|
|
468
|
+
|
|
469
|
+
@Override
|
|
470
|
+
public void sendCameraFovSetting() {}
|
|
471
|
+
|
|
472
|
+
@Override
|
|
473
|
+
public void setBrightness(int level, boolean autoMode) {
|
|
474
|
+
int brightness = Math.max(0, Math.min(100, level));
|
|
475
|
+
enqueueMessage(
|
|
476
|
+
FUNC_COMM_DISPLAY,
|
|
477
|
+
CMD_DISPLAY_SET_AUTO_ADJUST_LIGHT,
|
|
478
|
+
buildProtoMessageBool(1, autoMode),
|
|
479
|
+
TARGET_GLASS_MCU_DISPLAY);
|
|
480
|
+
enqueueMessage(
|
|
481
|
+
FUNC_COMM_DISPLAY,
|
|
482
|
+
CMD_DISPLAY_SET_BRIGHT,
|
|
483
|
+
buildProtoMessageUInt32(1, brightness),
|
|
484
|
+
TARGET_GLASS_MCU_DISPLAY);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
@Override
|
|
488
|
+
public void clearDisplay() {
|
|
489
|
+
clearMinimalText();
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
@Override
|
|
493
|
+
public void sendTextWall(String text) {
|
|
494
|
+
sendMinimalText(text);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
@Override
|
|
498
|
+
public void sendText(String text) {
|
|
499
|
+
sendTextWall(text);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
@Override
|
|
503
|
+
public void sendDoubleTextWall(String top, String bottom) {
|
|
504
|
+
sendMinimalText(joinDisplayLines(top, bottom));
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
@Override
|
|
508
|
+
public boolean displayBitmap(
|
|
509
|
+
String base64ImageData, Integer x, Integer y, Integer width, Integer height) {
|
|
510
|
+
return displayBitmap(base64ImageData);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
public boolean displayBitmap(String base64ImageData) {
|
|
514
|
+
return false;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
@Override
|
|
518
|
+
public void showDashboard() {}
|
|
519
|
+
|
|
520
|
+
@Override
|
|
521
|
+
public void setDashboardPosition(int height, int depth) {}
|
|
522
|
+
|
|
523
|
+
@Override
|
|
524
|
+
public void setHeadUpAngle(int angle) {}
|
|
525
|
+
|
|
526
|
+
@Override
|
|
527
|
+
public void getBatteryStatus() {
|
|
528
|
+
enqueueMessage(
|
|
529
|
+
FUNC_COMM_DISPLAY,
|
|
530
|
+
CMD_DISPLAY_GET_BATTERY,
|
|
531
|
+
new byte[0],
|
|
532
|
+
TARGET_GLASS_MCU_DISPLAY);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
@Override
|
|
536
|
+
public void setSilentMode(boolean enabled) {}
|
|
537
|
+
|
|
538
|
+
@Override
|
|
539
|
+
public void exit() {}
|
|
540
|
+
|
|
541
|
+
@Override
|
|
542
|
+
public void sendShutdown() {}
|
|
543
|
+
|
|
544
|
+
@Override
|
|
545
|
+
public void sendReboot() {}
|
|
546
|
+
|
|
547
|
+
public void sendFactoryReset() {
|
|
548
|
+
Bridge.log(TAG + ": sending factory reset");
|
|
549
|
+
enqueueMessage(
|
|
550
|
+
FUNC_COMM_DISPLAY,
|
|
551
|
+
CMD_DISPLAY_CTRL_FACTORY_RESET,
|
|
552
|
+
buildProtoMessageUInt32(1, FACTORY_RESET_CONFIRM_CODE),
|
|
553
|
+
TARGET_GLASS_MCU_DISPLAY);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
@Override
|
|
557
|
+
public void sendRgbLedControl(
|
|
558
|
+
String requestId,
|
|
559
|
+
String packageName,
|
|
560
|
+
String action,
|
|
561
|
+
String color,
|
|
562
|
+
int ontime,
|
|
563
|
+
int offtime,
|
|
564
|
+
int count) {}
|
|
565
|
+
|
|
566
|
+
@Override
|
|
567
|
+
public void disconnect() {
|
|
568
|
+
stopScan();
|
|
569
|
+
cleanupGatt();
|
|
570
|
+
updateConnectionState(ConnTypes.DISCONNECTED);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
@Override
|
|
574
|
+
public void forget() {
|
|
575
|
+
targetIdentifier = null;
|
|
576
|
+
disconnect();
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
@Override
|
|
580
|
+
public void findCompatibleDevices() {
|
|
581
|
+
discoveredNames.clear();
|
|
582
|
+
startScan(false);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
@Override
|
|
586
|
+
public void connectById(String id) {
|
|
587
|
+
targetIdentifier = id != null ? id.trim() : null;
|
|
588
|
+
discoveredNames.clear();
|
|
589
|
+
startScan(true);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
@Override
|
|
593
|
+
public String getConnectedBluetoothName() {
|
|
594
|
+
if (controlGatt != null && controlGatt.getDevice() != null && controlGatt.getDevice().getName() != null) {
|
|
595
|
+
return controlGatt.getDevice().getName();
|
|
596
|
+
}
|
|
597
|
+
return "";
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
@Override
|
|
601
|
+
public void cleanup() {
|
|
602
|
+
disconnect();
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
@Override
|
|
606
|
+
public void ping() {
|
|
607
|
+
requestDeviceInfo();
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
@Override
|
|
611
|
+
public void dbg1() {}
|
|
612
|
+
|
|
613
|
+
@Override
|
|
614
|
+
public void dbg2() {}
|
|
615
|
+
|
|
616
|
+
@Override
|
|
617
|
+
public void requestWifiScan(String scanId) {}
|
|
618
|
+
|
|
619
|
+
@Override
|
|
620
|
+
public void sendWifiCredentials(String ssid, String password) {}
|
|
621
|
+
|
|
622
|
+
@Override
|
|
623
|
+
public void forgetWifiNetwork(String ssid) {}
|
|
624
|
+
|
|
625
|
+
@Override
|
|
626
|
+
public void sendHotspotState(boolean enabled) {}
|
|
627
|
+
|
|
628
|
+
@Override
|
|
629
|
+
public void sendUserEmailToGlasses(String email) {}
|
|
630
|
+
|
|
631
|
+
@Override
|
|
632
|
+
public void sendIncidentId(String incidentId, String apiBaseUrl) {
|
|
633
|
+
Bridge.log(TAG + ": sendIncidentId not implemented for AR99 yet");
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
@Override
|
|
637
|
+
public void queryGalleryStatus() {}
|
|
638
|
+
|
|
639
|
+
@Override
|
|
640
|
+
public void sendGalleryMode() {}
|
|
641
|
+
|
|
642
|
+
@Override
|
|
643
|
+
public void requestVersionInfo() {
|
|
644
|
+
requestDeviceInfo();
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
private void startScan(boolean forConnection) {
|
|
648
|
+
if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
|
|
649
|
+
Bridge.log(TAG + ": Bluetooth unavailable or disabled");
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
stopScan();
|
|
654
|
+
|
|
655
|
+
scanner = bluetoothAdapter.getBluetoothLeScanner();
|
|
656
|
+
if (scanner == null) {
|
|
657
|
+
Bridge.log(TAG + ": BLE scanner unavailable");
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
updateConnectionState(ConnTypes.SCANNING);
|
|
662
|
+
connecting = forConnection;
|
|
663
|
+
isScanning = true;
|
|
664
|
+
|
|
665
|
+
scanCallback =
|
|
666
|
+
new ScanCallback() {
|
|
667
|
+
@Override
|
|
668
|
+
public void onScanResult(int callbackType, ScanResult result) {
|
|
669
|
+
handleScanResult(result, forConnection);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
@Override
|
|
673
|
+
public void onScanFailed(int errorCode) {
|
|
674
|
+
Bridge.log(TAG + ": scan failed with code=" + errorCode);
|
|
675
|
+
stopScan();
|
|
676
|
+
if (ConnTypes.SCANNING.equals(getConnectionState())) {
|
|
677
|
+
updateConnectionState(ConnTypes.DISCONNECTED);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
};
|
|
681
|
+
|
|
682
|
+
ScanSettings settings =
|
|
683
|
+
new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
|
|
684
|
+
|
|
685
|
+
BluetoothLeScanner localScanner = scanner;
|
|
686
|
+
ScanCallback localCallback = scanCallback;
|
|
687
|
+
if (localScanner == null || localCallback == null) return;
|
|
688
|
+
|
|
689
|
+
localScanner.startScan(Collections.emptyList(), settings, localCallback);
|
|
690
|
+
scanTimeoutRunnable = this::stopScan;
|
|
691
|
+
handler.postDelayed(scanTimeoutRunnable, SCAN_DURATION_MS);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
@Override
|
|
695
|
+
public void stopScan() {
|
|
696
|
+
if (scanTimeoutRunnable != null) {
|
|
697
|
+
handler.removeCallbacks(scanTimeoutRunnable);
|
|
698
|
+
scanTimeoutRunnable = null;
|
|
699
|
+
}
|
|
700
|
+
if (!isScanning) return;
|
|
701
|
+
|
|
702
|
+
ScanCallback cb = scanCallback;
|
|
703
|
+
if (scanner != null && cb != null) {
|
|
704
|
+
try {
|
|
705
|
+
scanner.stopScan(cb);
|
|
706
|
+
} catch (Throwable t) {
|
|
707
|
+
// ignore
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
isScanning = false;
|
|
712
|
+
scanCallback = null;
|
|
713
|
+
scanner = null;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
private void handleScanResult(ScanResult result, boolean forConnection) {
|
|
717
|
+
if (result == null || result.getDevice() == null) return;
|
|
718
|
+
BluetoothDevice device = result.getDevice();
|
|
719
|
+
|
|
720
|
+
String name = device.getName();
|
|
721
|
+
ParsedAdvertisement advertisement = parseAdvertisement(result.getScanRecord() != null ? result.getScanRecord().getBytes() : null);
|
|
722
|
+
if (name == null) name = advertisement != null ? advertisement.bleName : null;
|
|
723
|
+
if (name == null) return;
|
|
724
|
+
|
|
725
|
+
if (!matchesAr99(name, advertisement != null ? advertisement.projectName : null)) return;
|
|
726
|
+
|
|
727
|
+
String displayName = buildDisplayName(name, advertisement, device.getAddress());
|
|
728
|
+
|
|
729
|
+
if (!forConnection) {
|
|
730
|
+
if (discoveredNames.add(displayName)) {
|
|
731
|
+
Bridge.sendDiscoveredDevice(
|
|
732
|
+
type,
|
|
733
|
+
displayName,
|
|
734
|
+
advertisement != null && advertisement.bleAddress != null && !advertisement.bleAddress.trim().isEmpty() ? advertisement.bleAddress.trim() : "",
|
|
735
|
+
result.getRssi(),
|
|
736
|
+
advertisement != null ? advertisement.projectName : null);
|
|
737
|
+
}
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
if (advertisement != null && advertisement.projectName != null && !advertisement.projectName.trim().isEmpty()) {
|
|
741
|
+
currentProjectName = advertisement.projectName.trim();
|
|
742
|
+
currentBroadcastMacAddress = advertisement.bleAddress != null && !advertisement.bleAddress.trim().isEmpty()
|
|
743
|
+
? advertisement.bleAddress.trim()
|
|
744
|
+
: null;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
String target = targetIdentifier != null ? targetIdentifier : "";
|
|
748
|
+
String normalizedTarget = normalizeDisplayIdentifier(target);
|
|
749
|
+
String targetSerial = extractTargetSerial(target);
|
|
750
|
+
boolean matchesTarget =
|
|
751
|
+
target.isEmpty()
|
|
752
|
+
|| name.equalsIgnoreCase(target)
|
|
753
|
+
|| displayName.equalsIgnoreCase(target)
|
|
754
|
+
|| (device.getAddress() != null
|
|
755
|
+
&& (device.getAddress().equalsIgnoreCase(target)
|
|
756
|
+
|| device.getAddress().equalsIgnoreCase(normalizedTarget)))
|
|
757
|
+
|| (advertisement != null
|
|
758
|
+
&& ((advertisement.bleAddress != null
|
|
759
|
+
&& (advertisement.bleAddress.equalsIgnoreCase(target)
|
|
760
|
+
|| advertisement.bleAddress.equalsIgnoreCase(normalizedTarget)))
|
|
761
|
+
|| (advertisement.btAddress != null
|
|
762
|
+
&& (advertisement.btAddress.equalsIgnoreCase(target)
|
|
763
|
+
|| advertisement.btAddress.equalsIgnoreCase(normalizedTarget)))
|
|
764
|
+
|| (advertisement.serialNumber != null
|
|
765
|
+
&& (advertisement.serialNumber.equalsIgnoreCase(target)
|
|
766
|
+
|| advertisement.serialNumber.equalsIgnoreCase(normalizedTarget)))
|
|
767
|
+
|| (advertisement.serialNumber != null && advertisement.serialNumber.equalsIgnoreCase(targetSerial))));
|
|
768
|
+
|
|
769
|
+
if (!matchesTarget) return;
|
|
770
|
+
|
|
771
|
+
String reconnectAddress =
|
|
772
|
+
advertisement != null && advertisement.bleAddress != null && !advertisement.bleAddress.trim().isEmpty()
|
|
773
|
+
? advertisement.bleAddress.trim()
|
|
774
|
+
: device.getAddress();
|
|
775
|
+
if (reconnectAddress != null && !reconnectAddress.trim().isEmpty()) {
|
|
776
|
+
DeviceManager.getInstance().setDeviceAddress(reconnectAddress);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
stopScan();
|
|
780
|
+
connectGatt(device);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
private void connectGatt(BluetoothDevice device) {
|
|
784
|
+
cleanupGatt();
|
|
785
|
+
updateConnectionState(ConnTypes.CONNECTING);
|
|
786
|
+
connecting = true;
|
|
787
|
+
|
|
788
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
789
|
+
controlGatt = device.connectGatt(context, false, gattCallback, BluetoothDevice.TRANSPORT_LE);
|
|
790
|
+
} else {
|
|
791
|
+
controlGatt = device.connectGatt(context, false, gattCallback);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
private void processNextNotifyDescriptor() {
|
|
796
|
+
BluetoothGatt gatt = controlGatt;
|
|
797
|
+
if (gatt == null) return;
|
|
798
|
+
if (isNotifyWriteInFlight) return;
|
|
799
|
+
|
|
800
|
+
BluetoothGattCharacteristic characteristic = notifyQueue.pollFirst();
|
|
801
|
+
if (characteristic == null) {
|
|
802
|
+
markReady();
|
|
803
|
+
Ar99OtaManager.getInstance().tryResumeOtaAfterGattReady();
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
gatt.setCharacteristicNotification(characteristic, true);
|
|
808
|
+
|
|
809
|
+
BluetoothGattDescriptor descriptor = null;
|
|
810
|
+
if (characteristic.getDescriptors() != null) {
|
|
811
|
+
for (BluetoothGattDescriptor d : characteristic.getDescriptors()) {
|
|
812
|
+
if (d != null && CLIENT_CHARACTERISTIC_CONFIG_UUID.equals(d.getUuid())) {
|
|
813
|
+
descriptor = d;
|
|
814
|
+
break;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
if (descriptor == null) {
|
|
820
|
+
processNextNotifyDescriptor();
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
|
|
825
|
+
isNotifyWriteInFlight = gatt.writeDescriptor(descriptor);
|
|
826
|
+
|
|
827
|
+
if (!isNotifyWriteInFlight) {
|
|
828
|
+
Bridge.log(TAG + ": failed to start descriptor write for " + characteristic.getUuid());
|
|
829
|
+
processNextNotifyDescriptor();
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
public boolean startOtaFromFile(String path) {
|
|
834
|
+
if (path == null || path.trim().isEmpty()) {
|
|
835
|
+
sendAr99OtaStatus("failed", 0, 0, 0, "Firmware path is empty");
|
|
836
|
+
return false;
|
|
837
|
+
}
|
|
838
|
+
Ar99OtaManager manager = Ar99OtaManager.getInstance();
|
|
839
|
+
// Match the validated c100_client flow: if the user manually restarts OTA after a
|
|
840
|
+
// BLE disconnect, abandon the paused/in-progress native session first so the fresh
|
|
841
|
+
// start call is not rejected as "already in progress".
|
|
842
|
+
manager.setCallback(null);
|
|
843
|
+
if (manager.isOTAInProgress()) {
|
|
844
|
+
manager.cancelOTA();
|
|
845
|
+
}
|
|
846
|
+
manager.setCallback(
|
|
847
|
+
new Ar99OtaManager.OTACallback() {
|
|
848
|
+
private int lastOffset = 0;
|
|
849
|
+
private int lastTotal = 0;
|
|
850
|
+
|
|
851
|
+
@Override
|
|
852
|
+
public void onProgress(int offset, int total, int progress) {
|
|
853
|
+
lastOffset = offset;
|
|
854
|
+
lastTotal = total;
|
|
855
|
+
sendAr99OtaStatus("transferring", progress, offset, total, null);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
@Override
|
|
859
|
+
public void onCompleted(boolean needReboot) {
|
|
860
|
+
sendAr99OtaStatus("success", 100, lastTotal, lastTotal, null);
|
|
861
|
+
manager.setCallback(null);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
@Override
|
|
865
|
+
public void onError(int errorCode, String message) {
|
|
866
|
+
sendAr99OtaStatus("failed", manager.getProgress(), lastOffset, lastTotal, message);
|
|
867
|
+
manager.setCallback(null);
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
@Override
|
|
871
|
+
public void onCancelled() {
|
|
872
|
+
sendAr99OtaStatus("cancelled", manager.getProgress(), lastOffset, lastTotal, null);
|
|
873
|
+
manager.setCallback(null);
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
@Override
|
|
877
|
+
public void onPausedWaitingReconnect() {
|
|
878
|
+
sendAr99OtaStatus(
|
|
879
|
+
"paused", manager.getProgress(), lastOffset, lastTotal, "Waiting for reconnect");
|
|
880
|
+
}
|
|
881
|
+
});
|
|
882
|
+
sendAr99OtaStatus("preparing", 0, 0, 0, null);
|
|
883
|
+
boolean started = manager.startOTA(new File(path));
|
|
884
|
+
if (!started) {
|
|
885
|
+
sendAr99OtaStatus("failed", 0, 0, 0, "Unable to start AR99 OTA");
|
|
886
|
+
manager.setCallback(null);
|
|
887
|
+
}
|
|
888
|
+
return started;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
public void cancelAr99Ota() {
|
|
892
|
+
Ar99OtaManager.getInstance().cancelOTA();
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
private boolean enableOtaNotification() {
|
|
896
|
+
BluetoothGatt gatt = controlGatt;
|
|
897
|
+
BluetoothGattCharacteristic characteristic = otaNotifyCharacteristic;
|
|
898
|
+
if (gatt == null || characteristic == null) return false;
|
|
899
|
+
if (isOtaNotificationEnabled) {
|
|
900
|
+
Ar99OtaManager.getInstance().onOtaNotifyEnabled();
|
|
901
|
+
return true;
|
|
902
|
+
}
|
|
903
|
+
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_UUID);
|
|
904
|
+
if (descriptor == null) return false;
|
|
905
|
+
gatt.setCharacteristicNotification(characteristic, true);
|
|
906
|
+
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
|
|
907
|
+
return gatt.writeDescriptor(descriptor);
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
private void sendOtaData(byte[] data) {
|
|
911
|
+
if (data == null || data.length == 0) return;
|
|
912
|
+
if (controlGatt == null || otaWriteCharacteristic == null || !isOtaNotificationEnabled) return;
|
|
913
|
+
synchronized (otaWriteQueue) {
|
|
914
|
+
otaWriteQueue.add(data);
|
|
915
|
+
if (!isOtaWriteInFlight) {
|
|
916
|
+
processNextOtaWrite();
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
private void processNextOtaWrite() {
|
|
922
|
+
synchronized (otaWriteQueue) {
|
|
923
|
+
BluetoothGatt gatt = controlGatt;
|
|
924
|
+
BluetoothGattCharacteristic characteristic = otaWriteCharacteristic;
|
|
925
|
+
if (gatt == null || characteristic == null || isOtaWriteInFlight) return;
|
|
926
|
+
byte[] next = otaWriteQueue.pollFirst();
|
|
927
|
+
if (next == null) return;
|
|
928
|
+
|
|
929
|
+
isOtaWriteInFlight = true;
|
|
930
|
+
int props = characteristic.getProperties();
|
|
931
|
+
int writeType =
|
|
932
|
+
(props & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) != 0
|
|
933
|
+
? BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
|
|
934
|
+
: BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT;
|
|
935
|
+
characteristic.setWriteType(writeType);
|
|
936
|
+
otaWriteUsesNoResponseMode = writeType == BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE;
|
|
937
|
+
characteristic.setValue(next);
|
|
938
|
+
boolean started = gatt.writeCharacteristic(characteristic);
|
|
939
|
+
if (!started) {
|
|
940
|
+
isOtaWriteInFlight = false;
|
|
941
|
+
otaWriteUsesNoResponseMode = false;
|
|
942
|
+
otaWriteQueue.addFirst(next);
|
|
943
|
+
handler.postDelayed(this::processNextOtaWrite, 15L);
|
|
944
|
+
} else if (writeType == BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE) {
|
|
945
|
+
isOtaWriteInFlight = false;
|
|
946
|
+
handler.post(this::processNextOtaWrite);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
|
|
952
|
+
private void sendAr99OtaStatus(String phase, int progress, int offset, int total, String errorMessage) {
|
|
953
|
+
java.util.HashMap<String, Object> body = new java.util.HashMap<>();
|
|
954
|
+
body.put("type", "ar99_ota_status");
|
|
955
|
+
body.put("phase", phase);
|
|
956
|
+
body.put("progress", Math.max(0, Math.min(100, progress)));
|
|
957
|
+
body.put("offset", offset);
|
|
958
|
+
body.put("total", total);
|
|
959
|
+
if (errorMessage != null && !errorMessage.isEmpty()) {
|
|
960
|
+
body.put("errorMessage", errorMessage);
|
|
961
|
+
body.put("error_message", errorMessage);
|
|
962
|
+
}
|
|
963
|
+
Bridge.sendTypedMessage("ar99_ota_status", body);
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
private void enqueueMessage(int function, int cmd, byte[] payload, int receiver) {
|
|
967
|
+
if (payload == null) payload = new byte[0];
|
|
968
|
+
byte[] packet = buildHeader(function, cmd, payload, TARGET_MOBILE_APP, receiver, false);
|
|
969
|
+
MessageHeader header = parseHeader(packet);
|
|
970
|
+
if (header != null) {
|
|
971
|
+
logSend(
|
|
972
|
+
">>>"
|
|
973
|
+
+ bytesToHex(packet)
|
|
974
|
+
+ ",len="
|
|
975
|
+
+ packet.length
|
|
976
|
+
+ " function="
|
|
977
|
+
+ header.function
|
|
978
|
+
+ " cmd="
|
|
979
|
+
+ header.cmd);
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
for (int start = 0; start < packet.length; start += BLE_WRITE_CHUNK_SIZE) {
|
|
983
|
+
int end = Math.min(packet.length, start + BLE_WRITE_CHUNK_SIZE);
|
|
984
|
+
writeQueue.add(Arrays.copyOfRange(packet, start, end));
|
|
985
|
+
}
|
|
986
|
+
processNextWrite();
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
private void processNextWrite() {
|
|
990
|
+
BluetoothGatt gatt = controlGatt;
|
|
991
|
+
if (gatt == null) return;
|
|
992
|
+
BluetoothGattCharacteristic characteristic = controlWriteCharacteristic;
|
|
993
|
+
if (characteristic == null) return;
|
|
994
|
+
if (isWriteInFlight) return;
|
|
995
|
+
|
|
996
|
+
byte[] packet = writeQueue.pollFirst();
|
|
997
|
+
if (packet == null) return;
|
|
998
|
+
|
|
999
|
+
characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
|
|
1000
|
+
characteristic.setValue(packet);
|
|
1001
|
+
|
|
1002
|
+
isWriteInFlight = gatt.writeCharacteristic(characteristic);
|
|
1003
|
+
if (!isWriteInFlight) {
|
|
1004
|
+
writeQueue.addFirst(packet);
|
|
1005
|
+
logSend("writeCharacteristic(false) requeue chunk len=" + packet.length);
|
|
1006
|
+
handler.removeCallbacks(writeDrainRunnable);
|
|
1007
|
+
handler.postDelayed(writeDrainRunnable, BLE_WRITE_CHUNK_DELAY_MS * 2);
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
handler.removeCallbacks(writeDrainRunnable);
|
|
1011
|
+
handler.postDelayed(writeDrainRunnable, BLE_WRITE_CHUNK_DELAY_MS);
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
private byte[] buildHeader(
|
|
1015
|
+
int function,
|
|
1016
|
+
int cmd,
|
|
1017
|
+
byte[] payload,
|
|
1018
|
+
int sender,
|
|
1019
|
+
int receiver,
|
|
1020
|
+
boolean isResponse) {
|
|
1021
|
+
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
|
1022
|
+
CodedOutputStream coded = CodedOutputStream.newInstance(output);
|
|
1023
|
+
|
|
1024
|
+
int ctrl =
|
|
1025
|
+
(sender & CTRL_SENDER_MASK)
|
|
1026
|
+
| ((receiver & CTRL_SENDER_MASK) << 2)
|
|
1027
|
+
| (isResponse ? CTRL_ACTION_RESPONSE : 0);
|
|
1028
|
+
int seq = nextSequence();
|
|
1029
|
+
|
|
1030
|
+
try {
|
|
1031
|
+
coded.writeFixed32(1, MAGIC_NUMBER);
|
|
1032
|
+
coded.writeUInt32(2, function);
|
|
1033
|
+
coded.writeUInt32(3, cmd);
|
|
1034
|
+
if (payload != null && payload.length > 0) {
|
|
1035
|
+
coded.writeUInt32(4, payload.length);
|
|
1036
|
+
}
|
|
1037
|
+
coded.writeUInt32(5, ctrl);
|
|
1038
|
+
coded.writeUInt32(6, seq);
|
|
1039
|
+
if (payload != null && payload.length > 0) {
|
|
1040
|
+
coded.writeUInt32(7, checksum16(payload));
|
|
1041
|
+
coded.writeByteArray(8, payload);
|
|
1042
|
+
}
|
|
1043
|
+
coded.flush();
|
|
1044
|
+
} catch (IOException e) {
|
|
1045
|
+
Bridge.log(TAG + ": buildHeader IO error: " + e.getMessage());
|
|
1046
|
+
}
|
|
1047
|
+
return output.toByteArray();
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
private int nextSequence() {
|
|
1051
|
+
while (true) {
|
|
1052
|
+
int seq = sequence.getAndIncrement() & 0x7F;
|
|
1053
|
+
if (seq != 0) return seq;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
private int checksum16(byte[] data) {
|
|
1058
|
+
if (data == null) return 0;
|
|
1059
|
+
int sum = 0;
|
|
1060
|
+
for (byte b : data) {
|
|
1061
|
+
sum += b & 0xFF;
|
|
1062
|
+
}
|
|
1063
|
+
return sum & 0xFFFF;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
// ---------------- Payload builders (minimal protobuf encoding) ----------------
|
|
1067
|
+
|
|
1068
|
+
private byte[] buildProtoMessageUInt32(int fieldNumber, int value) {
|
|
1069
|
+
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
|
1070
|
+
CodedOutputStream coded = CodedOutputStream.newInstance(output);
|
|
1071
|
+
try {
|
|
1072
|
+
coded.writeUInt32(fieldNumber, value);
|
|
1073
|
+
coded.flush();
|
|
1074
|
+
} catch (IOException e) {
|
|
1075
|
+
Bridge.log(TAG + ": buildProtoMessageUInt32 error: " + e.getMessage());
|
|
1076
|
+
}
|
|
1077
|
+
return output.toByteArray();
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
private byte[] buildProtoMessageBool(int fieldNumber, boolean value) {
|
|
1081
|
+
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
|
1082
|
+
CodedOutputStream coded = CodedOutputStream.newInstance(output);
|
|
1083
|
+
try {
|
|
1084
|
+
coded.writeBool(fieldNumber, value);
|
|
1085
|
+
coded.flush();
|
|
1086
|
+
} catch (IOException e) {
|
|
1087
|
+
Bridge.log(TAG + ": buildProtoMessageBool error: " + e.getMessage());
|
|
1088
|
+
}
|
|
1089
|
+
return output.toByteArray();
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
private byte[] buildSetTimePayload(long timestampMs) {
|
|
1093
|
+
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
|
1094
|
+
CodedOutputStream coded = CodedOutputStream.newInstance(output);
|
|
1095
|
+
try {
|
|
1096
|
+
TimeZone timeZone = TimeZone.getDefault();
|
|
1097
|
+
int timezoneOffsetMinutes = timeZone.getOffset(timestampMs) / (60 * 1000);
|
|
1098
|
+
boolean is24Hour = DateFormat.is24HourFormat(context);
|
|
1099
|
+
|
|
1100
|
+
coded.writeInt64(1, timestampMs);
|
|
1101
|
+
coded.writeUInt32(2, 1);
|
|
1102
|
+
coded.writeBool(3, is24Hour);
|
|
1103
|
+
coded.writeInt32(4, timezoneOffsetMinutes);
|
|
1104
|
+
coded.writeString(5, timeZone.getID());
|
|
1105
|
+
coded.flush();
|
|
1106
|
+
} catch (IOException e) {
|
|
1107
|
+
Bridge.log(TAG + ": buildSetTimePayload error: " + e.getMessage());
|
|
1108
|
+
}
|
|
1109
|
+
return output.toByteArray();
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
private byte[] buildTranslationDisplayStartPayload(
|
|
1113
|
+
int sourceLanguage, int targetLanguage, int micSource) {
|
|
1114
|
+
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
|
1115
|
+
CodedOutputStream coded = CodedOutputStream.newInstance(output);
|
|
1116
|
+
try {
|
|
1117
|
+
coded.writeUInt32(1, sourceLanguage);
|
|
1118
|
+
coded.writeUInt32(2, targetLanguage);
|
|
1119
|
+
coded.writeUInt32(3, micSource);
|
|
1120
|
+
coded.flush();
|
|
1121
|
+
} catch (IOException e) {
|
|
1122
|
+
Bridge.log(TAG + ": buildTranslationDisplayStartPayload error: " + e.getMessage());
|
|
1123
|
+
}
|
|
1124
|
+
return output.toByteArray();
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
private byte[] buildTranslationDisplaySetContentPayload(
|
|
1128
|
+
int state, int fontSize, String sourceText, String targetText) {
|
|
1129
|
+
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
|
1130
|
+
CodedOutputStream coded = CodedOutputStream.newInstance(output);
|
|
1131
|
+
try {
|
|
1132
|
+
coded.writeUInt32(1, state);
|
|
1133
|
+
coded.writeUInt32(2, fontSize);
|
|
1134
|
+
coded.writeUInt32(3, TRANS_DISPLAY_SOURCE_LANGUAGE);
|
|
1135
|
+
coded.writeUInt32(4, TRANS_DISPLAY_TARGET_LANGUAGE);
|
|
1136
|
+
coded.writeString(5, sourceText != null ? sourceText : "");
|
|
1137
|
+
coded.writeString(6, targetText != null ? targetText : "");
|
|
1138
|
+
coded.flush();
|
|
1139
|
+
} catch (IOException e) {
|
|
1140
|
+
Bridge.log(TAG + ": buildTranslationDisplaySetContentPayload error: " + e.getMessage());
|
|
1141
|
+
}
|
|
1142
|
+
return output.toByteArray();
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
private byte[] buildDisplaySystemFunctionPayload(int functionType, int action) {
|
|
1146
|
+
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
|
1147
|
+
CodedOutputStream coded = CodedOutputStream.newInstance(output);
|
|
1148
|
+
try {
|
|
1149
|
+
coded.writeUInt32(1, functionType);
|
|
1150
|
+
coded.writeUInt32(2, action);
|
|
1151
|
+
coded.flush();
|
|
1152
|
+
} catch (IOException e) {
|
|
1153
|
+
Bridge.log(TAG + ": buildDisplaySystemFunctionPayload error: " + e.getMessage());
|
|
1154
|
+
}
|
|
1155
|
+
return output.toByteArray();
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
private byte[] buildControlDisplayAudioRecordPayload(boolean enabled) {
|
|
1159
|
+
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
|
1160
|
+
CodedOutputStream coded = CodedOutputStream.newInstance(output);
|
|
1161
|
+
try {
|
|
1162
|
+
// Old 161 protocol uses start=false to begin recording, start=true to stop.
|
|
1163
|
+
coded.writeBool(1, !enabled);
|
|
1164
|
+
coded.flush();
|
|
1165
|
+
} catch (IOException e) {
|
|
1166
|
+
Bridge.log(TAG + ": buildControlDisplayAudioRecordPayload error: " + e.getMessage());
|
|
1167
|
+
}
|
|
1168
|
+
return output.toByteArray();
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
private byte[] buildMinimalTextSetPayload(String text) {
|
|
1172
|
+
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
|
1173
|
+
CodedOutputStream coded = CodedOutputStream.newInstance(output);
|
|
1174
|
+
try {
|
|
1175
|
+
coded.writeString(1, text != null ? text : "");
|
|
1176
|
+
coded.flush();
|
|
1177
|
+
} catch (IOException e) {
|
|
1178
|
+
Bridge.log(TAG + ": buildMinimalTextSetPayload error: " + e.getMessage());
|
|
1179
|
+
}
|
|
1180
|
+
return output.toByteArray();
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
// ---------------- High-level AR99 messages ----------------
|
|
1184
|
+
|
|
1185
|
+
private void sendMinimalText(String text) {
|
|
1186
|
+
String normalizedText = text != null ? text : "";
|
|
1187
|
+
if (!minimalModeReady) {
|
|
1188
|
+
pendingDisplayText = normalizedText;
|
|
1189
|
+
hasPendingDisplayUpdate = true;
|
|
1190
|
+
logSend(
|
|
1191
|
+
"minimal text deferred until minimal mode ready: len=" + pendingDisplayText.length());
|
|
1192
|
+
return;
|
|
1193
|
+
}
|
|
1194
|
+
long now = System.currentTimeMillis();
|
|
1195
|
+
if (displaySessionStarted
|
|
1196
|
+
&& lastDisplayContentAtMs > 0L
|
|
1197
|
+
&& now - lastDisplayContentAtMs >= DISPLAY_SESSION_IDLE_RESTART_MS) {
|
|
1198
|
+
displaySessionStarted = false;
|
|
1199
|
+
lastDisplayText = "";
|
|
1200
|
+
}
|
|
1201
|
+
sendMinimalTextNow(normalizedText);
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
private void sendMinimalTextNow(String text) {
|
|
1205
|
+
String normalizedText = text != null ? text : "";
|
|
1206
|
+
if (normalizedText.equals(lastDisplayText)) {
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
if (normalizedText.isEmpty()) {
|
|
1211
|
+
clearMinimalTextNow();
|
|
1212
|
+
return;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
startMinimalTextIfNeeded();
|
|
1216
|
+
logDisplayRequest(normalizedText);
|
|
1217
|
+
|
|
1218
|
+
enqueueMessage(
|
|
1219
|
+
FUNC_COMM_DISPLAY,
|
|
1220
|
+
CMD_DISPLAY_CTRL_MINIMAL_TEXT_SET,
|
|
1221
|
+
buildMinimalTextSetPayload(normalizedText),
|
|
1222
|
+
TARGET_GLASS_MCU_DISPLAY);
|
|
1223
|
+
|
|
1224
|
+
lastDisplayText = normalizedText;
|
|
1225
|
+
lastDisplayContentAtMs = System.currentTimeMillis();
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
private void flushPendingDisplayUpdateIfNeeded() {
|
|
1229
|
+
if (!minimalModeReady || !hasPendingDisplayUpdate) return;
|
|
1230
|
+
String text = pendingDisplayText;
|
|
1231
|
+
hasPendingDisplayUpdate = false;
|
|
1232
|
+
pendingDisplayText = "";
|
|
1233
|
+
logSend("flushing deferred minimal text update: len=" + text.length());
|
|
1234
|
+
sendMinimalTextNow(text);
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
private void startMinimalTextIfNeeded() {
|
|
1238
|
+
if (displaySessionStarted) return;
|
|
1239
|
+
enqueueMessage(
|
|
1240
|
+
FUNC_COMM_DISPLAY,
|
|
1241
|
+
CMD_DISPLAY_CTRL_MINIMAL_TEXT_START,
|
|
1242
|
+
new byte[0],
|
|
1243
|
+
TARGET_GLASS_MCU_DISPLAY);
|
|
1244
|
+
displaySessionStarted = true;
|
|
1245
|
+
lastDisplayContentAtMs = System.currentTimeMillis();
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
private void clearMinimalText() {
|
|
1249
|
+
if (!minimalModeReady) {
|
|
1250
|
+
pendingDisplayText = "";
|
|
1251
|
+
hasPendingDisplayUpdate = false;
|
|
1252
|
+
lastDisplayText = "";
|
|
1253
|
+
return;
|
|
1254
|
+
}
|
|
1255
|
+
clearMinimalTextNow();
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
private void clearMinimalTextNow() {
|
|
1259
|
+
enqueueMessage(
|
|
1260
|
+
FUNC_COMM_DISPLAY,
|
|
1261
|
+
CMD_DISPLAY_CTRL_MINIMAL_TEXT_CLEAR,
|
|
1262
|
+
new byte[0],
|
|
1263
|
+
TARGET_GLASS_MCU_DISPLAY);
|
|
1264
|
+
resetMinimalTextSession();
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
private void resetMinimalTextSession() {
|
|
1268
|
+
displaySessionStarted = false;
|
|
1269
|
+
lastDisplayContentAtMs = 0L;
|
|
1270
|
+
lastDisplayText = "";
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
private String joinDisplayLines(String top, String bottom) {
|
|
1274
|
+
String first = top != null ? top.trim() : "";
|
|
1275
|
+
String second = bottom != null ? bottom.trim() : "";
|
|
1276
|
+
if (first.isEmpty()) return second;
|
|
1277
|
+
if (second.isEmpty()) return first;
|
|
1278
|
+
return first + "\n" + second;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
private void logDisplayRequest(String text) {
|
|
1282
|
+
logSend(
|
|
1283
|
+
"minimalText len="
|
|
1284
|
+
+ (text != null ? text.length() : 0)
|
|
1285
|
+
+ " text=\""
|
|
1286
|
+
+ formatDisplayTextForLog(text)
|
|
1287
|
+
+ "\"");
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
private String formatDisplayTextForLog(String text) {
|
|
1291
|
+
if (text == null) {
|
|
1292
|
+
return "<null>";
|
|
1293
|
+
}
|
|
1294
|
+
return text.replace("\\", "\\\\").replace("\r", "\\r").replace("\n", "\\n");
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
private void sendControlDisplayAudioRecord(boolean enabled) {
|
|
1298
|
+
byte[] payload = buildControlDisplayAudioRecordPayload(enabled);
|
|
1299
|
+
enqueueMessage(
|
|
1300
|
+
FUNC_COMM_DISPLAY, CMD_DISPLAY_CTRL_AUDIO_RECORD, payload, TARGET_GLASS_ANDROID);
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
private void setMinimalDisplayMode() {
|
|
1304
|
+
enqueueMessage(
|
|
1305
|
+
FUNC_COMM_DISPLAY,
|
|
1306
|
+
CMD_DISPLAY_CTRL_SYS_FUNCTION,
|
|
1307
|
+
buildDisplaySystemFunctionPayload(DISPLAY_FUNCTION_TYPE_UI_MODE, DISPLAY_UI_MODE_MINIMAL),
|
|
1308
|
+
TARGET_GLASS_MCU_DISPLAY);
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
private void setDisplayLanguage(int language) {
|
|
1312
|
+
enqueueMessage(
|
|
1313
|
+
FUNC_COMM_DISPLAY,
|
|
1314
|
+
CMD_DISPLAY_SET_LANGUAGE,
|
|
1315
|
+
buildProtoMessageUInt32(1, language),
|
|
1316
|
+
TARGET_GLASS_MCU_DISPLAY);
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
private void requestDeviceInfo() {
|
|
1320
|
+
enqueueMessage(
|
|
1321
|
+
FUNC_COMM_DISPLAY,
|
|
1322
|
+
CMD_DISPLAY_GET_DEVICEINFO,
|
|
1323
|
+
new byte[0],
|
|
1324
|
+
TARGET_GLASS_MCU_DISPLAY);
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
private void requestBrightness() {
|
|
1328
|
+
enqueueMessage(
|
|
1329
|
+
FUNC_COMM_DISPLAY,
|
|
1330
|
+
CMD_DISPLAY_GET_BRIGHT,
|
|
1331
|
+
new byte[0],
|
|
1332
|
+
TARGET_GLASS_MCU_DISPLAY);
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
@Override
|
|
1336
|
+
public void sendSetSystemTime(long timestampMs) {
|
|
1337
|
+
Bridge.log(TAG + ": sending set time timestampMs=" + timestampMs);
|
|
1338
|
+
enqueueMessage(
|
|
1339
|
+
FUNC_COMM,
|
|
1340
|
+
CMD_COMM_SET_TIME,
|
|
1341
|
+
buildSetTimePayload(timestampMs),
|
|
1342
|
+
TARGET_GLASS_MCU_DISPLAY);
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
private void handleControlPacket(byte[] data) {
|
|
1346
|
+
MessageHeader header = parseHeader(data);
|
|
1347
|
+
if (header == null) return;
|
|
1348
|
+
boolean isResponse = (header.ctrl & CTRL_ACTION_RESPONSE) != 0;
|
|
1349
|
+
logRecv(
|
|
1350
|
+
"<<<"
|
|
1351
|
+
+ bytesToHex(data)
|
|
1352
|
+
+ ",len="
|
|
1353
|
+
+ data.length
|
|
1354
|
+
+ " function="
|
|
1355
|
+
+ header.function
|
|
1356
|
+
+ " cmd="
|
|
1357
|
+
+ header.cmd
|
|
1358
|
+
+ " ctrl="
|
|
1359
|
+
+ header.ctrl
|
|
1360
|
+
+ " isResponse="
|
|
1361
|
+
+ isResponse);
|
|
1362
|
+
|
|
1363
|
+
if (header.function == FUNC_COMM) {
|
|
1364
|
+
handleCommMessage(header.cmd, isResponse, header.payload);
|
|
1365
|
+
} else if (header.function == FUNC_COMM_DISPLAY) {
|
|
1366
|
+
if (header.cmd == CMD_DISPLAY_CTRL_AUDIO_RECORD) {
|
|
1367
|
+
handleLegacyAudioRecordMessage(isResponse, header.payload);
|
|
1368
|
+
} else {
|
|
1369
|
+
handleDisplayMessage(header.cmd, isResponse, header.payload);
|
|
1370
|
+
}
|
|
1371
|
+
} else if (header.function == FUNC_TRANS) {
|
|
1372
|
+
handleTransMessage(header.cmd, isResponse, header.payload);
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
private void handleTransMessage(int cmd, boolean isResponse, byte[] payload) {
|
|
1377
|
+
if (!isResponse && cmd == CMD_TRANS_DISPLAY_STOP) {
|
|
1378
|
+
setMicEnabled(false);
|
|
1379
|
+
resetMinimalTextSession();
|
|
1380
|
+
logRecv("parsed trans stop request; local display session reset");
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
if (isResponse && cmd == CMD_TRANS_DISPLAY_STOP) {
|
|
1385
|
+
Boolean success = parseSuccessResponse(payload);
|
|
1386
|
+
if (success != null && success) {
|
|
1387
|
+
resetMinimalTextSession();
|
|
1388
|
+
}
|
|
1389
|
+
logRecv(
|
|
1390
|
+
"parsed trans stop response success="
|
|
1391
|
+
+ success
|
|
1392
|
+
+ " localDisplaySessionStarted="
|
|
1393
|
+
+ displaySessionStarted);
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
if (isResponse && cmd == CMD_TRANS_DISPLAY_START) {
|
|
1398
|
+
Boolean success = parseSuccessResponse(payload);
|
|
1399
|
+
if (success == null || !success) {
|
|
1400
|
+
logRecv(
|
|
1401
|
+
"parsed trans start response success="
|
|
1402
|
+
+ success
|
|
1403
|
+
+ " localDisplaySessionStarted="
|
|
1404
|
+
+ displaySessionStarted);
|
|
1405
|
+
}
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
if (isResponse && cmd == CMD_TRANS_DISPLAY_SET_CONTENT) {
|
|
1410
|
+
Boolean success = parseSuccessResponse(payload);
|
|
1411
|
+
if (success == null || !success) {
|
|
1412
|
+
logRecv(
|
|
1413
|
+
"parsed trans set_content response success="
|
|
1414
|
+
+ success
|
|
1415
|
+
+ " localDisplaySessionStarted="
|
|
1416
|
+
+ displaySessionStarted);
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
private void handleDisplayMessage(int cmd, boolean isResponse, byte[] payload) {
|
|
1422
|
+
if (cmd == CMD_DISPLAY_GET_BATTERY) {
|
|
1423
|
+
BatteryInfo info = parseBatteryResponse(payload);
|
|
1424
|
+
if (info != null) {
|
|
1425
|
+
logRecv(
|
|
1426
|
+
"parsed cmd="
|
|
1427
|
+
+ cmd
|
|
1428
|
+
+ " isResponse="
|
|
1429
|
+
+ isResponse
|
|
1430
|
+
+ " battery="
|
|
1431
|
+
+ info.battery
|
|
1432
|
+
+ " charging="
|
|
1433
|
+
+ info.charging);
|
|
1434
|
+
if (info.battery >= 0 && info.battery <= 100) {
|
|
1435
|
+
DeviceStore.INSTANCE.apply("glasses", "batteryLevel", info.battery);
|
|
1436
|
+
boolean charging =
|
|
1437
|
+
info.charging != null
|
|
1438
|
+
? info.charging
|
|
1439
|
+
: (DeviceStore.INSTANCE.get("glasses", "charging") instanceof Boolean
|
|
1440
|
+
? (Boolean) DeviceStore.INSTANCE.get("glasses", "charging")
|
|
1441
|
+
: false);
|
|
1442
|
+
if (info.charging != null) {
|
|
1443
|
+
DeviceStore.INSTANCE.apply("glasses", "charging", charging);
|
|
1444
|
+
}
|
|
1445
|
+
Bridge.sendBatteryStatus(info.battery, charging);
|
|
1446
|
+
hasReceivedBatteryForCurrentConnection = true;
|
|
1447
|
+
pendingBatteryRetries = 0;
|
|
1448
|
+
batteryQueryHandler.removeCallbacks(initialBatteryRetryRunnable);
|
|
1449
|
+
} else {
|
|
1450
|
+
Bridge.log(TAG + ": ignoring out-of-range battery response: " + info.battery);
|
|
1451
|
+
}
|
|
1452
|
+
} else {
|
|
1453
|
+
Bridge.log(
|
|
1454
|
+
TAG + ": failed to parse battery response payload len="
|
|
1455
|
+
+ payload.length
|
|
1456
|
+
+ " hex="
|
|
1457
|
+
+ bytesToHex(payload));
|
|
1458
|
+
}
|
|
1459
|
+
} else if (cmd == CMD_DISPLAY_GET_DEVICEINFO) {
|
|
1460
|
+
DeviceInfo info = parseDeviceInfoResponse(payload);
|
|
1461
|
+
if (info != null) {
|
|
1462
|
+
logRecv(
|
|
1463
|
+
"parsed cmd="
|
|
1464
|
+
+ cmd
|
|
1465
|
+
+ " isResponse="
|
|
1466
|
+
+ isResponse
|
|
1467
|
+
+ " firmware="
|
|
1468
|
+
+ info.firmwareVersion
|
|
1469
|
+
+ " product="
|
|
1470
|
+
+ info.productName
|
|
1471
|
+
+ " btMac="
|
|
1472
|
+
+ info.btMac
|
|
1473
|
+
+ " serial="
|
|
1474
|
+
+ info.serialNumber
|
|
1475
|
+
+ " deviceName="
|
|
1476
|
+
+ info.deviceName);
|
|
1477
|
+
if (info.firmwareVersion != null) {
|
|
1478
|
+
DeviceStore.INSTANCE.apply("glasses", "firmwareVersion", info.firmwareVersion);
|
|
1479
|
+
}
|
|
1480
|
+
if (info.productName != null) {
|
|
1481
|
+
DeviceStore.INSTANCE.apply("glasses", "deviceModel", info.productName);
|
|
1482
|
+
}
|
|
1483
|
+
String preferredBtMac = currentBroadcastMacAddress != null && !currentBroadcastMacAddress.trim().isEmpty() ? currentBroadcastMacAddress : info.btMac;
|
|
1484
|
+
if (preferredBtMac != null && !preferredBtMac.trim().isEmpty()) {
|
|
1485
|
+
DeviceStore.INSTANCE.apply("glasses", "bluetoothMacAddress", preferredBtMac);
|
|
1486
|
+
}
|
|
1487
|
+
if (info.serialNumber != null) {
|
|
1488
|
+
DeviceStore.INSTANCE.apply("glasses", "serialNumber", info.serialNumber);
|
|
1489
|
+
}
|
|
1490
|
+
sendVersionInfo(info);
|
|
1491
|
+
}
|
|
1492
|
+
} else if (cmd == CMD_DISPLAY_GET_BRIGHT) {
|
|
1493
|
+
Integer brightness = parseBrightnessResponse(payload);
|
|
1494
|
+
if (brightness != null) {
|
|
1495
|
+
DeviceStore.INSTANCE.apply("glasses", "brightness", brightness);
|
|
1496
|
+
logRecv(
|
|
1497
|
+
"parsed cmd="
|
|
1498
|
+
+ cmd
|
|
1499
|
+
+ " isResponse="
|
|
1500
|
+
+ isResponse
|
|
1501
|
+
+ " brightness="
|
|
1502
|
+
+ brightness);
|
|
1503
|
+
}
|
|
1504
|
+
} else if (cmd == CMD_DISPLAY_SET_LANGUAGE) {
|
|
1505
|
+
LanguageResponse response = parseLanguageResponse(payload);
|
|
1506
|
+
if (response != null) {
|
|
1507
|
+
logRecv(
|
|
1508
|
+
"parsed cmd="
|
|
1509
|
+
+ cmd
|
|
1510
|
+
+ " isResponse="
|
|
1511
|
+
+ isResponse
|
|
1512
|
+
+ " success="
|
|
1513
|
+
+ response.success
|
|
1514
|
+
+ " language="
|
|
1515
|
+
+ response.language);
|
|
1516
|
+
} else {
|
|
1517
|
+
Bridge.log(
|
|
1518
|
+
TAG
|
|
1519
|
+
+ ": failed to parse display response cmd="
|
|
1520
|
+
+ cmd
|
|
1521
|
+
+ " payload="
|
|
1522
|
+
+ bytesToHex(payload));
|
|
1523
|
+
}
|
|
1524
|
+
} else if (cmd == CMD_DISPLAY_SET_BRIGHT) {
|
|
1525
|
+
Integer brightness = parseSetBrightnessResponse(payload);
|
|
1526
|
+
if (brightness != null) {
|
|
1527
|
+
DeviceStore.INSTANCE.apply("glasses", "brightness", brightness);
|
|
1528
|
+
logRecv(
|
|
1529
|
+
"parsed cmd="
|
|
1530
|
+
+ cmd
|
|
1531
|
+
+ " isResponse="
|
|
1532
|
+
+ isResponse
|
|
1533
|
+
+ " currentBrightness="
|
|
1534
|
+
+ brightness);
|
|
1535
|
+
}
|
|
1536
|
+
} else if (cmd == CMD_DISPLAY_CTRL_FACTORY_RESET) {
|
|
1537
|
+
Boolean success = null;
|
|
1538
|
+
Integer confirmCode = null;
|
|
1539
|
+
try {
|
|
1540
|
+
CodedInputStream input = CodedInputStream.newInstance(payload);
|
|
1541
|
+
while (!input.isAtEnd()) {
|
|
1542
|
+
int tag = input.readTag();
|
|
1543
|
+
if (tag == 0) break;
|
|
1544
|
+
switch (WireFormat.getTagFieldNumber(tag)) {
|
|
1545
|
+
case 1:
|
|
1546
|
+
success = input.readBool();
|
|
1547
|
+
break;
|
|
1548
|
+
case 2:
|
|
1549
|
+
confirmCode = input.readUInt32();
|
|
1550
|
+
break;
|
|
1551
|
+
default:
|
|
1552
|
+
input.skipField(tag);
|
|
1553
|
+
break;
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
} catch (IOException e) {
|
|
1557
|
+
Bridge.log(TAG + ": failed parsing factory reset response: " + e.getMessage());
|
|
1558
|
+
}
|
|
1559
|
+
logRecv(
|
|
1560
|
+
"parsed cmd="
|
|
1561
|
+
+ cmd
|
|
1562
|
+
+ " isResponse="
|
|
1563
|
+
+ isResponse
|
|
1564
|
+
+ " success="
|
|
1565
|
+
+ success
|
|
1566
|
+
+ " confirmCode="
|
|
1567
|
+
+ confirmCode);
|
|
1568
|
+
} else if (cmd == CMD_DISPLAY_CTRL_SYS_FUNCTION) {
|
|
1569
|
+
Boolean success = parseSuccessResponse(payload);
|
|
1570
|
+
if (success != null) {
|
|
1571
|
+
logRecv("parsed cmd=" + cmd + " isResponse=" + isResponse + " success=" + success);
|
|
1572
|
+
if (isResponse && success) {
|
|
1573
|
+
minimalModeReady = true;
|
|
1574
|
+
flushPendingDisplayUpdateIfNeeded();
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
} else if (cmd == CMD_DISPLAY_CTRL_MINIMAL_TEXT_START
|
|
1578
|
+
|| cmd == CMD_DISPLAY_CTRL_MINIMAL_TEXT_SET
|
|
1579
|
+
|| cmd == CMD_DISPLAY_CTRL_MINIMAL_TEXT_CLEAR
|
|
1580
|
+
|| cmd == CMD_DISPLAY_CTRL_MINIMAL_TEXT_STOP) {
|
|
1581
|
+
Boolean success = parseSuccessResponse(payload);
|
|
1582
|
+
logRecv("parsed cmd=" + cmd + " isResponse=" + isResponse + " success=" + success);
|
|
1583
|
+
if (isResponse && Boolean.FALSE.equals(success)) {
|
|
1584
|
+
if (cmd == CMD_DISPLAY_CTRL_MINIMAL_TEXT_START) {
|
|
1585
|
+
displaySessionStarted = false;
|
|
1586
|
+
} else if (cmd == CMD_DISPLAY_CTRL_MINIMAL_TEXT_CLEAR
|
|
1587
|
+
|| cmd == CMD_DISPLAY_CTRL_MINIMAL_TEXT_STOP) {
|
|
1588
|
+
resetMinimalTextSession();
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
private void handleCommMessage(int cmd, boolean isResponse, byte[] payload) {
|
|
1595
|
+
if (cmd == CMD_COMM_SET_TIME) {
|
|
1596
|
+
SetTimeResponse response = parseSetTimeResponse(payload);
|
|
1597
|
+
if (response != null) {
|
|
1598
|
+
StringBuilder log =
|
|
1599
|
+
new StringBuilder("parsed cmd=")
|
|
1600
|
+
.append(cmd)
|
|
1601
|
+
.append(" isResponse=")
|
|
1602
|
+
.append(isResponse)
|
|
1603
|
+
.append(" success=")
|
|
1604
|
+
.append(response.success);
|
|
1605
|
+
if (response.timeValue != null) {
|
|
1606
|
+
log.append(" timeValue=").append(response.timeValue);
|
|
1607
|
+
}
|
|
1608
|
+
logRecv(log.toString());
|
|
1609
|
+
} else {
|
|
1610
|
+
Bridge.log(
|
|
1611
|
+
TAG + ": failed to parse comm response cmd=" + cmd + " payload=" + bytesToHex(payload));
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
private void handleLegacyAudioRecordMessage(boolean isResponse, byte[] payload) {
|
|
1617
|
+
Boolean currentRecording = parseLegacyAudioRecordResponse(payload);
|
|
1618
|
+
if (currentRecording != null) {
|
|
1619
|
+
if (!currentRecording) {
|
|
1620
|
+
awaitingMicDisableAck = false;
|
|
1621
|
+
if (opusPcmDecoder != null) {
|
|
1622
|
+
opusPcmDecoder.resetBuffer();
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
DeviceStore.INSTANCE.apply("glasses", "micEnabled", currentRecording);
|
|
1626
|
+
logRecv(
|
|
1627
|
+
"parsed legacy cmd="
|
|
1628
|
+
+ CMD_DISPLAY_CTRL_AUDIO_RECORD
|
|
1629
|
+
+ " isResponse="
|
|
1630
|
+
+ isResponse
|
|
1631
|
+
+ " currentRecording="
|
|
1632
|
+
+ currentRecording);
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
private CommonResult parseCommonResult(CodedInputStream input) throws IOException {
|
|
1637
|
+
byte[] raw = input.readByteArray();
|
|
1638
|
+
return parseCommonResult(raw);
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
private CommonResult parseCommonResult(byte[] raw) throws IOException {
|
|
1642
|
+
CodedInputStream nested = CodedInputStream.newInstance(raw);
|
|
1643
|
+
boolean success = false;
|
|
1644
|
+
int errorCode = 0;
|
|
1645
|
+
String errorMessage = "";
|
|
1646
|
+
|
|
1647
|
+
while (!nested.isAtEnd()) {
|
|
1648
|
+
int tag = nested.readTag();
|
|
1649
|
+
if (tag == 0) break;
|
|
1650
|
+
switch (WireFormat.getTagFieldNumber(tag)) {
|
|
1651
|
+
case 1:
|
|
1652
|
+
success = nested.readBool();
|
|
1653
|
+
break;
|
|
1654
|
+
case 2:
|
|
1655
|
+
errorCode = nested.readUInt32();
|
|
1656
|
+
break;
|
|
1657
|
+
case 3:
|
|
1658
|
+
errorMessage = nested.readString();
|
|
1659
|
+
break;
|
|
1660
|
+
default:
|
|
1661
|
+
nested.skipField(tag);
|
|
1662
|
+
break;
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
return new CommonResult(success, errorCode, errorMessage);
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
private Boolean readSuccessField(CodedInputStream input, int tag) throws IOException {
|
|
1670
|
+
int wireType = WireFormat.getTagWireType(tag);
|
|
1671
|
+
if (wireType == WireFormat.WIRETYPE_VARINT) {
|
|
1672
|
+
return input.readBool();
|
|
1673
|
+
}
|
|
1674
|
+
if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
|
|
1675
|
+
return parseCommonResult(input).success;
|
|
1676
|
+
}
|
|
1677
|
+
input.skipField(tag);
|
|
1678
|
+
return null;
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
private BatteryInfo parseBatteryResponse(byte[] payload) {
|
|
1682
|
+
BatteryInfo nested = parseBatteryResponseNested(payload);
|
|
1683
|
+
if (nested != null) {
|
|
1684
|
+
return nested;
|
|
1685
|
+
}
|
|
1686
|
+
return parseBatteryResponseFlat(payload);
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
private BatteryInfo parseBatteryResponseNested(byte[] payload) {
|
|
1690
|
+
try {
|
|
1691
|
+
CodedInputStream input = CodedInputStream.newInstance(payload);
|
|
1692
|
+
Boolean success = null;
|
|
1693
|
+
int battery = -1;
|
|
1694
|
+
Boolean charging = null;
|
|
1695
|
+
|
|
1696
|
+
while (!input.isAtEnd()) {
|
|
1697
|
+
int tag = input.readTag();
|
|
1698
|
+
if (tag == 0) break;
|
|
1699
|
+
switch (WireFormat.getTagFieldNumber(tag)) {
|
|
1700
|
+
case 1:
|
|
1701
|
+
success = readSuccessField(input, tag);
|
|
1702
|
+
break;
|
|
1703
|
+
case 2:
|
|
1704
|
+
battery = input.readUInt32();
|
|
1705
|
+
break;
|
|
1706
|
+
case 3:
|
|
1707
|
+
charging = input.readBool();
|
|
1708
|
+
break;
|
|
1709
|
+
default:
|
|
1710
|
+
input.skipField(tag);
|
|
1711
|
+
break;
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
if (success == null || !success) return null;
|
|
1716
|
+
return new BatteryInfo(battery, charging);
|
|
1717
|
+
} catch (Throwable t) {
|
|
1718
|
+
return null;
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
/**
|
|
1723
|
+
* Some AR99 firmware revisions appear to flatten the battery response instead of nesting a
|
|
1724
|
+
* CommonResult submessage in field 1. Accept both layouts so the UI doesn't stay stuck at -1.
|
|
1725
|
+
*/
|
|
1726
|
+
private BatteryInfo parseBatteryResponseFlat(byte[] payload) {
|
|
1727
|
+
try {
|
|
1728
|
+
CodedInputStream input = CodedInputStream.newInstance(payload);
|
|
1729
|
+
Boolean success = null;
|
|
1730
|
+
Integer battery = null;
|
|
1731
|
+
Boolean charging = null;
|
|
1732
|
+
|
|
1733
|
+
while (!input.isAtEnd()) {
|
|
1734
|
+
int tag = input.readTag();
|
|
1735
|
+
if (tag == 0) break;
|
|
1736
|
+
|
|
1737
|
+
int fieldNumber = WireFormat.getTagFieldNumber(tag);
|
|
1738
|
+
int wireType = WireFormat.getTagWireType(tag);
|
|
1739
|
+
|
|
1740
|
+
switch (fieldNumber) {
|
|
1741
|
+
case 1:
|
|
1742
|
+
if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
|
|
1743
|
+
CommonResult result = parseCommonResult(input.readByteArray());
|
|
1744
|
+
success = result.success;
|
|
1745
|
+
} else if (wireType == WireFormat.WIRETYPE_VARINT) {
|
|
1746
|
+
long value = input.readUInt64();
|
|
1747
|
+
if (value == 0L || value == 1L) {
|
|
1748
|
+
success = value == 1L;
|
|
1749
|
+
} else if (battery == null && value >= 0L && value <= 100L) {
|
|
1750
|
+
battery = (int) value;
|
|
1751
|
+
}
|
|
1752
|
+
} else {
|
|
1753
|
+
input.skipField(tag);
|
|
1754
|
+
}
|
|
1755
|
+
break;
|
|
1756
|
+
case 2:
|
|
1757
|
+
if (wireType == WireFormat.WIRETYPE_VARINT) {
|
|
1758
|
+
long value = input.readUInt64();
|
|
1759
|
+
if (battery == null && value >= 0L && value <= 100L) {
|
|
1760
|
+
battery = (int) value;
|
|
1761
|
+
} else if (charging == null && (value == 0L || value == 1L)) {
|
|
1762
|
+
charging = value == 1L;
|
|
1763
|
+
}
|
|
1764
|
+
} else {
|
|
1765
|
+
input.skipField(tag);
|
|
1766
|
+
}
|
|
1767
|
+
break;
|
|
1768
|
+
case 3:
|
|
1769
|
+
if (wireType == WireFormat.WIRETYPE_VARINT) {
|
|
1770
|
+
charging = input.readBool();
|
|
1771
|
+
} else {
|
|
1772
|
+
input.skipField(tag);
|
|
1773
|
+
}
|
|
1774
|
+
break;
|
|
1775
|
+
default:
|
|
1776
|
+
input.skipField(tag);
|
|
1777
|
+
break;
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
if (battery == null) {
|
|
1782
|
+
return null;
|
|
1783
|
+
}
|
|
1784
|
+
if (success != null && !success) {
|
|
1785
|
+
return null;
|
|
1786
|
+
}
|
|
1787
|
+
return new BatteryInfo(battery, charging);
|
|
1788
|
+
} catch (Throwable t) {
|
|
1789
|
+
return null;
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
private DeviceInfo parseDeviceInfoResponse(byte[] payload) {
|
|
1794
|
+
try {
|
|
1795
|
+
CodedInputStream input = CodedInputStream.newInstance(payload);
|
|
1796
|
+
Boolean success = null;
|
|
1797
|
+
String firmware = null;
|
|
1798
|
+
String version = null;
|
|
1799
|
+
String deviceName = null;
|
|
1800
|
+
String productName = null;
|
|
1801
|
+
String btMac = null;
|
|
1802
|
+
String serial = null;
|
|
1803
|
+
|
|
1804
|
+
while (!input.isAtEnd()) {
|
|
1805
|
+
int tag = input.readTag();
|
|
1806
|
+
if (tag == 0) break;
|
|
1807
|
+
switch (WireFormat.getTagFieldNumber(tag)) {
|
|
1808
|
+
case 1:
|
|
1809
|
+
success = readSuccessField(input, tag);
|
|
1810
|
+
break;
|
|
1811
|
+
case 2:
|
|
1812
|
+
firmware = input.readString();
|
|
1813
|
+
break;
|
|
1814
|
+
case 5:
|
|
1815
|
+
version = input.readString();
|
|
1816
|
+
break;
|
|
1817
|
+
case 6:
|
|
1818
|
+
deviceName = input.readString();
|
|
1819
|
+
break;
|
|
1820
|
+
case 7:
|
|
1821
|
+
productName = input.readString();
|
|
1822
|
+
break;
|
|
1823
|
+
case 8:
|
|
1824
|
+
btMac = input.readString();
|
|
1825
|
+
break;
|
|
1826
|
+
case 9:
|
|
1827
|
+
serial = input.readString();
|
|
1828
|
+
break;
|
|
1829
|
+
default:
|
|
1830
|
+
input.skipField(tag);
|
|
1831
|
+
break;
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
if (success == null || !success) return null;
|
|
1836
|
+
return new DeviceInfo(
|
|
1837
|
+
version != null && !version.isEmpty() ? version : firmware,
|
|
1838
|
+
deviceName,
|
|
1839
|
+
productName,
|
|
1840
|
+
btMac,
|
|
1841
|
+
serial);
|
|
1842
|
+
} catch (Throwable t) {
|
|
1843
|
+
return null;
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
private LanguageResponse parseLanguageResponse(byte[] payload) {
|
|
1848
|
+
try {
|
|
1849
|
+
CodedInputStream input = CodedInputStream.newInstance(payload);
|
|
1850
|
+
Boolean success = null;
|
|
1851
|
+
Integer language = null;
|
|
1852
|
+
|
|
1853
|
+
while (!input.isAtEnd()) {
|
|
1854
|
+
int tag = input.readTag();
|
|
1855
|
+
if (tag == 0) break;
|
|
1856
|
+
switch (WireFormat.getTagFieldNumber(tag)) {
|
|
1857
|
+
case 1:
|
|
1858
|
+
success = readSuccessField(input, tag);
|
|
1859
|
+
break;
|
|
1860
|
+
case 2:
|
|
1861
|
+
language = input.readUInt32();
|
|
1862
|
+
break;
|
|
1863
|
+
default:
|
|
1864
|
+
input.skipField(tag);
|
|
1865
|
+
break;
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
if (success == null) return null;
|
|
1870
|
+
return new LanguageResponse(success, language);
|
|
1871
|
+
} catch (Throwable t) {
|
|
1872
|
+
return null;
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
private SetTimeResponse parseSetTimeResponse(byte[] payload) {
|
|
1877
|
+
try {
|
|
1878
|
+
CodedInputStream input = CodedInputStream.newInstance(payload);
|
|
1879
|
+
Boolean success = null;
|
|
1880
|
+
Long timeValue = null;
|
|
1881
|
+
|
|
1882
|
+
while (!input.isAtEnd()) {
|
|
1883
|
+
int tag = input.readTag();
|
|
1884
|
+
if (tag == 0) break;
|
|
1885
|
+
int fieldNumber = WireFormat.getTagFieldNumber(tag);
|
|
1886
|
+
int wireType = WireFormat.getTagWireType(tag);
|
|
1887
|
+
|
|
1888
|
+
switch (fieldNumber) {
|
|
1889
|
+
case 1:
|
|
1890
|
+
success = readSuccessField(input, tag);
|
|
1891
|
+
break;
|
|
1892
|
+
case 2:
|
|
1893
|
+
if (wireType == WireFormat.WIRETYPE_VARINT) {
|
|
1894
|
+
timeValue = input.readUInt64();
|
|
1895
|
+
} else {
|
|
1896
|
+
input.skipField(tag);
|
|
1897
|
+
}
|
|
1898
|
+
break;
|
|
1899
|
+
default:
|
|
1900
|
+
input.skipField(tag);
|
|
1901
|
+
break;
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
if (success == null) return null;
|
|
1906
|
+
return new SetTimeResponse(success, timeValue);
|
|
1907
|
+
} catch (Throwable t) {
|
|
1908
|
+
return null;
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
private void sendVersionInfo(DeviceInfo info) {
|
|
1913
|
+
HashMap<String, Object> values = new HashMap<>();
|
|
1914
|
+
if (info.firmwareVersion != null) {
|
|
1915
|
+
values.put("firmwareVersion", info.firmwareVersion);
|
|
1916
|
+
}
|
|
1917
|
+
if (info.productName != null) {
|
|
1918
|
+
values.put("deviceModel", info.productName);
|
|
1919
|
+
}
|
|
1920
|
+
if (info.serialNumber != null) {
|
|
1921
|
+
values.put("serialNumber", info.serialNumber);
|
|
1922
|
+
}
|
|
1923
|
+
Bridge.sendVersionInfo(values);
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
private Integer parseBrightnessResponse(byte[] payload) {
|
|
1927
|
+
try {
|
|
1928
|
+
CodedInputStream input = CodedInputStream.newInstance(payload);
|
|
1929
|
+
Boolean success = null;
|
|
1930
|
+
Integer brightness = null;
|
|
1931
|
+
|
|
1932
|
+
while (!input.isAtEnd()) {
|
|
1933
|
+
int tag = input.readTag();
|
|
1934
|
+
if (tag == 0) break;
|
|
1935
|
+
switch (WireFormat.getTagFieldNumber(tag)) {
|
|
1936
|
+
case 1:
|
|
1937
|
+
success = readSuccessField(input, tag);
|
|
1938
|
+
break;
|
|
1939
|
+
case 2:
|
|
1940
|
+
brightness = input.readUInt32();
|
|
1941
|
+
break;
|
|
1942
|
+
default:
|
|
1943
|
+
input.skipField(tag);
|
|
1944
|
+
break;
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
return success != null && success ? brightness : null;
|
|
1949
|
+
} catch (Throwable t) {
|
|
1950
|
+
return null;
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
private Integer parseSetBrightnessResponse(byte[] payload) {
|
|
1955
|
+
try {
|
|
1956
|
+
CodedInputStream input = CodedInputStream.newInstance(payload);
|
|
1957
|
+
Boolean success = null;
|
|
1958
|
+
Integer brightness = null;
|
|
1959
|
+
|
|
1960
|
+
while (!input.isAtEnd()) {
|
|
1961
|
+
int tag = input.readTag();
|
|
1962
|
+
if (tag == 0) break;
|
|
1963
|
+
switch (WireFormat.getTagFieldNumber(tag)) {
|
|
1964
|
+
case 1:
|
|
1965
|
+
success = readSuccessField(input, tag);
|
|
1966
|
+
break;
|
|
1967
|
+
case 2:
|
|
1968
|
+
brightness = input.readUInt32();
|
|
1969
|
+
break;
|
|
1970
|
+
default:
|
|
1971
|
+
input.skipField(tag);
|
|
1972
|
+
break;
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
return success != null && success ? brightness : null;
|
|
1977
|
+
} catch (Throwable t) {
|
|
1978
|
+
return null;
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
private Boolean parseSuccessResponse(byte[] payload) {
|
|
1983
|
+
try {
|
|
1984
|
+
CodedInputStream input = CodedInputStream.newInstance(payload);
|
|
1985
|
+
Boolean success = null;
|
|
1986
|
+
|
|
1987
|
+
while (!input.isAtEnd()) {
|
|
1988
|
+
int tag = input.readTag();
|
|
1989
|
+
if (tag == 0) break;
|
|
1990
|
+
switch (WireFormat.getTagFieldNumber(tag)) {
|
|
1991
|
+
case 1:
|
|
1992
|
+
success = readSuccessField(input, tag);
|
|
1993
|
+
break;
|
|
1994
|
+
default:
|
|
1995
|
+
input.skipField(tag);
|
|
1996
|
+
break;
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
return success;
|
|
2001
|
+
} catch (Throwable t) {
|
|
2002
|
+
return null;
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
|
|
2006
|
+
private Boolean parseLegacyAudioRecordResponse(byte[] payload) {
|
|
2007
|
+
try {
|
|
2008
|
+
CodedInputStream input = CodedInputStream.newInstance(payload);
|
|
2009
|
+
Boolean success = null;
|
|
2010
|
+
Boolean start = null;
|
|
2011
|
+
|
|
2012
|
+
while (!input.isAtEnd()) {
|
|
2013
|
+
int tag = input.readTag();
|
|
2014
|
+
if (tag == 0) break;
|
|
2015
|
+
switch (WireFormat.getTagFieldNumber(tag)) {
|
|
2016
|
+
case 1:
|
|
2017
|
+
success = input.readBool();
|
|
2018
|
+
break;
|
|
2019
|
+
case 2:
|
|
2020
|
+
start = input.readBool();
|
|
2021
|
+
break;
|
|
2022
|
+
default:
|
|
2023
|
+
input.skipField(tag);
|
|
2024
|
+
break;
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
if (success == null || !success || start == null) {
|
|
2029
|
+
return null;
|
|
2030
|
+
}
|
|
2031
|
+
return !start;
|
|
2032
|
+
} catch (Throwable t) {
|
|
2033
|
+
return null;
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
private static final class LanguageResponse {
|
|
2038
|
+
final boolean success;
|
|
2039
|
+
final Integer language;
|
|
2040
|
+
|
|
2041
|
+
LanguageResponse(boolean success, Integer language) {
|
|
2042
|
+
this.success = success;
|
|
2043
|
+
this.language = language;
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2047
|
+
private static final class SetTimeResponse {
|
|
2048
|
+
final boolean success;
|
|
2049
|
+
final Long timeValue;
|
|
2050
|
+
|
|
2051
|
+
SetTimeResponse(boolean success, Long timeValue) {
|
|
2052
|
+
this.success = success;
|
|
2053
|
+
this.timeValue = timeValue;
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
private void handleControlNotifyChunk(byte[] data) {
|
|
2058
|
+
if (data == null || data.length == 0) return;
|
|
2059
|
+
|
|
2060
|
+
if (controlNotifyBuffer.length == 0) {
|
|
2061
|
+
controlNotifyBuffer = Arrays.copyOf(data, data.length);
|
|
2062
|
+
} else {
|
|
2063
|
+
byte[] merged = Arrays.copyOf(controlNotifyBuffer, controlNotifyBuffer.length + data.length);
|
|
2064
|
+
System.arraycopy(data, 0, merged, controlNotifyBuffer.length, data.length);
|
|
2065
|
+
controlNotifyBuffer = merged;
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
while (true) {
|
|
2069
|
+
int start = findMessageStart(controlNotifyBuffer);
|
|
2070
|
+
if (start < 0) {
|
|
2071
|
+
trimControlNotifyBuffer();
|
|
2072
|
+
return;
|
|
2073
|
+
}
|
|
2074
|
+
if (start > 0) {
|
|
2075
|
+
controlNotifyBuffer =
|
|
2076
|
+
Arrays.copyOfRange(controlNotifyBuffer, start, controlNotifyBuffer.length);
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
int nextStart = findMessageStart(controlNotifyBuffer, 1);
|
|
2080
|
+
byte[] candidatePacket =
|
|
2081
|
+
nextStart > 0
|
|
2082
|
+
? Arrays.copyOfRange(controlNotifyBuffer, 0, nextStart)
|
|
2083
|
+
: controlNotifyBuffer;
|
|
2084
|
+
|
|
2085
|
+
MessageHeader header = parseHeader(candidatePacket);
|
|
2086
|
+
if (header == null) {
|
|
2087
|
+
if (nextStart > 0) {
|
|
2088
|
+
controlNotifyBuffer =
|
|
2089
|
+
Arrays.copyOfRange(controlNotifyBuffer, nextStart, controlNotifyBuffer.length);
|
|
2090
|
+
continue;
|
|
2091
|
+
}
|
|
2092
|
+
trimControlNotifyBuffer();
|
|
2093
|
+
return;
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
if (controlNotifyBuffer.length < header.totalBytes) {
|
|
2097
|
+
trimControlNotifyBuffer();
|
|
2098
|
+
return;
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
byte[] packet = Arrays.copyOfRange(controlNotifyBuffer, 0, header.totalBytes);
|
|
2102
|
+
controlNotifyBuffer =
|
|
2103
|
+
Arrays.copyOfRange(controlNotifyBuffer, header.totalBytes, controlNotifyBuffer.length);
|
|
2104
|
+
handleControlPacket(packet);
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
private void trimControlNotifyBuffer() {
|
|
2109
|
+
if (controlNotifyBuffer.length <= 2048) return;
|
|
2110
|
+
|
|
2111
|
+
int candidateStart = findMessageStart(controlNotifyBuffer);
|
|
2112
|
+
if (candidateStart > 0 && candidateStart < controlNotifyBuffer.length) {
|
|
2113
|
+
controlNotifyBuffer = Arrays.copyOfRange(controlNotifyBuffer, candidateStart, controlNotifyBuffer.length);
|
|
2114
|
+
return;
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2117
|
+
controlNotifyBuffer = new byte[0];
|
|
2118
|
+
}
|
|
2119
|
+
|
|
2120
|
+
private int findMessageStart(byte[] data) {
|
|
2121
|
+
return findMessageStart(data, 0);
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
private int findMessageStart(byte[] data, int fromIndex) {
|
|
2125
|
+
if (data == null || data.length < 5) return -1;
|
|
2126
|
+
|
|
2127
|
+
int start = Math.max(0, fromIndex);
|
|
2128
|
+
for (int i = start; i <= data.length - 5; i++) {
|
|
2129
|
+
if ((data[i] & 0xFF) == 0x0D
|
|
2130
|
+
&& (data[i + 1] & 0xFF) == 0x4D
|
|
2131
|
+
&& (data[i + 2] & 0xFF) == 0x3C
|
|
2132
|
+
&& (data[i + 3] & 0xFF) == 0x2B
|
|
2133
|
+
&& (data[i + 4] & 0xFF) == 0x1A) {
|
|
2134
|
+
return i;
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
return -1;
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2141
|
+
|
|
2142
|
+
private void handleOpusData(byte[] data) {
|
|
2143
|
+
if (data == null || data.length == 0) {
|
|
2144
|
+
return;
|
|
2145
|
+
}
|
|
2146
|
+
// Logcat: adb logcat -s Ar99Opus (DEBUG = per-chunk length; INFO = first chunk + hex)
|
|
2147
|
+
Log.d(LOG_TAG_OPUS, "notify len=" + data.length);
|
|
2148
|
+
if (!opusLoggedFirstInSession) {
|
|
2149
|
+
opusLoggedFirstInSession = true;
|
|
2150
|
+
Log.i(
|
|
2151
|
+
LOG_TAG_OPUS,
|
|
2152
|
+
"Opus BLE (first chunk) len=" + data.length + " head16=" + hexPrefixUpper(data, 16));
|
|
2153
|
+
}
|
|
2154
|
+
if (!getMicEnabled()) {
|
|
2155
|
+
Log.w(LOG_TAG_OPUS, "received OPUS while glasses micEnabled=false; decoding anyway");
|
|
2156
|
+
}
|
|
2157
|
+
ensureOpusDecoder();
|
|
2158
|
+
if (opusPcmDecoder == null) {
|
|
2159
|
+
Log.w(LOG_TAG_OPUS, "decoder unavailable; skipping PCM");
|
|
2160
|
+
return;
|
|
2161
|
+
}
|
|
2162
|
+
opusPcmDecoder.submitBleNotify(data);
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
private static String hexPrefixUpper(byte[] data, int maxBytes) {
|
|
2166
|
+
int n = Math.min(maxBytes, data.length);
|
|
2167
|
+
StringBuilder sb = new StringBuilder(n * 3);
|
|
2168
|
+
for (int i = 0; i < n; i++) {
|
|
2169
|
+
if (i > 0) {
|
|
2170
|
+
sb.append(' ');
|
|
2171
|
+
}
|
|
2172
|
+
sb.append(String.format("%02X", data[i] & 0xFF));
|
|
2173
|
+
}
|
|
2174
|
+
if (data.length > maxBytes) {
|
|
2175
|
+
sb.append(" ...");
|
|
2176
|
+
}
|
|
2177
|
+
return sb.toString();
|
|
2178
|
+
}
|
|
2179
|
+
|
|
2180
|
+
private static String bytesToHex(byte[] data) {
|
|
2181
|
+
if (data == null || data.length == 0) return "";
|
|
2182
|
+
StringBuilder sb = new StringBuilder(data.length * 2);
|
|
2183
|
+
for (byte b : data) {
|
|
2184
|
+
sb.append(String.format("%02X", b & 0xFF));
|
|
2185
|
+
}
|
|
2186
|
+
return sb.toString();
|
|
2187
|
+
}
|
|
2188
|
+
|
|
2189
|
+
private void logSend(String message) {
|
|
2190
|
+
Log.d(TAG, message);
|
|
2191
|
+
}
|
|
2192
|
+
|
|
2193
|
+
private void logRecv(String message) {
|
|
2194
|
+
Log.d(TAG, message);
|
|
2195
|
+
}
|
|
2196
|
+
|
|
2197
|
+
private synchronized void ensureOpusDecoder() {
|
|
2198
|
+
if (opusPcmDecoder != null) {
|
|
2199
|
+
return;
|
|
2200
|
+
}
|
|
2201
|
+
try {
|
|
2202
|
+
opusPcmDecoder =
|
|
2203
|
+
new Ar99OpusPcmDecoder(
|
|
2204
|
+
new Ar99OpusPcmDecoder.Callback() {
|
|
2205
|
+
@Override
|
|
2206
|
+
public void onPcmDecoded(byte[] pcmData) {
|
|
2207
|
+
DeviceManager.getInstance().reportGlassesAudioActivity();
|
|
2208
|
+
DeviceManager.getInstance().handlePcm(pcmData);
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2211
|
+
@Override
|
|
2212
|
+
public void onNotifyProcessed(int rawLen, int pcmFrames, int pcmBytesOut) {
|
|
2213
|
+
if (pcmFrames > 0) {
|
|
2214
|
+
Log.d(
|
|
2215
|
+
LOG_TAG_OPUS,
|
|
2216
|
+
"decoded PCM chunks="
|
|
2217
|
+
+ pcmFrames
|
|
2218
|
+
+ " bytesOut="
|
|
2219
|
+
+ pcmBytesOut
|
|
2220
|
+
+ " rawLen="
|
|
2221
|
+
+ rawLen);
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
});
|
|
2225
|
+
} catch (Throwable e) {
|
|
2226
|
+
Bridge.log(TAG + ": failed to create Opus decoder: " + e.getMessage());
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
// ---------------- Parsing ----------------
|
|
2231
|
+
|
|
2232
|
+
private MessageHeader parseHeader(byte[] data) {
|
|
2233
|
+
try {
|
|
2234
|
+
CodedInputStream input = CodedInputStream.newInstance(data);
|
|
2235
|
+
int magic = 0;
|
|
2236
|
+
int function = 0;
|
|
2237
|
+
int cmd = 0;
|
|
2238
|
+
int ctrl = 0;
|
|
2239
|
+
int seq = 0;
|
|
2240
|
+
byte[] payload = new byte[0];
|
|
2241
|
+
int totalBytes = -1;
|
|
2242
|
+
|
|
2243
|
+
while (!input.isAtEnd()) {
|
|
2244
|
+
int tag = input.readTag();
|
|
2245
|
+
if (tag == 0) break;
|
|
2246
|
+
int fieldNumber = WireFormat.getTagFieldNumber(tag);
|
|
2247
|
+
switch (fieldNumber) {
|
|
2248
|
+
case 1:
|
|
2249
|
+
magic = input.readFixed32();
|
|
2250
|
+
break;
|
|
2251
|
+
case 2:
|
|
2252
|
+
function = input.readUInt32();
|
|
2253
|
+
break;
|
|
2254
|
+
case 3:
|
|
2255
|
+
cmd = input.readUInt32();
|
|
2256
|
+
break;
|
|
2257
|
+
case 5:
|
|
2258
|
+
ctrl = input.readUInt32();
|
|
2259
|
+
break;
|
|
2260
|
+
case 6:
|
|
2261
|
+
seq = input.readUInt32();
|
|
2262
|
+
break;
|
|
2263
|
+
case 8:
|
|
2264
|
+
payload = input.readByteArray();
|
|
2265
|
+
totalBytes = input.getTotalBytesRead();
|
|
2266
|
+
break;
|
|
2267
|
+
default:
|
|
2268
|
+
input.skipField(tag);
|
|
2269
|
+
break;
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2273
|
+
if (magic != MAGIC_NUMBER) return null;
|
|
2274
|
+
if (totalBytes <= 0) {
|
|
2275
|
+
totalBytes = data.length;
|
|
2276
|
+
}
|
|
2277
|
+
return new MessageHeader(function, cmd, ctrl, seq, payload, totalBytes);
|
|
2278
|
+
} catch (Throwable t) {
|
|
2279
|
+
return null;
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
|
|
2283
|
+
|
|
2284
|
+
// ---------------- Connection state ----------------
|
|
2285
|
+
|
|
2286
|
+
private void markReady() {
|
|
2287
|
+
if (controlWriteCharacteristic == null || controlNotifyCharacteristic == null) return;
|
|
2288
|
+
if (ConnTypes.CONNECTED.equals(getConnectionState()) && getConnected() && getFullyBooted()) return;
|
|
2289
|
+
|
|
2290
|
+
minimalModeReady = false;
|
|
2291
|
+
hasPendingDisplayUpdate = false;
|
|
2292
|
+
pendingDisplayText = "";
|
|
2293
|
+
resetMinimalTextSession();
|
|
2294
|
+
hasReceivedBatteryForCurrentConnection = false;
|
|
2295
|
+
if (currentProjectName != null && !currentProjectName.trim().isEmpty()) {
|
|
2296
|
+
DeviceStore.INSTANCE.apply("bluetooth", "project_name", currentProjectName.trim());
|
|
2297
|
+
}
|
|
2298
|
+
String connectedBluetoothName = getConnectedBluetoothName();
|
|
2299
|
+
if (connectedBluetoothName != null && !connectedBluetoothName.trim().isEmpty()) {
|
|
2300
|
+
DeviceStore.INSTANCE.apply("glasses", "bluetoothName", connectedBluetoothName.trim());
|
|
2301
|
+
}
|
|
2302
|
+
DeviceStore.INSTANCE.apply("glasses", "connected", true);
|
|
2303
|
+
DeviceStore.INSTANCE.apply("glasses", "fullyBooted", true);
|
|
2304
|
+
updateConnectionState(ConnTypes.CONNECTED);
|
|
2305
|
+
|
|
2306
|
+
handler.removeCallbacks(readyMinimalModeRunnable);
|
|
2307
|
+
handler.removeCallbacks(readyLanguageRunnable);
|
|
2308
|
+
handler.removeCallbacks(readyDeviceInfoRunnable);
|
|
2309
|
+
handler.removeCallbacks(readyBrightnessRunnable);
|
|
2310
|
+
handler.removeCallbacks(readyBatteryRunnable);
|
|
2311
|
+
handler.postDelayed(readyMinimalModeRunnable, READY_MINIMAL_MODE_DELAY_MS);
|
|
2312
|
+
handler.postDelayed(readyLanguageRunnable, READY_LANGUAGE_DELAY_MS);
|
|
2313
|
+
handler.postDelayed(readyDeviceInfoRunnable, READY_DEVICE_INFO_DELAY_MS);
|
|
2314
|
+
handler.postDelayed(readyBrightnessRunnable, READY_BRIGHTNESS_DELAY_MS);
|
|
2315
|
+
handler.postDelayed(readyBatteryRunnable, READY_BATTERY_DELAY_MS);
|
|
2316
|
+
scheduleBatteryQueries();
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2319
|
+
private void scheduleBatteryQueries() {
|
|
2320
|
+
pendingBatteryRetries = 0;
|
|
2321
|
+
batteryQueryHandler.removeCallbacks(initialBatteryRetryRunnable);
|
|
2322
|
+
batteryQueryHandler.removeCallbacks(periodicBatteryPollRunnable);
|
|
2323
|
+
|
|
2324
|
+
batteryQueryHandler.postDelayed(initialBatteryRetryRunnable, INITIAL_BATTERY_RETRY_DELAY_MS);
|
|
2325
|
+
batteryQueryHandler.postDelayed(periodicBatteryPollRunnable, BATTERY_POLL_INTERVAL_MS);
|
|
2326
|
+
}
|
|
2327
|
+
|
|
2328
|
+
private void cleanupGatt() {
|
|
2329
|
+
writeQueue.clear();
|
|
2330
|
+
synchronized (otaWriteQueue) {
|
|
2331
|
+
otaWriteQueue.clear();
|
|
2332
|
+
isOtaWriteInFlight = false;
|
|
2333
|
+
otaWriteUsesNoResponseMode = false;
|
|
2334
|
+
}
|
|
2335
|
+
notifyQueue.clear();
|
|
2336
|
+
isWriteInFlight = false;
|
|
2337
|
+
isOtaNotificationEnabled = false;
|
|
2338
|
+
isNotifyWriteInFlight = false;
|
|
2339
|
+
minimalModeReady = false;
|
|
2340
|
+
resetMinimalTextSession();
|
|
2341
|
+
hasPendingDisplayUpdate = false;
|
|
2342
|
+
pendingDisplayText = "";
|
|
2343
|
+
hasReceivedBatteryForCurrentConnection = false;
|
|
2344
|
+
controlNotifyBuffer = new byte[0];
|
|
2345
|
+
pendingBatteryRetries = 0;
|
|
2346
|
+
handler.removeCallbacks(readyMinimalModeRunnable);
|
|
2347
|
+
handler.removeCallbacks(readyLanguageRunnable);
|
|
2348
|
+
handler.removeCallbacks(readyDeviceInfoRunnable);
|
|
2349
|
+
handler.removeCallbacks(readyBrightnessRunnable);
|
|
2350
|
+
handler.removeCallbacks(readyBatteryRunnable);
|
|
2351
|
+
handler.removeCallbacks(writeDrainRunnable);
|
|
2352
|
+
batteryQueryHandler.removeCallbacks(initialBatteryRetryRunnable);
|
|
2353
|
+
batteryQueryHandler.removeCallbacks(periodicBatteryPollRunnable);
|
|
2354
|
+
|
|
2355
|
+
controlWriteCharacteristic = null;
|
|
2356
|
+
controlNotifyCharacteristic = null;
|
|
2357
|
+
opusNotifyCharacteristic = null;
|
|
2358
|
+
otaWriteCharacteristic = null;
|
|
2359
|
+
otaNotifyCharacteristic = null;
|
|
2360
|
+
|
|
2361
|
+
BluetoothGatt gatt = controlGatt;
|
|
2362
|
+
if (gatt != null) {
|
|
2363
|
+
try {
|
|
2364
|
+
gatt.disconnect();
|
|
2365
|
+
} catch (Throwable t) {
|
|
2366
|
+
// ignore
|
|
2367
|
+
}
|
|
2368
|
+
try {
|
|
2369
|
+
gatt.close();
|
|
2370
|
+
} catch (Throwable t) {
|
|
2371
|
+
// ignore
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
controlGatt = null;
|
|
2375
|
+
|
|
2376
|
+
opusLoggedFirstInSession = false;
|
|
2377
|
+
|
|
2378
|
+
if (opusPcmDecoder != null) {
|
|
2379
|
+
opusPcmDecoder.resetBuffer();
|
|
2380
|
+
}
|
|
2381
|
+
|
|
2382
|
+
awaitingMicDisableAck = false;
|
|
2383
|
+
DeviceStore.INSTANCE.apply("glasses", "connected", false);
|
|
2384
|
+
DeviceStore.INSTANCE.apply("glasses", "fullyBooted", false);
|
|
2385
|
+
DeviceStore.INSTANCE.apply("glasses", "micEnabled", false);
|
|
2386
|
+
DeviceStore.INSTANCE.apply("glasses", "batteryLevel", -1);
|
|
2387
|
+
DeviceStore.INSTANCE.apply("glasses", "charging", false);
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
private void updateConnectionState(String state) {
|
|
2391
|
+
DeviceStore.INSTANCE.apply("glasses", "connectionState", state);
|
|
2392
|
+
}
|
|
2393
|
+
|
|
2394
|
+
// ---------------- Advertisement parsing (BtUtil-like) ----------------
|
|
2395
|
+
|
|
2396
|
+
private boolean matchesAr99(String name, String projectName) {
|
|
2397
|
+
return isSupportedProjectName(projectName);
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
static boolean isSupportedProjectName(String projectName) {
|
|
2401
|
+
String project = projectName != null ? projectName.trim() : "";
|
|
2402
|
+
for (String supported : SUPPORTED_PROJECT_NAMES) {
|
|
2403
|
+
if (project.equalsIgnoreCase(supported)) return true;
|
|
2404
|
+
}
|
|
2405
|
+
return false;
|
|
2406
|
+
}
|
|
2407
|
+
private ParsedAdvertisement parseAdvertisement(byte[] scanRecord) {
|
|
2408
|
+
if (scanRecord == null || scanRecord.length == 0) return null;
|
|
2409
|
+
|
|
2410
|
+
int pos = 0;
|
|
2411
|
+
String bleName = null;
|
|
2412
|
+
String projectName = null;
|
|
2413
|
+
String bleAddress = null;
|
|
2414
|
+
String btAddress = null;
|
|
2415
|
+
String serialNumber = null;
|
|
2416
|
+
|
|
2417
|
+
while (pos < scanRecord.length - 1) {
|
|
2418
|
+
int size = scanRecord[pos] & 0xFF;
|
|
2419
|
+
if (size == 0 || pos + size >= scanRecord.length) break;
|
|
2420
|
+
int type = scanRecord[pos + 1] & 0xFF;
|
|
2421
|
+
|
|
2422
|
+
switch (type) {
|
|
2423
|
+
case 0x09:
|
|
2424
|
+
bleName = new String(scanRecord, pos + 2, size - 1);
|
|
2425
|
+
break;
|
|
2426
|
+
case 0xFF:
|
|
2427
|
+
int p = pos + 2;
|
|
2428
|
+
if (size >= 21 && p + 19 < scanRecord.length) {
|
|
2429
|
+
projectName = trimAscii4(scanRecord, p);
|
|
2430
|
+
bleAddress = xorMac6ToColon(scanRecord, p + 8);
|
|
2431
|
+
btAddress = xorMac6ToColon(scanRecord, p + 14);
|
|
2432
|
+
serialNumber = parseAr99SerialNumber(scanRecord, pos, size);
|
|
2433
|
+
}
|
|
2434
|
+
break;
|
|
2435
|
+
default:
|
|
2436
|
+
break;
|
|
2437
|
+
}
|
|
2438
|
+
|
|
2439
|
+
pos += size + 1;
|
|
2440
|
+
}
|
|
2441
|
+
|
|
2442
|
+
if (!matchesAr99(bleName, projectName)) return null;
|
|
2443
|
+
return new ParsedAdvertisement(bleName, projectName, bleAddress, btAddress, serialNumber);
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2446
|
+
private String trimAscii4(byte[] data, int offset) {
|
|
2447
|
+
int end = offset + 4;
|
|
2448
|
+
while (end > offset && data[end - 1] == 0) end--;
|
|
2449
|
+
if (end <= offset) return "";
|
|
2450
|
+
try {
|
|
2451
|
+
return new String(data, offset, end - offset, "UTF-8");
|
|
2452
|
+
} catch (Exception e) {
|
|
2453
|
+
return "";
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2456
|
+
|
|
2457
|
+
private String xorMac6ToColon(byte[] data, int offset) {
|
|
2458
|
+
StringBuilder sb = new StringBuilder();
|
|
2459
|
+
for (int i = 0; i < 6; i++) {
|
|
2460
|
+
int decoded = (data[offset + i] ^ 0xAD) & 0xFF;
|
|
2461
|
+
if (i > 0) sb.append(":");
|
|
2462
|
+
sb.append(String.format("%02X", decoded));
|
|
2463
|
+
}
|
|
2464
|
+
return sb.toString();
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
private String parseAr99SerialNumber(byte[] scanRecord, int fieldStart, int size) {
|
|
2468
|
+
String project = trimAscii4(scanRecord, fieldStart + 2);
|
|
2469
|
+
if (!matchesAr99(null, project)) return null;
|
|
2470
|
+
|
|
2471
|
+
int lastPayloadIdx = fieldStart + size;
|
|
2472
|
+
int serialOffset = fieldStart + 22;
|
|
2473
|
+
|
|
2474
|
+
if (serialOffset + 12 <= scanRecord.length && serialOffset + 12 - 1 <= lastPayloadIdx) {
|
|
2475
|
+
if (!isAllZeroBytes(scanRecord, serialOffset, 12) && isAsciiDigits(scanRecord, serialOffset, 12)) {
|
|
2476
|
+
return safeAscii(scanRecord, serialOffset, 12);
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2480
|
+
if (serialOffset + 6 <= scanRecord.length && serialOffset + 6 - 1 <= lastPayloadIdx) {
|
|
2481
|
+
if (!isAllZeroBytes(scanRecord, serialOffset, 6) && isAsciiDigits(scanRecord, serialOffset, 6)) {
|
|
2482
|
+
return safeAscii(scanRecord, serialOffset, 6);
|
|
2483
|
+
}
|
|
2484
|
+
if (!isAllZeroBytes(scanRecord, serialOffset, 6)) {
|
|
2485
|
+
return bytes6ToHex12(scanRecord, serialOffset);
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
|
|
2489
|
+
return null;
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
private boolean isAsciiDigits(byte[] data, int offset, int length) {
|
|
2493
|
+
for (int i = 0; i < length; i++) {
|
|
2494
|
+
int b = data[offset + i] & 0xFF;
|
|
2495
|
+
if (b < '0' || b > '9') return false;
|
|
2496
|
+
}
|
|
2497
|
+
return true;
|
|
2498
|
+
}
|
|
2499
|
+
|
|
2500
|
+
private boolean isAllZeroBytes(byte[] data, int offset, int length) {
|
|
2501
|
+
boolean allZero = true;
|
|
2502
|
+
boolean allAsciiZero = true;
|
|
2503
|
+
for (int i = 0; i < length; i++) {
|
|
2504
|
+
int b = data[offset + i] & 0xFF;
|
|
2505
|
+
if (b != 0) {
|
|
2506
|
+
allZero = false;
|
|
2507
|
+
}
|
|
2508
|
+
if (b != '0') {
|
|
2509
|
+
allAsciiZero = false;
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
return allZero || allAsciiZero;
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2515
|
+
private String safeAscii(byte[] data, int offset, int length) {
|
|
2516
|
+
try {
|
|
2517
|
+
return new String(data, offset, length, "UTF-8");
|
|
2518
|
+
} catch (Exception e) {
|
|
2519
|
+
return null;
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2523
|
+
private String bytes6ToHex12(byte[] data, int offset) {
|
|
2524
|
+
StringBuilder sb = new StringBuilder(12);
|
|
2525
|
+
for (int i = 0; i < 6; i++) {
|
|
2526
|
+
sb.append(String.format("%02X", data[offset + i] & 0xFF));
|
|
2527
|
+
}
|
|
2528
|
+
return sb.toString();
|
|
2529
|
+
}
|
|
2530
|
+
|
|
2531
|
+
private String buildDisplayName(
|
|
2532
|
+
String fallbackName, ParsedAdvertisement advertisement, String deviceAddress) {
|
|
2533
|
+
if (advertisement != null && advertisement.serialNumber != null && !advertisement.serialNumber.trim().isEmpty()) {
|
|
2534
|
+
return "SN: " + advertisement.serialNumber.trim();
|
|
2535
|
+
}
|
|
2536
|
+
String bleName = advertisement != null ? advertisement.bleName : fallbackName;
|
|
2537
|
+
String legacyNameSerial = extractLegacyNameSerial(bleName);
|
|
2538
|
+
if (!legacyNameSerial.isEmpty()) {
|
|
2539
|
+
return "SN: " + legacyNameSerial;
|
|
2540
|
+
}
|
|
2541
|
+
if (deviceAddress != null && !deviceAddress.trim().isEmpty()) {
|
|
2542
|
+
return "MAC: " + deviceAddress.trim();
|
|
2543
|
+
}
|
|
2544
|
+
return fallbackName;
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
private String extractTargetSerial(String target) {
|
|
2548
|
+
String trimmed = normalizeDisplayIdentifier(target);
|
|
2549
|
+
if (trimmed.isEmpty()) return "";
|
|
2550
|
+
int underscore = trimmed.lastIndexOf('_');
|
|
2551
|
+
if (underscore >= 0 && underscore + 1 < trimmed.length()) {
|
|
2552
|
+
return trimmed.substring(underscore + 1).trim();
|
|
2553
|
+
}
|
|
2554
|
+
return trimmed;
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2557
|
+
private String normalizeDisplayIdentifier(String value) {
|
|
2558
|
+
if (value == null) return "";
|
|
2559
|
+
String trimmed = value.trim();
|
|
2560
|
+
String upper = trimmed.toUpperCase();
|
|
2561
|
+
if (upper.startsWith("SN:")) {
|
|
2562
|
+
return trimmed.substring(3).trim();
|
|
2563
|
+
}
|
|
2564
|
+
if (upper.startsWith("MAC:")) {
|
|
2565
|
+
return trimmed.substring(4).trim();
|
|
2566
|
+
}
|
|
2567
|
+
return trimmed;
|
|
2568
|
+
}
|
|
2569
|
+
|
|
2570
|
+
private String extractLegacyNameSerial(String bleName) {
|
|
2571
|
+
if (bleName == null) return "";
|
|
2572
|
+
String trimmed = bleName.trim();
|
|
2573
|
+
int underscore = trimmed.indexOf('_');
|
|
2574
|
+
if (underscore < 0 || underscore + 1 >= trimmed.length()) {
|
|
2575
|
+
return "";
|
|
2576
|
+
}
|
|
2577
|
+
return trimmed.substring(underscore + 1).trim();
|
|
2578
|
+
}
|
|
2579
|
+
|
|
2580
|
+
// ---------------- Data holders ----------------
|
|
2581
|
+
|
|
2582
|
+
private static class ParsedAdvertisement {
|
|
2583
|
+
final String bleName;
|
|
2584
|
+
final String projectName;
|
|
2585
|
+
final String bleAddress;
|
|
2586
|
+
final String btAddress;
|
|
2587
|
+
final String serialNumber;
|
|
2588
|
+
|
|
2589
|
+
ParsedAdvertisement(String bleName, String projectName, String bleAddress, String btAddress, String serialNumber) {
|
|
2590
|
+
this.bleName = bleName;
|
|
2591
|
+
this.projectName = projectName;
|
|
2592
|
+
this.bleAddress = bleAddress;
|
|
2593
|
+
this.btAddress = btAddress;
|
|
2594
|
+
this.serialNumber = serialNumber;
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
|
|
2598
|
+
private static class MessageHeader {
|
|
2599
|
+
final int function;
|
|
2600
|
+
final int cmd;
|
|
2601
|
+
final int ctrl;
|
|
2602
|
+
final int sequence;
|
|
2603
|
+
final byte[] payload;
|
|
2604
|
+
final int totalBytes;
|
|
2605
|
+
|
|
2606
|
+
MessageHeader(int function, int cmd, int ctrl, int sequence, byte[] payload, int totalBytes) {
|
|
2607
|
+
this.function = function;
|
|
2608
|
+
this.cmd = cmd;
|
|
2609
|
+
this.ctrl = ctrl;
|
|
2610
|
+
this.sequence = sequence;
|
|
2611
|
+
this.payload = payload;
|
|
2612
|
+
this.totalBytes = totalBytes;
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2616
|
+
private static class DeviceInfo {
|
|
2617
|
+
final String firmwareVersion;
|
|
2618
|
+
final String deviceName;
|
|
2619
|
+
final String productName;
|
|
2620
|
+
final String btMac;
|
|
2621
|
+
final String serialNumber;
|
|
2622
|
+
|
|
2623
|
+
DeviceInfo(String firmwareVersion, String deviceName, String productName, String btMac, String serialNumber) {
|
|
2624
|
+
this.firmwareVersion = firmwareVersion;
|
|
2625
|
+
this.deviceName = deviceName;
|
|
2626
|
+
this.productName = productName;
|
|
2627
|
+
this.btMac = btMac;
|
|
2628
|
+
this.serialNumber = serialNumber;
|
|
2629
|
+
}
|
|
2630
|
+
}
|
|
2631
|
+
|
|
2632
|
+
private static class CommonResult {
|
|
2633
|
+
final boolean success;
|
|
2634
|
+
final int errorCode;
|
|
2635
|
+
final String errorMessage;
|
|
2636
|
+
|
|
2637
|
+
CommonResult(boolean success, int errorCode, String errorMessage) {
|
|
2638
|
+
this.success = success;
|
|
2639
|
+
this.errorCode = errorCode;
|
|
2640
|
+
this.errorMessage = errorMessage;
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2644
|
+
private static class BatteryInfo {
|
|
2645
|
+
final int battery;
|
|
2646
|
+
final Boolean charging;
|
|
2647
|
+
|
|
2648
|
+
BatteryInfo(int battery, Boolean charging) {
|
|
2649
|
+
this.battery = battery;
|
|
2650
|
+
this.charging = charging;
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
}
|