ilabs-flir 1.0.1 → 1.0.3

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 (117) 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/ErrorCodeException.java +14 -0
  7. package/android/Flir/src/main/java/com/flir/thermalsdk/image/ImageBuffer.java +11 -0
  8. package/android/Flir/src/main/java/com/flir/thermalsdk/image/JavaImageBuffer.java +35 -0
  9. package/android/Flir/src/main/java/com/flir/thermalsdk/image/Palette.java +15 -0
  10. package/android/Flir/src/main/java/com/flir/thermalsdk/image/PaletteManager.java +16 -0
  11. package/android/Flir/src/main/java/com/flir/thermalsdk/image/Point.java +11 -0
  12. package/android/Flir/src/main/java/com/flir/thermalsdk/image/ThermalImage.java +23 -0
  13. package/android/Flir/src/main/java/com/flir/thermalsdk/image/ThermalValue.java +9 -0
  14. package/android/Flir/src/main/java/com/flir/thermalsdk/live/CameraType.java +8 -0
  15. package/android/Flir/src/main/java/com/flir/thermalsdk/live/CommunicationInterface.java +16 -0
  16. package/android/Flir/src/main/java/com/flir/thermalsdk/live/Identity.java +23 -0
  17. package/android/Flir/src/main/java/com/flir/thermalsdk/live/IpSettings.java +9 -0
  18. package/android/Flir/src/main/java/com/flir/thermalsdk/live/connectivity/ConnectionStatusListener.java +7 -0
  19. package/android/Flir/src/main/java/com/flir/thermalsdk/live/remote/OnReceived.java +5 -0
  20. package/android/Flir/src/main/java/com/flir/thermalsdk/live/remote/OnRemoteError.java +7 -0
  21. package/android/Flir/src/main/java/flir/android/CameraHandler.java +224 -194
  22. package/android/Flir/src/main/java/flir/android/FlirCommands.java +111 -0
  23. package/android/Flir/src/main/java/flir/android/FlirConnectionManager.java +354 -0
  24. package/android/Flir/src/main/java/flir/android/FlirController.kt +11 -11
  25. package/android/Flir/src/main/java/flir/android/FlirDiscoveryManager.java +236 -0
  26. package/android/Flir/src/main/java/flir/android/FlirDownloadManager.kt +75 -75
  27. package/android/Flir/src/main/java/flir/android/FlirDownloadPackage.kt +16 -16
  28. package/android/Flir/src/main/java/flir/android/FlirFrameCache.kt +6 -6
  29. package/android/Flir/src/main/java/flir/android/FlirManager.kt +254 -248
  30. package/android/Flir/src/main/java/flir/android/FlirModule.kt +74 -74
  31. package/android/Flir/src/main/java/flir/android/FlirPackage.kt +19 -16
  32. package/android/Flir/src/main/java/flir/android/FlirSDKLoader.kt +195 -191
  33. package/android/Flir/src/main/java/flir/android/FlirSdkManager.java +890 -0
  34. package/android/Flir/src/main/java/flir/android/FlirStatus.kt +12 -12
  35. package/android/Flir/src/main/java/flir/android/FlirView.kt +48 -48
  36. package/android/Flir/src/main/java/flir/android/FlirViewManager.kt +13 -13
  37. package/android/Flir/src/main/java/flir/android/FrameDataHolder.java +14 -14
  38. package/app.plugin.js +264 -264
  39. package/expo-module.config.json +5 -5
  40. package/ios/Flir/Framework/ThermalSDK/FLIRBattery.h +76 -76
  41. package/ios/Flir/Framework/ThermalSDK/FLIRCalibration.h +108 -108
  42. package/ios/Flir/Framework/ThermalSDK/FLIRCamera.h +156 -156
  43. package/ios/Flir/Framework/ThermalSDK/FLIRCameraDeviceInfo.h +53 -53
  44. package/ios/Flir/Framework/ThermalSDK/FLIRCameraEvent.h +132 -132
  45. package/ios/Flir/Framework/ThermalSDK/FLIRCameraImport.h +204 -204
  46. package/ios/Flir/Framework/ThermalSDK/FLIRColorDistributionSettings.h +204 -204
  47. package/ios/Flir/Framework/ThermalSDK/FLIRColorizer.h +82 -82
  48. package/ios/Flir/Framework/ThermalSDK/FLIRDiscoveredCamera.h +44 -44
  49. package/ios/Flir/Framework/ThermalSDK/FLIRDiscovery.h +132 -132
  50. package/ios/Flir/Framework/ThermalSDK/FLIRDisplaySettings.h +29 -29
  51. package/ios/Flir/Framework/ThermalSDK/FLIRFocus.h +70 -70
  52. package/ios/Flir/Framework/ThermalSDK/FLIRFusion.h +192 -192
  53. package/ios/Flir/Framework/ThermalSDK/FLIRFusionController.h +136 -136
  54. package/ios/Flir/Framework/ThermalSDK/FLIRFusionTransformation.h +35 -35
  55. package/ios/Flir/Framework/ThermalSDK/FLIRIdentity.h +264 -264
  56. package/ios/Flir/Framework/ThermalSDK/FLIRImageBase.h +196 -196
  57. package/ios/Flir/Framework/ThermalSDK/FLIRImageColorizer.h +26 -26
  58. package/ios/Flir/Framework/ThermalSDK/FLIRImageStatistics.h +61 -61
  59. package/ios/Flir/Framework/ThermalSDK/FLIRIsotherms.h +208 -208
  60. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementArea.h +38 -38
  61. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementCollection.h +147 -147
  62. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementDelta.h +62 -62
  63. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementDimensions.h +33 -33
  64. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementEllipse.h +49 -49
  65. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementLine.h +66 -66
  66. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementMarker.h +69 -69
  67. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementParameters.h +41 -41
  68. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementRectangle.h +36 -36
  69. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementReference.h +27 -27
  70. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementShape.h +46 -46
  71. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementSpot.h +33 -33
  72. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementsController.h +160 -160
  73. package/ios/Flir/Framework/ThermalSDK/FLIRMeterLinkSensorPoll.h +247 -247
  74. package/ios/Flir/Framework/ThermalSDK/FLIROverlayController.h +27 -27
  75. package/ios/Flir/Framework/ThermalSDK/FLIRPalette.h +60 -60
  76. package/ios/Flir/Framework/ThermalSDK/FLIRPaletteController.h +36 -36
  77. package/ios/Flir/Framework/ThermalSDK/FLIRPaletteManager.h +97 -97
  78. package/ios/Flir/Framework/ThermalSDK/FLIRQuantification.h +55 -55
  79. package/ios/Flir/Framework/ThermalSDK/FLIRRemoteControl.h +393 -393
  80. package/ios/Flir/Framework/ThermalSDK/FLIRRenderer.h +35 -35
  81. package/ios/Flir/Framework/ThermalSDK/FLIRRendererImpl.h +17 -17
  82. package/ios/Flir/Framework/ThermalSDK/FLIRScale.h +99 -99
  83. package/ios/Flir/Framework/ThermalSDK/FLIRScaleController.h +44 -44
  84. package/ios/Flir/Framework/ThermalSDK/FLIRStream.h +109 -109
  85. package/ios/Flir/Framework/ThermalSDK/FLIRStreamer.h +124 -124
  86. package/ios/Flir/Framework/ThermalSDK/FLIRSystem.h +40 -40
  87. package/ios/Flir/Framework/ThermalSDK/FLIRTemperatureRange.h +43 -43
  88. package/ios/Flir/Framework/ThermalSDK/FLIRThermalDelta.h +77 -77
  89. package/ios/Flir/Framework/ThermalSDK/FLIRThermalImage.h +331 -331
  90. package/ios/Flir/Framework/ThermalSDK/FLIRThermalImageFile.h +56 -56
  91. package/ios/Flir/Framework/ThermalSDK/FLIRThermalParameters.h +31 -31
  92. package/ios/Flir/Framework/ThermalSDK/FLIRThermalValue.h +92 -92
  93. package/ios/Flir/Framework/ThermalSDK/FLIRWirelessCameraDetails.h +88 -88
  94. package/ios/Flir/Framework/ThermalSDK/ThermalSDK.h +73 -73
  95. package/ios/Flir/SDKLoader/FlirSDKLoader.m +13 -13
  96. package/ios/Flir/SDKLoader/FlirSDKLoader.swift +175 -175
  97. package/ios/Flir/src/FlirEventEmitter.h +12 -12
  98. package/ios/Flir/src/FlirEventEmitter.m +33 -33
  99. package/ios/Flir/src/FlirModule.h +10 -10
  100. package/ios/Flir/src/FlirModule.m +381 -381
  101. package/ios/Flir/src/FlirPreviewView.h +13 -13
  102. package/ios/Flir/src/FlirPreviewView.m +24 -24
  103. package/ios/Flir/src/FlirState.h +20 -20
  104. package/ios/Flir/src/FlirState.m +79 -79
  105. package/ios/Flir/src/FlirViewManager.h +9 -9
  106. package/ios/Flir/src/FlirViewManager.m +16 -16
  107. package/package.json +61 -61
  108. package/react-native.config.js +14 -14
  109. package/scripts/copy_ios_libs.sh +32 -32
  110. package/scripts/create_stubs.py +174 -174
  111. package/scripts/download-sdk.js +62 -62
  112. package/scripts/prepare-binaries.sh +171 -171
  113. package/sdk-manifest.json +30 -30
  114. package/src/FlirDownload.ts +78 -78
  115. package/src/index.d.ts +17 -17
  116. package/src/index.js +7 -7
  117. package/src/index.ts +7 -7
