@bricks-soft/capacitor-location-tracking 0.0.0-stage → 8.0.0

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 (168) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2759 -2
  3. package/android/build.gradle +106 -0
  4. package/android/consumer-rules.pro +42 -0
  5. package/android/src/main/AndroidManifest.xml +52 -0
  6. package/android/src/main/java/com/brickssoft/locationtracking/LocationTrackingPlugin.kt +431 -0
  7. package/android/src/main/java/com/brickssoft/locationtracking/api/LocationTrackingListener.kt +52 -0
  8. package/android/src/main/java/com/brickssoft/locationtracking/api/LocationTrackingNative.kt +267 -0
  9. package/android/src/main/java/com/brickssoft/locationtracking/api/NativeListeners.kt +270 -0
  10. package/android/src/main/java/com/brickssoft/locationtracking/api/NativeThread.kt +57 -0
  11. package/android/src/main/java/com/brickssoft/locationtracking/bridge/BridgeServices.kt +64 -0
  12. package/android/src/main/java/com/brickssoft/locationtracking/bridge/CallResponder.kt +41 -0
  13. package/android/src/main/java/com/brickssoft/locationtracking/bridge/OptionParsers.kt +281 -0
  14. package/android/src/main/java/com/brickssoft/locationtracking/bridge/PluginHandlers.kt +541 -0
  15. package/android/src/main/java/com/brickssoft/locationtracking/bridge/PluginPermissionHost.kt +76 -0
  16. package/android/src/main/java/com/brickssoft/locationtracking/bridge/WatchRegistry.kt +84 -0
  17. package/android/src/main/java/com/brickssoft/locationtracking/config/Config.kt +231 -0
  18. package/android/src/main/java/com/brickssoft/locationtracking/config/ConfigJson.kt +574 -0
  19. package/android/src/main/java/com/brickssoft/locationtracking/config/ConfigStore.kt +31 -0
  20. package/android/src/main/java/com/brickssoft/locationtracking/config/ConfigValidator.kt +170 -0
  21. package/android/src/main/java/com/brickssoft/locationtracking/config/RuntimeStateJson.kt +73 -0
  22. package/android/src/main/java/com/brickssoft/locationtracking/config/SharedPrefsConfigStore.kt +135 -0
  23. package/android/src/main/java/com/brickssoft/locationtracking/config/State.kt +45 -0
  24. package/android/src/main/java/com/brickssoft/locationtracking/core/AppDispatchers.kt +21 -0
  25. package/android/src/main/java/com/brickssoft/locationtracking/core/Clock.kt +41 -0
  26. package/android/src/main/java/com/brickssoft/locationtracking/core/Components.kt +217 -0
  27. package/android/src/main/java/com/brickssoft/locationtracking/core/Constants.kt +78 -0
  28. package/android/src/main/java/com/brickssoft/locationtracking/core/Errors.kt +27 -0
  29. package/android/src/main/java/com/brickssoft/locationtracking/core/EventBus.kt +39 -0
  30. package/android/src/main/java/com/brickssoft/locationtracking/core/ForceStopProbe.kt +63 -0
  31. package/android/src/main/java/com/brickssoft/locationtracking/core/Iso8601.kt +63 -0
  32. package/android/src/main/java/com/brickssoft/locationtracking/core/Logger.kt +68 -0
  33. package/android/src/main/java/com/brickssoft/locationtracking/core/RecordHooks.kt +47 -0
  34. package/android/src/main/java/com/brickssoft/locationtracking/core/TrackingEvent.kt +64 -0
  35. package/android/src/main/java/com/brickssoft/locationtracking/data/SqlSupport.kt +79 -0
  36. package/android/src/main/java/com/brickssoft/locationtracking/data/SqliteGeofenceStore.kt +144 -0
  37. package/android/src/main/java/com/brickssoft/locationtracking/data/SqliteLocationStore.kt +215 -0
  38. package/android/src/main/java/com/brickssoft/locationtracking/data/Stores.kt +45 -0
  39. package/android/src/main/java/com/brickssoft/locationtracking/data/TrackingDatabase.kt +111 -0
  40. package/android/src/main/java/com/brickssoft/locationtracking/device/DefaultDeviceInfoProvider.kt +104 -0
  41. package/android/src/main/java/com/brickssoft/locationtracking/device/DefaultDeviceMonitor.kt +335 -0
  42. package/android/src/main/java/com/brickssoft/locationtracking/device/DeviceMonitor.kt +36 -0
  43. package/android/src/main/java/com/brickssoft/locationtracking/device/DeviceStateMapping.kt +69 -0
  44. package/android/src/main/java/com/brickssoft/locationtracking/engine/DefaultTrackingEngine.kt +1281 -0
  45. package/android/src/main/java/com/brickssoft/locationtracking/engine/ElasticDistance.kt +68 -0
  46. package/android/src/main/java/com/brickssoft/locationtracking/engine/LocationRequests.kt +74 -0
  47. package/android/src/main/java/com/brickssoft/locationtracking/engine/MotionStateMachine.kt +291 -0
  48. package/android/src/main/java/com/brickssoft/locationtracking/engine/StationaryRegion.kt +41 -0
  49. package/android/src/main/java/com/brickssoft/locationtracking/engine/TrackingEngine.kt +60 -0
  50. package/android/src/main/java/com/brickssoft/locationtracking/engine/TrackingTimers.kt +90 -0
  51. package/android/src/main/java/com/brickssoft/locationtracking/geofence/DefaultGeofenceManager.kt +899 -0
  52. package/android/src/main/java/com/brickssoft/locationtracking/geofence/DwellTracker.kt +80 -0
  53. package/android/src/main/java/com/brickssoft/locationtracking/geofence/GeofenceManager.kt +33 -0
  54. package/android/src/main/java/com/brickssoft/locationtracking/geofence/PolygonMath.kt +194 -0
  55. package/android/src/main/java/com/brickssoft/locationtracking/heartbeat/DefaultHeartbeatScheduler.kt +713 -0
  56. package/android/src/main/java/com/brickssoft/locationtracking/heartbeat/HeartbeatAlarmReceiver.kt +69 -0
  57. package/android/src/main/java/com/brickssoft/locationtracking/heartbeat/HeartbeatAlarms.kt +100 -0
  58. package/android/src/main/java/com/brickssoft/locationtracking/heartbeat/HeartbeatScheduler.kt +25 -0
  59. package/android/src/main/java/com/brickssoft/locationtracking/heartbeat/HeartbeatWindow.kt +179 -0
  60. package/android/src/main/java/com/brickssoft/locationtracking/http/AuthorizationManager.kt +213 -0
  61. package/android/src/main/java/com/brickssoft/locationtracking/http/BodyBuilder.kt +73 -0
  62. package/android/src/main/java/com/brickssoft/locationtracking/http/HttpSupport.kt +118 -0
  63. package/android/src/main/java/com/brickssoft/locationtracking/http/HttpSyncer.kt +18 -0
  64. package/android/src/main/java/com/brickssoft/locationtracking/http/OkHttpSyncer.kt +551 -0
  65. package/android/src/main/java/com/brickssoft/locationtracking/http/StrictJson.kt +135 -0
  66. package/android/src/main/java/com/brickssoft/locationtracking/http/SyncPolicy.kt +109 -0
  67. package/android/src/main/java/com/brickssoft/locationtracking/http/TemplateRenderer.kt +143 -0
  68. package/android/src/main/java/com/brickssoft/locationtracking/logging/FileLogger.kt +461 -0
  69. package/android/src/main/java/com/brickssoft/locationtracking/logging/LogFileProvider.kt +9 -0
  70. package/android/src/main/java/com/brickssoft/locationtracking/logging/LogFormatter.kt +148 -0
  71. package/android/src/main/java/com/brickssoft/locationtracking/logging/LogStore.kt +30 -0
  72. package/android/src/main/java/com/brickssoft/locationtracking/model/DeviceModels.kt +176 -0
  73. package/android/src/main/java/com/brickssoft/locationtracking/model/GeofenceSpec.kt +25 -0
  74. package/android/src/main/java/com/brickssoft/locationtracking/model/ModelJson.kt +550 -0
  75. package/android/src/main/java/com/brickssoft/locationtracking/model/Record.kt +106 -0
  76. package/android/src/main/java/com/brickssoft/locationtracking/model/TrackedLocation.kt +53 -0
  77. package/android/src/main/java/com/brickssoft/locationtracking/permission/BackgroundRationaleDialog.kt +85 -0
  78. package/android/src/main/java/com/brickssoft/locationtracking/permission/CurrentActivityTracker.kt +72 -0
  79. package/android/src/main/java/com/brickssoft/locationtracking/permission/DefaultPermissionManager.kt +333 -0
  80. package/android/src/main/java/com/brickssoft/locationtracking/permission/PermissionManager.kt +54 -0
  81. package/android/src/main/java/com/brickssoft/locationtracking/position/DefaultPositionService.kt +277 -0
  82. package/android/src/main/java/com/brickssoft/locationtracking/position/PositionService.kt +39 -0
  83. package/android/src/main/java/com/brickssoft/locationtracking/position/PositionWatch.kt +115 -0
  84. package/android/src/main/java/com/brickssoft/locationtracking/processing/DefaultLocationProcessor.kt +93 -0
  85. package/android/src/main/java/com/brickssoft/locationtracking/processing/DefaultOdometer.kt +99 -0
  86. package/android/src/main/java/com/brickssoft/locationtracking/processing/DefaultRecordFactory.kt +166 -0
  87. package/android/src/main/java/com/brickssoft/locationtracking/processing/Geo.kt +56 -0
  88. package/android/src/main/java/com/brickssoft/locationtracking/processing/KalmanFilter.kt +72 -0
  89. package/android/src/main/java/com/brickssoft/locationtracking/processing/Processing.kt +48 -0
  90. package/android/src/main/java/com/brickssoft/locationtracking/provider/Backends.kt +187 -0
  91. package/android/src/main/java/com/brickssoft/locationtracking/provider/DefaultProviderFactory.kt +169 -0
  92. package/android/src/main/java/com/brickssoft/locationtracking/provider/ProviderPackaging.kt +135 -0
  93. package/android/src/main/java/com/brickssoft/locationtracking/provider/android/AndroidActivityBackend.kt +25 -0
  94. package/android/src/main/java/com/brickssoft/locationtracking/provider/android/AndroidGeofenceBackend.kt +181 -0
  95. package/android/src/main/java/com/brickssoft/locationtracking/provider/android/AndroidGeofenceReceiver.kt +115 -0
  96. package/android/src/main/java/com/brickssoft/locationtracking/provider/android/AndroidLocationBackend.kt +265 -0
  97. package/android/src/main/java/com/brickssoft/locationtracking/provider/android/AndroidProviderBundle.kt +31 -0
  98. package/android/src/main/java/com/brickssoft/locationtracking/provider/android/LocationProviders.kt +98 -0
  99. package/android/src/main/java/com/brickssoft/locationtracking/provider/gms/GmsActivityBackend.kt +80 -0
  100. package/android/src/main/java/com/brickssoft/locationtracking/provider/gms/GmsActivityReceiver.kt +62 -0
  101. package/android/src/main/java/com/brickssoft/locationtracking/provider/gms/GmsErrors.kt +72 -0
  102. package/android/src/main/java/com/brickssoft/locationtracking/provider/gms/GmsGeofenceBackend.kt +152 -0
  103. package/android/src/main/java/com/brickssoft/locationtracking/provider/gms/GmsGeofenceReceiver.kt +78 -0
  104. package/android/src/main/java/com/brickssoft/locationtracking/provider/gms/GmsLocationBackend.kt +176 -0
  105. package/android/src/main/java/com/brickssoft/locationtracking/provider/gms/GmsProviderBundle.kt +62 -0
  106. package/android/src/main/java/com/brickssoft/locationtracking/provider/gms/GmsReceiverSupport.kt +88 -0
  107. package/android/src/main/java/com/brickssoft/locationtracking/provider/gms/GmsTasks.kt +55 -0
  108. package/android/src/main/java/com/brickssoft/locationtracking/provider/hms/HmsActivityBackend.kt +71 -0
  109. package/android/src/main/java/com/brickssoft/locationtracking/provider/hms/HmsActivityReceiver.kt +57 -0
  110. package/android/src/main/java/com/brickssoft/locationtracking/provider/hms/HmsErrors.kt +76 -0
  111. package/android/src/main/java/com/brickssoft/locationtracking/provider/hms/HmsGeofenceBackend.kt +116 -0
  112. package/android/src/main/java/com/brickssoft/locationtracking/provider/hms/HmsGeofenceReceiver.kt +67 -0
  113. package/android/src/main/java/com/brickssoft/locationtracking/provider/hms/HmsIntents.kt +83 -0
  114. package/android/src/main/java/com/brickssoft/locationtracking/provider/hms/HmsLocationBackend.kt +197 -0
  115. package/android/src/main/java/com/brickssoft/locationtracking/provider/hms/HmsProviderBundle.kt +55 -0
  116. package/android/src/main/java/com/brickssoft/locationtracking/provider/hms/HmsTasks.kt +44 -0
  117. package/android/src/main/java/com/brickssoft/locationtracking/record/RecordSink.kt +95 -0
  118. package/android/src/main/java/com/brickssoft/locationtracking/service/BootCountStore.kt +44 -0
  119. package/android/src/main/java/com/brickssoft/locationtracking/service/BootReceiver.kt +175 -0
  120. package/android/src/main/java/com/brickssoft/locationtracking/service/DefaultServiceController.kt +196 -0
  121. package/android/src/main/java/com/brickssoft/locationtracking/service/LocationTrackingService.kt +319 -0
  122. package/android/src/main/java/com/brickssoft/locationtracking/service/NotificationActionReceiver.kt +42 -0
  123. package/android/src/main/java/com/brickssoft/locationtracking/service/NotificationFactory.kt +338 -0
  124. package/android/src/main/java/com/brickssoft/locationtracking/service/ServiceCommands.kt +204 -0
  125. package/android/src/main/java/com/brickssoft/locationtracking/service/ServiceController.kt +22 -0
  126. package/android/src/main/java/com/brickssoft/locationtracking/service/ServiceDeps.kt +61 -0
  127. package/android/src/main/java/com/brickssoft/locationtracking/service/WhileInUseStartCheck.kt +115 -0
  128. package/android/src/main/java/com/brickssoft/locationtracking/settings/DefaultDeviceSettings.kt +79 -0
  129. package/android/src/main/java/com/brickssoft/locationtracking/settings/DeviceSettings.kt +21 -0
  130. package/android/src/main/java/com/brickssoft/locationtracking/settings/OemPowerManagers.kt +198 -0
  131. package/android/src/main/res/drawable/lt_ic_notification.xml +11 -0
  132. package/android/src/main/res/values/lt_strings.xml +11 -0
  133. package/android/src/main/res/xml/lt_file_paths.xml +5 -0
  134. package/dist/esm/definitions.d.ts +719 -0
  135. package/dist/esm/definitions.js +2 -0
  136. package/dist/esm/definitions.js.map +1 -0
  137. package/dist/esm/events.d.ts +50 -0
  138. package/dist/esm/events.js +52 -0
  139. package/dist/esm/events.js.map +1 -0
  140. package/dist/esm/index.d.ts +3 -0
  141. package/dist/esm/index.js +5 -0
  142. package/dist/esm/index.js.map +1 -0
  143. package/dist/esm/plugin.d.ts +3 -0
  144. package/dist/esm/plugin.js +10 -0
  145. package/dist/esm/plugin.js.map +1 -0
  146. package/dist/esm/web/config.d.ts +19 -0
  147. package/dist/esm/web/config.js +243 -0
  148. package/dist/esm/web/config.js.map +1 -0
  149. package/dist/esm/web/device.d.ts +9 -0
  150. package/dist/esm/web/device.js +90 -0
  151. package/dist/esm/web/device.js.map +1 -0
  152. package/dist/esm/web/errors.d.ts +14 -0
  153. package/dist/esm/web/errors.js +19 -0
  154. package/dist/esm/web/errors.js.map +1 -0
  155. package/dist/esm/web/geolocation.d.ts +26 -0
  156. package/dist/esm/web/geolocation.js +117 -0
  157. package/dist/esm/web/geolocation.js.map +1 -0
  158. package/dist/esm/web/location.d.ts +7 -0
  159. package/dist/esm/web/location.js +70 -0
  160. package/dist/esm/web/location.js.map +1 -0
  161. package/dist/esm/web.d.ts +164 -0
  162. package/dist/esm/web.js +328 -0
  163. package/dist/esm/web.js.map +1 -0
  164. package/dist/plugin.cjs.js +945 -0
  165. package/dist/plugin.cjs.js.map +1 -0
  166. package/dist/plugin.js +948 -0
  167. package/dist/plugin.js.map +1 -0
  168. package/package.json +59 -4
