ilabs-flir 1.0.2 → 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 (116) 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/src/main/AndroidManifest.xml +31 -31
  5. package/android/Flir/src/main/java/com/flir/thermalsdk/ErrorCodeException.java +14 -0
  6. package/android/Flir/src/main/java/com/flir/thermalsdk/image/ImageBuffer.java +11 -0
  7. package/android/Flir/src/main/java/com/flir/thermalsdk/image/JavaImageBuffer.java +35 -0
  8. package/android/Flir/src/main/java/com/flir/thermalsdk/image/Palette.java +15 -0
  9. package/android/Flir/src/main/java/com/flir/thermalsdk/image/PaletteManager.java +16 -0
  10. package/android/Flir/src/main/java/com/flir/thermalsdk/image/Point.java +11 -0
  11. package/android/Flir/src/main/java/com/flir/thermalsdk/image/ThermalImage.java +23 -0
  12. package/android/Flir/src/main/java/com/flir/thermalsdk/image/ThermalValue.java +9 -0
  13. package/android/Flir/src/main/java/com/flir/thermalsdk/live/CameraType.java +8 -0
  14. package/android/Flir/src/main/java/com/flir/thermalsdk/live/CommunicationInterface.java +16 -0
  15. package/android/Flir/src/main/java/com/flir/thermalsdk/live/Identity.java +23 -0
  16. package/android/Flir/src/main/java/com/flir/thermalsdk/live/IpSettings.java +9 -0
  17. package/android/Flir/src/main/java/com/flir/thermalsdk/live/connectivity/ConnectionStatusListener.java +7 -0
  18. package/android/Flir/src/main/java/com/flir/thermalsdk/live/remote/OnReceived.java +5 -0
  19. package/android/Flir/src/main/java/com/flir/thermalsdk/live/remote/OnRemoteError.java +7 -0
  20. package/android/Flir/src/main/java/flir/android/CameraHandler.java +224 -194
  21. package/android/Flir/src/main/java/flir/android/FlirCommands.java +111 -0
  22. package/android/Flir/src/main/java/flir/android/FlirConnectionManager.java +354 -0
  23. package/android/Flir/src/main/java/flir/android/FlirController.kt +11 -11
  24. package/android/Flir/src/main/java/flir/android/FlirDiscoveryManager.java +236 -0
  25. package/android/Flir/src/main/java/flir/android/FlirDownloadManager.kt +75 -75
  26. package/android/Flir/src/main/java/flir/android/FlirDownloadPackage.kt +16 -16
  27. package/android/Flir/src/main/java/flir/android/FlirFrameCache.kt +6 -6
  28. package/android/Flir/src/main/java/flir/android/FlirManager.kt +254 -248
  29. package/android/Flir/src/main/java/flir/android/FlirModule.kt +74 -74
  30. package/android/Flir/src/main/java/flir/android/FlirPackage.kt +19 -19
  31. package/android/Flir/src/main/java/flir/android/FlirSDKLoader.kt +195 -195
  32. package/android/Flir/src/main/java/flir/android/FlirSdkManager.java +890 -0
  33. package/android/Flir/src/main/java/flir/android/FlirStatus.kt +12 -12
  34. package/android/Flir/src/main/java/flir/android/FlirView.kt +48 -48
  35. package/android/Flir/src/main/java/flir/android/FlirViewManager.kt +13 -13
  36. package/android/Flir/src/main/java/flir/android/FrameDataHolder.java +14 -14
  37. package/app.plugin.js +264 -264
  38. package/expo-module.config.json +5 -5
  39. package/ios/Flir/Framework/ThermalSDK/FLIRBattery.h +76 -76
  40. package/ios/Flir/Framework/ThermalSDK/FLIRCalibration.h +108 -108
  41. package/ios/Flir/Framework/ThermalSDK/FLIRCamera.h +156 -156
  42. package/ios/Flir/Framework/ThermalSDK/FLIRCameraDeviceInfo.h +53 -53
  43. package/ios/Flir/Framework/ThermalSDK/FLIRCameraEvent.h +132 -132
  44. package/ios/Flir/Framework/ThermalSDK/FLIRCameraImport.h +204 -204
  45. package/ios/Flir/Framework/ThermalSDK/FLIRColorDistributionSettings.h +204 -204
  46. package/ios/Flir/Framework/ThermalSDK/FLIRColorizer.h +82 -82
  47. package/ios/Flir/Framework/ThermalSDK/FLIRDiscoveredCamera.h +44 -44
  48. package/ios/Flir/Framework/ThermalSDK/FLIRDiscovery.h +132 -132
  49. package/ios/Flir/Framework/ThermalSDK/FLIRDisplaySettings.h +29 -29
  50. package/ios/Flir/Framework/ThermalSDK/FLIRFocus.h +70 -70
  51. package/ios/Flir/Framework/ThermalSDK/FLIRFusion.h +192 -192
  52. package/ios/Flir/Framework/ThermalSDK/FLIRFusionController.h +136 -136
  53. package/ios/Flir/Framework/ThermalSDK/FLIRFusionTransformation.h +35 -35
  54. package/ios/Flir/Framework/ThermalSDK/FLIRIdentity.h +264 -264
  55. package/ios/Flir/Framework/ThermalSDK/FLIRImageBase.h +196 -196
  56. package/ios/Flir/Framework/ThermalSDK/FLIRImageColorizer.h +26 -26
  57. package/ios/Flir/Framework/ThermalSDK/FLIRImageStatistics.h +61 -61
  58. package/ios/Flir/Framework/ThermalSDK/FLIRIsotherms.h +208 -208
  59. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementArea.h +38 -38
  60. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementCollection.h +147 -147
  61. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementDelta.h +62 -62
  62. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementDimensions.h +33 -33
  63. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementEllipse.h +49 -49
  64. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementLine.h +66 -66
  65. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementMarker.h +69 -69
  66. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementParameters.h +41 -41
  67. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementRectangle.h +36 -36
  68. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementReference.h +27 -27
  69. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementShape.h +46 -46
  70. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementSpot.h +33 -33
  71. package/ios/Flir/Framework/ThermalSDK/FLIRMeasurementsController.h +160 -160
  72. package/ios/Flir/Framework/ThermalSDK/FLIRMeterLinkSensorPoll.h +247 -247
  73. package/ios/Flir/Framework/ThermalSDK/FLIROverlayController.h +27 -27
  74. package/ios/Flir/Framework/ThermalSDK/FLIRPalette.h +60 -60
  75. package/ios/Flir/Framework/ThermalSDK/FLIRPaletteController.h +36 -36
  76. package/ios/Flir/Framework/ThermalSDK/FLIRPaletteManager.h +97 -97
  77. package/ios/Flir/Framework/ThermalSDK/FLIRQuantification.h +55 -55
  78. package/ios/Flir/Framework/ThermalSDK/FLIRRemoteControl.h +393 -393
  79. package/ios/Flir/Framework/ThermalSDK/FLIRRenderer.h +35 -35
  80. package/ios/Flir/Framework/ThermalSDK/FLIRRendererImpl.h +17 -17
  81. package/ios/Flir/Framework/ThermalSDK/FLIRScale.h +99 -99
  82. package/ios/Flir/Framework/ThermalSDK/FLIRScaleController.h +44 -44
  83. package/ios/Flir/Framework/ThermalSDK/FLIRStream.h +109 -109
  84. package/ios/Flir/Framework/ThermalSDK/FLIRStreamer.h +124 -124
  85. package/ios/Flir/Framework/ThermalSDK/FLIRSystem.h +40 -40
  86. package/ios/Flir/Framework/ThermalSDK/FLIRTemperatureRange.h +43 -43
  87. package/ios/Flir/Framework/ThermalSDK/FLIRThermalDelta.h +77 -77
  88. package/ios/Flir/Framework/ThermalSDK/FLIRThermalImage.h +331 -331
  89. package/ios/Flir/Framework/ThermalSDK/FLIRThermalImageFile.h +56 -56
  90. package/ios/Flir/Framework/ThermalSDK/FLIRThermalParameters.h +31 -31
  91. package/ios/Flir/Framework/ThermalSDK/FLIRThermalValue.h +92 -92
  92. package/ios/Flir/Framework/ThermalSDK/FLIRWirelessCameraDetails.h +88 -88
  93. package/ios/Flir/Framework/ThermalSDK/ThermalSDK.h +73 -73
  94. package/ios/Flir/SDKLoader/FlirSDKLoader.m +13 -13
  95. package/ios/Flir/SDKLoader/FlirSDKLoader.swift +175 -175
  96. package/ios/Flir/src/FlirEventEmitter.h +12 -12
  97. package/ios/Flir/src/FlirEventEmitter.m +33 -33
  98. package/ios/Flir/src/FlirModule.h +10 -10
  99. package/ios/Flir/src/FlirModule.m +381 -381
  100. package/ios/Flir/src/FlirPreviewView.h +13 -13
  101. package/ios/Flir/src/FlirPreviewView.m +24 -24
  102. package/ios/Flir/src/FlirState.h +20 -20
  103. package/ios/Flir/src/FlirState.m +79 -79
  104. package/ios/Flir/src/FlirViewManager.h +9 -9
  105. package/ios/Flir/src/FlirViewManager.m +16 -16
  106. package/package.json +60 -60
  107. package/react-native.config.js +14 -14
  108. package/scripts/copy_ios_libs.sh +32 -32
  109. package/scripts/create_stubs.py +174 -174
  110. package/scripts/download-sdk.js +62 -62
  111. package/scripts/prepare-binaries.sh +171 -171
  112. package/sdk-manifest.json +30 -30
  113. package/src/FlirDownload.ts +78 -78
  114. package/src/index.d.ts +17 -17
  115. package/src/index.js +7 -7
  116. 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,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,195 +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 - 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
- }
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
+ }