@@ -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,16 +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(FlirModule(reactContext))
11
- }
12
-
13
- override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
14
- return listOf(FlirViewManager())
15
- }
16
- }
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,191 +1,195 @@
1
- package flir.android
2
-
3
- import android.content.Context
4
- import com.google.android.play.core.splitinstall.*
5
- import com.google.android.play.core.splitinstall.model.SplitInstallSessionStatus
6
- import kotlinx.coroutines.*
7
- import org.json.JSONObject
8
- import java.io.File
9
- import java.io.FileOutputStream
10
- import java.net.URL
11
- import java.security.MessageDigest
12
- import java.util.zip.ZipInputStream
13
-
14
- object FlirSDKLoader {
15
-
16
- private const val FEATURE_MODULE = "flir_sdk"
17
- private var splitInstallManager: SplitInstallManager? = null
18
-
19
- fun init(context: Context) {
20
- splitInstallManager = SplitInstallManagerFactory.create(context)
21
- }
22
-
23
- private fun getSDKDirectory(context: Context) = File(context.filesDir, "FlirSDK")
24
-
25
- fun isSDKAvailable(context: Context): Boolean {
26
- // Check Play Feature module
27
- splitInstallManager?.installedModules?.let {
28
- if (FEATURE_MODULE in it) return true
29
- }
30
- // Check direct download
31
- return File(getSDKDirectory(context), "thermalsdk.aar").exists()
32
- }
33
-
34
- fun getDownloadSize(context: Context): Long {
35
- return loadManifest(context)?.android?.directDownload?.sizeBytes ?: 52_428_800L
36
- }
37
-
38
- fun downloadViaPlayStore(
39
- onProgress: (Float) -> Unit,
40
- onComplete: () -> Unit,
41
- onError: (String) -> Unit
42
- ) {
43
- val manager = splitInstallManager ?: run {
44
- onError("SplitInstallManager not initialized")
45
- return
46
- }
47
-
48
- val request = SplitInstallRequest.newBuilder()
49
- .addModule(FEATURE_MODULE)
50
- .build()
51
-
52
- manager.registerListener { state ->
53
- when (state.status()) {
54
- SplitInstallSessionStatus.DOWNLOADING -> {
55
- val progress = state.bytesDownloaded().toFloat() / state.totalBytesToDownload()
56
- onProgress(progress)
57
- }
58
- SplitInstallSessionStatus.INSTALLED -> onComplete()
59
- SplitInstallSessionStatus.FAILED -> onError("Install failed: ${state.errorCode()}")
60
- else -> {}
61
- }
62
- }
63
-
64
- manager.startInstall(request)
65
- }
66
-
67
- suspend fun downloadDirect(
68
- context: Context,
69
- onProgress: (downloaded: Long, total: Long) -> Unit
70
- ): Result<Unit> = withContext(Dispatchers.IO) {
71
- try {
72
- val manifest = loadManifest(context) ?: return@withContext Result.failure(
73
- Exception("Failed to load manifest"))
74
-
75
- val downloadUrl = manifest.android.directDownload.downloadUrl
76
- val expectedHash = manifest.android.directDownload.sha256
77
- val totalSize = manifest.android.directDownload.sizeBytes
78
-
79
- val sdkDir = getSDKDirectory(context).apply { mkdirs() }
80
- val zipFile = File(context.cacheDir, "flir-sdk.zip")
81
-
82
- // Download
83
- URL(downloadUrl).openStream().use { input ->
84
- FileOutputStream(zipFile).use { output ->
85
- val buffer = ByteArray(8192)
86
- var totalRead = 0L
87
- var bytesRead: Int
88
-
89
- while (input.read(buffer).also { bytesRead = it } != -1) {
90
- output.write(buffer, 0, bytesRead)
91
- totalRead += bytesRead
92
- withContext(Dispatchers.Main) {
93
- onProgress(totalRead, totalSize)
94
- }
95
- }
96
- }
97
- }
98
-
99
- // Verify checksum
100
- val actualHash = sha256(zipFile)
101
- if (actualHash != expectedHash) {
102
- zipFile.delete()
103
- return@withContext Result.failure(SecurityException("Checksum mismatch"))
104
- }
105
-
106
- // Extract
107
- unzip(zipFile, sdkDir)
108
- zipFile.delete()
109
-
110
- Result.success(Unit)
111
- } catch (e: Exception) {
112
- Result.failure(e)
113
- }
114
- }
115
-
116
- fun deleteSDK(context: Context): Boolean {
117
- splitInstallManager?.deferredUninstall(listOf(FEATURE_MODULE))
118
- return getSDKDirectory(context).deleteRecursively()
119
- }
120
-
121
- private fun loadManifest(context: Context): SDKManifest? {
122
- return try {
123
- val json = context.assets.open("sdk-manifest.json").bufferedReader().readText()
124
- SDKManifest.fromJson(json)
125
- } catch (e: Exception) { null }
126
- }
127
-
128
- private fun sha256(file: File): String {
129
- val digest = MessageDigest.getInstance("SHA-256")
130
- file.inputStream().use { input ->
131
- val buffer = ByteArray(8192)
132
- var bytesRead: Int
133
- while (input.read(buffer).also { bytesRead = it } != -1) {
134
- digest.update(buffer, 0, bytesRead)
135
- }
136
- }
137
- return digest.digest().joinToString("") { "%02x".format(it) }
138
- }
139
-
140
- private fun unzip(source: File, destination: File) {
141
- ZipInputStream(source.inputStream()).use { zip ->
142
- var entry = zip.nextEntry
143
- while (entry != null) {
144
- val file = File(destination, entry.name)
145
- if (entry.isDirectory) {
146
- file.mkdirs()
147
- } else {
148
- file.parentFile?.mkdirs()
149
- file.outputStream().use { zip.copyTo(it) }
150
- }
151
- entry = zip.nextEntry
152
- }
153
- }
154
- }
155
- }
156
-
157
- data class SDKManifest(
158
- val version: String,
159
- val android: AndroidManifest
160
- ) {
161
- data class AndroidManifest(
162
- val playFeatureModule: String,
163
- val directDownload: DirectDownload
164
- )
165
-
166
- data class DirectDownload(
167
- val downloadUrl: String,
168
- val sha256: String,
169
- val sizeBytes: Long
170
- )
171
-
172
- companion object {
173
- fun fromJson(json: String): SDKManifest {
174
- val root = JSONObject(json)
175
- val android = root.getJSONObject("android")
176
- val direct = android.getJSONObject("directDownload")
177
-
178
- return SDKManifest(
179
- version = root.getString("version"),
180
- android = AndroidManifest(
181
- playFeatureModule = android.getString("playFeatureModule"),
182
- directDownload = DirectDownload(
183
- downloadUrl = direct.getString("downloadUrl"),
184
- sha256 = direct.getString("sha256"),
185
- sizeBytes = direct.getLong("sizeBytes")
186
- )
187
- )
188
- )
189
- }
190
- }
191
- }
1
+ package flir.android
2
+
3
+ import android.content.Context
4
+ import com.google.android.play.core.splitinstall.*
5
+ import com.google.android.play.core.splitinstall.model.SplitInstallSessionStatus
6
+ import kotlinx.coroutines.*
7
+ import org.json.JSONObject
8
+ import java.io.File
9
+ import java.io.FileOutputStream
10
+ import java.net.URL
11
+ import java.security.MessageDigest
12
+ import java.util.zip.ZipInputStream
13
+
14
+ object FlirSDKLoader {
15
+
16
+ private const val FEATURE_MODULE = "flir_sdk"
17
+ private var splitInstallManager: SplitInstallManager? = null
18
+
19
+ fun init(context: Context) {
20
+ splitInstallManager = SplitInstallManagerFactory.create(context)
21
+ }
22
+
23
+ private fun getSDKDirectory(context: Context) = File(context.filesDir, "FlirSDK")
24
+
25
+ fun isSDKAvailable(context: Context): Boolean {
26
+ // Check Play Feature module
27
+ splitInstallManager?.installedModules?.let {
28
+ if (FEATURE_MODULE in it) return true
29
+ }
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
+ }
36
+
37
+ fun getDownloadSize(context: Context): Long {
38
+ return loadManifest(context)?.android?.directDownload?.sizeBytes ?: 52_428_800L
39
+ }
40
+
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
+ }
50
+
51
+ val request = SplitInstallRequest.newBuilder()
52
+ .addModule(FEATURE_MODULE)
53
+ .build()
54
+
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
+ }
66
+
67
+ manager.startInstall(request)
68
+ }
69
+
70
+ suspend fun downloadDirect(
71
+ context: Context,
72
+ onProgress: (downloaded: Long, total: Long) -> Unit
73
+ ): Result<Unit> = withContext(Dispatchers.IO) {
74
+ try {
75
+ val manifest = loadManifest(context) ?: return@withContext Result.failure(
76
+ Exception("Failed to load manifest")
77
+ )
78
+
79
+ val downloadUrl = manifest.android.directDownload.downloadUrl
80
+ val expectedHash = manifest.android.directDownload.sha256
81
+ val totalSize = manifest.android.directDownload.sizeBytes
82
+
83
+ val sdkDir = getSDKDirectory(context).apply { mkdirs() }
84
+ val zipFile = File(context.cacheDir, "flir-sdk.zip")
85
+
86
+ // Download
87
+ URL(downloadUrl).openStream().use { input ->
88
+ FileOutputStream(zipFile).use { output ->
89
+ val buffer = ByteArray(8192)
90
+ var totalRead = 0L
91
+ var bytesRead: Int
92
+
93
+ while (input.read(buffer).also { bytesRead = it } != -1) {
94
+ output.write(buffer, 0, bytesRead)
95
+ totalRead += bytesRead
96
+ withContext(Dispatchers.Main) {
97
+ onProgress(totalRead, totalSize)
98
+ }
99
+ }
100
+ }
101
+ }
102
+
103
+ // Verify checksum
104
+ val actualHash = sha256(zipFile)
105
+ if (actualHash != expectedHash) {
106
+ zipFile.delete()
107
+ return@withContext Result.failure(SecurityException("Checksum mismatch"))
108
+ }
109
+
110
+ // Extract
111
+ unzip(zipFile, sdkDir)
112
+ zipFile.delete()
113
+
114
+ Result.success(Unit)
115
+ } catch (e: Exception) {
116
+ Result.failure(e)
117
+ }
118
+ }
119
+
120
+ fun deleteSDK(context: Context): Boolean {
121
+ splitInstallManager?.deferredUninstall(listOf(FEATURE_MODULE))
122
+ return getSDKDirectory(context).deleteRecursively()
123
+ }
124
+
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
+ private fun unzip(source: File, destination: File) {
145
+ ZipInputStream(source.inputStream()).use { zip ->
146
+ var entry = zip.nextEntry
147
+ while (entry != null) {
148
+ val file = File(destination, entry.name)
149
+ if (entry.isDirectory) {
150
+ file.mkdirs()
151
+ } else {
152
+ file.parentFile?.mkdirs()
153
+ file.outputStream().use { zip.copyTo(it) }
154
+ }
155
+ entry = zip.nextEntry
156
+ }
157
+ }
158
+ }
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
+ }