@@ -0,0 +1,106 @@
1
+ ext {
2
+ junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
3
+ androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1'
4
+ androidxCoreKTXVersion = project.hasProperty('androidxCoreKTXVersion') ? rootProject.ext.androidxCoreKTXVersion : '1.17.0'
5
+ playServicesLocationVersion = project.hasProperty('playServicesLocationVersion') ? rootProject.ext.playServicesLocationVersion : '21.3.0'
6
+ hmsLocationVersion = project.hasProperty('hmsLocationVersion') ? rootProject.ext.hmsLocationVersion : '6.16.0.302'
7
+ kotlinxCoroutinesVersion = project.hasProperty('kotlinxCoroutinesVersion') ? rootProject.ext.kotlinxCoroutinesVersion : '1.10.2'
8
+ okhttpVersion = '4.12.0'
9
+ }
10
+
11
+ buildscript {
12
+ ext.kotlin_version = project.hasProperty('kotlin_version') ? rootProject.ext.kotlin_version : '2.2.20'
13
+ repositories {
14
+ google()
15
+ mavenCentral()
16
+ }
17
+ dependencies {
18
+ classpath 'com.android.tools.build:gradle:8.13.0'
19
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
20
+ }
21
+ }
22
+
23
+ apply plugin: 'com.android.library'
24
+ if (project.extensions.findByName('kotlin') == null) {
25
+ apply plugin: 'kotlin-android'
26
+ }
27
+
28
+ // Which location SDKs are packaged into the app: -PlocationTracking.providers=gms,hms (default: gms).
29
+ // The plugin always compiles against both (compileOnly) and selects one at runtime via reflection.
30
+ // An app with Play and AppGallery product flavors sets `none` and adds the SDKs per flavor itself (README).
31
+ def ltProviders = (project.findProperty('locationTracking.providers') ?: 'gms').toString()
32
+ .split(',').collect { it.trim().toLowerCase() }.findAll { it && it != 'none' }.toSet()
33
+
34
+ android {
35
+ namespace = 'com.brickssoft.locationtracking'
36
+ compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36
37
+ resourcePrefix 'lt_'
38
+ defaultConfig {
39
+ minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 24
40
+ targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 36
41
+ consumerProguardFiles 'consumer-rules.pro'
42
+ buildConfigField 'String', 'PLUGIN_VERSION', '"8.0.0"'
43
+ }
44
+ buildFeatures {
45
+ buildConfig = true
46
+ }
47
+ buildTypes {
48
+ release {
49
+ minifyEnabled false
50
+ }
51
+ }
52
+ compileOptions {
53
+ sourceCompatibility JavaVersion.VERSION_21
54
+ targetCompatibility JavaVersion.VERSION_21
55
+ }
56
+ lint {
57
+ abortOnError = false
58
+ }
59
+ testOptions {
60
+ unitTests {
61
+ includeAndroidResources = true
62
+ returnDefaultValues = true
63
+ all {
64
+ maxHeapSize = '1024m'
65
+ maxParallelForks = 1
66
+ jvmArgs '-XX:+UseSerialGC', '-XX:+EnableDynamicAgentLoading'
67
+ testLogging {
68
+ events 'failed'
69
+ exceptionFormat 'full'
70
+ }
71
+ }
72
+ }
73
+ }
74
+ }
75
+
76
+ kotlin {
77
+ jvmToolchain(21)
78
+ }
79
+
80
+ repositories {
81
+ google()
82
+ mavenCentral()
83
+ maven { url = 'https://developer.huawei.com/repo/' }
84
+ }
85
+
86
+ dependencies {
87
+ implementation project(':capacitor-android')
88
+ implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
89
+ implementation "androidx.core:core-ktx:$androidxCoreKTXVersion"
90
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinxCoroutinesVersion"
91
+ implementation "com.squareup.okhttp3:okhttp:$okhttpVersion"
92
+
93
+ compileOnly "com.google.android.gms:play-services-location:$playServicesLocationVersion"
94
+ compileOnly "com.huawei.hms:location:$hmsLocationVersion"
95
+ if (ltProviders.contains('gms')) implementation "com.google.android.gms:play-services-location:$playServicesLocationVersion"
96
+ if (ltProviders.contains('hms')) implementation "com.huawei.hms:location:$hmsLocationVersion"
97
+
98
+ testImplementation "junit:junit:$junitVersion"
99
+ testImplementation 'org.robolectric:robolectric:4.16.1'
100
+ testImplementation 'androidx.test:core-ktx:1.7.0'
101
+ testImplementation 'io.mockk:mockk:1.14.6'
102
+ testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinxCoroutinesVersion"
103
+ testImplementation "com.squareup.okhttp3:mockwebserver:$okhttpVersion"
104
+ testImplementation "com.google.android.gms:play-services-location:$playServicesLocationVersion"
105
+ testImplementation "com.huawei.hms:location:$hmsLocationVersion"
106
+ }
@@ -0,0 +1,42 @@
1
+ # Consumer ProGuard/R8 rules for @bricks-soft/capacitor-location-tracking.
2
+
3
+ # The plugin compiles against both location SDKs (compileOnly); an app may package only one of them.
4
+ -dontwarn com.google.android.gms.**
5
+ -dontwarn com.huawei.**
6
+
7
+ # Provider bundles are instantiated reflectively by DefaultProviderFactory.
8
+ -keep class com.brickssoft.locationtracking.provider.**.*ProviderBundle {
9
+ public <init>(android.content.Context);
10
+ }
11
+
12
+ # DefaultProviderFactory probes for the packaged SDKs by class name (Class.forName). Keep those names, or a
13
+ # minified release app would never detect Google Play services and fall back to the Android backend.
14
+ -keepnames class com.google.android.gms.location.LocationServices
15
+ -keepnames class com.google.android.gms.common.GoogleApiAvailability
16
+ -keepnames class com.huawei.hms.location.LocationServices
17
+
18
+ # Companion native API (docs/native-api.md): companion plugins and app code call these classes and interfaces. Keep
19
+ # every public member of the package, and the error type its callbacks deliver (TrackingException with an ErrorCode),
20
+ # so a companion library that is shrunk separately still finds them.
21
+ -keep class com.brickssoft.locationtracking.api.** { public *; }
22
+ -keep class com.brickssoft.locationtracking.core.TrackingException { public *; }
23
+ -keep class com.brickssoft.locationtracking.core.ErrorCode { public *; }
24
+
25
+ # Listener classes named in <meta-data android:name="com.brickssoft.locationtracking.LISTENER[.suffix]"> are created
26
+ # by reflection (Class.forName with the manifest name, then the public no-arg constructor). Keep the class names,
27
+ # that constructor and the listener methods of every implementation.
28
+ -keep class * implements com.brickssoft.locationtracking.api.LocationTrackingListener {
29
+ public <init>();
30
+ public void onRecord(android.content.Context, org.json.JSONObject);
31
+ public void onEvent(android.content.Context, java.lang.String, org.json.JSONObject);
32
+ }
33
+
34
+ # Huawei HMS recommended keeps.
35
+ -keep class com.huawei.hms.** { *; }
36
+ -keep class com.huawei.hianalytics.** { *; }
37
+ -keep class com.huawei.updatesdk.** { *; }
38
+ -keepattributes *Annotation*
39
+ -keepattributes Exceptions
40
+ -keepattributes InnerClasses,EnclosingMethod
41
+ -keepattributes Signature
42
+ -keepattributes SourceFile,LineNumberTable
@@ -0,0 +1,52 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
3
+ <uses-permission android:name="android.permission.INTERNET"/>
4
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
5
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
6
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
7
+ <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
8
+ <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION"/>
9
+ <uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION" android:maxSdkVersion="28"/>
10
+ <uses-permission android:name="com.huawei.hms.permission.ACTIVITY_RECOGNITION" android:maxSdkVersion="28"/>
11
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
12
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION"/>
13
+ <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
14
+ <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
15
+ <uses-permission android:name="android.permission.WAKE_LOCK"/>
16
+ <!-- intentionally NOT declared: SCHEDULE_EXACT_ALARM, USE_EXACT_ALARM, REQUEST_IGNORE_BATTERY_OPTIMIZATIONS -->
17
+ <uses-feature android:name="android.hardware.location.gps" android:required="false"/>
18
+ <queries>
19
+ <intent><action android:name="android.intent.action.SENDTO"/><data android:scheme="mailto"/></intent>
20
+ <package android:name="com.huawei.systemmanager"/><package android:name="com.hihonor.systemmanager"/>
21
+ <package android:name="com.miui.securitycenter"/><package android:name="com.miui.powerkeeper"/>
22
+ <package android:name="com.coloros.safecenter"/><package android:name="com.oppo.safe"/><package android:name="com.coloros.oppoguardelf"/>
23
+ <package android:name="com.iqoo.secure"/><package android:name="com.vivo.permissionmanager"/>
24
+ <package android:name="com.samsung.android.lool"/><package android:name="com.samsung.android.sm"/>
25
+ <package android:name="com.oneplus.security"/><package android:name="com.asus.mobilemanager"/>
26
+ <package android:name="com.letv.android.letvsafe"/><package android:name="com.htc.pitroad"/>
27
+ <package android:name="com.meizu.safe"/><package android:name="com.evenwell.powersaving.g3"/>
28
+ </queries>
29
+ <application>
30
+ <service android:name="com.brickssoft.locationtracking.service.LocationTrackingService"
31
+ android:exported="false" android:foregroundServiceType="location" android:stopWithTask="false"/>
32
+ <receiver android:name="com.brickssoft.locationtracking.service.BootReceiver" android:exported="true">
33
+ <intent-filter>
34
+ <action android:name="android.intent.action.BOOT_COMPLETED"/>
35
+ <action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
36
+ <action android:name="android.intent.action.QUICKBOOT_POWERON"/>
37
+ <action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/>
38
+ </intent-filter>
39
+ </receiver>
40
+ <receiver android:name="com.brickssoft.locationtracking.heartbeat.HeartbeatAlarmReceiver" android:exported="false"/>
41
+ <receiver android:name="com.brickssoft.locationtracking.service.NotificationActionReceiver" android:exported="false"/>
42
+ <receiver android:name="com.brickssoft.locationtracking.provider.gms.GmsActivityReceiver" android:exported="false"/>
43
+ <receiver android:name="com.brickssoft.locationtracking.provider.gms.GmsGeofenceReceiver" android:exported="false"/>
44
+ <receiver android:name="com.brickssoft.locationtracking.provider.hms.HmsActivityReceiver" android:exported="false"/>
45
+ <receiver android:name="com.brickssoft.locationtracking.provider.hms.HmsGeofenceReceiver" android:exported="false"/>
46
+ <receiver android:name="com.brickssoft.locationtracking.provider.android.AndroidGeofenceReceiver" android:exported="false"/>
47
+ <provider android:name="com.brickssoft.locationtracking.logging.LogFileProvider"
48
+ android:authorities="${applicationId}.locationtracking.logs" android:exported="false" android:grantUriPermissions="true">
49
+ <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/lt_file_paths"/>
50
+ </provider>
51
+ </application>
52
+ </manifest>
@@ -0,0 +1,431 @@
1
+ package com.brickssoft.locationtracking
2
+
3
+ import android.Manifest
4
+ import com.brickssoft.locationtracking.bridge.CallResponder
5
+ import com.brickssoft.locationtracking.bridge.CallWatchTarget
6
+ import com.brickssoft.locationtracking.bridge.ComponentServices
7
+ import com.brickssoft.locationtracking.bridge.PermissionRequestLauncher
8
+ import com.brickssoft.locationtracking.bridge.PluginHandlers
9
+ import com.brickssoft.locationtracking.bridge.PluginPermissionHost
10
+ import com.brickssoft.locationtracking.core.Components
11
+ import com.brickssoft.locationtracking.core.Logger
12
+ import com.brickssoft.locationtracking.core.Subscription
13
+ import com.brickssoft.locationtracking.permission.CurrentActivityTracker
14
+ import com.getcapacitor.JSObject
15
+ import com.getcapacitor.Plugin
16
+ import com.getcapacitor.PluginCall
17
+ import com.getcapacitor.PluginMethod
18
+ import com.getcapacitor.annotation.CapacitorPlugin
19
+ import com.getcapacitor.annotation.Permission
20
+ import com.getcapacitor.annotation.PermissionCallback
21
+ import kotlinx.coroutines.CancellationException
22
+ import kotlinx.coroutines.CoroutineStart
23
+ import kotlinx.coroutines.Job
24
+ import kotlinx.coroutines.isActive
25
+ import kotlinx.coroutines.launch
26
+ import org.json.JSONObject
27
+ import java.util.concurrent.ConcurrentHashMap
28
+
29
+ /**
30
+ * Capacitor bridge for `LocationTracking`. A thin adapter: every method runs the matching [PluginHandlers] function
31
+ * on `Components.scope` and settles the call (TrackingException -> `reject(message, code)`, anything else ->
32
+ * INTERNAL). Tracking events from the EventBus are forwarded to JS listeners.
33
+ */
34
+ @CapacitorPlugin(
35
+ name = "LocationTracking",
36
+ permissions = [
37
+ Permission(
38
+ strings = [Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION],
39
+ alias = "location",
40
+ ),
41
+ Permission(strings = [Manifest.permission.ACCESS_BACKGROUND_LOCATION], alias = "backgroundLocation"),
42
+ Permission(strings = [Manifest.permission.ACTIVITY_RECOGNITION], alias = "activityRecognition"),
43
+ Permission(strings = [Manifest.permission.POST_NOTIFICATIONS], alias = "notifications"),
44
+ ],
45
+ )
46
+ class LocationTrackingPlugin : Plugin() {
47
+ private lateinit var components: Components
48
+ private lateinit var handlers: PluginHandlers
49
+
50
+ @Volatile
51
+ private var eventSubscription: Subscription? = null
52
+
53
+ /** `onDone` of the in-flight permission request of each call, by callback id. */
54
+ private val pendingPermissionRequests = ConcurrentHashMap<String, () -> Unit>()
55
+
56
+ /** Running `requestPermissions` coroutines by callback id, cancelled in [handleOnDestroy]. */
57
+ private val permissionJobs = ConcurrentHashMap<String, Job>()
58
+
59
+ @Volatile
60
+ private var destroyed = false
61
+
62
+ override fun load() {
63
+ components = Components.get(context)
64
+ // The Activity is already created when plugins load, so the permission manager's lifecycle tracker would
65
+ // only learn about it at its next lifecycle event; seed it for rationale checks in checkPermissions().
66
+ activity?.let { CurrentActivityTracker.attach(context).remember(it) }
67
+ handlers = PluginHandlers(
68
+ ComponentServices(components),
69
+ activityProvider = { PluginPermissionHost.usableActivity(this) },
70
+ )
71
+ eventSubscription = handlers.forwardEvents { name, payload -> notifyOnBridgeThread(name, payload) }
72
+ }
73
+
74
+ override fun handleOnResume() {
75
+ super.handleOnResume()
76
+ if (!::handlers.isInitialized) return
77
+ components.scope.launch { handlers.onResume() }
78
+ }
79
+
80
+ /**
81
+ * Stops event forwarding, releases every watch and ends in-flight permission requests: their calls are
82
+ * abandoned (the WebView is going away), but each pending `onDone` still runs so the PermissionManager's request
83
+ * completes; later steps of it complete at once because the host has no usable activity and no launcher.
84
+ */
85
+ override fun handleOnDestroy() {
86
+ destroyed = true
87
+ eventSubscription?.cancel()
88
+ eventSubscription = null
89
+ if (::handlers.isInitialized) handlers.releaseAllWatches()
90
+ for (id in pendingPermissionRequests.keys.toList()) {
91
+ val onDone = pendingPermissionRequests.remove(id) ?: continue
92
+ try {
93
+ onDone()
94
+ } catch (t: Throwable) {
95
+ Logger.w(TAG, "completing permission request $id on destroy failed", t)
96
+ }
97
+ }
98
+ permissionJobs.values.forEach { it.cancel() }
99
+ permissionJobs.clear()
100
+ super.handleOnDestroy()
101
+ }
102
+
103
+ // ---- lifecycle and config
104
+
105
+ @PluginMethod
106
+ fun ready(call: PluginCall) {
107
+ respond(call) { handlers.ready(it) }
108
+ }
109
+
110
+ @PluginMethod
111
+ fun setConfig(call: PluginCall) {
112
+ respond(call) { handlers.setConfig(it) }
113
+ }
114
+
115
+ @PluginMethod
116
+ fun reset(call: PluginCall) {
117
+ respond(call) { handlers.reset(it) }
118
+ }
119
+
120
+ @PluginMethod
121
+ fun getState(call: PluginCall) {
122
+ respond(call) { handlers.getState() }
123
+ }
124
+
125
+ @PluginMethod
126
+ fun start(call: PluginCall) {
127
+ respond(call) { handlers.start() }
128
+ }
129
+
130
+ @PluginMethod
131
+ fun startGeofences(call: PluginCall) {
132
+ respond(call) { handlers.startGeofences() }
133
+ }
134
+
135
+ @PluginMethod
136
+ fun stop(call: PluginCall) {
137
+ respond(call) { handlers.stop() }
138
+ }
139
+
140
+ @PluginMethod
141
+ fun changePace(call: PluginCall) {
142
+ respond(call) { handlers.changePace(it) }
143
+ }
144
+
145
+ // ---- positions
146
+
147
+ @PluginMethod
148
+ fun getCurrentPosition(call: PluginCall) {
149
+ respond(call) { handlers.getCurrentPosition(it) }
150
+ }
151
+
152
+ /**
153
+ * Kept alive until `clearWatch`: the bridge saves the call and every location/error invokes the JS callback.
154
+ * Kept alive before registering so an early delivery cannot release the call.
155
+ */
156
+ @PluginMethod(returnType = PluginMethod.RETURN_CALLBACK)
157
+ fun watchPosition(call: PluginCall) {
158
+ call.setKeepAlive(true)
159
+ try {
160
+ handlers.watchPosition(call.callbackId, optionsOf(call), CallWatchTarget(call) { bridge })
161
+ } catch (t: Throwable) { // also Errors (e.g. NoClassDefFoundError of a missing SDK): never crash the bridge
162
+ call.setKeepAlive(false)
163
+ CallResponder.reject(call, t)
164
+ }
165
+ }
166
+
167
+ @PluginMethod
168
+ fun clearWatch(call: PluginCall) {
169
+ respond(call) { handlers.clearWatch(it) }
170
+ }
171
+
172
+ // ---- odometer
173
+
174
+ @PluginMethod
175
+ fun getOdometer(call: PluginCall) {
176
+ respond(call) { handlers.getOdometer() }
177
+ }
178
+
179
+ @PluginMethod
180
+ fun setOdometer(call: PluginCall) {
181
+ respond(call) { handlers.setOdometer(it) }
182
+ }
183
+
184
+ @PluginMethod
185
+ fun resetOdometer(call: PluginCall) {
186
+ respond(call) { handlers.resetOdometer() }
187
+ }
188
+
189
+ // ---- records
190
+
191
+ @PluginMethod
192
+ fun getLocations(call: PluginCall) {
193
+ respond(call) { handlers.getLocations(it) }
194
+ }
195
+
196
+ @PluginMethod
197
+ fun getCount(call: PluginCall) {
198
+ respond(call) { handlers.getCount() }
199
+ }
200
+
201
+ @PluginMethod
202
+ fun insertLocation(call: PluginCall) {
203
+ respond(call) { handlers.insertLocation(it) }
204
+ }
205
+
206
+ @PluginMethod
207
+ fun destroyLocations(call: PluginCall) {
208
+ respond(call) { handlers.destroyLocations() }
209
+ }
210
+
211
+ @PluginMethod
212
+ fun destroyLocation(call: PluginCall) {
213
+ respond(call) { handlers.destroyLocation(it) }
214
+ }
215
+
216
+ @PluginMethod
217
+ fun sync(call: PluginCall) {
218
+ respond(call) { handlers.sync() }
219
+ }
220
+
221
+ // ---- geofences
222
+
223
+ @PluginMethod
224
+ fun addGeofence(call: PluginCall) {
225
+ respond(call) { handlers.addGeofence(it) }
226
+ }
227
+
228
+ @PluginMethod
229
+ fun addGeofences(call: PluginCall) {
230
+ respond(call) { handlers.addGeofences(it) }
231
+ }
232
+
233
+ @PluginMethod
234
+ fun removeGeofence(call: PluginCall) {
235
+ respond(call) { handlers.removeGeofence(it) }
236
+ }
237
+
238
+ @PluginMethod
239
+ fun removeGeofences(call: PluginCall) {
240
+ respond(call) { handlers.removeGeofences(it) }
241
+ }
242
+
243
+ @PluginMethod
244
+ fun getGeofences(call: PluginCall) {
245
+ respond(call) { handlers.getGeofences() }
246
+ }
247
+
248
+ @PluginMethod
249
+ fun getGeofence(call: PluginCall) {
250
+ respond(call) { handlers.getGeofence(it) }
251
+ }
252
+
253
+ @PluginMethod
254
+ fun geofenceExists(call: PluginCall) {
255
+ respond(call) { handlers.geofenceExists(it) }
256
+ }
257
+
258
+ // ---- heartbeat and device
259
+
260
+ @PluginMethod
261
+ fun getHeartbeatStatus(call: PluginCall) {
262
+ respond(call) { handlers.getHeartbeatStatus() }
263
+ }
264
+
265
+ @PluginMethod
266
+ fun getProviderState(call: PluginCall) {
267
+ respond(call) { handlers.getProviderState() }
268
+ }
269
+
270
+ @PluginMethod
271
+ fun isPowerSaveMode(call: PluginCall) {
272
+ respond(call) { handlers.isPowerSaveMode() }
273
+ }
274
+
275
+ @PluginMethod
276
+ fun getBatteryOptimizationStatus(call: PluginCall) {
277
+ respond(call) { handlers.getBatteryOptimizationStatus() }
278
+ }
279
+
280
+ @PluginMethod
281
+ fun openBatteryOptimizationSettings(call: PluginCall) {
282
+ respond(call) { handlers.openBatteryOptimizationSettings() }
283
+ }
284
+
285
+ @PluginMethod
286
+ fun getPowerManagerInfo(call: PluginCall) {
287
+ respond(call) { handlers.getPowerManagerInfo() }
288
+ }
289
+
290
+ @PluginMethod
291
+ fun openPowerManagerSettings(call: PluginCall) {
292
+ respond(call) { handlers.openPowerManagerSettings() }
293
+ }
294
+
295
+ @PluginMethod
296
+ fun openLocationSettings(call: PluginCall) {
297
+ respond(call) { handlers.openLocationSettings() }
298
+ }
299
+
300
+ @PluginMethod
301
+ fun openAppSettings(call: PluginCall) {
302
+ respond(call) { handlers.openAppSettings() }
303
+ }
304
+
305
+ @PluginMethod
306
+ fun getDeviceInfo(call: PluginCall) {
307
+ respond(call) { handlers.getDeviceInfo() }
308
+ }
309
+
310
+ @PluginMethod
311
+ fun getSensors(call: PluginCall) {
312
+ respond(call) { handlers.getSensors() }
313
+ }
314
+
315
+ // ---- permissions
316
+
317
+ @PluginMethod
318
+ override fun checkPermissions(call: PluginCall) {
319
+ respond(call) { handlers.checkPermissions() }
320
+ }
321
+
322
+ @PluginMethod
323
+ override fun requestPermissions(call: PluginCall) {
324
+ val id = call.callbackId
325
+ val host = PluginPermissionHost(
326
+ this,
327
+ launcher = PermissionRequestLauncher { aliases, onDone -> launchPermissionRequest(call, aliases, onDone) },
328
+ )
329
+ val job = respond(call, CoroutineStart.LAZY) { handlers.requestPermissions(it, host) }
330
+ permissionJobs[id] = job
331
+ job.invokeOnCompletion {
332
+ permissionJobs.remove(id, job)
333
+ pendingPermissionRequests.remove(id)
334
+ }
335
+ job.start()
336
+ }
337
+
338
+ /** Main thread (called by the PermissionManager through [PluginPermissionHost]). */
339
+ private fun launchPermissionRequest(call: PluginCall, aliases: List<String>, onDone: () -> Unit) {
340
+ if (destroyed) {
341
+ onDone()
342
+ return
343
+ }
344
+ val id = call.callbackId
345
+ pendingPermissionRequests[id] = onDone
346
+ try {
347
+ requestPermissionForAliases(aliases.toTypedArray(), call, PERMISSION_CALLBACK)
348
+ } catch (e: Exception) {
349
+ pendingPermissionRequests.remove(id)
350
+ throw e
351
+ }
352
+ }
353
+
354
+ /** Resolved by name ([PERMISSION_CALLBACK]) through reflection by Capacitor; main thread. */
355
+ @Suppress("unused")
356
+ @PermissionCallback
357
+ private fun onPermissionRequestResult(call: PluginCall?) {
358
+ val id = call?.callbackId ?: return
359
+ val onDone = pendingPermissionRequests.remove(id) ?: return
360
+ onDone()
361
+ }
362
+
363
+ // ---- log
364
+
365
+ @PluginMethod
366
+ fun log(call: PluginCall) {
367
+ respond(call) { handlers.log(it) }
368
+ }
369
+
370
+ @PluginMethod
371
+ fun getLog(call: PluginCall) {
372
+ respond(call) { handlers.getLog(it) }
373
+ }
374
+
375
+ @PluginMethod
376
+ fun destroyLog(call: PluginCall) {
377
+ respond(call) { handlers.destroyLog() }
378
+ }
379
+
380
+ @PluginMethod
381
+ fun uploadLog(call: PluginCall) {
382
+ respond(call) { handlers.uploadLog(it) }
383
+ }
384
+
385
+ @PluginMethod
386
+ fun emailLog(call: PluginCall) {
387
+ respond(call) { handlers.emailLog(it) }
388
+ }
389
+
390
+ // ---- plumbing
391
+
392
+ /** Runs [block] on `Components.scope` and settles [call] with its result (null = void) or error. */
393
+ private fun respond(
394
+ call: PluginCall,
395
+ start: CoroutineStart = CoroutineStart.DEFAULT,
396
+ block: suspend (JSONObject) -> JSONObject?,
397
+ ): Job = components.scope.launch(start = start) {
398
+ try {
399
+ CallResponder.resolve(call, block(optionsOf(call)))
400
+ } catch (t: Throwable) { // also Errors (e.g. NoClassDefFoundError of a missing SDK): always settle the call
401
+ // Our own cancellation (plugin destroyed / scope cancelled): nobody is listening any more.
402
+ if (t is CancellationException && !isActive) return@launch
403
+ CallResponder.reject(call, t)
404
+ }
405
+ }
406
+
407
+ /**
408
+ * Emits on the bridge's plugin thread, where Capacitor also adds and removes listeners, so the listener list is
409
+ * never read and modified concurrently. The payload is built only if JS listens; events are dropped while no
410
+ * bridge is attached.
411
+ */
412
+ private fun notifyOnBridgeThread(name: String, payload: () -> JSONObject) {
413
+ val b = bridge ?: return
414
+ b.execute {
415
+ try {
416
+ if (hasListeners(name)) notifyListeners(name, JSObject.fromJSONObject(payload()))
417
+ } catch (t: Throwable) {
418
+ Logger.e(TAG, "notifyListeners($name) failed", t)
419
+ }
420
+ }
421
+ }
422
+
423
+ private fun optionsOf(call: PluginCall): JSONObject = call.data ?: JSObject()
424
+
425
+ private companion object {
426
+ const val TAG = "LT.Plugin"
427
+
428
+ /** Name of the `@PermissionCallback` method; Capacitor looks it up by name. */
429
+ const val PERMISSION_CALLBACK = "onPermissionRequestResult"
430
+ }
431
+ }
@@ -0,0 +1,52 @@
1
+ // CONTRACT (scaffold, round 2): signatures fixed by docs/e2e/architecture.md §2. Owned by Unit 5 (Companion native
2
+ // API) for KDoc and behavior only; do not change the signatures.
3
+ package com.brickssoft.locationtracking.api
4
+
5
+ import android.content.Context
6
+ import org.json.JSONObject
7
+
8
+ /**
9
+ * Receives every queued record and every tracking event natively, also in processes where no WebView (and no JS)
10
+ * exists: after a reboot, a heartbeat alarm or a `START_STICKY` restart.
11
+ *
12
+ * Register it in one of two ways:
13
+ * - **Manifest** (recommended for a companion plugin that must never miss an event): a `<meta-data>` inside
14
+ * `<application>` whose name is [LocationTrackingNative.LISTENER_META_DATA] (or starts with it followed by `.`, for
15
+ * a second listener) and whose value is the fully qualified class name. The class needs a public no-arg
16
+ * constructor. It is instantiated once per process in `Components.bootstrap()`, which every entry point runs
17
+ * (plugin load, service, receivers) before the engine can emit anything.
18
+ * - **Programmatically**: [LocationTrackingNative.addListener]; receives only what is emitted after the call.
19
+ *
20
+ * Methods run on the plugin's single `LT-native` background thread, in emission order (a record's [onRecord] comes
21
+ * before the events that carry it). Every exception (also an Error) thrown by a method is caught and logged, and the
22
+ * other listeners still receive the call. Delivery never blocks the engine, but one slow listener delays every later
23
+ * delivery to every listener: keep the methods short and hand long work (I/O, uploads) to your own executor.
24
+ *
25
+ * Both methods receive the application context and a JSONObject built for this listener alone (changing it does not
26
+ * affect other listeners). Guide: `docs/native-api.md`.
27
+ */
28
+ interface LocationTrackingListener {
29
+ /**
30
+ * Every record queued for upload, including `tracking_start`/`tracking_stop`, `providerchange`, `heartbeat`,
31
+ * `geofence` and `insertLocation()` records: the wire JSON of `RecordJson.toJson(record)` (no `sent_at`). A record
32
+ * the plugin creates is delivered also when the plugin failed to store it in its database; an `insertLocation()`
33
+ * whose insert fails is rejected instead and not delivered.
34
+ */
35
+ fun onRecord(context: Context, record: JSONObject) {}
36
+
37
+ /** Every JS event, with the same name and payload as the JS `addListener` API (`EventJson.name` / `payload`). */
38
+ fun onEvent(context: Context, name: String, payload: JSONObject) {}
39
+ }
40
+
41
+ /** Returned by [LocationTrackingNative.addListener]. Idempotent. */
42
+ fun interface NativeSubscription {
43
+ fun remove()
44
+ }
45
+
46
+ /**
47
+ * Result of a [LocationTrackingNative] call, always invoked on the `LT-native` thread. A failure carries a
48
+ * `com.brickssoft.locationtracking.core.TrackingException` whose `code` is the JS `ErrorCode`.
49
+ */
50
+ fun interface NativeCallback<T> {
51
+ fun onResult(result: Result<T>)
52
+ }