@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,677 @@
|
|
|
1
|
+
package com.glassly.bluetoothsdk.utils;
|
|
2
|
+
|
|
3
|
+
import android.graphics.Bitmap;
|
|
4
|
+
import android.graphics.BitmapFactory;
|
|
5
|
+
import android.os.Build;
|
|
6
|
+
import android.util.Log;
|
|
7
|
+
|
|
8
|
+
import androidx.annotation.Nullable;
|
|
9
|
+
import androidx.annotation.VisibleForTesting;
|
|
10
|
+
import androidx.exifinterface.media.ExifInterface;
|
|
11
|
+
|
|
12
|
+
import com.glassly.bluetoothsdk.debug.BleTraceLogger;
|
|
13
|
+
import com.glassly.bluetoothsdk.photoreceiver.LocalPhotoReceiverRegistry;
|
|
14
|
+
import com.radzivon.bartoshyk.avif.coder.HeifCoder;
|
|
15
|
+
import com.radzivon.bartoshyk.avif.coder.PreferredColorConfig;
|
|
16
|
+
|
|
17
|
+
import org.json.JSONArray;
|
|
18
|
+
|
|
19
|
+
import java.io.File;
|
|
20
|
+
import java.io.FileOutputStream;
|
|
21
|
+
import java.io.IOException;
|
|
22
|
+
import java.net.URI;
|
|
23
|
+
import java.nio.charset.StandardCharsets;
|
|
24
|
+
import java.util.concurrent.TimeUnit;
|
|
25
|
+
|
|
26
|
+
import okhttp3.MediaType;
|
|
27
|
+
import okhttp3.MultipartBody;
|
|
28
|
+
import okhttp3.OkHttpClient;
|
|
29
|
+
import okhttp3.Request;
|
|
30
|
+
import okhttp3.RequestBody;
|
|
31
|
+
import okhttp3.Response;
|
|
32
|
+
import org.json.JSONObject;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Service to handle BLE photo uploads including AVIF decoding and webhook posting.
|
|
36
|
+
* Preserves IMU metadata embedded in EXIF UserComment when re-encoding to JPEG.
|
|
37
|
+
*/
|
|
38
|
+
public class BlePhotoUploadService {
|
|
39
|
+
private static final String TAG = "BlePhotoUploadService";
|
|
40
|
+
// AVIF sources already went through a lossy pass on the glasses; re-encode at
|
|
41
|
+
// high-but-not-max quality to avoid inflating upload size. JPEG fast-path
|
|
42
|
+
// payloads from text mode are uploaded as-is.
|
|
43
|
+
private static final int AVIF_TO_JPEG_QUALITY = 90;
|
|
44
|
+
|
|
45
|
+
public interface UploadCallback {
|
|
46
|
+
void onSuccess(String requestId, String responseBody);
|
|
47
|
+
void onError(String requestId, String error);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Process image data and upload to webhook
|
|
52
|
+
* @param imageData Raw image data (AVIF or JPEG)
|
|
53
|
+
* @param requestId Original request ID for tracking
|
|
54
|
+
* @param webhookUrl Destination webhook URL
|
|
55
|
+
* @param authToken Optional authentication token for upload
|
|
56
|
+
* @param callback Callback for success/error
|
|
57
|
+
*/
|
|
58
|
+
public static void processAndUploadPhoto(byte[] imageData, String requestId,
|
|
59
|
+
String webhookUrl, @Nullable String authToken,
|
|
60
|
+
UploadCallback callback) {
|
|
61
|
+
new Thread(() -> {
|
|
62
|
+
try {
|
|
63
|
+
Log.d(TAG, "Processing BLE photo for upload. Image size: " + imageData.length + " bytes");
|
|
64
|
+
|
|
65
|
+
byte[] jpegData = convertToJpegPreservingExif(imageData);
|
|
66
|
+
Log.d(TAG, "Converted to JPEG for upload. Size: " + jpegData.length + " bytes");
|
|
67
|
+
|
|
68
|
+
// 3. Upload to webhook
|
|
69
|
+
String responseBody =
|
|
70
|
+
uploadToWebhook(jpegData, imageData.length, requestId, webhookUrl, authToken);
|
|
71
|
+
|
|
72
|
+
Log.d(TAG, "Photo uploaded successfully for requestId: " + requestId);
|
|
73
|
+
callback.onSuccess(requestId, responseBody);
|
|
74
|
+
|
|
75
|
+
} catch (Exception e) {
|
|
76
|
+
Log.e(TAG, "Error processing BLE photo for requestId: " + requestId, e);
|
|
77
|
+
callback.onError(requestId, e.getMessage());
|
|
78
|
+
}
|
|
79
|
+
}).start();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Decode incoming AVIF/JPEG, re-encode as JPEG, and re-attach IMU EXIF when present.
|
|
84
|
+
*/
|
|
85
|
+
@VisibleForTesting
|
|
86
|
+
static byte[] convertToJpegPreservingExif(byte[] imageData) throws Exception {
|
|
87
|
+
long conversionStartMs = System.currentTimeMillis();
|
|
88
|
+
if (isJpeg(imageData)) {
|
|
89
|
+
Log.d(
|
|
90
|
+
TAG,
|
|
91
|
+
"BLE relay pass-through: input already JPEG ("
|
|
92
|
+
+ imageData.length
|
|
93
|
+
+ " bytes), skipping decode/re-encode in "
|
|
94
|
+
+ (System.currentTimeMillis() - conversionStartMs)
|
|
95
|
+
+ "ms");
|
|
96
|
+
return imageData;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
File inputFile = File.createTempFile("ble_photo_in_", guessExtension(imageData));
|
|
100
|
+
File outputFile = File.createTempFile("ble_photo_out_", ".jpg");
|
|
101
|
+
try {
|
|
102
|
+
try (FileOutputStream fos = new FileOutputStream(inputFile)) {
|
|
103
|
+
fos.write(imageData);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
logIncomingImageDiagnostics(imageData, inputFile.getAbsolutePath());
|
|
107
|
+
|
|
108
|
+
String imuJson = readImuJsonFromBleImage(imageData, inputFile.getAbsolutePath());
|
|
109
|
+
|
|
110
|
+
long decodeStartMs = System.currentTimeMillis();
|
|
111
|
+
Bitmap bitmap = decodeImage(imageData);
|
|
112
|
+
long decodeDurationMs = System.currentTimeMillis() - decodeStartMs;
|
|
113
|
+
if (bitmap == null) {
|
|
114
|
+
throw new Exception("Failed to decode image data");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
Log.d(
|
|
118
|
+
TAG,
|
|
119
|
+
"AVIF decode complete: "
|
|
120
|
+
+ bitmap.getWidth()
|
|
121
|
+
+ "x"
|
|
122
|
+
+ bitmap.getHeight()
|
|
123
|
+
+ " in "
|
|
124
|
+
+ decodeDurationMs
|
|
125
|
+
+ "ms");
|
|
126
|
+
|
|
127
|
+
long encodeStartMs = System.currentTimeMillis();
|
|
128
|
+
try (FileOutputStream fos = new FileOutputStream(outputFile)) {
|
|
129
|
+
bitmap.compress(Bitmap.CompressFormat.JPEG, AVIF_TO_JPEG_QUALITY, fos);
|
|
130
|
+
} finally {
|
|
131
|
+
bitmap.recycle();
|
|
132
|
+
}
|
|
133
|
+
long encodeDurationMs = System.currentTimeMillis() - encodeStartMs;
|
|
134
|
+
Log.d(
|
|
135
|
+
TAG,
|
|
136
|
+
"AVIF->JPEG encode complete: quality="
|
|
137
|
+
+ AVIF_TO_JPEG_QUALITY
|
|
138
|
+
+ " in "
|
|
139
|
+
+ encodeDurationMs
|
|
140
|
+
+ "ms");
|
|
141
|
+
|
|
142
|
+
long metadataStartMs = System.currentTimeMillis();
|
|
143
|
+
if (imuJson != null && !imuJson.isEmpty()) {
|
|
144
|
+
logImuData(imuJson);
|
|
145
|
+
// IMU EXIF is enrichment, not the payload: a write failure must not drop the
|
|
146
|
+
// already-decoded photo. Log and upload the plain JPEG instead.
|
|
147
|
+
try {
|
|
148
|
+
writeImuJsonToJpeg(outputFile.getAbsolutePath(), imuJson);
|
|
149
|
+
Log.d(TAG, "Re-attached IMU EXIF UserComment on output JPEG (" + imuJson.length() + " chars)");
|
|
150
|
+
} catch (Exception e) {
|
|
151
|
+
Log.w(TAG, "Failed to attach IMU EXIF; uploading photo without it", e);
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
boolean rawHasExif = containsExifMarkerInBytes(imageData);
|
|
155
|
+
Log.w(
|
|
156
|
+
TAG,
|
|
157
|
+
"No IMU from ExifInterface on "
|
|
158
|
+
+ inputFile.getName()
|
|
159
|
+
+ " (container="
|
|
160
|
+
+ describeContainer(imageData)
|
|
161
|
+
+ ", rawHasExifMarker="
|
|
162
|
+
+ rawHasExif
|
|
163
|
+
+ "). If rawHasExifMarker=true, EXIF may be present but unreadable via"
|
|
164
|
+
+ " ExifInterface on this container.");
|
|
165
|
+
}
|
|
166
|
+
Log.d(
|
|
167
|
+
TAG,
|
|
168
|
+
"AVIF metadata handling complete in "
|
|
169
|
+
+ (System.currentTimeMillis() - metadataStartMs)
|
|
170
|
+
+ "ms");
|
|
171
|
+
|
|
172
|
+
byte[] jpegData = java.nio.file.Files.readAllBytes(outputFile.toPath());
|
|
173
|
+
Log.d(
|
|
174
|
+
TAG,
|
|
175
|
+
"Phone image conversion complete: AVIF "
|
|
176
|
+
+ imageData.length
|
|
177
|
+
+ " bytes -> JPEG "
|
|
178
|
+
+ jpegData.length
|
|
179
|
+
+ " bytes in "
|
|
180
|
+
+ (System.currentTimeMillis() - conversionStartMs)
|
|
181
|
+
+ "ms (decode="
|
|
182
|
+
+ decodeDurationMs
|
|
183
|
+
+ "ms, encode="
|
|
184
|
+
+ encodeDurationMs
|
|
185
|
+
+ "ms)");
|
|
186
|
+
return jpegData;
|
|
187
|
+
} finally {
|
|
188
|
+
if (!inputFile.delete()) {
|
|
189
|
+
inputFile.deleteOnExit();
|
|
190
|
+
}
|
|
191
|
+
if (!outputFile.delete()) {
|
|
192
|
+
outputFile.deleteOnExit();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
@Nullable
|
|
198
|
+
@VisibleForTesting
|
|
199
|
+
static String readImuJsonFromBleImage(byte[] imageData, String imagePath) {
|
|
200
|
+
String fromExif = readImuJsonFromImageFile(imagePath);
|
|
201
|
+
if (fromExif != null && !fromExif.isEmpty()) {
|
|
202
|
+
return fromExif;
|
|
203
|
+
}
|
|
204
|
+
if (containsExifMarkerInBytes(imageData)) {
|
|
205
|
+
String fromTiff = HeifExifTagReader.readImuJson(imageData);
|
|
206
|
+
if (fromTiff != null && !fromTiff.isEmpty()) {
|
|
207
|
+
Log.d(
|
|
208
|
+
TAG,
|
|
209
|
+
"Read IMU UserComment via TIFF scan ("
|
|
210
|
+
+ fromTiff.length()
|
|
211
|
+
+ " chars), container="
|
|
212
|
+
+ describeContainer(imageData));
|
|
213
|
+
return fromTiff;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
@Nullable
|
|
220
|
+
@VisibleForTesting
|
|
221
|
+
static String readImuJsonFromImageFile(String imagePath) {
|
|
222
|
+
try {
|
|
223
|
+
ExifInterface exif = new ExifInterface(imagePath);
|
|
224
|
+
String userComment = exif.getAttribute(ExifInterface.TAG_USER_COMMENT);
|
|
225
|
+
String imageDescription = exif.getAttribute(ExifInterface.TAG_IMAGE_DESCRIPTION);
|
|
226
|
+
Log.d(
|
|
227
|
+
TAG,
|
|
228
|
+
"ExifInterface on "
|
|
229
|
+
+ imagePath
|
|
230
|
+
+ ": UserComment="
|
|
231
|
+
+ describeExifAttribute(userComment)
|
|
232
|
+
+ ", ImageDescription="
|
|
233
|
+
+ describeExifAttribute(imageDescription));
|
|
234
|
+
if (userComment != null && !userComment.isEmpty()) {
|
|
235
|
+
return userComment;
|
|
236
|
+
}
|
|
237
|
+
return imageDescription;
|
|
238
|
+
} catch (IOException e) {
|
|
239
|
+
Log.w(TAG, "Could not read EXIF from BLE image: " + imagePath, e);
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
private static void logIncomingImageDiagnostics(byte[] imageData, String tempPath) {
|
|
245
|
+
Log.d(
|
|
246
|
+
TAG,
|
|
247
|
+
"BLE image diagnostics: size="
|
|
248
|
+
+ imageData.length
|
|
249
|
+
+ " bytes, container="
|
|
250
|
+
+ describeContainer(imageData)
|
|
251
|
+
+ ", tempFile="
|
|
252
|
+
+ tempPath
|
|
253
|
+
+ ", rawHasExifMarker="
|
|
254
|
+
+ containsExifMarkerInBytes(imageData));
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** ISO BMFF major brand or JPEG; used to correlate with ExifInterface behavior on AVIF. */
|
|
258
|
+
@VisibleForTesting
|
|
259
|
+
static String describeContainer(byte[] data) {
|
|
260
|
+
if (data.length >= 2 && (data[0] & 0xFF) == 0xFF && (data[1] & 0xFF) == 0xD8) {
|
|
261
|
+
return "jpeg";
|
|
262
|
+
}
|
|
263
|
+
if (data.length >= 12
|
|
264
|
+
&& data[4] == 'f'
|
|
265
|
+
&& data[5] == 't'
|
|
266
|
+
&& data[6] == 'y'
|
|
267
|
+
&& data[7] == 'p') {
|
|
268
|
+
String brand = new String(data, 8, 4, StandardCharsets.US_ASCII);
|
|
269
|
+
return "iso_bmff/ftyp=" + brand;
|
|
270
|
+
}
|
|
271
|
+
return "unknown";
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** Scans file bytes for the TIFF EXIF header (does not parse tags). */
|
|
275
|
+
@VisibleForTesting
|
|
276
|
+
static boolean containsExifMarkerInBytes(byte[] data) {
|
|
277
|
+
byte[] marker = new byte[] {'E', 'x', 'i', 'f', 0, 0};
|
|
278
|
+
outer:
|
|
279
|
+
for (int i = 0; i <= data.length - marker.length; i++) {
|
|
280
|
+
for (int j = 0; j < marker.length; j++) {
|
|
281
|
+
if (data[i + j] != marker[j]) {
|
|
282
|
+
continue outer;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
private static String describeExifAttribute(@Nullable String value) {
|
|
291
|
+
if (value == null) {
|
|
292
|
+
return "null";
|
|
293
|
+
}
|
|
294
|
+
if (value.isEmpty()) {
|
|
295
|
+
return "empty";
|
|
296
|
+
}
|
|
297
|
+
String preview = value.length() > 80 ? value.substring(0, 80) + "…" : value;
|
|
298
|
+
return "len=" + value.length() + " preview=\"" + preview + "\"";
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
@VisibleForTesting
|
|
302
|
+
static void writeImuJsonToJpeg(String jpegPath, String imuJson) throws IOException {
|
|
303
|
+
ExifInterface exif = new ExifInterface(jpegPath);
|
|
304
|
+
exif.setAttribute(ExifInterface.TAG_USER_COMMENT, imuJson);
|
|
305
|
+
exif.saveAttributes();
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
private static boolean isJpeg(byte[] imageData) {
|
|
309
|
+
return imageData.length >= 2
|
|
310
|
+
&& (imageData[0] & 0xFF) == 0xFF
|
|
311
|
+
&& (imageData[1] & 0xFF) == 0xD8;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
private static String guessExtension(byte[] imageData) {
|
|
315
|
+
if (imageData.length > 12
|
|
316
|
+
&& imageData[4] == 'f'
|
|
317
|
+
&& imageData[5] == 't'
|
|
318
|
+
&& imageData[6] == 'y'
|
|
319
|
+
&& imageData[7] == 'p') {
|
|
320
|
+
return ".avif";
|
|
321
|
+
}
|
|
322
|
+
return ".jpg";
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Decode image data (AVIF or JPEG) to Bitmap
|
|
327
|
+
* @param imageData Raw image bytes
|
|
328
|
+
* @return Decoded bitmap or null if failed
|
|
329
|
+
*/
|
|
330
|
+
private static Bitmap decodeImage(byte[] imageData) {
|
|
331
|
+
try {
|
|
332
|
+
boolean isAvif = imageData.length > 12
|
|
333
|
+
&& imageData[4] == 'f' && imageData[5] == 't'
|
|
334
|
+
&& imageData[6] == 'y' && imageData[7] == 'p'
|
|
335
|
+
&& imageData[8] == 'a' && imageData[9] == 'v'
|
|
336
|
+
&& imageData[10] == 'i' && imageData[11] == 'f';
|
|
337
|
+
|
|
338
|
+
if (isAvif) {
|
|
339
|
+
Log.d(TAG, "Detected AVIF image format");
|
|
340
|
+
byte[] strippedBytes = imageData;
|
|
341
|
+
if (containsExifMarkerInBytes(imageData)) {
|
|
342
|
+
try {
|
|
343
|
+
strippedBytes = AvifExifStripper.stripForDecode(imageData);
|
|
344
|
+
Log.d(
|
|
345
|
+
TAG,
|
|
346
|
+
"Stripped Exif metadata item for decode: "
|
|
347
|
+
+ imageData.length
|
|
348
|
+
+ " -> "
|
|
349
|
+
+ strippedBytes.length
|
|
350
|
+
+ " bytes");
|
|
351
|
+
} catch (Exception e) {
|
|
352
|
+
Log.w(TAG, "stripForDecode failed, using raw AVIF: " + e.getMessage());
|
|
353
|
+
strippedBytes = imageData;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
Bitmap bmp = decodeAvifBytes(strippedBytes);
|
|
357
|
+
if (bmp == null && strippedBytes != imageData) {
|
|
358
|
+
Log.w(TAG, "Stripped AVIF decode failed; retrying original BLE bytes");
|
|
359
|
+
bmp = decodeAvifBytes(imageData);
|
|
360
|
+
}
|
|
361
|
+
return bmp;
|
|
362
|
+
}
|
|
363
|
+
Log.d(TAG, "Detected JPEG image format");
|
|
364
|
+
return BitmapFactory.decodeByteArray(imageData, 0, imageData.length);
|
|
365
|
+
} catch (Exception e) {
|
|
366
|
+
Log.e(TAG, "Failed to decode image", e);
|
|
367
|
+
return null;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
@Nullable
|
|
372
|
+
private static Bitmap decodeAvifBytes(byte[] avifBytes) {
|
|
373
|
+
try {
|
|
374
|
+
Bitmap bmp = new HeifCoder().decode(avifBytes, PreferredColorConfig.RGBA_8888);
|
|
375
|
+
if (bmp != null) {
|
|
376
|
+
return bmp;
|
|
377
|
+
}
|
|
378
|
+
} catch (Exception | LinkageError e) {
|
|
379
|
+
Log.w(TAG, "HeifCoder AVIF decode unavailable/failed, trying BitmapFactory: " + e.getMessage());
|
|
380
|
+
}
|
|
381
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
382
|
+
return BitmapFactory.decodeByteArray(avifBytes, 0, avifBytes.length);
|
|
383
|
+
}
|
|
384
|
+
Log.e(TAG, "AVIF decoding requires Android 12+ (API 31+). Current API: " + Build.VERSION.SDK_INT);
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Upload JPEG data to webhook
|
|
390
|
+
*/
|
|
391
|
+
private static void logImuData(String imuJson) {
|
|
392
|
+
try {
|
|
393
|
+
JSONObject root = new JSONObject(imuJson);
|
|
394
|
+
int sampleCount = root.optInt("sampleCount", 0);
|
|
395
|
+
double samplingRateHz = root.optDouble("samplingRateHz", 0);
|
|
396
|
+
long durationMs = root.optLong("durationMs", 0);
|
|
397
|
+
long startTimeNs = root.optLong("startTimeNs", 0);
|
|
398
|
+
JSONArray samples = root.optJSONArray("samples");
|
|
399
|
+
|
|
400
|
+
StringBuilder sb = new StringBuilder();
|
|
401
|
+
sb.append("IMU data received: sampleCount=").append(sampleCount)
|
|
402
|
+
.append(" rate=").append(samplingRateHz).append("Hz")
|
|
403
|
+
.append(" duration=").append(durationMs).append("ms")
|
|
404
|
+
.append(" startNs=").append(startTimeNs);
|
|
405
|
+
Log.d(TAG, sb.toString());
|
|
406
|
+
|
|
407
|
+
if (samples != null && samples.length() > 0) {
|
|
408
|
+
// Log first and last sample: [timestampMs, ax, ay, az, gx, gy, gz]
|
|
409
|
+
logSample("first", samples.optJSONArray(0));
|
|
410
|
+
if (samples.length() > 1) {
|
|
411
|
+
logSample("last", samples.optJSONArray(samples.length() - 1));
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
} catch (Exception e) {
|
|
415
|
+
Log.w(TAG, "logImuData: failed to parse IMU JSON: " + e.getMessage());
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
private static void logSample(String label, @Nullable JSONArray sample) {
|
|
420
|
+
if (sample == null || sample.length() < 7) return;
|
|
421
|
+
try {
|
|
422
|
+
Log.d(
|
|
423
|
+
TAG,
|
|
424
|
+
"IMU " + label + " sample:"
|
|
425
|
+
+ " t=" + sample.optLong(0) + "ms"
|
|
426
|
+
+ " accel=[" + String.format("%.3f", sample.optDouble(1))
|
|
427
|
+
+ ", " + String.format("%.3f", sample.optDouble(2))
|
|
428
|
+
+ ", " + String.format("%.3f", sample.optDouble(3)) + "]m/s²"
|
|
429
|
+
+ " gyro=[" + String.format("%.3f", sample.optDouble(4))
|
|
430
|
+
+ ", " + String.format("%.3f", sample.optDouble(5))
|
|
431
|
+
+ ", " + String.format("%.3f", sample.optDouble(6)) + "]rad/s");
|
|
432
|
+
} catch (Exception e) {
|
|
433
|
+
Log.w(TAG, "logSample: " + e.getMessage());
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
private static String uploadToWebhook(byte[] jpegData, String requestId,
|
|
438
|
+
String webhookUrl, @Nullable String authToken) throws IOException {
|
|
439
|
+
return uploadToWebhook(jpegData, -1, requestId, webhookUrl, authToken);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
private static String uploadToWebhook(byte[] jpegData, int sourceImageBytes, String requestId,
|
|
443
|
+
String webhookUrl, @Nullable String authToken) throws IOException {
|
|
444
|
+
String effectiveWebhookUrl = resolvePhoneRelayWebhookUrl(webhookUrl);
|
|
445
|
+
OkHttpClient client = new OkHttpClient.Builder()
|
|
446
|
+
.connectTimeout(30, TimeUnit.SECONDS)
|
|
447
|
+
.writeTimeout(30, TimeUnit.SECONDS)
|
|
448
|
+
.readTimeout(30, TimeUnit.SECONDS)
|
|
449
|
+
.build();
|
|
450
|
+
|
|
451
|
+
RequestBody requestBody = new MultipartBody.Builder()
|
|
452
|
+
.setType(MultipartBody.FORM)
|
|
453
|
+
.addFormDataPart("requestId", requestId)
|
|
454
|
+
.addFormDataPart("source", "ble_transfer")
|
|
455
|
+
.addFormDataPart("photo", requestId + ".jpg",
|
|
456
|
+
RequestBody.create(MediaType.parse("image/jpeg"), jpegData))
|
|
457
|
+
.build();
|
|
458
|
+
|
|
459
|
+
Request.Builder requestBuilder = new Request.Builder()
|
|
460
|
+
.url(effectiveWebhookUrl)
|
|
461
|
+
.post(requestBody);
|
|
462
|
+
|
|
463
|
+
if (authToken != null && !authToken.isEmpty()) {
|
|
464
|
+
requestBuilder.addHeader("Authorization", "Bearer " + authToken);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
Request request = requestBuilder.build();
|
|
468
|
+
|
|
469
|
+
if (!effectiveWebhookUrl.equals(webhookUrl)) {
|
|
470
|
+
Log.d(TAG, "Uploading BLE fallback photo to local receiver via loopback: " + effectiveWebhookUrl);
|
|
471
|
+
} else {
|
|
472
|
+
Log.d(TAG, "Uploading photo to webhook: " + webhookUrl);
|
|
473
|
+
}
|
|
474
|
+
long startMs = System.currentTimeMillis();
|
|
475
|
+
traceRelayUploadStart(requestId, effectiveWebhookUrl, webhookUrl, authToken, sourceImageBytes, jpegData.length, startMs);
|
|
476
|
+
|
|
477
|
+
boolean responseTraced = false;
|
|
478
|
+
try (Response response = client.newCall(request).execute()) {
|
|
479
|
+
if (!response.isSuccessful()) {
|
|
480
|
+
String errorBody = response.body() != null ? response.body().string() : "No response body";
|
|
481
|
+
traceRelayUploadEnd(
|
|
482
|
+
requestId,
|
|
483
|
+
effectiveWebhookUrl,
|
|
484
|
+
webhookUrl,
|
|
485
|
+
sourceImageBytes,
|
|
486
|
+
jpegData.length,
|
|
487
|
+
startMs,
|
|
488
|
+
response.code(),
|
|
489
|
+
false,
|
|
490
|
+
"http_error");
|
|
491
|
+
responseTraced = true;
|
|
492
|
+
throw new IOException("Upload failed with code " + response.code() + ": " + errorBody);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
String responseBody = response.body() != null ? response.body().string() : "";
|
|
496
|
+
traceRelayUploadEnd(
|
|
497
|
+
requestId,
|
|
498
|
+
effectiveWebhookUrl,
|
|
499
|
+
webhookUrl,
|
|
500
|
+
sourceImageBytes,
|
|
501
|
+
jpegData.length,
|
|
502
|
+
startMs,
|
|
503
|
+
response.code(),
|
|
504
|
+
true,
|
|
505
|
+
"uploaded");
|
|
506
|
+
responseTraced = true;
|
|
507
|
+
Log.d(TAG, "Upload successful. Response code: " + response.code());
|
|
508
|
+
return responseBody;
|
|
509
|
+
} catch (IOException e) {
|
|
510
|
+
if (!responseTraced) {
|
|
511
|
+
traceRelayUploadError(
|
|
512
|
+
requestId,
|
|
513
|
+
effectiveWebhookUrl,
|
|
514
|
+
webhookUrl,
|
|
515
|
+
sourceImageBytes,
|
|
516
|
+
jpegData.length,
|
|
517
|
+
startMs,
|
|
518
|
+
e,
|
|
519
|
+
"failed");
|
|
520
|
+
}
|
|
521
|
+
throw e;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
private static String resolvePhoneRelayWebhookUrl(String webhookUrl) {
|
|
526
|
+
String loopbackUrl = LocalPhotoReceiverRegistry.loopbackUploadUrlFor(webhookUrl);
|
|
527
|
+
return loopbackUrl != null ? loopbackUrl : webhookUrl;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
private static void traceRelayUploadStart(String requestId, String webhookUrl,
|
|
531
|
+
String originalWebhookUrl,
|
|
532
|
+
@Nullable String authToken, int sourceImageBytes,
|
|
533
|
+
int jpegBytes, long startMs) {
|
|
534
|
+
JSONObject payload = createRelayUploadPayload(
|
|
535
|
+
"photo_relay_upload_start",
|
|
536
|
+
requestId,
|
|
537
|
+
webhookUrl,
|
|
538
|
+
sourceImageBytes,
|
|
539
|
+
jpegBytes,
|
|
540
|
+
startMs);
|
|
541
|
+
putRelayRewrite(payload, webhookUrl, originalWebhookUrl);
|
|
542
|
+
putJson(payload, "bearerHeaderPresent", authToken != null && !authToken.isEmpty());
|
|
543
|
+
safeTraceJson("phone_to_wifi", "wifi_http_output", payload);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
private static void traceRelayUploadEnd(String requestId, String webhookUrl,
|
|
547
|
+
String originalWebhookUrl,
|
|
548
|
+
int sourceImageBytes, int jpegBytes, long startMs,
|
|
549
|
+
int statusCode, boolean success, String outcome) {
|
|
550
|
+
JSONObject payload = createRelayUploadPayload(
|
|
551
|
+
"photo_relay_upload_end",
|
|
552
|
+
requestId,
|
|
553
|
+
webhookUrl,
|
|
554
|
+
sourceImageBytes,
|
|
555
|
+
jpegBytes,
|
|
556
|
+
startMs);
|
|
557
|
+
putRelayRewrite(payload, webhookUrl, originalWebhookUrl);
|
|
558
|
+
long endMs = System.currentTimeMillis();
|
|
559
|
+
putJson(payload, "endMs", endMs);
|
|
560
|
+
putJson(payload, "durationMs", endMs - startMs);
|
|
561
|
+
putJson(payload, "statusCode", statusCode);
|
|
562
|
+
putJson(payload, "success", success);
|
|
563
|
+
putJson(payload, "outcome", outcome);
|
|
564
|
+
safeTraceJson("wifi_to_phone", "wifi_http_input", payload);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
private static void traceRelayUploadError(String requestId, String webhookUrl,
|
|
568
|
+
String originalWebhookUrl,
|
|
569
|
+
int sourceImageBytes, int jpegBytes, long startMs,
|
|
570
|
+
Exception error, String outcome) {
|
|
571
|
+
JSONObject payload = createRelayUploadPayload(
|
|
572
|
+
"photo_relay_upload_error",
|
|
573
|
+
requestId,
|
|
574
|
+
webhookUrl,
|
|
575
|
+
sourceImageBytes,
|
|
576
|
+
jpegBytes,
|
|
577
|
+
startMs);
|
|
578
|
+
putRelayRewrite(payload, webhookUrl, originalWebhookUrl);
|
|
579
|
+
long endMs = System.currentTimeMillis();
|
|
580
|
+
putJson(payload, "endMs", endMs);
|
|
581
|
+
putJson(payload, "durationMs", endMs - startMs);
|
|
582
|
+
putJson(payload, "success", false);
|
|
583
|
+
putJson(payload, "outcome", outcome);
|
|
584
|
+
putJson(payload, "errorClass", error.getClass().getSimpleName());
|
|
585
|
+
putJson(payload, "errorMessage", error.getMessage());
|
|
586
|
+
safeTraceJson("wifi_to_phone", "wifi_http_input", payload);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
private static JSONObject createRelayUploadPayload(String type, String requestId,
|
|
590
|
+
String webhookUrl, int sourceImageBytes,
|
|
591
|
+
int jpegBytes, long startMs) {
|
|
592
|
+
JSONObject payload = new JSONObject();
|
|
593
|
+
putJson(payload, "type", type);
|
|
594
|
+
putJson(payload, "requestId", requestId);
|
|
595
|
+
putJson(payload, "source", "ble_fallback");
|
|
596
|
+
if (sourceImageBytes >= 0) {
|
|
597
|
+
putJson(payload, "imageBytes", sourceImageBytes);
|
|
598
|
+
}
|
|
599
|
+
putJson(payload, "jpegBytes", jpegBytes);
|
|
600
|
+
putJson(payload, "startMs", startMs);
|
|
601
|
+
putWebhookSummary(payload, webhookUrl);
|
|
602
|
+
return payload;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
private static void putRelayRewrite(JSONObject payload, String webhookUrl, String originalWebhookUrl) {
|
|
606
|
+
if (webhookUrl == null || originalWebhookUrl == null || webhookUrl.equals(originalWebhookUrl)) {
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
putJson(payload, "relayRewrite", "loopback");
|
|
611
|
+
try {
|
|
612
|
+
URI original = URI.create(originalWebhookUrl);
|
|
613
|
+
putJson(payload, "originalUrlHost", original.getHost());
|
|
614
|
+
if (original.getPort() != -1) {
|
|
615
|
+
putJson(payload, "originalUrlPort", original.getPort());
|
|
616
|
+
}
|
|
617
|
+
} catch (Exception e) {
|
|
618
|
+
putJson(payload, "originalUrlParseError", e.getClass().getSimpleName());
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
private static void putWebhookSummary(JSONObject payload, String webhookUrl) {
|
|
623
|
+
if (webhookUrl == null || webhookUrl.isEmpty()) {
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
try {
|
|
628
|
+
URI uri = URI.create(webhookUrl);
|
|
629
|
+
putJson(payload, "urlScheme", uri.getScheme());
|
|
630
|
+
putJson(payload, "urlHost", uri.getHost());
|
|
631
|
+
if (uri.getPort() != -1) {
|
|
632
|
+
putJson(payload, "urlPort", uri.getPort());
|
|
633
|
+
}
|
|
634
|
+
String path = uri.getRawPath();
|
|
635
|
+
putJson(payload, "urlHasPath", path != null && !path.isEmpty() && !"/".equals(path));
|
|
636
|
+
putJson(payload, "urlHasQuery", uri.getRawQuery() != null && !uri.getRawQuery().isEmpty());
|
|
637
|
+
} catch (Exception e) {
|
|
638
|
+
putJson(payload, "urlParseError", e.getClass().getSimpleName());
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
private static void putJson(JSONObject payload, String key, Object value) {
|
|
643
|
+
if (payload == null || key == null || value == null) {
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
try {
|
|
647
|
+
payload.put(key, value);
|
|
648
|
+
} catch (Exception ignored) {
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
private static void safeTraceJson(String direction, String layer, JSONObject payload) {
|
|
653
|
+
try {
|
|
654
|
+
BleTraceLogger.logJson(direction, layer, payload, null);
|
|
655
|
+
} catch (Throwable ignored) {
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Alternative method for platforms without AVIF support
|
|
661
|
+
* Expects already-decoded JPEG data instead of AVIF
|
|
662
|
+
*/
|
|
663
|
+
public static void uploadJpegPhoto(byte[] jpegData, String requestId,
|
|
664
|
+
String webhookUrl, @Nullable String authToken,
|
|
665
|
+
UploadCallback callback) {
|
|
666
|
+
new Thread(() -> {
|
|
667
|
+
try {
|
|
668
|
+
Log.d(TAG, "Uploading pre-decoded JPEG. Size: " + jpegData.length + " bytes");
|
|
669
|
+
String responseBody = uploadToWebhook(jpegData, requestId, webhookUrl, authToken);
|
|
670
|
+
callback.onSuccess(requestId, responseBody);
|
|
671
|
+
} catch (Exception e) {
|
|
672
|
+
Log.e(TAG, "Error uploading JPEG photo", e);
|
|
673
|
+
callback.onError(requestId, e.getMessage());
|
|
674
|
+
}
|
|
675
|
+
}).start();
|
|
676
|
+
}
|
|
677
|
+
}
|