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.
Files changed (126) hide show
  1. package/Flir.podspec +31 -31
  2. package/README.md +1271 -1271
  3. package/android/Flir/build.gradle.kts +85 -80
  4. package/android/Flir/libs/flir-stubs.jar +0 -0
  5. package/android/Flir/src/main/AndroidManifest.xml +31 -31
  6. package/android/Flir/src/main/java/com/flir/thermalsdk/ErrorCode.java +13 -0
  7. package/android/Flir/src/main/java/com/flir/thermalsdk/ErrorCodeException.java +14 -0
  8. package/android/Flir/src/main/java/com/flir/thermalsdk/ThermalSdkAndroid.java +16 -0
  9. package/android/Flir/src/main/java/com/flir/thermalsdk/androidsdk/image/BitmapAndroid.java +20 -0
  10. package/android/Flir/src/main/java/com/flir/thermalsdk/image/ImageBuffer.java +11 -0
  11. package/android/Flir/src/main/java/com/flir/thermalsdk/image/JavaImageBuffer.java +35 -0
  12. package/android/Flir/src/main/java/com/flir/thermalsdk/image/Palette.java +15 -0
  13. package/android/Flir/src/main/java/com/flir/thermalsdk/image/PaletteManager.java +16 -0
  14. package/android/Flir/src/main/java/com/flir/thermalsdk/image/Point.java +11 -0
  15. package/android/Flir/src/main/java/com/flir/thermalsdk/image/ThermalImage.java +23 -0
  16. package/android/Flir/src/main/java/com/flir/thermalsdk/image/ThermalValue.java +9 -0
  17. package/android/Flir/src/main/java/com/flir/thermalsdk/live/Camera.java +26 -0
  18. package/android/Flir/src/main/java/com/flir/thermalsdk/live/CameraType.java +8 -0
  19. package/android/Flir/src/main/java/com/flir/thermalsdk/live/CommunicationInterface.java +16 -0
  20. package/android/Flir/src/main/java/com/flir/thermalsdk/live/ConnectParameters.java +16 -0
  21. package/android/Flir/src/main/java/com/flir/thermalsdk/live/Identity.java +23 -0
  22. package/android/Flir/src/main/java/com/flir/thermalsdk/live/IpSettings.java +9 -0
  23. package/android/Flir/src/main/java/com/flir/thermalsdk/live/RemoteControl.java +16 -0
  24. package/android/Flir/src/main/java/com/flir/thermalsdk/live/connectivity/ConnectionStatusListener.java +7 -0
  25. package/android/Flir/src/main/java/com/flir/thermalsdk/live/discovery/DiscoveryEventListener.java +14 -0
  26. package/android/Flir/src/main/java/com/flir/thermalsdk/live/discovery/DiscoveryFactory.java +33 -0
  27. package/android/Flir/src/main/java/com/flir/thermalsdk/live/remote/OnReceived.java +5 -0
  28. package/android/Flir/src/main/java/com/flir/thermalsdk/live/remote/OnRemoteError.java +7 -0
  29. package/android/Flir/src/main/java/com/flir/thermalsdk/live/streaming/Stream.java +8 -0
  30. package/android/Flir/src/main/java/com/flir/thermalsdk/live/streaming/ThermalStreamer.java +28 -0
  31. package/android/Flir/src/main/java/com/flir/thermalsdk/live/streaming/VisualStreamer.java +18 -0
  32. package/android/Flir/src/main/java/flir/android/FlirCommands.java +136 -0
  33. package/android/Flir/src/main/java/flir/android/FlirDownloadManager.kt +76 -75
  34. package/android/Flir/src/main/java/flir/android/FlirDownloadPackage.kt +16 -16
  35. package/android/Flir/src/main/java/flir/android/FlirFrameCache.kt +6 -6
  36. package/android/Flir/src/main/java/flir/android/FlirManager.kt +477 -248
  37. package/android/Flir/src/main/java/flir/android/FlirModule.kt +74 -74
  38. package/android/Flir/src/main/java/flir/android/FlirPackage.kt +19 -19
  39. package/android/Flir/src/main/java/flir/android/FlirSDKLoader.kt +165 -117
  40. package/android/Flir/src/main/java/flir/android/FlirSdkManager.java +1511 -0
  41. package/android/Flir/src/main/java/flir/android/FlirStatus.kt +12 -12
  42. package/android/Flir/src/main/java/flir/android/FlirView.kt +48 -48
  43. package/android/Flir/src/main/java/flir/android/FlirViewManager.kt +13 -13
  44. package/app.plugin.js +264 -264
  45. package/expo-module.config.json +5 -5
  46. package/ios/Flir/Framework/ThermalSDK/FLIRBattery.h +76 -76
  47. package/ios/Flir/Framework/ThermalSDK/FLIRCalibration.h +108 -108
  48. package/ios/Flir/Framework/ThermalSDK/FLIRCamera.h +156 -156
  49. package/ios/Flir/Framework/ThermalSDK/FLIRCameraDeviceInfo.h +53 -53
  50. package/ios/Flir/Framework/ThermalSDK/FLIRCameraEvent.h +132 -132
  51. package/ios/Flir/Framework/ThermalSDK/FLIRCameraImport.h +204 -204
  52. package/ios/Flir/Framework/ThermalSDK/FLIRColorDistributionSettings.h +204 -204
  53. package/ios/Flir/Framework/ThermalSDK/FLIRColorizer.h +82 -82
  54. package/ios/Flir/Framework/ThermalSDK/FLIRDiscoveredCamera.h +44 -44
  55. package/ios/Flir/Framework/ThermalSDK/FLIRDiscovery.h +132 -132
  56. package/ios/Flir/Framework/ThermalSDK/FLIRDisplaySettings.h +29 -29
  57. package/ios/Flir/Framework/ThermalSDK/FLIRFocus.h +70 -70
  58. package/ios/Flir/Framework/ThermalSDK/FLIRFusion.h +192 -192
  59. package/ios/Flir/Framework/ThermalSDK/FLIRFusionController.h +136 -136
  60. package/ios/Flir/Framework/ThermalSDK/FLIRFusionTransformation.h +35 -35
  61. package/ios/Flir/Framework/ThermalSDK/FLIRIdentity.h +264 -264
  62. package/ios/Flir/Framework/ThermalSDK/FLIRImageBase.h +196 -196
  63. package/ios/Flir/Framework/ThermalSDK/FLIRImageColorizer.h +26 -26
  64. package/ios/Flir/Framework/ThermalSDK/FLIRImageStatistics.h +61 -61
  65. package/ios/Flir/Framework/ThermalSDK/FLIRIsotherms.h +208 -208
  66. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementArea.h +38 -38
  67. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementCollection.h +147 -147
  68. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementDelta.h +62 -62
  69. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementDimensions.h +33 -33
  70. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementEllipse.h +49 -49
  71. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementLine.h +66 -66
  72. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementMarker.h +69 -69
  73. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementParameters.h +41 -41
  74. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementRectangle.h +36 -36
  75. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementReference.h +27 -27
  76. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementShape.h +46 -46
  77. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementSpot.h +33 -33
  78. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementsController.h +160 -160
  79. package/ios/Flir/Framework/ThermalSDK/FLIRMeterLinkSensorPoll.h +247 -247
  80. package/ios/Flir/Framework/ThermalSDK/FLIROverlayController.h +27 -27
  81. package/ios/Flir/Framework/ThermalSDK/FLIRPalette.h +60 -60
  82. package/ios/Flir/Framework/ThermalSDK/FLIRPaletteController.h +36 -36
  83. package/ios/Flir/Framework/ThermalSDK/FLIRPaletteManager.h +97 -97
  84. package/ios/Flir/Framework/ThermalSDK/FLIRQuantification.h +55 -55
  85. package/ios/Flir/Framework/ThermalSDK/FLIRRemoteControl.h +393 -393
  86. package/ios/Flir/Framework/ThermalSDK/FLIRRenderer.h +35 -35
  87. package/ios/Flir/Framework/ThermalSDK/FLIRRendererImpl.h +17 -17
  88. package/ios/Flir/Framework/ThermalSDK/FLIRScale.h +99 -99
  89. package/ios/Flir/Framework/ThermalSDK/FLIRScaleController.h +44 -44
  90. package/ios/Flir/Framework/ThermalSDK/FLIRStream.h +109 -109
  91. package/ios/Flir/Framework/ThermalSDK/FLIRStreamer.h +124 -124
  92. package/ios/Flir/Framework/ThermalSDK/FLIRSystem.h +40 -40
  93. package/ios/Flir/Framework/ThermalSDK/FLIRTemperatureRange.h +43 -43
  94. package/ios/Flir/Framework/ThermalSDK/FLIRThermalDelta.h +77 -77
  95. package/ios/Flir/Framework/ThermalSDK/FLIRThermalImage.h +331 -331
  96. package/ios/Flir/Framework/ThermalSDK/FLIRThermalImageFile.h +56 -56
  97. package/ios/Flir/Framework/ThermalSDK/FLIRThermalParameters.h +31 -31
  98. package/ios/Flir/Framework/ThermalSDK/FLIRThermalValue.h +92 -92
  99. package/ios/Flir/Framework/ThermalSDK/FLIRWirelessCameraDetails.h +88 -88
  100. package/ios/Flir/Framework/ThermalSDK/ThermalSDK.h +73 -73
  101. package/ios/Flir/SDKLoader/FlirSDKLoader.m +13 -13
  102. package/ios/Flir/SDKLoader/FlirSDKLoader.swift +175 -175
  103. package/ios/Flir/src/FlirEventEmitter.h +12 -12
  104. package/ios/Flir/src/FlirEventEmitter.m +33 -33
  105. package/ios/Flir/src/FlirModule.h +10 -10
  106. package/ios/Flir/src/FlirModule.m +381 -381
  107. package/ios/Flir/src/FlirPreviewView.h +13 -13
  108. package/ios/Flir/src/FlirPreviewView.m +24 -24
  109. package/ios/Flir/src/FlirState.h +20 -20
  110. package/ios/Flir/src/FlirState.m +79 -79
  111. package/ios/Flir/src/FlirViewManager.h +9 -9
  112. package/ios/Flir/src/FlirViewManager.m +16 -16
  113. package/package.json +60 -60
  114. package/react-native.config.js +14 -14
  115. package/scripts/copy_ios_libs.sh +32 -32
  116. package/scripts/create_stubs.py +174 -174
  117. package/scripts/download-sdk.js +62 -62
  118. package/scripts/prepare-binaries.sh +171 -171
  119. package/sdk-manifest.json +37 -30
  120. package/src/FlirDownload.ts +78 -78
  121. package/src/index.d.ts +17 -17
  122. package/src/index.js +7 -7
  123. package/src/index.ts +7 -7
  124. package/android/Flir/src/main/java/flir/android/CameraHandler.java +0 -194
  125. package/android/Flir/src/main/java/flir/android/FlirController.kt +0 -11
  126. package/android/Flir/src/main/java/flir/android/FrameDataHolder.java +0 -14
