ilabs-flir 1.0.2 → 1.0.4
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/Flir.podspec +31 -31
- package/README.md +1271 -1271
- package/android/Flir/build.gradle.kts +85 -80
- package/android/Flir/libs/flir-stubs.jar +0 -0
- package/android/Flir/src/main/AndroidManifest.xml +31 -31
- package/android/Flir/src/main/java/com/flir/thermalsdk/ErrorCode.java +13 -0
- package/android/Flir/src/main/java/com/flir/thermalsdk/ErrorCodeException.java +14 -0
- package/android/Flir/src/main/java/com/flir/thermalsdk/ThermalSdkAndroid.java +16 -0
- package/android/Flir/src/main/java/com/flir/thermalsdk/androidsdk/image/BitmapAndroid.java +20 -0
- package/android/Flir/src/main/java/com/flir/thermalsdk/image/ImageBuffer.java +11 -0
- package/android/Flir/src/main/java/com/flir/thermalsdk/image/JavaImageBuffer.java +35 -0
- package/android/Flir/src/main/java/com/flir/thermalsdk/image/Palette.java +15 -0
- package/android/Flir/src/main/java/com/flir/thermalsdk/image/PaletteManager.java +16 -0
- package/android/Flir/src/main/java/com/flir/thermalsdk/image/Point.java +11 -0
- package/android/Flir/src/main/java/com/flir/thermalsdk/image/ThermalImage.java +23 -0
- package/android/Flir/src/main/java/com/flir/thermalsdk/image/ThermalValue.java +9 -0
- package/android/Flir/src/main/java/com/flir/thermalsdk/live/Camera.java +26 -0
- package/android/Flir/src/main/java/com/flir/thermalsdk/live/CameraType.java +8 -0
- package/android/Flir/src/main/java/com/flir/thermalsdk/live/CommunicationInterface.java +16 -0
- package/android/Flir/src/main/java/com/flir/thermalsdk/live/ConnectParameters.java +16 -0
- package/android/Flir/src/main/java/com/flir/thermalsdk/live/Identity.java +23 -0
- package/android/Flir/src/main/java/com/flir/thermalsdk/live/IpSettings.java +9 -0
- package/android/Flir/src/main/java/com/flir/thermalsdk/live/RemoteControl.java +16 -0
- package/android/Flir/src/main/java/com/flir/thermalsdk/live/connectivity/ConnectionStatusListener.java +7 -0
- package/android/Flir/src/main/java/com/flir/thermalsdk/live/discovery/DiscoveryEventListener.java +14 -0
- package/android/Flir/src/main/java/com/flir/thermalsdk/live/discovery/DiscoveryFactory.java +33 -0
- package/android/Flir/src/main/java/com/flir/thermalsdk/live/remote/OnReceived.java +5 -0
- package/android/Flir/src/main/java/com/flir/thermalsdk/live/remote/OnRemoteError.java +7 -0
- package/android/Flir/src/main/java/com/flir/thermalsdk/live/streaming/Stream.java +8 -0
- package/android/Flir/src/main/java/com/flir/thermalsdk/live/streaming/ThermalStreamer.java +28 -0
- package/android/Flir/src/main/java/com/flir/thermalsdk/live/streaming/VisualStreamer.java +18 -0
- package/android/Flir/src/main/java/flir/android/FlirCommands.java +136 -0
- package/android/Flir/src/main/java/flir/android/FlirDownloadManager.kt +76 -75
- package/android/Flir/src/main/java/flir/android/FlirDownloadPackage.kt +16 -16
- package/android/Flir/src/main/java/flir/android/FlirFrameCache.kt +6 -6
- package/android/Flir/src/main/java/flir/android/FlirManager.kt +477 -248
- package/android/Flir/src/main/java/flir/android/FlirModule.kt +74 -74
- package/android/Flir/src/main/java/flir/android/FlirPackage.kt +19 -19
- package/android/Flir/src/main/java/flir/android/FlirSDKLoader.kt +165 -117
- package/android/Flir/src/main/java/flir/android/FlirSdkManager.java +1511 -0
- package/android/Flir/src/main/java/flir/android/FlirStatus.kt +12 -12
- package/android/Flir/src/main/java/flir/android/FlirView.kt +48 -48
- package/android/Flir/src/main/java/flir/android/FlirViewManager.kt +13 -13
- package/app.plugin.js +264 -264
- package/expo-module.config.json +5 -5
- package/ios/Flir/Framework/ThermalSDK/FLIRBattery.h +76 -76
- package/ios/Flir/Framework/ThermalSDK/FLIRCalibration.h +108 -108
- package/ios/Flir/Framework/ThermalSDK/FLIRCamera.h +156 -156
- package/ios/Flir/Framework/ThermalSDK/FLIRCameraDeviceInfo.h +53 -53
- package/ios/Flir/Framework/ThermalSDK/FLIRCameraEvent.h +132 -132
- package/ios/Flir/Framework/ThermalSDK/FLIRCameraImport.h +204 -204
- package/ios/Flir/Framework/ThermalSDK/FLIRColorDistributionSettings.h +204 -204
- package/ios/Flir/Framework/ThermalSDK/FLIRColorizer.h +82 -82
- package/ios/Flir/Framework/ThermalSDK/FLIRDiscoveredCamera.h +44 -44
- package/ios/Flir/Framework/ThermalSDK/FLIRDiscovery.h +132 -132
- package/ios/Flir/Framework/ThermalSDK/FLIRDisplaySettings.h +29 -29
- package/ios/Flir/Framework/ThermalSDK/FLIRFocus.h +70 -70
- package/ios/Flir/Framework/ThermalSDK/FLIRFusion.h +192 -192
- package/ios/Flir/Framework/ThermalSDK/FLIRFusionController.h +136 -136
- package/ios/Flir/Framework/ThermalSDK/FLIRFusionTransformation.h +35 -35
- package/ios/Flir/Framework/ThermalSDK/FLIRIdentity.h +264 -264
- package/ios/Flir/Framework/ThermalSDK/FLIRImageBase.h +196 -196
- package/ios/Flir/Framework/ThermalSDK/FLIRImageColorizer.h +26 -26
- package/ios/Flir/Framework/ThermalSDK/FLIRImageStatistics.h +61 -61
- package/ios/Flir/Framework/ThermalSDK/FLIRIsotherms.h +208 -208
- package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementArea.h +38 -38
- package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementCollection.h +147 -147
- package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementDelta.h +62 -62
- package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementDimensions.h +33 -33
- package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementEllipse.h +49 -49
- package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementLine.h +66 -66
- package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementMarker.h +69 -69
- package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementParameters.h +41 -41
- package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementRectangle.h +36 -36
- package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementReference.h +27 -27
- package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementShape.h +46 -46
- package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementSpot.h +33 -33
- package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementsController.h +160 -160
- package/ios/Flir/Framework/ThermalSDK/FLIRMeterLinkSensorPoll.h +247 -247
- package/ios/Flir/Framework/ThermalSDK/FLIROverlayController.h +27 -27
- package/ios/Flir/Framework/ThermalSDK/FLIRPalette.h +60 -60
- package/ios/Flir/Framework/ThermalSDK/FLIRPaletteController.h +36 -36
- package/ios/Flir/Framework/ThermalSDK/FLIRPaletteManager.h +97 -97
- package/ios/Flir/Framework/ThermalSDK/FLIRQuantification.h +55 -55
- package/ios/Flir/Framework/ThermalSDK/FLIRRemoteControl.h +393 -393
- package/ios/Flir/Framework/ThermalSDK/FLIRRenderer.h +35 -35
- package/ios/Flir/Framework/ThermalSDK/FLIRRendererImpl.h +17 -17
- package/ios/Flir/Framework/ThermalSDK/FLIRScale.h +99 -99
- package/ios/Flir/Framework/ThermalSDK/FLIRScaleController.h +44 -44
- package/ios/Flir/Framework/ThermalSDK/FLIRStream.h +109 -109
- package/ios/Flir/Framework/ThermalSDK/FLIRStreamer.h +124 -124
- package/ios/Flir/Framework/ThermalSDK/FLIRSystem.h +40 -40
- package/ios/Flir/Framework/ThermalSDK/FLIRTemperatureRange.h +43 -43
- package/ios/Flir/Framework/ThermalSDK/FLIRThermalDelta.h +77 -77
- package/ios/Flir/Framework/ThermalSDK/FLIRThermalImage.h +331 -331
- package/ios/Flir/Framework/ThermalSDK/FLIRThermalImageFile.h +56 -56
- package/ios/Flir/Framework/ThermalSDK/FLIRThermalParameters.h +31 -31
- package/ios/Flir/Framework/ThermalSDK/FLIRThermalValue.h +92 -92
- package/ios/Flir/Framework/ThermalSDK/FLIRWirelessCameraDetails.h +88 -88
- package/ios/Flir/Framework/ThermalSDK/ThermalSDK.h +73 -73
- package/ios/Flir/SDKLoader/FlirSDKLoader.m +13 -13
- package/ios/Flir/SDKLoader/FlirSDKLoader.swift +175 -175
- package/ios/Flir/src/FlirEventEmitter.h +12 -12
- package/ios/Flir/src/FlirEventEmitter.m +33 -33
- package/ios/Flir/src/FlirModule.h +10 -10
- package/ios/Flir/src/FlirModule.m +381 -381
- package/ios/Flir/src/FlirPreviewView.h +13 -13
- package/ios/Flir/src/FlirPreviewView.m +24 -24
- package/ios/Flir/src/FlirState.h +20 -20
- package/ios/Flir/src/FlirState.m +79 -79
- package/ios/Flir/src/FlirViewManager.h +9 -9
- package/ios/Flir/src/FlirViewManager.m +16 -16
- package/package.json +60 -60
- package/react-native.config.js +14 -14
- package/scripts/copy_ios_libs.sh +32 -32
- package/scripts/create_stubs.py +174 -174
- package/scripts/download-sdk.js +62 -62
- package/scripts/prepare-binaries.sh +171 -171
- package/sdk-manifest.json +37 -30
- package/src/FlirDownload.ts +78 -78
- package/src/index.d.ts +17 -17
- package/src/index.js +7 -7
- package/src/index.ts +7 -7
- package/android/Flir/src/main/java/flir/android/CameraHandler.java +0 -194
- package/android/Flir/src/main/java/flir/android/FlirController.kt +0 -11
- package/android/Flir/src/main/java/flir/android/FrameDataHolder.java +0 -14
|
@@ -0,0 +1,1511 @@
|
|
|
1
|
+
package flir.android;
|
|
2
|
+
|
|
3
|
+
import android.graphics.Bitmap;
|
|
4
|
+
import android.os.Handler;
|
|
5
|
+
import android.os.Looper;
|
|
6
|
+
import android.util.Log;
|
|
7
|
+
|
|
8
|
+
import java.io.File;
|
|
9
|
+
import java.io.FileOutputStream;
|
|
10
|
+
import java.lang.reflect.Method;
|
|
11
|
+
import java.lang.reflect.Proxy;
|
|
12
|
+
import java.util.ArrayList;
|
|
13
|
+
import java.util.List;
|
|
14
|
+
import java.util.concurrent.CopyOnWriteArrayList;
|
|
15
|
+
import java.util.concurrent.Executors;
|
|
16
|
+
import java.util.concurrent.ScheduledExecutorService;
|
|
17
|
+
import java.util.concurrent.ScheduledFuture;
|
|
18
|
+
import java.util.concurrent.TimeUnit;
|
|
19
|
+
import java.util.concurrent.atomic.AtomicBoolean;
|
|
20
|
+
import java.util.zip.ZipEntry;
|
|
21
|
+
import java.util.zip.ZipFile;
|
|
22
|
+
|
|
23
|
+
import dalvik.system.DexClassLoader;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* FLIR SDK Manager - Handles device discovery, connection, and streaming using reflection.
|
|
27
|
+
* Supports USB, NETWORK (FLIR ONE Edge), and EMULATOR interfaces.
|
|
28
|
+
* All SDK calls use reflection for loose binding - no compile-time dependency on FLIR SDK.
|
|
29
|
+
*
|
|
30
|
+
* Flow:
|
|
31
|
+
* isFlir=true → startDiscovery()
|
|
32
|
+
* ↓ disconnect current device (if any)
|
|
33
|
+
* ↓ (timeout: 0s if isEmu=true, 5s otherwise)
|
|
34
|
+
* ├─ Devices found → emit to RN → stream from device[0]
|
|
35
|
+
* │ ↓ RN sends deviceId
|
|
36
|
+
* │ └─ disconnect → switch to deviceId
|
|
37
|
+
* ├─ No device OR isEmu=true → emulatorDiscovery()
|
|
38
|
+
* │ ↓ Check emulator type setting
|
|
39
|
+
* │ ├─ FLIR_ONE_EDGE (default)
|
|
40
|
+
* │ └─ FLIR_ONE
|
|
41
|
+
* │ └─ stream from emulator
|
|
42
|
+
* └─ Streaming: connect → start stream → upload to texture
|
|
43
|
+
* ↓ acol changes → setPalette()
|
|
44
|
+
* ↓ touch point → getTemperatureAt(x,y)
|
|
45
|
+
*/
|
|
46
|
+
public class FlirSdkManager {
|
|
47
|
+
private static final String TAG = "FlirSdkManager";
|
|
48
|
+
private static final String FLOW_TAG = "FLIR_FLOW"; // For step-by-step flow logging
|
|
49
|
+
|
|
50
|
+
// Step counter for tracking flow
|
|
51
|
+
private int stepCounter = 0;
|
|
52
|
+
private long flowStartTime = 0;
|
|
53
|
+
|
|
54
|
+
private void logStep(String step, String details) {
|
|
55
|
+
stepCounter++;
|
|
56
|
+
long elapsed = flowStartTime > 0 ? System.currentTimeMillis() - flowStartTime : 0;
|
|
57
|
+
String msg = String.format("[Step %d] [+%dms] %s: %s", stepCounter, elapsed, step, details);
|
|
58
|
+
Log.i(FLOW_TAG, msg);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private void resetFlowTracking() {
|
|
62
|
+
stepCounter = 0;
|
|
63
|
+
flowStartTime = System.currentTimeMillis();
|
|
64
|
+
Log.i(FLOW_TAG, "========== FLIR FLOW STARTED ==========");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Discovery timeout in milliseconds
|
|
68
|
+
private static final long DISCOVERY_TIMEOUT_DEVICE_MS = 5000; // 5 seconds for real devices
|
|
69
|
+
private static final long DISCOVERY_TIMEOUT_EMULATOR_MS = 0; // Immediate for emulator mode
|
|
70
|
+
|
|
71
|
+
// Emulator types
|
|
72
|
+
public enum EmulatorType {
|
|
73
|
+
FLIR_ONE_EDGE, // Default - WiFi emulator
|
|
74
|
+
FLIR_ONE // USB emulator
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Communication interfaces (mirrors SDK enum)
|
|
78
|
+
public enum CommInterface {
|
|
79
|
+
USB,
|
|
80
|
+
NETWORK,
|
|
81
|
+
EMULATOR
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Listener interface for callbacks
|
|
85
|
+
public interface Listener {
|
|
86
|
+
void onFrame(Bitmap bitmap);
|
|
87
|
+
void onTemperature(double temp, int x, int y);
|
|
88
|
+
void onDeviceFound(String deviceId, String deviceName, boolean isEmulator);
|
|
89
|
+
void onDeviceListUpdated(List<DeviceInfo> devices);
|
|
90
|
+
void onDeviceConnected(String deviceId, String deviceName, boolean isEmulator);
|
|
91
|
+
void onDeviceDisconnected();
|
|
92
|
+
void onDiscoveryStarted();
|
|
93
|
+
void onDiscoveryTimeout();
|
|
94
|
+
void onStreamStarted(String streamType);
|
|
95
|
+
void onError(String error);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Device info class for discovered devices
|
|
99
|
+
public static class DeviceInfo {
|
|
100
|
+
public final String deviceId;
|
|
101
|
+
public final String deviceName;
|
|
102
|
+
public final boolean isEmulator;
|
|
103
|
+
public final CommInterface commInterface;
|
|
104
|
+
public final Object identity; // SDK Identity object (kept for connection)
|
|
105
|
+
|
|
106
|
+
DeviceInfo(String id, String name, boolean emu, CommInterface iface, Object identity) {
|
|
107
|
+
this.deviceId = id;
|
|
108
|
+
this.deviceName = name;
|
|
109
|
+
this.isEmulator = emu;
|
|
110
|
+
this.commInterface = iface;
|
|
111
|
+
this.identity = identity;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private final Listener listener;
|
|
116
|
+
private final android.content.Context appContext;
|
|
117
|
+
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
|
118
|
+
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
|
|
119
|
+
|
|
120
|
+
// SDK objects (all via reflection)
|
|
121
|
+
private ClassLoader sdkClassLoader = null;
|
|
122
|
+
private Object discoveryFactory = null;
|
|
123
|
+
private Object discoveryListener = null;
|
|
124
|
+
private Object cameraObj = null;
|
|
125
|
+
private Object streamerObj = null;
|
|
126
|
+
private Object currentStream = null;
|
|
127
|
+
private Object currentPalette = null;
|
|
128
|
+
|
|
129
|
+
// State tracking
|
|
130
|
+
private final AtomicBoolean isDiscovering = new AtomicBoolean(false);
|
|
131
|
+
private final AtomicBoolean isConnected = new AtomicBoolean(false);
|
|
132
|
+
private final AtomicBoolean isStreaming = new AtomicBoolean(false);
|
|
133
|
+
private final AtomicBoolean isEmulatorMode = new AtomicBoolean(false);
|
|
134
|
+
private final CopyOnWriteArrayList<DeviceInfo> discoveredDevices = new CopyOnWriteArrayList<>();
|
|
135
|
+
private ScheduledFuture<?> discoveryTimeoutFuture = null;
|
|
136
|
+
private DeviceInfo connectedDevice = null;
|
|
137
|
+
private EmulatorType emulatorType = EmulatorType.FLIR_ONE_EDGE;
|
|
138
|
+
|
|
139
|
+
// Frame state
|
|
140
|
+
private volatile Bitmap latestFrame = null;
|
|
141
|
+
private String sdkJarPath = null;
|
|
142
|
+
private String currentStreamKind = null;
|
|
143
|
+
|
|
144
|
+
// Frame counting for debug logging
|
|
145
|
+
private int frameCount = 0;
|
|
146
|
+
private long lastFrameLogTime = 0;
|
|
147
|
+
private int successfulBitmapCount = 0;
|
|
148
|
+
|
|
149
|
+
FlirSdkManager(Listener listener, android.content.Context context) {
|
|
150
|
+
this.listener = listener;
|
|
151
|
+
this.appContext = context != null ? context.getApplicationContext() : null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ==================== PUBLIC API ====================
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Set the emulator type to use when no physical device is found
|
|
158
|
+
*/
|
|
159
|
+
public void setEmulatorType(EmulatorType type) {
|
|
160
|
+
this.emulatorType = type;
|
|
161
|
+
Log.i(TAG, "[FLIR] Emulator type set to: " + type);
|
|
162
|
+
logStep("SET_EMULATOR_TYPE", "type=" + type + " (FLIR_ONE_EDGE=WiFi, FLIR_ONE=USB)");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Start device discovery. Will disconnect current device first.
|
|
167
|
+
* @param forceEmulator If true, skip device discovery and connect to emulator immediately
|
|
168
|
+
*/
|
|
169
|
+
public void startDiscovery(boolean forceEmulator) {
|
|
170
|
+
resetFlowTracking();
|
|
171
|
+
logStep("START_DISCOVERY", "forceEmulator=" + forceEmulator + ", emulatorType=" + emulatorType);
|
|
172
|
+
Log.i(TAG, "[FLIR] startDiscovery(forceEmulator=" + forceEmulator + ")");
|
|
173
|
+
|
|
174
|
+
// Always disconnect first
|
|
175
|
+
if (isConnected.get()) {
|
|
176
|
+
logStep("DISCONNECT_PREVIOUS", "Disconnecting current device before discovery");
|
|
177
|
+
disconnect();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Clear discovered devices
|
|
181
|
+
discoveredDevices.clear();
|
|
182
|
+
logStep("CLEAR_DEVICES", "Cleared discovered devices list");
|
|
183
|
+
|
|
184
|
+
if (forceEmulator) {
|
|
185
|
+
// Immediate emulator mode
|
|
186
|
+
logStep("MODE_EMULATOR", "Forcing emulator mode - skipping device discovery");
|
|
187
|
+
isEmulatorMode.set(true);
|
|
188
|
+
startEmulatorDiscovery();
|
|
189
|
+
} else {
|
|
190
|
+
// Normal discovery with timeout
|
|
191
|
+
logStep("MODE_FULL_DISCOVERY", "Starting full discovery (USB+NETWORK+EMULATOR), timeout=" + DISCOVERY_TIMEOUT_DEVICE_MS + "ms");
|
|
192
|
+
isEmulatorMode.set(false);
|
|
193
|
+
startFullDiscovery();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Stop discovery scan
|
|
199
|
+
*/
|
|
200
|
+
public void stopDiscovery() {
|
|
201
|
+
Log.i(TAG, "[FLIR] stopDiscovery()");
|
|
202
|
+
cancelDiscoveryTimeout();
|
|
203
|
+
isDiscovering.set(false);
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
if (discoveryFactory != null) {
|
|
207
|
+
Method stopMethod = discoveryFactory.getClass().getMethod("stop");
|
|
208
|
+
stopMethod.invoke(discoveryFactory);
|
|
209
|
+
}
|
|
210
|
+
} catch (Throwable t) {
|
|
211
|
+
Log.w(TAG, "[FLIR] stopDiscovery failed: " + t.getMessage());
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Connect to a specific device by ID
|
|
217
|
+
*/
|
|
218
|
+
public void connectToDevice(String deviceId) {
|
|
219
|
+
Log.i(TAG, "[FLIR] connectToDevice: " + deviceId);
|
|
220
|
+
|
|
221
|
+
// Find device in discovered list
|
|
222
|
+
DeviceInfo target = null;
|
|
223
|
+
for (DeviceInfo d : discoveredDevices) {
|
|
224
|
+
if (d.deviceId.equals(deviceId)) {
|
|
225
|
+
target = d;
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (target == null) {
|
|
231
|
+
notifyError("Device not found: " + deviceId);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Disconnect current if needed
|
|
236
|
+
if (isConnected.get()) {
|
|
237
|
+
disconnect();
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Connect to target
|
|
241
|
+
connectToIdentity(target);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Disconnect current device/emulator
|
|
246
|
+
*/
|
|
247
|
+
public void disconnect() {
|
|
248
|
+
Log.i(TAG, "[FLIR] disconnect()");
|
|
249
|
+
|
|
250
|
+
// Stop streaming first
|
|
251
|
+
stopStreaming();
|
|
252
|
+
|
|
253
|
+
// Disconnect camera
|
|
254
|
+
if (cameraObj != null) {
|
|
255
|
+
try {
|
|
256
|
+
Method disconnectMethod = cameraObj.getClass().getMethod("disconnect");
|
|
257
|
+
disconnectMethod.invoke(cameraObj);
|
|
258
|
+
Log.i(TAG, "[FLIR] Camera disconnected");
|
|
259
|
+
} catch (Throwable t) {
|
|
260
|
+
Log.w(TAG, "[FLIR] disconnect failed: " + t.getMessage());
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
cameraObj = null;
|
|
265
|
+
streamerObj = null;
|
|
266
|
+
currentStream = null;
|
|
267
|
+
connectedDevice = null;
|
|
268
|
+
isConnected.set(false);
|
|
269
|
+
isStreaming.set(false);
|
|
270
|
+
|
|
271
|
+
if (listener != null) {
|
|
272
|
+
mainHandler.post(() -> listener.onDeviceDisconnected());
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Set palette by name (iron, rainbow, etc.)
|
|
278
|
+
*/
|
|
279
|
+
public void setPalette(String paletteName) {
|
|
280
|
+
Log.d(TAG, "[FLIR] setPalette: " + paletteName);
|
|
281
|
+
|
|
282
|
+
if (streamerObj == null) {
|
|
283
|
+
Log.w(TAG, "[FLIR] Cannot set palette - no active streamer");
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
scheduler.submit(() -> {
|
|
288
|
+
try {
|
|
289
|
+
// Get PaletteManager.getDefaultPalettes()
|
|
290
|
+
Class<?> paletteManagerClass = findSdkClass("com.flir.thermalsdk.image.PaletteManager");
|
|
291
|
+
Method getDefaultPalettes = paletteManagerClass.getMethod("getDefaultPalettes");
|
|
292
|
+
Object palettes = getDefaultPalettes.invoke(null);
|
|
293
|
+
|
|
294
|
+
// Find matching palette
|
|
295
|
+
Object targetPalette = null;
|
|
296
|
+
if (palettes instanceof List) {
|
|
297
|
+
for (Object p : (List<?>) palettes) {
|
|
298
|
+
Method getName = p.getClass().getMethod("getName");
|
|
299
|
+
String name = (String) getName.invoke(p);
|
|
300
|
+
if (name != null && name.equalsIgnoreCase(paletteName)) {
|
|
301
|
+
targetPalette = p;
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (targetPalette != null) {
|
|
308
|
+
currentPalette = targetPalette;
|
|
309
|
+
Log.i(TAG, "[FLIR] Palette set to: " + paletteName);
|
|
310
|
+
} else {
|
|
311
|
+
Log.w(TAG, "[FLIR] Palette not found: " + paletteName);
|
|
312
|
+
}
|
|
313
|
+
} catch (Throwable t) {
|
|
314
|
+
Log.w(TAG, "[FLIR] setPalette failed: " + t.getMessage());
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Initialize default "iron" palette for thermal streaming.
|
|
321
|
+
* Called automatically when thermal streaming starts if no palette is set.
|
|
322
|
+
*/
|
|
323
|
+
private void initializeDefaultPalette() {
|
|
324
|
+
try {
|
|
325
|
+
Class<?> paletteManagerClass = findSdkClass("com.flir.thermalsdk.image.PaletteManager");
|
|
326
|
+
Method getDefaultPalettes = paletteManagerClass.getMethod("getDefaultPalettes");
|
|
327
|
+
Object palettes = getDefaultPalettes.invoke(null);
|
|
328
|
+
|
|
329
|
+
if (palettes instanceof List) {
|
|
330
|
+
// Try to find "iron" palette first, then fall back to first available
|
|
331
|
+
Object ironPalette = null;
|
|
332
|
+
Object firstPalette = null;
|
|
333
|
+
|
|
334
|
+
for (Object p : (List<?>) palettes) {
|
|
335
|
+
if (firstPalette == null) firstPalette = p;
|
|
336
|
+
|
|
337
|
+
try {
|
|
338
|
+
Method getName = p.getClass().getMethod("getName");
|
|
339
|
+
String name = (String) getName.invoke(p);
|
|
340
|
+
if (name != null && name.toLowerCase().contains("iron")) {
|
|
341
|
+
ironPalette = p;
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
} catch (Throwable ignored) {}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
currentPalette = ironPalette != null ? ironPalette : firstPalette;
|
|
348
|
+
|
|
349
|
+
if (currentPalette != null) {
|
|
350
|
+
try {
|
|
351
|
+
Method getName = currentPalette.getClass().getMethod("getName");
|
|
352
|
+
String name = (String) getName.invoke(currentPalette);
|
|
353
|
+
Log.i(TAG, "[FLIR] Default palette initialized: " + name);
|
|
354
|
+
} catch (Throwable ignored) {
|
|
355
|
+
Log.i(TAG, "[FLIR] Default palette initialized");
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
} catch (Throwable t) {
|
|
360
|
+
Log.w(TAG, "[FLIR] initializeDefaultPalette failed: " + t.getMessage());
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Get temperature at a specific point
|
|
366
|
+
*/
|
|
367
|
+
public double getTemperatureAtPoint(int x, int y) {
|
|
368
|
+
if (streamerObj == null) return Double.NaN;
|
|
369
|
+
|
|
370
|
+
try {
|
|
371
|
+
// Get the thermal image from streamer
|
|
372
|
+
Method getImage = streamerObj.getClass().getMethod("getImage");
|
|
373
|
+
Object thermalImage = getImage.invoke(streamerObj);
|
|
374
|
+
|
|
375
|
+
if (thermalImage != null) {
|
|
376
|
+
// Try getValueAt(Point)
|
|
377
|
+
try {
|
|
378
|
+
Method getValueAt = thermalImage.getClass().getMethod("getValueAt", android.graphics.Point.class);
|
|
379
|
+
android.graphics.Point p = new android.graphics.Point(x, y);
|
|
380
|
+
Object temp = getValueAt.invoke(thermalImage, p);
|
|
381
|
+
if (temp instanceof Double) return (Double) temp;
|
|
382
|
+
if (temp instanceof Float) return ((Float) temp).doubleValue();
|
|
383
|
+
} catch (NoSuchMethodException ignored) {}
|
|
384
|
+
|
|
385
|
+
// Try getValues().getValueAt(x, y)
|
|
386
|
+
try {
|
|
387
|
+
Method getValues = thermalImage.getClass().getMethod("getValues");
|
|
388
|
+
Object values = getValues.invoke(thermalImage);
|
|
389
|
+
if (values != null) {
|
|
390
|
+
Method valGetAt = values.getClass().getMethod("getValueAt", int.class, int.class);
|
|
391
|
+
Object temp = valGetAt.invoke(values, x, y);
|
|
392
|
+
if (temp instanceof Double) return (Double) temp;
|
|
393
|
+
}
|
|
394
|
+
} catch (Throwable ignored) {}
|
|
395
|
+
}
|
|
396
|
+
} catch (Throwable t) {
|
|
397
|
+
Log.d(TAG, "[FLIR] getTemperatureAtPoint failed: " + t.getMessage());
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return Double.NaN;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Get latest frame bitmap
|
|
405
|
+
*/
|
|
406
|
+
public Bitmap getLatestFrame() {
|
|
407
|
+
return latestFrame;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Check if streaming is active
|
|
412
|
+
*/
|
|
413
|
+
public boolean isStreamingActive() {
|
|
414
|
+
return isStreaming.get();
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Check if connected
|
|
419
|
+
*/
|
|
420
|
+
public boolean isConnected() {
|
|
421
|
+
return isConnected.get();
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Get current stream type
|
|
426
|
+
*/
|
|
427
|
+
public String getCurrentStreamKind() {
|
|
428
|
+
return currentStreamKind;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Get list of discovered devices
|
|
433
|
+
*/
|
|
434
|
+
public List<DeviceInfo> getDiscoveredDevices() {
|
|
435
|
+
return new ArrayList<>(discoveredDevices);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Cleanup resources
|
|
440
|
+
*/
|
|
441
|
+
public void stop() {
|
|
442
|
+
Log.i(TAG, "[FLIR] stop()");
|
|
443
|
+
stopDiscovery();
|
|
444
|
+
disconnect();
|
|
445
|
+
scheduler.shutdownNow();
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// ==================== DISCOVERY IMPLEMENTATION ====================
|
|
449
|
+
|
|
450
|
+
private void startFullDiscovery() {
|
|
451
|
+
Log.i(TAG, "[FLIR] Starting full discovery (USB, NETWORK, EMULATOR)");
|
|
452
|
+
|
|
453
|
+
if (!initializeSdk()) {
|
|
454
|
+
Log.w(TAG, "[FLIR] SDK not available, falling back to emulator");
|
|
455
|
+
startEmulatorDiscovery();
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
isDiscovering.set(true);
|
|
460
|
+
if (listener != null) {
|
|
461
|
+
mainHandler.post(() -> listener.onDiscoveryStarted());
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
try {
|
|
465
|
+
// Get DiscoveryFactory.getInstance()
|
|
466
|
+
Class<?> discoveryFactoryClass = findSdkClass("com.flir.thermalsdk.live.discovery.DiscoveryFactory");
|
|
467
|
+
Method getInstance = discoveryFactoryClass.getMethod("getInstance");
|
|
468
|
+
discoveryFactory = getInstance.invoke(null);
|
|
469
|
+
|
|
470
|
+
// Get CommunicationInterface enum values
|
|
471
|
+
Class<?> commIfaceClass = findSdkClass("com.flir.thermalsdk.live.CommunicationInterface");
|
|
472
|
+
Object usbInterface = Enum.valueOf((Class<Enum>) commIfaceClass, "USB");
|
|
473
|
+
Object networkInterface = Enum.valueOf((Class<Enum>) commIfaceClass, "NETWORK");
|
|
474
|
+
Object emulatorInterface = Enum.valueOf((Class<Enum>) commIfaceClass, "EMULATOR");
|
|
475
|
+
|
|
476
|
+
// Create discovery listener proxy
|
|
477
|
+
Class<?> listenerClass = findSdkClass("com.flir.thermalsdk.live.discovery.DiscoveryEventListener");
|
|
478
|
+
discoveryListener = Proxy.newProxyInstance(
|
|
479
|
+
getEffectiveClassLoader(),
|
|
480
|
+
new Class<?>[] { listenerClass },
|
|
481
|
+
(proxy, method, args) -> handleDiscoveryCallback(method.getName(), args)
|
|
482
|
+
);
|
|
483
|
+
|
|
484
|
+
// Create interface array [USB, NETWORK, EMULATOR]
|
|
485
|
+
Object ifaceArray = java.lang.reflect.Array.newInstance(commIfaceClass, 3);
|
|
486
|
+
java.lang.reflect.Array.set(ifaceArray, 0, usbInterface);
|
|
487
|
+
java.lang.reflect.Array.set(ifaceArray, 1, networkInterface);
|
|
488
|
+
java.lang.reflect.Array.set(ifaceArray, 2, emulatorInterface);
|
|
489
|
+
|
|
490
|
+
// Start discovery scan
|
|
491
|
+
Method scanMethod = discoveryFactoryClass.getMethod("scan", listenerClass, ifaceArray.getClass());
|
|
492
|
+
scanMethod.invoke(discoveryFactory, discoveryListener, ifaceArray);
|
|
493
|
+
|
|
494
|
+
Log.i(TAG, "[FLIR] Discovery scan started for USB, NETWORK, EMULATOR");
|
|
495
|
+
|
|
496
|
+
// Set discovery timeout
|
|
497
|
+
startDiscoveryTimeout(DISCOVERY_TIMEOUT_DEVICE_MS);
|
|
498
|
+
|
|
499
|
+
} catch (Throwable t) {
|
|
500
|
+
Log.e(TAG, "[FLIR] startFullDiscovery failed: " + t.getMessage(), t);
|
|
501
|
+
notifyError("Discovery failed: " + t.getMessage());
|
|
502
|
+
// Fallback to emulator
|
|
503
|
+
startEmulatorDiscovery();
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
private void startEmulatorDiscovery() {
|
|
508
|
+
logStep("EMULATOR_DISCOVERY_START", "type=" + emulatorType);
|
|
509
|
+
Log.i(TAG, "[FLIR] Starting emulator discovery (type=" + emulatorType + ")");
|
|
510
|
+
|
|
511
|
+
if (!initializeSdk()) {
|
|
512
|
+
logStep("SDK_INIT_FAILED", "FLIR SDK not available - check if SDK JAR is loaded");
|
|
513
|
+
notifyError("FLIR SDK not available");
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
logStep("SDK_INITIALIZED", "FLIR SDK successfully initialized");
|
|
517
|
+
|
|
518
|
+
isDiscovering.set(true);
|
|
519
|
+
isEmulatorMode.set(true);
|
|
520
|
+
|
|
521
|
+
try {
|
|
522
|
+
// Get DiscoveryFactory
|
|
523
|
+
Class<?> discoveryFactoryClass = findSdkClass("com.flir.thermalsdk.live.discovery.DiscoveryFactory");
|
|
524
|
+
Method getInstance = discoveryFactoryClass.getMethod("getInstance");
|
|
525
|
+
discoveryFactory = getInstance.invoke(null);
|
|
526
|
+
|
|
527
|
+
// Get EMULATOR interface
|
|
528
|
+
Class<?> commIfaceClass = findSdkClass("com.flir.thermalsdk.live.CommunicationInterface");
|
|
529
|
+
Object emulatorInterface = Enum.valueOf((Class<Enum>) commIfaceClass, "EMULATOR");
|
|
530
|
+
|
|
531
|
+
// Create discovery listener
|
|
532
|
+
Class<?> listenerClass = findSdkClass("com.flir.thermalsdk.live.discovery.DiscoveryEventListener");
|
|
533
|
+
discoveryListener = Proxy.newProxyInstance(
|
|
534
|
+
getEffectiveClassLoader(),
|
|
535
|
+
new Class<?>[] { listenerClass },
|
|
536
|
+
(proxy, method, args) -> handleDiscoveryCallback(method.getName(), args)
|
|
537
|
+
);
|
|
538
|
+
|
|
539
|
+
// Create interface array [EMULATOR]
|
|
540
|
+
Object ifaceArray = java.lang.reflect.Array.newInstance(commIfaceClass, 1);
|
|
541
|
+
java.lang.reflect.Array.set(ifaceArray, 0, emulatorInterface);
|
|
542
|
+
|
|
543
|
+
// Start discovery
|
|
544
|
+
Method scanMethod = discoveryFactoryClass.getMethod("scan", listenerClass, ifaceArray.getClass());
|
|
545
|
+
scanMethod.invoke(discoveryFactory, discoveryListener, ifaceArray);
|
|
546
|
+
|
|
547
|
+
logStep("EMULATOR_SCAN_STARTED", "Scanning for emulator devices...");
|
|
548
|
+
Log.i(TAG, "[FLIR] Emulator discovery started");
|
|
549
|
+
|
|
550
|
+
} catch (Throwable t) {
|
|
551
|
+
logStep("EMULATOR_DISCOVERY_ERROR", "Failed: " + t.getMessage());
|
|
552
|
+
Log.e(TAG, "[FLIR] startEmulatorDiscovery failed: " + t.getMessage(), t);
|
|
553
|
+
notifyError("Emulator discovery failed: " + t.getMessage());
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
private Object handleDiscoveryCallback(String methodName, Object[] args) {
|
|
558
|
+
switch (methodName) {
|
|
559
|
+
case "onCameraFound":
|
|
560
|
+
if (args != null && args.length > 0) {
|
|
561
|
+
handleCameraFound(args[0]);
|
|
562
|
+
}
|
|
563
|
+
break;
|
|
564
|
+
|
|
565
|
+
case "onCameraLost":
|
|
566
|
+
if (args != null && args.length > 0) {
|
|
567
|
+
handleCameraLost(args[0]);
|
|
568
|
+
}
|
|
569
|
+
break;
|
|
570
|
+
|
|
571
|
+
case "onDiscoveryError":
|
|
572
|
+
if (args != null && args.length > 1) {
|
|
573
|
+
Log.w(TAG, "[FLIR] Discovery error: " + args[1]);
|
|
574
|
+
}
|
|
575
|
+
break;
|
|
576
|
+
}
|
|
577
|
+
return null;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
private void handleCameraFound(Object discoveredCamera) {
|
|
581
|
+
try {
|
|
582
|
+
// Get identity from DiscoveredCamera
|
|
583
|
+
Method getIdentity = discoveredCamera.getClass().getMethod("getIdentity");
|
|
584
|
+
Object identity = getIdentity.invoke(discoveredCamera);
|
|
585
|
+
|
|
586
|
+
// Extract device info from identity
|
|
587
|
+
String deviceId = extractDeviceId(identity);
|
|
588
|
+
String deviceName = extractDeviceName(identity);
|
|
589
|
+
CommInterface commInterface = extractCommInterface(identity);
|
|
590
|
+
boolean isEmulator = commInterface == CommInterface.EMULATOR;
|
|
591
|
+
|
|
592
|
+
logStep("DEVICE_FOUND", "name=" + deviceName + ", id=" + deviceId + ", interface=" + commInterface + ", isEmulator=" + isEmulator);
|
|
593
|
+
Log.i(TAG, "[FLIR] Camera found: " + deviceName + " (" + commInterface + ")");
|
|
594
|
+
|
|
595
|
+
// Create device info
|
|
596
|
+
DeviceInfo deviceInfo = new DeviceInfo(deviceId, deviceName, isEmulator, commInterface, identity);
|
|
597
|
+
|
|
598
|
+
// Add to list if not already present
|
|
599
|
+
boolean exists = false;
|
|
600
|
+
for (DeviceInfo d : discoveredDevices) {
|
|
601
|
+
if (d.deviceId.equals(deviceId)) {
|
|
602
|
+
exists = true;
|
|
603
|
+
break;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
if (!exists) {
|
|
608
|
+
discoveredDevices.add(deviceInfo);
|
|
609
|
+
|
|
610
|
+
// Notify listener
|
|
611
|
+
if (listener != null) {
|
|
612
|
+
final List<DeviceInfo> devices = new ArrayList<>(discoveredDevices);
|
|
613
|
+
mainHandler.post(() -> {
|
|
614
|
+
listener.onDeviceFound(deviceId, deviceName, isEmulator);
|
|
615
|
+
listener.onDeviceListUpdated(devices);
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// If this is first device found (not emulator in normal mode), connect to it
|
|
620
|
+
if (!isEmulator && discoveredDevices.size() == 1 && !isEmulatorMode.get()) {
|
|
621
|
+
cancelDiscoveryTimeout();
|
|
622
|
+
stopDiscovery();
|
|
623
|
+
connectToIdentity(deviceInfo);
|
|
624
|
+
} else if (isEmulator && isEmulatorMode.get()) {
|
|
625
|
+
// In emulator mode, connect to first emulator found
|
|
626
|
+
stopDiscovery();
|
|
627
|
+
connectToIdentity(deviceInfo);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
} catch (Throwable t) {
|
|
632
|
+
Log.e(TAG, "[FLIR] handleCameraFound failed: " + t.getMessage(), t);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
private void handleCameraLost(Object discoveredCamera) {
|
|
637
|
+
try {
|
|
638
|
+
Method getIdentity = discoveredCamera.getClass().getMethod("getIdentity");
|
|
639
|
+
Object identity = getIdentity.invoke(discoveredCamera);
|
|
640
|
+
String deviceId = extractDeviceId(identity);
|
|
641
|
+
|
|
642
|
+
Log.i(TAG, "[FLIR] Camera lost: " + deviceId);
|
|
643
|
+
|
|
644
|
+
// Remove from list
|
|
645
|
+
discoveredDevices.removeIf(d -> d.deviceId.equals(deviceId));
|
|
646
|
+
|
|
647
|
+
// If this was our connected device, disconnect
|
|
648
|
+
if (connectedDevice != null && connectedDevice.deviceId.equals(deviceId)) {
|
|
649
|
+
disconnect();
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
} catch (Throwable t) {
|
|
653
|
+
Log.w(TAG, "[FLIR] handleCameraLost failed: " + t.getMessage());
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
private void startDiscoveryTimeout(long timeoutMs) {
|
|
658
|
+
cancelDiscoveryTimeout();
|
|
659
|
+
|
|
660
|
+
if (timeoutMs <= 0) return;
|
|
661
|
+
|
|
662
|
+
discoveryTimeoutFuture = scheduler.schedule(() -> {
|
|
663
|
+
Log.i(TAG, "[FLIR] Discovery timeout");
|
|
664
|
+
isDiscovering.set(false);
|
|
665
|
+
|
|
666
|
+
if (listener != null) {
|
|
667
|
+
mainHandler.post(() -> listener.onDiscoveryTimeout());
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// If no physical devices found, try emulator
|
|
671
|
+
boolean hasPhysicalDevice = false;
|
|
672
|
+
for (DeviceInfo d : discoveredDevices) {
|
|
673
|
+
if (!d.isEmulator) {
|
|
674
|
+
hasPhysicalDevice = true;
|
|
675
|
+
break;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
if (!hasPhysicalDevice) {
|
|
680
|
+
Log.i(TAG, "[FLIR] No physical devices found, starting emulator");
|
|
681
|
+
startEmulatorDiscovery();
|
|
682
|
+
} else if (!discoveredDevices.isEmpty()) {
|
|
683
|
+
// Connect to first device
|
|
684
|
+
connectToIdentity(discoveredDevices.get(0));
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
}, timeoutMs, TimeUnit.MILLISECONDS);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
private void cancelDiscoveryTimeout() {
|
|
691
|
+
if (discoveryTimeoutFuture != null) {
|
|
692
|
+
discoveryTimeoutFuture.cancel(false);
|
|
693
|
+
discoveryTimeoutFuture = null;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// ==================== CONNECTION IMPLEMENTATION ====================
|
|
698
|
+
|
|
699
|
+
private void connectToIdentity(DeviceInfo deviceInfo) {
|
|
700
|
+
logStep("CONNECT_START", "Connecting to: " + deviceInfo.deviceName + " (" + deviceInfo.commInterface + ")");
|
|
701
|
+
Log.i(TAG, "[FLIR] Connecting to: " + deviceInfo.deviceName);
|
|
702
|
+
|
|
703
|
+
scheduler.submit(() -> {
|
|
704
|
+
try {
|
|
705
|
+
// Create Camera instance
|
|
706
|
+
logStep("CREATE_CAMERA", "Creating Camera instance");
|
|
707
|
+
Class<?> cameraClass = findSdkClass("com.flir.thermalsdk.live.Camera");
|
|
708
|
+
cameraObj = cameraClass.newInstance();
|
|
709
|
+
|
|
710
|
+
// Create connection status listener
|
|
711
|
+
Class<?> connStatusClass = findSdkClass("com.flir.thermalsdk.live.connectivity.ConnectionStatusListener");
|
|
712
|
+
Object connListener = Proxy.newProxyInstance(
|
|
713
|
+
getEffectiveClassLoader(),
|
|
714
|
+
new Class<?>[] { connStatusClass },
|
|
715
|
+
(proxy, method, args) -> {
|
|
716
|
+
if ("onDisconnected".equals(method.getName())) {
|
|
717
|
+
Log.w(TAG, "[FLIR] Camera disconnected (callback)");
|
|
718
|
+
handleDisconnected();
|
|
719
|
+
}
|
|
720
|
+
return null;
|
|
721
|
+
}
|
|
722
|
+
);
|
|
723
|
+
|
|
724
|
+
// Try to connect with different method signatures
|
|
725
|
+
boolean connected = false;
|
|
726
|
+
|
|
727
|
+
// Try connect(Identity, ConnectionStatusListener, ConnectParameters)
|
|
728
|
+
try {
|
|
729
|
+
Class<?> connectParamsClass = findSdkClass("com.flir.thermalsdk.live.ConnectParameters");
|
|
730
|
+
Object connectParams = connectParamsClass.newInstance();
|
|
731
|
+
Class<?> identityClass = findSdkClass("com.flir.thermalsdk.live.Identity");
|
|
732
|
+
Method connectMethod = cameraClass.getMethod("connect",
|
|
733
|
+
identityClass, connStatusClass, connectParamsClass);
|
|
734
|
+
connectMethod.invoke(cameraObj, deviceInfo.identity, connListener, connectParams);
|
|
735
|
+
connected = true;
|
|
736
|
+
} catch (NoSuchMethodException ignored) {}
|
|
737
|
+
|
|
738
|
+
// Try connect(Identity, ConnectionStatusListener)
|
|
739
|
+
if (!connected) {
|
|
740
|
+
try {
|
|
741
|
+
Class<?> identityClass = findSdkClass("com.flir.thermalsdk.live.Identity");
|
|
742
|
+
Method connectMethod = cameraClass.getMethod("connect",
|
|
743
|
+
identityClass, connStatusClass);
|
|
744
|
+
connectMethod.invoke(cameraObj, deviceInfo.identity, connListener);
|
|
745
|
+
connected = true;
|
|
746
|
+
} catch (NoSuchMethodException ignored) {}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// Try connect(Identity)
|
|
750
|
+
if (!connected) {
|
|
751
|
+
try {
|
|
752
|
+
Class<?> identityClass = findSdkClass("com.flir.thermalsdk.live.Identity");
|
|
753
|
+
Method connectMethod = cameraClass.getMethod("connect", identityClass);
|
|
754
|
+
connectMethod.invoke(cameraObj, deviceInfo.identity);
|
|
755
|
+
connected = true;
|
|
756
|
+
} catch (NoSuchMethodException ignored) {}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
if (!connected) {
|
|
760
|
+
throw new Exception("No suitable connect method found");
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
connectedDevice = deviceInfo;
|
|
764
|
+
isConnected.set(true);
|
|
765
|
+
|
|
766
|
+
logStep("CONNECTED", "Successfully connected to: " + deviceInfo.deviceName);
|
|
767
|
+
Log.i(TAG, "[FLIR] Connected to: " + deviceInfo.deviceName);
|
|
768
|
+
|
|
769
|
+
// Notify listener
|
|
770
|
+
if (listener != null) {
|
|
771
|
+
mainHandler.post(() -> listener.onDeviceConnected(
|
|
772
|
+
deviceInfo.deviceId, deviceInfo.deviceName, deviceInfo.isEmulator));
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// Start streaming after brief delay
|
|
776
|
+
logStep("SCHEDULE_STREAMING", "Scheduling stream start in 500ms");
|
|
777
|
+
scheduler.schedule(this::startStreaming, 500, TimeUnit.MILLISECONDS);
|
|
778
|
+
|
|
779
|
+
} catch (Throwable t) {
|
|
780
|
+
logStep("CONNECT_FAILED", "Connection failed: " + t.getMessage());
|
|
781
|
+
Log.e(TAG, "[FLIR] Connection failed: " + t.getMessage(), t);
|
|
782
|
+
notifyError("Connection failed: " + t.getMessage());
|
|
783
|
+
}
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
private void handleDisconnected() {
|
|
788
|
+
cameraObj = null;
|
|
789
|
+
streamerObj = null;
|
|
790
|
+
currentStream = null;
|
|
791
|
+
connectedDevice = null;
|
|
792
|
+
isConnected.set(false);
|
|
793
|
+
isStreaming.set(false);
|
|
794
|
+
|
|
795
|
+
if (listener != null) {
|
|
796
|
+
mainHandler.post(() -> listener.onDeviceDisconnected());
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// ==================== STREAMING IMPLEMENTATION ====================
|
|
801
|
+
|
|
802
|
+
private void startStreaming() {
|
|
803
|
+
if (cameraObj == null) {
|
|
804
|
+
logStep("STREAM_SKIP", "Cannot start streaming - no camera object");
|
|
805
|
+
Log.w(TAG, "[FLIR] Cannot start streaming - no camera");
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
logStep("STREAM_START", "Starting streaming process...");
|
|
810
|
+
Log.i(TAG, "[FLIR] Starting streaming...");
|
|
811
|
+
|
|
812
|
+
try {
|
|
813
|
+
// Get camera streams
|
|
814
|
+
Method getStreams = cameraObj.getClass().getMethod("getStreams");
|
|
815
|
+
Object streams = getStreams.invoke(cameraObj);
|
|
816
|
+
|
|
817
|
+
if (streams == null || !(streams instanceof List) || ((List<?>) streams).isEmpty()) {
|
|
818
|
+
logStep("NO_STREAMS", "No streams available from camera");
|
|
819
|
+
Log.w(TAG, "[FLIR] No streams available");
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
List<?> streamList = (List<?>) streams;
|
|
824
|
+
logStep("STREAMS_FOUND", "Found " + streamList.size() + " stream(s)");
|
|
825
|
+
Log.i(TAG, "[FLIR] Found " + streamList.size() + " stream(s)");
|
|
826
|
+
|
|
827
|
+
// Prefer thermal stream, fallback to first available
|
|
828
|
+
Object chosenStream = null;
|
|
829
|
+
String streamType = "unknown";
|
|
830
|
+
|
|
831
|
+
for (Object s : streamList) {
|
|
832
|
+
if (s == null) continue;
|
|
833
|
+
|
|
834
|
+
try {
|
|
835
|
+
Method isThermal = s.getClass().getMethod("isThermal");
|
|
836
|
+
Boolean thermal = (Boolean) isThermal.invoke(s);
|
|
837
|
+
|
|
838
|
+
Method getName = null;
|
|
839
|
+
try { getName = s.getClass().getMethod("getName"); } catch (Throwable ignored) {}
|
|
840
|
+
String name = getName != null ? String.valueOf(getName.invoke(s)) : "unknown";
|
|
841
|
+
|
|
842
|
+
Log.d(TAG, "[FLIR] Stream: " + name + ", thermal=" + thermal);
|
|
843
|
+
|
|
844
|
+
if (thermal != null && thermal) {
|
|
845
|
+
chosenStream = s;
|
|
846
|
+
streamType = "thermal";
|
|
847
|
+
break;
|
|
848
|
+
} else if (chosenStream == null) {
|
|
849
|
+
chosenStream = s;
|
|
850
|
+
streamType = "visual";
|
|
851
|
+
}
|
|
852
|
+
} catch (Throwable ignored) {}
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
if (chosenStream == null) {
|
|
856
|
+
chosenStream = streamList.get(0);
|
|
857
|
+
streamType = "default";
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
currentStream = chosenStream;
|
|
861
|
+
currentStreamKind = streamType;
|
|
862
|
+
|
|
863
|
+
logStep("STREAM_SELECTED", "Using " + streamType + " stream");
|
|
864
|
+
Log.i(TAG, "[FLIR] Using stream type: " + streamType);
|
|
865
|
+
|
|
866
|
+
// Create appropriate streamer
|
|
867
|
+
if ("thermal".equals(streamType)) {
|
|
868
|
+
logStep("CREATE_THERMAL_STREAMER", "Creating ThermalStreamer");
|
|
869
|
+
Class<?> thermalStreamerClass = findSdkClass("com.flir.thermalsdk.live.streaming.ThermalStreamer");
|
|
870
|
+
Class<?> streamClass = findSdkClass("com.flir.thermalsdk.live.streaming.Stream");
|
|
871
|
+
streamerObj = thermalStreamerClass.getConstructor(streamClass).newInstance(chosenStream);
|
|
872
|
+
|
|
873
|
+
// Initialize default palette if not already set
|
|
874
|
+
if (currentPalette == null) {
|
|
875
|
+
logStep("INIT_PALETTE", "Initializing default 'iron' palette");
|
|
876
|
+
initializeDefaultPalette();
|
|
877
|
+
} else {
|
|
878
|
+
logStep("PALETTE_EXISTS", "Palette already set, skipping init");
|
|
879
|
+
}
|
|
880
|
+
} else {
|
|
881
|
+
logStep("CREATE_VISUAL_STREAMER", "Creating VisualStreamer");
|
|
882
|
+
Class<?> visualStreamerClass = findSdkClass("com.flir.thermalsdk.live.streaming.VisualStreamer");
|
|
883
|
+
Class<?> streamClass = findSdkClass("com.flir.thermalsdk.live.streaming.Stream");
|
|
884
|
+
streamerObj = visualStreamerClass.getConstructor(streamClass).newInstance(chosenStream);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// Create OnReceived callback
|
|
888
|
+
Class<?> onReceivedClass = findSdkClass("com.flir.thermalsdk.live.remote.OnReceived");
|
|
889
|
+
Object onReceivedCallback = Proxy.newProxyInstance(
|
|
890
|
+
getEffectiveClassLoader(),
|
|
891
|
+
new Class<?>[] { onReceivedClass },
|
|
892
|
+
(proxy, method, args) -> {
|
|
893
|
+
if ("run".equals(method.getName())) {
|
|
894
|
+
scheduler.submit(this::processFrame);
|
|
895
|
+
}
|
|
896
|
+
return null;
|
|
897
|
+
}
|
|
898
|
+
);
|
|
899
|
+
|
|
900
|
+
// Create OnRemoteError callback
|
|
901
|
+
Class<?> onErrorClass = findSdkClass("com.flir.thermalsdk.live.remote.OnRemoteError");
|
|
902
|
+
Object onErrorCallback = Proxy.newProxyInstance(
|
|
903
|
+
getEffectiveClassLoader(),
|
|
904
|
+
new Class<?>[] { onErrorClass },
|
|
905
|
+
(proxy, method, args) -> {
|
|
906
|
+
if ("run".equals(method.getName()) && args != null && args.length > 0) {
|
|
907
|
+
Log.e(TAG, "[FLIR] Stream error: " + args[0]);
|
|
908
|
+
}
|
|
909
|
+
return null;
|
|
910
|
+
}
|
|
911
|
+
);
|
|
912
|
+
|
|
913
|
+
// Start the stream
|
|
914
|
+
logStep("STREAM_STARTING", "Invoking stream.start()");
|
|
915
|
+
Method startMethod = chosenStream.getClass().getMethod("start", onReceivedClass, onErrorClass);
|
|
916
|
+
startMethod.invoke(chosenStream, onReceivedCallback, onErrorCallback);
|
|
917
|
+
|
|
918
|
+
isStreaming.set(true);
|
|
919
|
+
|
|
920
|
+
logStep("STREAM_STARTED", "Stream started successfully - type=" + streamType + ", waiting for frames...");
|
|
921
|
+
Log.i(TAG, "[FLIR] Streaming started (" + streamType + ")");
|
|
922
|
+
|
|
923
|
+
if (listener != null) {
|
|
924
|
+
final String type = streamType;
|
|
925
|
+
mainHandler.post(() -> listener.onStreamStarted(type));
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
} catch (Throwable t) {
|
|
929
|
+
logStep("STREAM_ERROR", "Streaming failed: " + t.getMessage());
|
|
930
|
+
Log.e(TAG, "[FLIR] startStreaming failed: " + t.getMessage(), t);
|
|
931
|
+
notifyError("Streaming failed: " + t.getMessage());
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
private void processFrame() {
|
|
936
|
+
if (streamerObj == null) return;
|
|
937
|
+
|
|
938
|
+
frameCount++;
|
|
939
|
+
long now = System.currentTimeMillis();
|
|
940
|
+
|
|
941
|
+
// Log first frame and then every 30 frames or every 5 seconds
|
|
942
|
+
boolean shouldLog = frameCount == 1 || frameCount % 30 == 0 || (now - lastFrameLogTime > 5000);
|
|
943
|
+
|
|
944
|
+
if (shouldLog) {
|
|
945
|
+
logStep("PROCESS_FRAME", "frame=" + frameCount + ", streamType=" + currentStreamKind + ", bitmapsSuccess=" + successfulBitmapCount);
|
|
946
|
+
lastFrameLogTime = now;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
try {
|
|
950
|
+
// Call streamer.update() first - this refreshes the streamer content
|
|
951
|
+
Method updateMethod = streamerObj.getClass().getMethod("update");
|
|
952
|
+
updateMethod.invoke(streamerObj);
|
|
953
|
+
|
|
954
|
+
// Get image buffer from streamer.getImage()
|
|
955
|
+
Method getImage = streamerObj.getClass().getMethod("getImage");
|
|
956
|
+
Object imageBuffer = getImage.invoke(streamerObj);
|
|
957
|
+
|
|
958
|
+
if (imageBuffer == null) {
|
|
959
|
+
if (shouldLog) logStep("FRAME_NULL_BUFFER", "imageBuffer is null at frame " + frameCount);
|
|
960
|
+
Log.d(TAG, "[FLIR] imageBuffer is null");
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
if (frameCount == 1) {
|
|
965
|
+
logStep("FIRST_BUFFER", "Got first imageBuffer, type=" + imageBuffer.getClass().getSimpleName());
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// For thermal streamer, we MUST create the bitmap INSIDE withThermalImage callback
|
|
969
|
+
// This is critical - the palette affects the imageBuffer rendering only while inside the callback
|
|
970
|
+
if ("thermal".equals(currentStreamKind)) {
|
|
971
|
+
processThermalFrameWithCallback(imageBuffer);
|
|
972
|
+
} else {
|
|
973
|
+
// For visual stream, just convert to bitmap directly
|
|
974
|
+
Bitmap bitmap = convertToBitmap(imageBuffer);
|
|
975
|
+
if (bitmap != null) {
|
|
976
|
+
successfulBitmapCount++;
|
|
977
|
+
latestFrame = bitmap;
|
|
978
|
+
if (frameCount == 1) {
|
|
979
|
+
logStep("FIRST_BITMAP", "Visual bitmap created: " + bitmap.getWidth() + "x" + bitmap.getHeight());
|
|
980
|
+
}
|
|
981
|
+
if (listener != null) {
|
|
982
|
+
listener.onFrame(bitmap);
|
|
983
|
+
}
|
|
984
|
+
} else if (frameCount <= 5) {
|
|
985
|
+
logStep("BITMAP_NULL", "Visual convertToBitmap returned null at frame " + frameCount);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
} catch (Throwable t) {
|
|
990
|
+
// Log errors periodically to avoid spam
|
|
991
|
+
if (System.currentTimeMillis() % 5000 < 100) {
|
|
992
|
+
Log.d(TAG, "[FLIR] Frame processing error: " + t.getMessage());
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
/**
|
|
998
|
+
* Process thermal frame using withThermalImage callback pattern.
|
|
999
|
+
* CRITICAL: Both palette setting AND bitmap conversion must happen INSIDE the callback!
|
|
1000
|
+
* This matches the LiveStreamingKotlin example exactly.
|
|
1001
|
+
*/
|
|
1002
|
+
private void processThermalFrameWithCallback(Object imageBuffer) {
|
|
1003
|
+
if (streamerObj == null) return;
|
|
1004
|
+
|
|
1005
|
+
try {
|
|
1006
|
+
Class<?> thermalStreamerClass = findSdkClass("com.flir.thermalsdk.live.streaming.ThermalStreamer");
|
|
1007
|
+
if (!thermalStreamerClass.isInstance(streamerObj)) {
|
|
1008
|
+
// Not a ThermalStreamer, use direct conversion
|
|
1009
|
+
Bitmap bitmap = convertToBitmap(imageBuffer);
|
|
1010
|
+
if (bitmap != null) {
|
|
1011
|
+
latestFrame = bitmap;
|
|
1012
|
+
if (listener != null) listener.onFrame(bitmap);
|
|
1013
|
+
}
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
// Find withThermalImage method
|
|
1018
|
+
Method withThermalImageMethod = null;
|
|
1019
|
+
for (Method m : thermalStreamerClass.getMethods()) {
|
|
1020
|
+
if ("withThermalImage".equals(m.getName()) && m.getParameterCount() == 1) {
|
|
1021
|
+
withThermalImageMethod = m;
|
|
1022
|
+
break;
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
if (withThermalImageMethod == null) {
|
|
1027
|
+
Log.w(TAG, "[FLIR] withThermalImage method not found, using fallback");
|
|
1028
|
+
// Fallback: try to set palette via getThermalImage, then convert
|
|
1029
|
+
try {
|
|
1030
|
+
Method getThermalImage = streamerObj.getClass().getMethod("getThermalImage");
|
|
1031
|
+
Object thermalImage = getThermalImage.invoke(streamerObj);
|
|
1032
|
+
if (thermalImage != null && currentPalette != null) {
|
|
1033
|
+
setPaletteOnThermalImage(thermalImage);
|
|
1034
|
+
}
|
|
1035
|
+
} catch (Throwable ignored) {}
|
|
1036
|
+
|
|
1037
|
+
Bitmap bitmap = convertToBitmap(imageBuffer);
|
|
1038
|
+
if (bitmap != null) {
|
|
1039
|
+
latestFrame = bitmap;
|
|
1040
|
+
if (listener != null) listener.onFrame(bitmap);
|
|
1041
|
+
}
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// Create Consumer proxy that does BOTH: set palette AND create bitmap
|
|
1046
|
+
// This is the key insight from LiveStreamingKotlin!
|
|
1047
|
+
final Object imgBuffer = imageBuffer;
|
|
1048
|
+
Class<?> consumerClass = withThermalImageMethod.getParameterTypes()[0];
|
|
1049
|
+
Object consumer = Proxy.newProxyInstance(
|
|
1050
|
+
getEffectiveClassLoader(),
|
|
1051
|
+
new Class<?>[] { consumerClass },
|
|
1052
|
+
(proxy, method, args) -> {
|
|
1053
|
+
if ("accept".equals(method.getName()) && args != null && args.length > 0) {
|
|
1054
|
+
Object thermalImage = args[0];
|
|
1055
|
+
if (thermalImage != null) {
|
|
1056
|
+
// Step 1: Set palette on thermal image
|
|
1057
|
+
if (currentPalette != null) {
|
|
1058
|
+
setPaletteOnThermalImage(thermalImage);
|
|
1059
|
+
if (frameCount == 1) {
|
|
1060
|
+
logStep("PALETTE_APPLIED", "Palette applied to ThermalImage inside callback");
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
// Step 2: Convert to bitmap INSIDE the callback
|
|
1065
|
+
// This is critical - must happen while we have access to thermal image context
|
|
1066
|
+
try {
|
|
1067
|
+
Bitmap bitmap = convertToBitmap(imgBuffer);
|
|
1068
|
+
if (bitmap != null) {
|
|
1069
|
+
successfulBitmapCount++;
|
|
1070
|
+
latestFrame = bitmap;
|
|
1071
|
+
if (frameCount == 1) {
|
|
1072
|
+
logStep("FIRST_THERMAL_BITMAP", "Thermal bitmap created INSIDE callback: " + bitmap.getWidth() + "x" + bitmap.getHeight());
|
|
1073
|
+
}
|
|
1074
|
+
if (listener != null) {
|
|
1075
|
+
listener.onFrame(bitmap);
|
|
1076
|
+
}
|
|
1077
|
+
} else if (frameCount <= 5) {
|
|
1078
|
+
logStep("THERMAL_BITMAP_NULL", "Thermal convertToBitmap returned null at frame " + frameCount);
|
|
1079
|
+
}
|
|
1080
|
+
} catch (Throwable t) {
|
|
1081
|
+
if (frameCount <= 5) {
|
|
1082
|
+
logStep("THERMAL_BITMAP_ERROR", "Bitmap conversion failed: " + t.getMessage());
|
|
1083
|
+
}
|
|
1084
|
+
Log.d(TAG, "[FLIR] Bitmap conversion in callback failed: " + t.getMessage());
|
|
1085
|
+
}
|
|
1086
|
+
} else if (frameCount <= 5) {
|
|
1087
|
+
logStep("THERMAL_IMAGE_NULL", "ThermalImage is null in callback");
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
return null;
|
|
1091
|
+
}
|
|
1092
|
+
);
|
|
1093
|
+
|
|
1094
|
+
withThermalImageMethod.invoke(streamerObj, consumer);
|
|
1095
|
+
|
|
1096
|
+
} catch (Throwable t) {
|
|
1097
|
+
Log.d(TAG, "[FLIR] processThermalFrameWithCallback failed: " + t.getMessage());
|
|
1098
|
+
// Fallback to direct conversion
|
|
1099
|
+
Bitmap bitmap = convertToBitmap(imageBuffer);
|
|
1100
|
+
if (bitmap != null) {
|
|
1101
|
+
latestFrame = bitmap;
|
|
1102
|
+
if (listener != null) listener.onFrame(bitmap);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
/**
|
|
1108
|
+
* Apply palette via ThermalStreamer.withThermalImage() callback pattern.
|
|
1109
|
+
* In the SDK: thermalStreamer.withThermalImage { it.palette = selectedPalette }
|
|
1110
|
+
* NOTE: This is now mostly unused - processThermalFrameWithCallback handles both palette and bitmap
|
|
1111
|
+
*/
|
|
1112
|
+
private void applyPaletteViaThermalImage() {
|
|
1113
|
+
if (streamerObj == null || currentPalette == null) return;
|
|
1114
|
+
|
|
1115
|
+
try {
|
|
1116
|
+
Class<?> thermalStreamerClass = findSdkClass("com.flir.thermalsdk.live.streaming.ThermalStreamer");
|
|
1117
|
+
if (!thermalStreamerClass.isInstance(streamerObj)) return;
|
|
1118
|
+
|
|
1119
|
+
// Find withThermalImage method - it takes a Consumer<ThermalImage> callback
|
|
1120
|
+
// We'll use reflection to create a proxy for the Consumer interface
|
|
1121
|
+
Method withThermalImageMethod = null;
|
|
1122
|
+
for (Method m : thermalStreamerClass.getMethods()) {
|
|
1123
|
+
if ("withThermalImage".equals(m.getName()) && m.getParameterCount() == 1) {
|
|
1124
|
+
withThermalImageMethod = m;
|
|
1125
|
+
break;
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
if (withThermalImageMethod == null) {
|
|
1130
|
+
// Fallback: Try to get ThermalImage directly
|
|
1131
|
+
try {
|
|
1132
|
+
Method getThermalImage = streamerObj.getClass().getMethod("getThermalImage");
|
|
1133
|
+
Object thermalImage = getThermalImage.invoke(streamerObj);
|
|
1134
|
+
if (thermalImage != null) {
|
|
1135
|
+
setPaletteOnThermalImage(thermalImage);
|
|
1136
|
+
}
|
|
1137
|
+
} catch (Throwable ignored) {}
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
// Create Consumer proxy to call setPalette on ThermalImage
|
|
1142
|
+
Class<?> consumerClass = withThermalImageMethod.getParameterTypes()[0];
|
|
1143
|
+
Object consumer = Proxy.newProxyInstance(
|
|
1144
|
+
getEffectiveClassLoader(),
|
|
1145
|
+
new Class<?>[] { consumerClass },
|
|
1146
|
+
(proxy, method, args) -> {
|
|
1147
|
+
if ("accept".equals(method.getName()) && args != null && args.length > 0) {
|
|
1148
|
+
Object thermalImage = args[0];
|
|
1149
|
+
if (thermalImage != null) {
|
|
1150
|
+
setPaletteOnThermalImage(thermalImage);
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
return null;
|
|
1154
|
+
}
|
|
1155
|
+
);
|
|
1156
|
+
|
|
1157
|
+
withThermalImageMethod.invoke(streamerObj, consumer);
|
|
1158
|
+
|
|
1159
|
+
} catch (Throwable t) {
|
|
1160
|
+
Log.d(TAG, "[FLIR] applyPaletteViaThermalImage failed: " + t.getMessage());
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
/**
|
|
1165
|
+
* Set palette on a ThermalImage object
|
|
1166
|
+
*/
|
|
1167
|
+
private void setPaletteOnThermalImage(Object thermalImage) {
|
|
1168
|
+
if (thermalImage == null || currentPalette == null) return;
|
|
1169
|
+
|
|
1170
|
+
try {
|
|
1171
|
+
Class<?> paletteClass = findSdkClass("com.flir.thermalsdk.image.Palette");
|
|
1172
|
+
Method setPalette = thermalImage.getClass().getMethod("setPalette", paletteClass);
|
|
1173
|
+
setPalette.invoke(thermalImage, currentPalette);
|
|
1174
|
+
} catch (Throwable t) {
|
|
1175
|
+
// Try with direct class
|
|
1176
|
+
try {
|
|
1177
|
+
Method setPalette = thermalImage.getClass().getMethod("setPalette", currentPalette.getClass());
|
|
1178
|
+
setPalette.invoke(thermalImage, currentPalette);
|
|
1179
|
+
} catch (Throwable ignored) {}
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
private Bitmap convertToBitmap(Object imageBuffer) {
|
|
1184
|
+
boolean isFirstConvert = frameCount == 1;
|
|
1185
|
+
|
|
1186
|
+
try {
|
|
1187
|
+
// Try BitmapAndroid.createBitmap(imageBuffer).bitMap (or getBitMap)
|
|
1188
|
+
Class<?> bitmapAndroidClass = findSdkClass("com.flir.thermalsdk.androidsdk.image.BitmapAndroid");
|
|
1189
|
+
|
|
1190
|
+
if (isFirstConvert) {
|
|
1191
|
+
logStep("BITMAP_CONVERT_START", "imageBuffer type=" + imageBuffer.getClass().getName());
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
// Find a createBitmap method that works with our imageBuffer
|
|
1195
|
+
for (Method m : bitmapAndroidClass.getMethods()) {
|
|
1196
|
+
if ("createBitmap".equals(m.getName()) && m.getParameterCount() == 1) {
|
|
1197
|
+
Class<?> paramType = m.getParameterTypes()[0];
|
|
1198
|
+
if (paramType.isInstance(imageBuffer)) {
|
|
1199
|
+
Object wrapper = m.invoke(null, imageBuffer);
|
|
1200
|
+
if (wrapper != null) {
|
|
1201
|
+
if (isFirstConvert) {
|
|
1202
|
+
logStep("BITMAP_WRAPPER", "BitmapAndroid wrapper created: " + wrapper.getClass().getSimpleName());
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
// Try different method names: bitMap, getBitMap, getBitmap
|
|
1206
|
+
String[] methodNames = {"getBitMap", "bitMap", "getBitmap"};
|
|
1207
|
+
for (String methodName : methodNames) {
|
|
1208
|
+
try {
|
|
1209
|
+
Method getBitMap = wrapper.getClass().getMethod(methodName);
|
|
1210
|
+
Object bmp = getBitMap.invoke(wrapper);
|
|
1211
|
+
if (bmp instanceof Bitmap) {
|
|
1212
|
+
Bitmap bitmap = (Bitmap) bmp;
|
|
1213
|
+
if (bitmap.getWidth() > 0 && bitmap.getHeight() > 0) {
|
|
1214
|
+
if (isFirstConvert) {
|
|
1215
|
+
logStep("BITMAP_SUCCESS", "Got bitmap via " + methodName + "(): " + bitmap.getWidth() + "x" + bitmap.getHeight());
|
|
1216
|
+
}
|
|
1217
|
+
return bitmap;
|
|
1218
|
+
} else if (isFirstConvert) {
|
|
1219
|
+
logStep("BITMAP_EMPTY", "Bitmap has zero dimensions via " + methodName);
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
} catch (NoSuchMethodException ignored) {}
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
// Also try as a field access (Kotlin property)
|
|
1226
|
+
try {
|
|
1227
|
+
java.lang.reflect.Field field = wrapper.getClass().getField("bitMap");
|
|
1228
|
+
Object bmp = field.get(wrapper);
|
|
1229
|
+
if (bmp instanceof Bitmap) {
|
|
1230
|
+
return (Bitmap) bmp;
|
|
1231
|
+
}
|
|
1232
|
+
} catch (Throwable ignored) {}
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
Log.d(TAG, "[FLIR] BitmapAndroid.createBitmap method not found for imageBuffer type: " + imageBuffer.getClass().getName());
|
|
1239
|
+
|
|
1240
|
+
} catch (Throwable t) {
|
|
1241
|
+
Log.d(TAG, "[FLIR] convertToBitmap primary method failed: " + t.getMessage());
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
// Try alternative: direct getBitmap() on imageBuffer
|
|
1245
|
+
try {
|
|
1246
|
+
Method getBitmap = imageBuffer.getClass().getMethod("getBitmap");
|
|
1247
|
+
Object bmp = getBitmap.invoke(imageBuffer);
|
|
1248
|
+
if (bmp instanceof Bitmap) {
|
|
1249
|
+
return (Bitmap) bmp;
|
|
1250
|
+
}
|
|
1251
|
+
} catch (Throwable ignored) {}
|
|
1252
|
+
|
|
1253
|
+
return null;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
private void stopStreaming() {
|
|
1257
|
+
if (currentStream != null) {
|
|
1258
|
+
try {
|
|
1259
|
+
Method stopMethod = currentStream.getClass().getMethod("stop");
|
|
1260
|
+
stopMethod.invoke(currentStream);
|
|
1261
|
+
Log.i(TAG, "[FLIR] Stream stopped");
|
|
1262
|
+
} catch (Throwable t) {
|
|
1263
|
+
Log.w(TAG, "[FLIR] stopStreaming failed: " + t.getMessage());
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
streamerObj = null;
|
|
1268
|
+
currentStream = null;
|
|
1269
|
+
isStreaming.set(false);
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
// ==================== SDK LOADING ====================
|
|
1273
|
+
|
|
1274
|
+
private boolean initializeSdk() {
|
|
1275
|
+
// Try direct class loading first
|
|
1276
|
+
try {
|
|
1277
|
+
Class.forName("com.flir.thermalsdk.live.CommunicationInterface");
|
|
1278
|
+
Log.i(TAG, "[FLIR SDK] Classes available on classpath");
|
|
1279
|
+
|
|
1280
|
+
// Initialize SDK
|
|
1281
|
+
initializeThermalSdk();
|
|
1282
|
+
return true;
|
|
1283
|
+
} catch (ClassNotFoundException e) {
|
|
1284
|
+
Log.w(TAG, "[FLIR SDK] Classes not on classpath, trying AAR load");
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
// Try loading from downloaded AAR
|
|
1288
|
+
if (attemptLoadSdkFromAar()) {
|
|
1289
|
+
initializeThermalSdk();
|
|
1290
|
+
return true;
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
return false;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
private void initializeThermalSdk() {
|
|
1297
|
+
try {
|
|
1298
|
+
Class<?> sdkClass = findSdkClass("com.flir.thermalsdk.live.ThermalSdkAndroid");
|
|
1299
|
+
Method initMethod = sdkClass.getMethod("init", android.content.Context.class);
|
|
1300
|
+
initMethod.invoke(null, appContext);
|
|
1301
|
+
Log.i(TAG, "[FLIR SDK] ThermalSdkAndroid.init() completed");
|
|
1302
|
+
} catch (Throwable t) {
|
|
1303
|
+
Log.w(TAG, "[FLIR SDK] ThermalSdkAndroid.init() failed: " + t.getMessage());
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
private boolean attemptLoadSdkFromAar() {
|
|
1308
|
+
android.content.Context ctx = appContext;
|
|
1309
|
+
if (ctx == null) {
|
|
1310
|
+
Log.w(TAG, "[FLIR SDK] No application context available for SDK load");
|
|
1311
|
+
return false;
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
try {
|
|
1315
|
+
// First try: Load from architecture-specific DEX (new format from FlirSDKLoader)
|
|
1316
|
+
File dexFile = FlirSDKLoader.INSTANCE.getDexPath(ctx);
|
|
1317
|
+
if (dexFile != null && dexFile.exists()) {
|
|
1318
|
+
Log.i(TAG, "[FLIR SDK] Found DEX file: " + dexFile.getAbsolutePath() + " (size=" + dexFile.length() + ")");
|
|
1319
|
+
|
|
1320
|
+
// Get native library directory path for DexClassLoader
|
|
1321
|
+
File nativeLibDir = FlirSDKLoader.INSTANCE.getNativeLibDir(ctx);
|
|
1322
|
+
String nativeLibPath = nativeLibDir != null ? nativeLibDir.getAbsolutePath() : null;
|
|
1323
|
+
Log.i(TAG, "[FLIR SDK] Native lib path: " + nativeLibPath);
|
|
1324
|
+
|
|
1325
|
+
// Create DexClassLoader with native lib path
|
|
1326
|
+
File dexOutDir = ctx.getDir("dex", android.content.Context.MODE_PRIVATE);
|
|
1327
|
+
DexClassLoader dcl = new DexClassLoader(
|
|
1328
|
+
dexFile.getAbsolutePath(),
|
|
1329
|
+
dexOutDir.getAbsolutePath(),
|
|
1330
|
+
nativeLibPath, // This allows SDK to find .so files
|
|
1331
|
+
ctx.getClassLoader()
|
|
1332
|
+
);
|
|
1333
|
+
|
|
1334
|
+
// Verify class loading
|
|
1335
|
+
Class<?> test = Class.forName("com.flir.thermalsdk.live.CommunicationInterface", true, dcl);
|
|
1336
|
+
if (test != null) {
|
|
1337
|
+
sdkClassLoader = dcl;
|
|
1338
|
+
sdkJarPath = dexFile.getAbsolutePath();
|
|
1339
|
+
Log.i(TAG, "[FLIR SDK] DexClassLoader created from DEX: " + dexFile.getAbsolutePath());
|
|
1340
|
+
return true;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
// Fallback: Legacy AAR loading
|
|
1345
|
+
Log.i(TAG, "[FLIR SDK] No DEX found, trying legacy AAR locations...");
|
|
1346
|
+
|
|
1347
|
+
File filesDir = ctx.getFilesDir();
|
|
1348
|
+
|
|
1349
|
+
// Candidate search locations (ordered by preference)
|
|
1350
|
+
List<File> candidates = new ArrayList<>();
|
|
1351
|
+
|
|
1352
|
+
// Primary: FlirSDKLoader download directory
|
|
1353
|
+
candidates.add(new File(filesDir, "FlirSDK/thermalsdk-release.aar"));
|
|
1354
|
+
candidates.add(new File(filesDir, "FlirSDK/androidsdk-release.aar"));
|
|
1355
|
+
candidates.add(new File(filesDir, "FlirSDK/thermalsdk.aar"));
|
|
1356
|
+
|
|
1357
|
+
// Legacy locations
|
|
1358
|
+
candidates.add(new File(filesDir, "flir-sdk/thermalsdk-release.aar"));
|
|
1359
|
+
candidates.add(new File(filesDir, "thermalsdk-release.aar"));
|
|
1360
|
+
candidates.add(new File(filesDir, "thermalsdk.aar"));
|
|
1361
|
+
|
|
1362
|
+
// External storage
|
|
1363
|
+
File extDir = ctx.getExternalFilesDir(null);
|
|
1364
|
+
if (extDir != null) {
|
|
1365
|
+
candidates.add(new File(extDir, "FlirSDK/thermalsdk-release.aar"));
|
|
1366
|
+
candidates.add(new File(extDir, "thermalsdk-release.aar"));
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
// Find first existing AAR
|
|
1370
|
+
File aarFile = null;
|
|
1371
|
+
StringBuilder tried = new StringBuilder();
|
|
1372
|
+
for (File f : candidates) {
|
|
1373
|
+
tried.append(f.getAbsolutePath()).append(f.exists() ? "(✓)," : "(✗),");
|
|
1374
|
+
if (f.exists()) {
|
|
1375
|
+
aarFile = f;
|
|
1376
|
+
break;
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
if (aarFile == null) {
|
|
1381
|
+
Log.w(TAG, "[FLIR SDK] No AAR found. Tried: " + tried);
|
|
1382
|
+
return false;
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
Log.i(TAG, "[FLIR SDK] Found AAR: " + aarFile.getAbsolutePath());
|
|
1386
|
+
|
|
1387
|
+
// Extract classes.jar from AAR to a private directory
|
|
1388
|
+
ZipFile zf = new ZipFile(aarFile);
|
|
1389
|
+
ZipEntry classesEntry = zf.getEntry("classes.jar");
|
|
1390
|
+
if (classesEntry == null) {
|
|
1391
|
+
Log.w(TAG, "[FLIR SDK] classes.jar not found in AAR");
|
|
1392
|
+
zf.close();
|
|
1393
|
+
return false;
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
// Use getDir() for a MODE_PRIVATE directory - required for DexClassLoader security
|
|
1397
|
+
File privateDir = ctx.getDir("flir_sdk", android.content.Context.MODE_PRIVATE);
|
|
1398
|
+
File outJar = new File(privateDir, "flir-classes.jar");
|
|
1399
|
+
|
|
1400
|
+
// Delete old file if exists to ensure clean extraction
|
|
1401
|
+
if (outJar.exists()) {
|
|
1402
|
+
outJar.delete();
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
FileOutputStream fos = new FileOutputStream(outJar);
|
|
1406
|
+
java.io.InputStream is = zf.getInputStream(classesEntry);
|
|
1407
|
+
byte[] buf = new byte[8192];
|
|
1408
|
+
int r;
|
|
1409
|
+
while ((r = is.read(buf)) != -1) fos.write(buf, 0, r);
|
|
1410
|
+
is.close();
|
|
1411
|
+
fos.close();
|
|
1412
|
+
zf.close();
|
|
1413
|
+
|
|
1414
|
+
// Set file to read-only (required for Android security)
|
|
1415
|
+
outJar.setReadOnly();
|
|
1416
|
+
|
|
1417
|
+
Log.i(TAG, "[FLIR SDK] Extracted classes.jar to: " + outJar.getAbsolutePath() + " (size=" + outJar.length() + ")");
|
|
1418
|
+
|
|
1419
|
+
// Create DexClassLoader with private dex output directory
|
|
1420
|
+
File dexOutDir = ctx.getDir("dex", android.content.Context.MODE_PRIVATE);
|
|
1421
|
+
DexClassLoader dcl = new DexClassLoader(
|
|
1422
|
+
outJar.getAbsolutePath(),
|
|
1423
|
+
dexOutDir.getAbsolutePath(),
|
|
1424
|
+
null,
|
|
1425
|
+
ctx.getClassLoader()
|
|
1426
|
+
);
|
|
1427
|
+
|
|
1428
|
+
// Verify class loading
|
|
1429
|
+
Class<?> test = Class.forName("com.flir.thermalsdk.live.CommunicationInterface", true, dcl);
|
|
1430
|
+
if (test != null) {
|
|
1431
|
+
sdkClassLoader = dcl;
|
|
1432
|
+
sdkJarPath = outJar.getAbsolutePath();
|
|
1433
|
+
Log.i(TAG, "[FLIR SDK] DexClassLoader created from: " + outJar.getAbsolutePath());
|
|
1434
|
+
return true;
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
} catch (Throwable t) {
|
|
1438
|
+
Log.e(TAG, "[FLIR SDK] attemptLoadSdkFromAar failed: " + t.getMessage(), t);
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
return false;
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
private Class<?> findSdkClass(String name) throws ClassNotFoundException {
|
|
1445
|
+
try {
|
|
1446
|
+
return Class.forName(name);
|
|
1447
|
+
} catch (ClassNotFoundException e) {
|
|
1448
|
+
if (sdkClassLoader != null) {
|
|
1449
|
+
return Class.forName(name, true, sdkClassLoader);
|
|
1450
|
+
}
|
|
1451
|
+
throw e;
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
private ClassLoader getEffectiveClassLoader() {
|
|
1456
|
+
return sdkClassLoader != null ? sdkClassLoader : getClass().getClassLoader();
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
// ==================== HELPERS ====================
|
|
1460
|
+
|
|
1461
|
+
private String extractDeviceId(Object identity) {
|
|
1462
|
+
try {
|
|
1463
|
+
Method getDeviceId = identity.getClass().getMethod("getDeviceId");
|
|
1464
|
+
Object result = getDeviceId.invoke(identity);
|
|
1465
|
+
return result != null ? result.toString() : "unknown";
|
|
1466
|
+
} catch (Throwable t) {
|
|
1467
|
+
return "device_" + System.currentTimeMillis();
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
private String extractDeviceName(Object identity) {
|
|
1472
|
+
try {
|
|
1473
|
+
// Try getName() first
|
|
1474
|
+
try {
|
|
1475
|
+
Method getName = identity.getClass().getMethod("getName");
|
|
1476
|
+
Object result = getName.invoke(identity);
|
|
1477
|
+
if (result != null && !result.toString().isEmpty()) {
|
|
1478
|
+
return result.toString();
|
|
1479
|
+
}
|
|
1480
|
+
} catch (Throwable ignored) {}
|
|
1481
|
+
|
|
1482
|
+
// Try getDeviceId() as fallback
|
|
1483
|
+
Method getDeviceId = identity.getClass().getMethod("getDeviceId");
|
|
1484
|
+
Object result = getDeviceId.invoke(identity);
|
|
1485
|
+
return result != null ? result.toString() : "FLIR Camera";
|
|
1486
|
+
} catch (Throwable t) {
|
|
1487
|
+
return "FLIR Camera";
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
private CommInterface extractCommInterface(Object identity) {
|
|
1492
|
+
try {
|
|
1493
|
+
Method getCommInterface = identity.getClass().getMethod("getCommunicationInterface");
|
|
1494
|
+
Object result = getCommInterface.invoke(identity);
|
|
1495
|
+
if (result != null) {
|
|
1496
|
+
String name = result.toString();
|
|
1497
|
+
if (name.contains("USB")) return CommInterface.USB;
|
|
1498
|
+
if (name.contains("NETWORK")) return CommInterface.NETWORK;
|
|
1499
|
+
if (name.contains("EMULATOR")) return CommInterface.EMULATOR;
|
|
1500
|
+
}
|
|
1501
|
+
} catch (Throwable ignored) {}
|
|
1502
|
+
return CommInterface.EMULATOR;
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
private void notifyError(String error) {
|
|
1506
|
+
Log.e(TAG, "[FLIR] Error: " + error);
|
|
1507
|
+
if (listener != null) {
|
|
1508
|
+
mainHandler.post(() -> listener.onError(error));
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
}
|