@@ -1,74 +1,74 @@
1
- package flir.android
2
-
3
- import com.facebook.react.bridge.Promise
4
- import com.facebook.react.bridge.ReactApplicationContext
5
- import com.facebook.react.bridge.ReactContextBaseJavaModule
6
- import com.facebook.react.bridge.ReactMethod
7
-
8
- class FlirModule(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
9
- override fun getName(): String = "FlirModule"
10
-
11
- // Simple placeholder conversion: converts an ARGB color to a pseudo-temperature value.
12
- // Replace with SDK call when integrating thermalsdk APIs.
13
- @ReactMethod
14
- fun getTemperatureFromColor(color: Int, promise: Promise) {
15
- try {
16
- val r = (color shr 16) and 0xFF
17
- val g = (color shr 8) and 0xFF
18
- val b = color and 0xFF
19
- // Luminance-like value scaled to a plausible temperature range (0°C - 400°C)
20
- val lum = 0.2126 * r + 0.7152 * g + 0.0722 * b
21
- val temp = 0.0 + (lum / 255.0) * 400.0
22
- promise.resolve(temp)
23
- } catch (e: Exception) {
24
- promise.reject("ERR_FLIR_CONVERT", e)
25
- }
26
- }
27
-
28
- @ReactMethod
29
- fun getLatestFramePath(promise: Promise) {
30
- try {
31
- val path = FlirFrameCache.latestFramePath
32
- if (path != null) promise.resolve(path) else promise.resolve(null)
33
- } catch (e: Exception) {
34
- promise.reject("ERR_FLIR_PATH", e)
35
- }
36
- }
37
-
38
- @ReactMethod
39
- fun getTemperatureAt(x: Int, y: Int, promise: Promise) {
40
- try {
41
- val temp = FlirManager.getTemperatureAt(x, y)
42
- if (temp != null) promise.resolve(temp) else promise.reject("ERR_NO_DATA", "No temperature data available")
43
- } catch (e: Exception) {
44
- promise.reject("ERR_FLIR_SAMPLE", e)
45
- }
46
- }
47
-
48
- @ReactMethod
49
- fun isEmulator(promise: Promise) {
50
- try {
51
- promise.resolve(FlirManager.isEmulator())
52
- } catch (e: Exception) {
53
- promise.reject("ERR_FLIR_EMULATOR_CHECK", e)
54
- }
55
- }
56
-
57
- @ReactMethod
58
- fun isDeviceConnected(promise: Promise) {
59
- try {
60
- promise.resolve(FlirManager.isDeviceConnected())
61
- } catch (e: Exception) {
62
- promise.reject("ERR_FLIR_DEVICE_CHECK", e)
63
- }
64
- }
65
-
66
- @ReactMethod
67
- fun getConnectedDeviceInfo(promise: Promise) {
68
- try {
69
- promise.resolve(FlirManager.getConnectedDeviceInfo())
70
- } catch (e: Exception) {
71
- promise.reject("ERR_FLIR_DEVICE_INFO", e)
72
- }
73
- }
74
- }
1
+ package flir.android
2
+
3
+ import com.facebook.react.bridge.Promise
4
+ import com.facebook.react.bridge.ReactApplicationContext
5
+ import com.facebook.react.bridge.ReactContextBaseJavaModule
6
+ import com.facebook.react.bridge.ReactMethod
7
+
8
+ class FlirModule(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
9
+ override fun getName(): String = "FlirModule"
10
+
11
+ // Simple placeholder conversion: converts an ARGB color to a pseudo-temperature value.
12
+ // Replace with SDK call when integrating thermalsdk APIs.
13
+ @ReactMethod
14
+ fun getTemperatureFromColor(color: Int, promise: Promise) {
15
+ try {
16
+ val r = (color shr 16) and 0xFF
17
+ val g = (color shr 8) and 0xFF
18
+ val b = color and 0xFF
19
+ // Luminance-like value scaled to a plausible temperature range (0°C - 400°C)
20
+ val lum = 0.2126 * r + 0.7152 * g + 0.0722 * b
21
+ val temp = 0.0 + (lum / 255.0) * 400.0
22
+ promise.resolve(temp)
23
+ } catch (e: Exception) {
24
+ promise.reject("ERR_FLIR_CONVERT", e)
25
+ }
26
+ }
27
+
28
+ @ReactMethod
29
+ fun getLatestFramePath(promise: Promise) {
30
+ try {
31
+ val path = FlirFrameCache.latestFramePath
32
+ if (path != null) promise.resolve(path) else promise.resolve(null)
33
+ } catch (e: Exception) {
34
+ promise.reject("ERR_FLIR_PATH", e)
35
+ }
36
+ }
37
+
38
+ @ReactMethod
39
+ fun getTemperatureAt(x: Int, y: Int, promise: Promise) {
40
+ try {
41
+ val temp = FlirManager.getTemperatureAt(x, y)
42
+ if (temp != null) promise.resolve(temp) else promise.reject("ERR_NO_DATA", "No temperature data available")
43
+ } catch (e: Exception) {
44
+ promise.reject("ERR_FLIR_SAMPLE", e)
45
+ }
46
+ }
47
+
48
+ @ReactMethod
49
+ fun isEmulator(promise: Promise) {
50
+ try {
51
+ promise.resolve(FlirManager.isEmulator())
52
+ } catch (e: Exception) {
53
+ promise.reject("ERR_FLIR_EMULATOR_CHECK", e)
54
+ }
55
+ }
56
+
57
+ @ReactMethod
58
+ fun isDeviceConnected(promise: Promise) {
59
+ try {
60
+ promise.resolve(FlirManager.isDeviceConnected())
61
+ } catch (e: Exception) {
62
+ promise.reject("ERR_FLIR_DEVICE_CHECK", e)
63
+ }
64
+ }
65
+
66
+ @ReactMethod
67
+ fun getConnectedDeviceInfo(promise: Promise) {
68
+ try {
69
+ promise.resolve(FlirManager.getConnectedDeviceInfo())
70
+ } catch (e: Exception) {
71
+ promise.reject("ERR_FLIR_DEVICE_INFO", e)
72
+ }
73
+ }
74
+ }
@@ -1,19 +1,19 @@
1
- package flir.android
2
-
3
- import com.facebook.react.ReactPackage
4
- import com.facebook.react.bridge.NativeModule
5
- import com.facebook.react.bridge.ReactApplicationContext
6
- import com.facebook.react.uimanager.ViewManager
7
-
8
- class FlirPackage : ReactPackage {
9
- override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
10
- return listOf(
11
- FlirModule(reactContext),
12
- FlirDownloadManager(reactContext)
13
- )
14
- }
15
-
16
- override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
17
- return listOf(FlirViewManager())
18
- }
19
- }
1
+ package flir.android
2
+
3
+ import com.facebook.react.ReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.uimanager.ViewManager
7
+
8
+ class FlirPackage : ReactPackage {
9
+ override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
10
+ return listOf(
11
+ FlirModule(reactContext),
12
+ FlirDownloadManager(reactContext)
13
+ )
14
+ }
15
+
16
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
17
+ return listOf(FlirViewManager())
18
+ }
19
+ }
@@ -1,90 +1,176 @@
1
1
  package flir.android
2
2
 
3
3
  import android.content.Context
4
- import com.google.android.play.core.splitinstall.*
5
- import com.google.android.play.core.splitinstall.model.SplitInstallSessionStatus
4
+ import android.os.Build
5
+ import android.util.Log
6
6
  import kotlinx.coroutines.*
7
7
  import org.json.JSONObject
8
8
  import java.io.File
9
9
  import java.io.FileOutputStream
10
+ import java.net.HttpURLConnection
10
11
  import java.net.URL
11
- import java.security.MessageDigest
12
12
  import java.util.zip.ZipInputStream
13
13
 
14
+ /**
15
+ * FlirSDKLoader - Downloads and manages architecture-specific FLIR SDK packages
16
+ *
17
+ * Each package contains:
18
+ * - classes.dex (combined DEX from thermalsdk + androidsdk)
19
+ * - Native .so libraries in jni/{arch}/ folder
20
+ *
21
+ * URLs are read from sdk-manifest.json in assets folder
22
+ */
14
23
  object FlirSDKLoader {
15
24
 
16
- private const val FEATURE_MODULE = "flir_sdk"
17
- private var splitInstallManager: SplitInstallManager? = null
25
+ private const val TAG = "FlirSDKLoader"
18
26
 
19
- fun init(context: Context) {
20
- splitInstallManager = SplitInstallManagerFactory.create(context)
21
- }
27
+ // Cached manifest data
28
+ private var cachedManifest: SDKManifest? = null
29
+
30
+ // Manifest data classes
31
+ data class ArchPackage(val downloadUrl: String, val sizeBytes: Long)
32
+ data class SDKManifest(val version: String, val packages: Map<String, ArchPackage>)
22
33
 
23
34
  private fun getSDKDirectory(context: Context) = File(context.filesDir, "FlirSDK")
24
35
 
25
- fun isSDKAvailable(context: Context): Boolean {
26
- // Check Play Feature module
27
- splitInstallManager?.installedModules?.let {
28
- if (FEATURE_MODULE in it) return true
36
+ /**
37
+ * Load manifest from assets
38
+ */
39
+ private fun loadManifest(context: Context): SDKManifest? {
40
+ if (cachedManifest != null) return cachedManifest
41
+
42
+ return try {
43
+ val json = context.assets.open("sdk-manifest.json").bufferedReader().readText()
44
+ val root = JSONObject(json)
45
+ val android = root.getJSONObject("android")
46
+ val packagesJson = android.getJSONObject("packages")
47
+
48
+ val packages = mutableMapOf<String, ArchPackage>()
49
+ packagesJson.keys().forEach { arch ->
50
+ val pkg = packagesJson.getJSONObject(arch)
51
+ packages[arch] = ArchPackage(
52
+ downloadUrl = pkg.getString("downloadUrl"),
53
+ sizeBytes = pkg.getLong("sizeBytes")
54
+ )
55
+ }
56
+
57
+ SDKManifest(
58
+ version = root.getString("version"),
59
+ packages = packages
60
+ ).also { cachedManifest = it }
61
+ } catch (e: Exception) {
62
+ Log.e(TAG, "Failed to load manifest: ${e.message}")
63
+ null
29
64
  }
30
- // Check direct download - look for either file from the manifest
31
- val sdkDir = getSDKDirectory(context)
32
- return File(sdkDir, "thermalsdk-release.aar").exists() ||
33
- File(sdkDir, "androidsdk-release.aar").exists() ||
34
- File(sdkDir, "thermalsdk.aar").exists()
35
65
  }
36
66
 
37
- fun getDownloadSize(context: Context): Long {
38
- return loadManifest(context)?.android?.directDownload?.sizeBytes ?: 52_428_800L
67
+ /**
68
+ * Get the primary ABI for this device
69
+ */
70
+ fun getDeviceArch(): String {
71
+ val supportedAbis = Build.SUPPORTED_ABIS
72
+ Log.d(TAG, "Device supported ABIs: ${supportedAbis.joinToString()}")
73
+
74
+ val knownArchs = setOf("arm64-v8a", "armeabi-v7a", "x86_64")
75
+ for (abi in supportedAbis) {
76
+ if (abi in knownArchs) {
77
+ Log.d(TAG, "Selected ABI: $abi")
78
+ return abi
79
+ }
80
+ }
81
+ return "arm64-v8a"
39
82
  }
40
83
 
41
- fun downloadViaPlayStore(
42
- onProgress: (Float) -> Unit,
43
- onComplete: () -> Unit,
44
- onError: (String) -> Unit
45
- ) {
46
- val manager = splitInstallManager ?: run {
47
- onError("SplitInstallManager not initialized")
48
- return
49
- }
84
+ /**
85
+ * Check if SDK is already downloaded
86
+ */
87
+ fun isSDKAvailable(context: Context): Boolean {
88
+ val sdkDir = getSDKDirectory(context)
89
+ val arch = getDeviceArch()
50
90
 
51
- val request = SplitInstallRequest.newBuilder()
52
- .addModule(FEATURE_MODULE)
53
- .build()
91
+ val dexFile = File(sdkDir, "classes.dex")
92
+ val soDir = File(sdkDir, "jni/$arch")
54
93
 
55
- manager.registerListener { state ->
56
- when (state.status()) {
57
- SplitInstallSessionStatus.DOWNLOADING -> {
58
- val progress = state.bytesDownloaded().toFloat() / state.totalBytesToDownload()
59
- onProgress(progress)
60
- }
61
- SplitInstallSessionStatus.INSTALLED -> onComplete()
62
- SplitInstallSessionStatus.FAILED -> onError("Install failed: ${state.errorCode()}")
63
- else -> {}
64
- }
65
- }
94
+ val hasDex = dexFile.exists() && dexFile.length() > 0
95
+ val hasSo = soDir.exists() && soDir.listFiles()?.isNotEmpty() == true
66
96
 
67
- manager.startInstall(request)
97
+ Log.d(TAG, "SDK available: dex=$hasDex, so=$hasSo")
98
+ return hasDex && hasSo
68
99
  }
69
100
 
70
- suspend fun downloadDirect(
101
+ /**
102
+ * Get path to the DEX file
103
+ */
104
+ fun getDexPath(context: Context): File? {
105
+ val dexFile = File(getSDKDirectory(context), "classes.dex")
106
+ return if (dexFile.exists()) dexFile else null
107
+ }
108
+
109
+ /**
110
+ * Get path to native libraries directory
111
+ */
112
+ fun getNativeLibDir(context: Context): File? {
113
+ val arch = getDeviceArch()
114
+ val libDir = File(getSDKDirectory(context), "jni/$arch")
115
+ return if (libDir.exists()) libDir else null
116
+ }
117
+
118
+ /**
119
+ * Get estimated download size from manifest
120
+ */
121
+ fun getDownloadSize(context: Context): Long {
122
+ val manifest = loadManifest(context) ?: return 15_000_000L
123
+ val arch = getDeviceArch()
124
+ return manifest.packages[arch]?.sizeBytes ?: 15_000_000L
125
+ }
126
+
127
+ /**
128
+ * Download the SDK package for this device's architecture
129
+ */
130
+ suspend fun downloadSDK(
71
131
  context: Context,
72
132
  onProgress: (downloaded: Long, total: Long) -> Unit
73
133
  ): Result<Unit> = withContext(Dispatchers.IO) {
74
134
  try {
75
- val manifest = loadManifest(context) ?: return@withContext Result.failure(
76
- Exception("Failed to load manifest")
77
- )
135
+ val arch = getDeviceArch()
136
+ val manifest = loadManifest(context)
137
+ ?: return@withContext Result.failure(Exception("Failed to load manifest"))
138
+
139
+ val archPackage = manifest.packages[arch]
140
+ ?: return@withContext Result.failure(Exception("No package for architecture: $arch"))
78
141
 
79
- val downloadUrl = manifest.android.directDownload.downloadUrl
80
- val expectedHash = manifest.android.directDownload.sha256
81
- val totalSize = manifest.android.directDownload.sizeBytes
142
+ val downloadUrl = archPackage.downloadUrl
143
+ Log.i(TAG, "Downloading SDK for $arch from $downloadUrl")
82
144
 
83
145
  val sdkDir = getSDKDirectory(context).apply { mkdirs() }
84
- val zipFile = File(context.cacheDir, "flir-sdk.zip")
146
+ val zipFile = File(context.cacheDir, "flir-sdk-$arch.zip")
85
147
 
86
- // Download
87
- URL(downloadUrl).openStream().use { input ->
148
+ // Download with redirect handling (GitHub uses 302 redirects)
149
+ var connection = URL(downloadUrl).openConnection() as HttpURLConnection
150
+ connection.instanceFollowRedirects = true
151
+ connection.connectTimeout = 30000
152
+ connection.readTimeout = 60000
153
+
154
+ // Handle redirects manually for cross-protocol redirects
155
+ var redirectCount = 0
156
+ while (connection.responseCode in 301..302 && redirectCount < 5) {
157
+ val redirectUrl = connection.getHeaderField("Location")
158
+ Log.d(TAG, "Redirect to: $redirectUrl")
159
+ connection.disconnect()
160
+ connection = URL(redirectUrl).openConnection() as HttpURLConnection
161
+ connection.instanceFollowRedirects = true
162
+ redirectCount++
163
+ }
164
+
165
+ if (connection.responseCode != HttpURLConnection.HTTP_OK) {
166
+ return@withContext Result.failure(Exception("HTTP error ${connection.responseCode}"))
167
+ }
168
+
169
+ val totalSize = connection.contentLengthLong.let {
170
+ if (it > 0) it else archPackage.sizeBytes
171
+ }
172
+
173
+ connection.inputStream.use { input ->
88
174
  FileOutputStream(zipFile).use { output ->
89
175
  val buffer = ByteArray(8192)
90
176
  var totalRead = 0L
@@ -99,97 +185,59 @@ object FlirSDKLoader {
99
185
  }
100
186
  }
101
187
  }
188
+ connection.disconnect()
102
189
 
103
- // Verify checksum
104
- val actualHash = sha256(zipFile)
105
- if (actualHash != expectedHash) {
106
- zipFile.delete()
107
- return@withContext Result.failure(SecurityException("Checksum mismatch"))
108
- }
190
+ Log.i(TAG, "Download complete: ${zipFile.length()} bytes")
109
191
 
110
192
  // Extract
111
193
  unzip(zipFile, sdkDir)
112
194
  zipFile.delete()
113
195
 
196
+ // Verify
197
+ val dexFile = File(sdkDir, "classes.dex")
198
+ val soDir = File(sdkDir, "jni/$arch")
199
+
200
+ if (!dexFile.exists()) {
201
+ return@withContext Result.failure(Exception("DEX file not extracted"))
202
+ }
203
+
204
+ dexFile.setReadOnly()
205
+ Log.i(TAG, "SDK installed: dex=${dexFile.length()} bytes, libs=${soDir.listFiles()?.size ?: 0} files")
206
+
114
207
  Result.success(Unit)
115
208
  } catch (e: Exception) {
209
+ Log.e(TAG, "Download failed", e)
116
210
  Result.failure(e)
117
211
  }
118
212
  }
119
213
 
214
+ /**
215
+ * Delete downloaded SDK
216
+ */
120
217
  fun deleteSDK(context: Context): Boolean {
121
- splitInstallManager?.deferredUninstall(listOf(FEATURE_MODULE))
122
218
  return getSDKDirectory(context).deleteRecursively()
123
219
  }
124
220
 
125
- private fun loadManifest(context: Context): SDKManifest? {
126
- return try {
127
- val json = context.assets.open("sdk-manifest.json").bufferedReader().readText()
128
- SDKManifest.fromJson(json)
129
- } catch (e: Exception) { null }
130
- }
131
-
132
- private fun sha256(file: File): String {
133
- val digest = MessageDigest.getInstance("SHA-256")
134
- file.inputStream().use { input ->
135
- val buffer = ByteArray(8192)
136
- var bytesRead: Int
137
- while (input.read(buffer).also { bytesRead = it } != -1) {
138
- digest.update(buffer, 0, bytesRead)
139
- }
140
- }
141
- return digest.digest().joinToString("") { "%02x".format(it) }
142
- }
143
-
144
221
  private fun unzip(source: File, destination: File) {
222
+ Log.d(TAG, "Extracting ${source.name} to ${destination.absolutePath}")
223
+
145
224
  ZipInputStream(source.inputStream()).use { zip ->
146
225
  var entry = zip.nextEntry
147
226
  while (entry != null) {
148
227
  val file = File(destination, entry.name)
228
+ Log.d(TAG, " Extracting: ${entry.name}")
229
+
149
230
  if (entry.isDirectory) {
150
231
  file.mkdirs()
151
232
  } else {
152
233
  file.parentFile?.mkdirs()
153
- file.outputStream().use { zip.copyTo(it) }
234
+ file.outputStream().use { output ->
235
+ zip.copyTo(output)
236
+ }
154
237
  }
238
+ zip.closeEntry()
155
239
  entry = zip.nextEntry
156
240
  }
157
241
  }
158
242
  }
159
- }
160
-
161
- data class SDKManifest(
162
- val version: String,
163
- val android: AndroidManifest
164
- ) {
165
- data class AndroidManifest(
166
- val playFeatureModule: String,
167
- val directDownload: DirectDownload
168
- )
169
-
170
- data class DirectDownload(
171
- val downloadUrl: String,
172
- val sha256: String,
173
- val sizeBytes: Long
174
- )
175
-
176
- companion object {
177
- fun fromJson(json: String): SDKManifest {
178
- val root = JSONObject(json)
179
- val android = root.getJSONObject("android")
180
- val direct = android.getJSONObject("directDownload")
181
-
182
- return SDKManifest(
183
- version = root.getString("version"),
184
- android = AndroidManifest(
185
- playFeatureModule = android.getString("playFeatureModule"),
186
- directDownload = DirectDownload(
187
- downloadUrl = direct.getString("downloadUrl"),
188
- sha256 = direct.getString("sha256"),
189
- sizeBytes = direct.getLong("sizeBytes")
190
- )
191
- )
192
- )
193
- }
194
- }
195
- }
243
+ }