@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.
- package/LICENSE +21 -0
- package/README.md +2759 -2
- package/android/build.gradle +106 -0
- package/android/consumer-rules.pro +42 -0
- package/android/src/main/AndroidManifest.xml +52 -0
- package/android/src/main/java/com/brickssoft/locationtracking/LocationTrackingPlugin.kt +431 -0
- package/android/src/main/java/com/brickssoft/locationtracking/api/LocationTrackingListener.kt +52 -0
- package/android/src/main/java/com/brickssoft/locationtracking/api/LocationTrackingNative.kt +267 -0
- package/android/src/main/java/com/brickssoft/locationtracking/api/NativeListeners.kt +270 -0
- package/android/src/main/java/com/brickssoft/locationtracking/api/NativeThread.kt +57 -0
- package/android/src/main/java/com/brickssoft/locationtracking/bridge/BridgeServices.kt +64 -0
- package/android/src/main/java/com/brickssoft/locationtracking/bridge/CallResponder.kt +41 -0
- package/android/src/main/java/com/brickssoft/locationtracking/bridge/OptionParsers.kt +281 -0
- package/android/src/main/java/com/brickssoft/locationtracking/bridge/PluginHandlers.kt +541 -0
- package/android/src/main/java/com/brickssoft/locationtracking/bridge/PluginPermissionHost.kt +76 -0
- package/android/src/main/java/com/brickssoft/locationtracking/bridge/WatchRegistry.kt +84 -0
- package/android/src/main/java/com/brickssoft/locationtracking/config/Config.kt +231 -0
- package/android/src/main/java/com/brickssoft/locationtracking/config/ConfigJson.kt +574 -0
- package/android/src/main/java/com/brickssoft/locationtracking/config/ConfigStore.kt +31 -0
- package/android/src/main/java/com/brickssoft/locationtracking/config/ConfigValidator.kt +170 -0
- package/android/src/main/java/com/brickssoft/locationtracking/config/RuntimeStateJson.kt +73 -0
- package/android/src/main/java/com/brickssoft/locationtracking/config/SharedPrefsConfigStore.kt +135 -0
- package/android/src/main/java/com/brickssoft/locationtracking/config/State.kt +45 -0
- package/android/src/main/java/com/brickssoft/locationtracking/core/AppDispatchers.kt +21 -0
- package/android/src/main/java/com/brickssoft/locationtracking/core/Clock.kt +41 -0
- package/android/src/main/java/com/brickssoft/locationtracking/core/Components.kt +217 -0
- package/android/src/main/java/com/brickssoft/locationtracking/core/Constants.kt +78 -0
- package/android/src/main/java/com/brickssoft/locationtracking/core/Errors.kt +27 -0
- package/android/src/main/java/com/brickssoft/locationtracking/core/EventBus.kt +39 -0
- package/android/src/main/java/com/brickssoft/locationtracking/core/ForceStopProbe.kt +63 -0
- package/android/src/main/java/com/brickssoft/locationtracking/core/Iso8601.kt +63 -0
- package/android/src/main/java/com/brickssoft/locationtracking/core/Logger.kt +68 -0
- package/android/src/main/java/com/brickssoft/locationtracking/core/RecordHooks.kt +47 -0
- package/android/src/main/java/com/brickssoft/locationtracking/core/TrackingEvent.kt +64 -0
- package/android/src/main/java/com/brickssoft/locationtracking/data/SqlSupport.kt +79 -0
- package/android/src/main/java/com/brickssoft/locationtracking/data/SqliteGeofenceStore.kt +144 -0
- package/android/src/main/java/com/brickssoft/locationtracking/data/SqliteLocationStore.kt +215 -0
- package/android/src/main/java/com/brickssoft/locationtracking/data/Stores.kt +45 -0
- package/android/src/main/java/com/brickssoft/locationtracking/data/TrackingDatabase.kt +111 -0
- package/android/src/main/java/com/brickssoft/locationtracking/device/DefaultDeviceInfoProvider.kt +104 -0
- package/android/src/main/java/com/brickssoft/locationtracking/device/DefaultDeviceMonitor.kt +335 -0
- package/android/src/main/java/com/brickssoft/locationtracking/device/DeviceMonitor.kt +36 -0
- package/android/src/main/java/com/brickssoft/locationtracking/device/DeviceStateMapping.kt +69 -0
- package/android/src/main/java/com/brickssoft/locationtracking/engine/DefaultTrackingEngine.kt +1281 -0
- package/android/src/main/java/com/brickssoft/locationtracking/engine/ElasticDistance.kt +68 -0
- package/android/src/main/java/com/brickssoft/locationtracking/engine/LocationRequests.kt +74 -0
- package/android/src/main/java/com/brickssoft/locationtracking/engine/MotionStateMachine.kt +291 -0
- package/android/src/main/java/com/brickssoft/locationtracking/engine/StationaryRegion.kt +41 -0
- package/android/src/main/java/com/brickssoft/locationtracking/engine/TrackingEngine.kt +60 -0
- package/android/src/main/java/com/brickssoft/locationtracking/engine/TrackingTimers.kt +90 -0
- package/android/src/main/java/com/brickssoft/locationtracking/geofence/DefaultGeofenceManager.kt +899 -0
- package/android/src/main/java/com/brickssoft/locationtracking/geofence/DwellTracker.kt +80 -0
- package/android/src/main/java/com/brickssoft/locationtracking/geofence/GeofenceManager.kt +33 -0
- package/android/src/main/java/com/brickssoft/locationtracking/geofence/PolygonMath.kt +194 -0
- package/android/src/main/java/com/brickssoft/locationtracking/heartbeat/DefaultHeartbeatScheduler.kt +713 -0
- package/android/src/main/java/com/brickssoft/locationtracking/heartbeat/HeartbeatAlarmReceiver.kt +69 -0
- package/android/src/main/java/com/brickssoft/locationtracking/heartbeat/HeartbeatAlarms.kt +100 -0
- package/android/src/main/java/com/brickssoft/locationtracking/heartbeat/HeartbeatScheduler.kt +25 -0
- package/android/src/main/java/com/brickssoft/locationtracking/heartbeat/HeartbeatWindow.kt +179 -0
- package/android/src/main/java/com/brickssoft/locationtracking/http/AuthorizationManager.kt +213 -0
- package/android/src/main/java/com/brickssoft/locationtracking/http/BodyBuilder.kt +73 -0
- package/android/src/main/java/com/brickssoft/locationtracking/http/HttpSupport.kt +118 -0
- package/android/src/main/java/com/brickssoft/locationtracking/http/HttpSyncer.kt +18 -0
- package/android/src/main/java/com/brickssoft/locationtracking/http/OkHttpSyncer.kt +551 -0
- package/android/src/main/java/com/brickssoft/locationtracking/http/StrictJson.kt +135 -0
- package/android/src/main/java/com/brickssoft/locationtracking/http/SyncPolicy.kt +109 -0
- package/android/src/main/java/com/brickssoft/locationtracking/http/TemplateRenderer.kt +143 -0
- package/android/src/main/java/com/brickssoft/locationtracking/logging/FileLogger.kt +461 -0
- package/android/src/main/java/com/brickssoft/locationtracking/logging/LogFileProvider.kt +9 -0
- package/android/src/main/java/com/brickssoft/locationtracking/logging/LogFormatter.kt +148 -0
- package/android/src/main/java/com/brickssoft/locationtracking/logging/LogStore.kt +30 -0
- package/android/src/main/java/com/brickssoft/locationtracking/model/DeviceModels.kt +176 -0
- package/android/src/main/java/com/brickssoft/locationtracking/model/GeofenceSpec.kt +25 -0
- package/android/src/main/java/com/brickssoft/locationtracking/model/ModelJson.kt +550 -0
- package/android/src/main/java/com/brickssoft/locationtracking/model/Record.kt +106 -0
- package/android/src/main/java/com/brickssoft/locationtracking/model/TrackedLocation.kt +53 -0
- package/android/src/main/java/com/brickssoft/locationtracking/permission/BackgroundRationaleDialog.kt +85 -0
- package/android/src/main/java/com/brickssoft/locationtracking/permission/CurrentActivityTracker.kt +72 -0
- package/android/src/main/java/com/brickssoft/locationtracking/permission/DefaultPermissionManager.kt +333 -0
- package/android/src/main/java/com/brickssoft/locationtracking/permission/PermissionManager.kt +54 -0
- package/android/src/main/java/com/brickssoft/locationtracking/position/DefaultPositionService.kt +277 -0
- package/android/src/main/java/com/brickssoft/locationtracking/position/PositionService.kt +39 -0
- package/android/src/main/java/com/brickssoft/locationtracking/position/PositionWatch.kt +115 -0
- package/android/src/main/java/com/brickssoft/locationtracking/processing/DefaultLocationProcessor.kt +93 -0
- package/android/src/main/java/com/brickssoft/locationtracking/processing/DefaultOdometer.kt +99 -0
- package/android/src/main/java/com/brickssoft/locationtracking/processing/DefaultRecordFactory.kt +166 -0
- package/android/src/main/java/com/brickssoft/locationtracking/processing/Geo.kt +56 -0
- package/android/src/main/java/com/brickssoft/locationtracking/processing/KalmanFilter.kt +72 -0
- package/android/src/main/java/com/brickssoft/locationtracking/processing/Processing.kt +48 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/Backends.kt +187 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/DefaultProviderFactory.kt +169 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/ProviderPackaging.kt +135 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/android/AndroidActivityBackend.kt +25 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/android/AndroidGeofenceBackend.kt +181 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/android/AndroidGeofenceReceiver.kt +115 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/android/AndroidLocationBackend.kt +265 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/android/AndroidProviderBundle.kt +31 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/android/LocationProviders.kt +98 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/gms/GmsActivityBackend.kt +80 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/gms/GmsActivityReceiver.kt +62 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/gms/GmsErrors.kt +72 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/gms/GmsGeofenceBackend.kt +152 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/gms/GmsGeofenceReceiver.kt +78 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/gms/GmsLocationBackend.kt +176 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/gms/GmsProviderBundle.kt +62 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/gms/GmsReceiverSupport.kt +88 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/gms/GmsTasks.kt +55 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/hms/HmsActivityBackend.kt +71 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/hms/HmsActivityReceiver.kt +57 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/hms/HmsErrors.kt +76 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/hms/HmsGeofenceBackend.kt +116 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/hms/HmsGeofenceReceiver.kt +67 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/hms/HmsIntents.kt +83 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/hms/HmsLocationBackend.kt +197 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/hms/HmsProviderBundle.kt +55 -0
- package/android/src/main/java/com/brickssoft/locationtracking/provider/hms/HmsTasks.kt +44 -0
- package/android/src/main/java/com/brickssoft/locationtracking/record/RecordSink.kt +95 -0
- package/android/src/main/java/com/brickssoft/locationtracking/service/BootCountStore.kt +44 -0
- package/android/src/main/java/com/brickssoft/locationtracking/service/BootReceiver.kt +175 -0
- package/android/src/main/java/com/brickssoft/locationtracking/service/DefaultServiceController.kt +196 -0
- package/android/src/main/java/com/brickssoft/locationtracking/service/LocationTrackingService.kt +319 -0
- package/android/src/main/java/com/brickssoft/locationtracking/service/NotificationActionReceiver.kt +42 -0
- package/android/src/main/java/com/brickssoft/locationtracking/service/NotificationFactory.kt +338 -0
- package/android/src/main/java/com/brickssoft/locationtracking/service/ServiceCommands.kt +204 -0
- package/android/src/main/java/com/brickssoft/locationtracking/service/ServiceController.kt +22 -0
- package/android/src/main/java/com/brickssoft/locationtracking/service/ServiceDeps.kt +61 -0
- package/android/src/main/java/com/brickssoft/locationtracking/service/WhileInUseStartCheck.kt +115 -0
- package/android/src/main/java/com/brickssoft/locationtracking/settings/DefaultDeviceSettings.kt +79 -0
- package/android/src/main/java/com/brickssoft/locationtracking/settings/DeviceSettings.kt +21 -0
- package/android/src/main/java/com/brickssoft/locationtracking/settings/OemPowerManagers.kt +198 -0
- package/android/src/main/res/drawable/lt_ic_notification.xml +11 -0
- package/android/src/main/res/values/lt_strings.xml +11 -0
- package/android/src/main/res/xml/lt_file_paths.xml +5 -0
- package/dist/esm/definitions.d.ts +719 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/events.d.ts +50 -0
- package/dist/esm/events.js +52 -0
- package/dist/esm/events.js.map +1 -0
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/plugin.d.ts +3 -0
- package/dist/esm/plugin.js +10 -0
- package/dist/esm/plugin.js.map +1 -0
- package/dist/esm/web/config.d.ts +19 -0
- package/dist/esm/web/config.js +243 -0
- package/dist/esm/web/config.js.map +1 -0
- package/dist/esm/web/device.d.ts +9 -0
- package/dist/esm/web/device.js +90 -0
- package/dist/esm/web/device.js.map +1 -0
- package/dist/esm/web/errors.d.ts +14 -0
- package/dist/esm/web/errors.js +19 -0
- package/dist/esm/web/errors.js.map +1 -0
- package/dist/esm/web/geolocation.d.ts +26 -0
- package/dist/esm/web/geolocation.js +117 -0
- package/dist/esm/web/geolocation.js.map +1 -0
- package/dist/esm/web/location.d.ts +7 -0
- package/dist/esm/web/location.js +70 -0
- package/dist/esm/web/location.js.map +1 -0
- package/dist/esm/web.d.ts +164 -0
- package/dist/esm/web.js +328 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +945 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +948 -0
- package/dist/plugin.js.map +1 -0
- package/package.json +59 -4
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
package com.brickssoft.locationtracking.http
|
|
2
|
+
|
|
3
|
+
import com.brickssoft.locationtracking.config.HttpConfig
|
|
4
|
+
import com.brickssoft.locationtracking.model.Connectivity
|
|
5
|
+
import com.brickssoft.locationtracking.model.ConnectivityType
|
|
6
|
+
import com.brickssoft.locationtracking.model.RecordEvent
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Decides what an automatic upload pass sends (architecture §2, "Priority records"; round 2 §4, `http.syncInterval`).
|
|
10
|
+
* Manual `sync()` bypasses it.
|
|
11
|
+
*
|
|
12
|
+
* - No `http.url`, or no connectivity: nothing.
|
|
13
|
+
* - A queued priority record (heartbeat, tracking_start, tracking_stop, providerchange) forces an upload, ignoring
|
|
14
|
+
* `autoSync`, `autoSyncThreshold` and `syncInterval`: only priority records on cellular with
|
|
15
|
+
* `disableAutoSyncOnCellular`, otherwise the whole queue.
|
|
16
|
+
* - Otherwise nothing if `autoSync` is off or cellular is restricted. Then, by [IntervalCheck]:
|
|
17
|
+
* - [IntervalCheck.OFF] (`syncInterval = 0`, or tracking off): the whole queue once it holds at least
|
|
18
|
+
* `autoSyncThreshold` records (0 = every record).
|
|
19
|
+
* - `syncInterval > 0` while tracking: the whole queue once the oldest pending normal record is at least
|
|
20
|
+
* `syncInterval` seconds old (age = now − `recorded_at`; a negative age, which a wall clock set back produces,
|
|
21
|
+
* counts as due), or once the queue holds `autoSyncThreshold` records when that is above 0 (a size cap that
|
|
22
|
+
* uploads earlier); nothing while a retry after a failed upload is pending.
|
|
23
|
+
*/
|
|
24
|
+
internal object SyncPolicy {
|
|
25
|
+
/** The records an upload pass sends. */
|
|
26
|
+
enum class Scope {
|
|
27
|
+
NONE,
|
|
28
|
+
ALL,
|
|
29
|
+
PRIORITY_ONLY,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The state of the `syncInterval` rule for one pass (decided by the syncer). */
|
|
33
|
+
enum class IntervalCheck {
|
|
34
|
+
/** The rule does not apply (`syncInterval = 0`, `autoSync` off, or tracking off): threshold rule. */
|
|
35
|
+
OFF,
|
|
36
|
+
|
|
37
|
+
/** The oldest pending normal record is younger than `syncInterval`: only the size cap uploads. */
|
|
38
|
+
NOT_DUE,
|
|
39
|
+
|
|
40
|
+
/** The oldest pending normal record is due ([isIntervalDue]). */
|
|
41
|
+
DUE,
|
|
42
|
+
|
|
43
|
+
/** An automatic upload failed less than `syncInterval` ago: normal records wait for the retry time. */
|
|
44
|
+
RETRY_PENDING,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
val PRIORITY_EVENTS: Set<RecordEvent> = RecordEvent.entries.filter { it.isPriority }.toSet()
|
|
48
|
+
|
|
49
|
+
/** The events that `autoSync`, `autoSyncThreshold` and `syncInterval` apply to. */
|
|
50
|
+
val NORMAL_EVENTS: Set<RecordEvent> = RecordEvent.entries.filterNot { it.isPriority }.toSet()
|
|
51
|
+
|
|
52
|
+
private const val MS_PER_SECOND = 1_000L
|
|
53
|
+
|
|
54
|
+
fun hasUrl(http: HttpConfig): Boolean = !http.url.isNullOrBlank()
|
|
55
|
+
|
|
56
|
+
/** True if [connectivity] is cellular and `disableAutoSyncOnCellular` is set. */
|
|
57
|
+
fun isCellularRestricted(http: HttpConfig, connectivity: Connectivity): Boolean =
|
|
58
|
+
http.disableAutoSyncOnCellular && connectivity.type == ConnectivityType.CELLULAR
|
|
59
|
+
|
|
60
|
+
/** True if normal records follow `syncInterval` (it is above 0 and `autoSync` is on). */
|
|
61
|
+
fun usesInterval(http: HttpConfig): Boolean = http.autoSync && http.syncInterval > 0
|
|
62
|
+
|
|
63
|
+
/** `syncInterval` in milliseconds (the validator keeps it at 0 or above). */
|
|
64
|
+
fun intervalMs(http: HttpConfig): Long = http.syncInterval * MS_PER_SECOND
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* True if the oldest pending normal record, created at [oldestRecordedAt], is due for upload at [now]: its age
|
|
68
|
+
* is at least `syncInterval`, or negative (the wall clock was set back after it was created).
|
|
69
|
+
*/
|
|
70
|
+
fun isIntervalDue(http: HttpConfig, oldestRecordedAt: Long, now: Long): Boolean {
|
|
71
|
+
val age = now - oldestRecordedAt
|
|
72
|
+
return age < 0 || age >= intervalMs(http)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The wall time (epoch ms) at which the oldest pending normal record, created at [oldestRecordedAt], becomes due:
|
|
77
|
+
* `oldestRecordedAt + syncInterval`; null if it is already due at [now] (see [isIntervalDue]).
|
|
78
|
+
*/
|
|
79
|
+
fun intervalDueAt(http: HttpConfig, oldestRecordedAt: Long, now: Long): Long? =
|
|
80
|
+
if (isIntervalDue(http, oldestRecordedAt, now)) null else oldestRecordedAt + intervalMs(http)
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @param queued number of queued records.
|
|
84
|
+
* @param priorityQueued number of queued priority records.
|
|
85
|
+
* @param interval the state of the `syncInterval` rule; [IntervalCheck.OFF] applies the threshold rule.
|
|
86
|
+
*/
|
|
87
|
+
fun autoScope(
|
|
88
|
+
http: HttpConfig,
|
|
89
|
+
connectivity: Connectivity,
|
|
90
|
+
queued: Int,
|
|
91
|
+
priorityQueued: Int,
|
|
92
|
+
interval: IntervalCheck = IntervalCheck.OFF,
|
|
93
|
+
): Scope {
|
|
94
|
+
if (!hasUrl(http) || !connectivity.connected) return Scope.NONE
|
|
95
|
+
val restricted = isCellularRestricted(http, connectivity)
|
|
96
|
+
if (priorityQueued > 0) return if (restricted) Scope.PRIORITY_ONLY else Scope.ALL
|
|
97
|
+
if (restricted || !http.autoSync || queued <= 0) return Scope.NONE
|
|
98
|
+
val upload = when (interval) {
|
|
99
|
+
IntervalCheck.OFF -> queued >= maxOf(1, http.autoSyncThreshold)
|
|
100
|
+
IntervalCheck.NOT_DUE -> http.autoSyncThreshold > 0 && queued >= http.autoSyncThreshold
|
|
101
|
+
IntervalCheck.DUE -> true
|
|
102
|
+
IntervalCheck.RETRY_PENDING -> false
|
|
103
|
+
}
|
|
104
|
+
return if (upload) Scope.ALL else Scope.NONE
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Records per request: `maxBatchSize` (at least 1) when `batchSync`, else 1. */
|
|
108
|
+
fun chunkSize(http: HttpConfig): Int = if (http.batchSync) maxOf(1, http.maxBatchSize) else 1
|
|
109
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
package com.brickssoft.locationtracking.http
|
|
2
|
+
|
|
3
|
+
import com.brickssoft.locationtracking.config.HttpConfig
|
|
4
|
+
import com.brickssoft.locationtracking.core.Iso8601
|
|
5
|
+
import com.brickssoft.locationtracking.core.Logger
|
|
6
|
+
import com.brickssoft.locationtracking.model.JsonUtil
|
|
7
|
+
import com.brickssoft.locationtracking.model.ProviderStateJson
|
|
8
|
+
import com.brickssoft.locationtracking.model.Record
|
|
9
|
+
import com.brickssoft.locationtracking.model.RecordEvent
|
|
10
|
+
import com.brickssoft.locationtracking.model.RecordJson
|
|
11
|
+
import org.json.JSONArray
|
|
12
|
+
import org.json.JSONObject
|
|
13
|
+
import org.json.JSONTokener
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Renders `http.locationTemplate` / `http.geofenceTemplate` (architecture §2).
|
|
17
|
+
*
|
|
18
|
+
* Each `<%= name %>` placeholder (surrounding whitespace allowed) is replaced by a raw JSON literal: numbers and
|
|
19
|
+
* booleans bare, null as `null`, strings JSON-escaped but without quotes (templates write `"<%= uuid %>"`), and
|
|
20
|
+
* `extras` as JSON object text (`{}` when the record has none), and `record` as the default record object (with
|
|
21
|
+
* `sent_at`), so `"raw": <%= record %>` keeps every field, and `provider` as the record's provider object (or null).
|
|
22
|
+
* A null value in a placeholder that is exactly wrapped
|
|
23
|
+
* in quotes (`"<%= timestamp %>"`) replaces the quotes too, so it becomes JSON `null` rather than the string "null".
|
|
24
|
+
* An unknown placeholder becomes "" and logs a warning.
|
|
25
|
+
*/
|
|
26
|
+
internal object TemplateRenderer {
|
|
27
|
+
private const val TAG = "LT.Template"
|
|
28
|
+
private const val NULL = "null"
|
|
29
|
+
|
|
30
|
+
/** A placeholder, with the quote directly before and after it when present. */
|
|
31
|
+
private val PLACEHOLDER = Regex("""("?)<%=\s*(.*?)\s*%>("?)""", RegexOption.DOT_MATCHES_ALL)
|
|
32
|
+
|
|
33
|
+
/** JSON literal text per placeholder; a resolver returns null for a null value. */
|
|
34
|
+
private val RESOLVERS: Map<String, (Record, Long) -> String?> = linkedMapOf(
|
|
35
|
+
"uuid" to { r, _ -> string(r.uuid) },
|
|
36
|
+
"event" to { r, _ -> string(r.event.wire) },
|
|
37
|
+
"timestamp" to { r, _ -> r.location?.let { string(Iso8601.format(it.time)) } },
|
|
38
|
+
"recorded_at" to { r, _ -> string(Iso8601.format(r.recordedAt)) },
|
|
39
|
+
"sent_at" to { _, sentAt -> string(Iso8601.format(sentAt)) },
|
|
40
|
+
"latitude" to { r, _ -> number(r.location?.latitude) },
|
|
41
|
+
"longitude" to { r, _ -> number(r.location?.longitude) },
|
|
42
|
+
"accuracy" to { r, _ -> number(r.location?.accuracy) },
|
|
43
|
+
"altitude" to { r, _ -> number(r.location?.altitude) },
|
|
44
|
+
"altitude_accuracy" to { r, _ -> number(r.location?.altitudeAccuracy) },
|
|
45
|
+
"speed" to { r, _ -> number(r.location?.speed) },
|
|
46
|
+
"speed_accuracy" to { r, _ -> number(r.location?.speedAccuracy) },
|
|
47
|
+
"heading" to { r, _ -> number(r.location?.heading) },
|
|
48
|
+
"heading_accuracy" to { r, _ -> number(r.location?.headingAccuracy) },
|
|
49
|
+
"is_moving" to { r, _ -> r.isMoving.toString() },
|
|
50
|
+
"odometer" to { r, _ -> number(r.odometer) },
|
|
51
|
+
"mock" to { r, _ -> (r.location?.isMock ?: false).toString() },
|
|
52
|
+
"activity.type" to { r, _ -> string(r.activity.type.wire) },
|
|
53
|
+
"activity.confidence" to { r, _ -> r.activity.confidence.toString() },
|
|
54
|
+
"battery.level" to { r, _ -> number(r.battery.level) },
|
|
55
|
+
"battery.is_charging" to { r, _ -> r.battery.isCharging.toString() },
|
|
56
|
+
"elapsed_realtime_ms" to { r, _ -> r.elapsedRealtimeMs.toString() },
|
|
57
|
+
"boot_count" to { r, _ -> r.bootCount.toString() },
|
|
58
|
+
"backend" to { r, _ -> r.backend?.let { string(it.wire) } },
|
|
59
|
+
"reason" to { r, _ -> r.reason?.let { string(it) } },
|
|
60
|
+
"geofence.identifier" to { r, _ -> r.geofence?.let { string(it.identifier) } },
|
|
61
|
+
"geofence.action" to { r, _ -> r.geofence?.let { string(it.action.name) } },
|
|
62
|
+
"provider.enabled" to { r, _ -> r.provider?.enabled?.toString() },
|
|
63
|
+
"provider.gps" to { r, _ -> r.provider?.gps?.toString() },
|
|
64
|
+
"provider.network" to { r, _ -> r.provider?.network?.toString() },
|
|
65
|
+
"provider.permission" to { r, _ -> r.provider?.let { string(it.permission.wire) } },
|
|
66
|
+
"extras" to { r, _ -> JsonUtil.parseObject(r.extras)?.toString() ?: "{}" },
|
|
67
|
+
"record" to { r, sentAt -> RecordJson.toJson(r, sentAt).toString() },
|
|
68
|
+
"provider" to { r, _ -> r.provider?.let { ProviderStateJson.toJson(it).toString() } },
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
/** Every supported placeholder name. */
|
|
72
|
+
val PLACEHOLDERS: Set<String> get() = RESOLVERS.keys
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The template for [record]: `geofenceTemplate` for geofence records (falling back to `locationTemplate`),
|
|
76
|
+
* otherwise `locationTemplate`. Blank templates count as absent; null means the default shape.
|
|
77
|
+
*/
|
|
78
|
+
fun templateFor(record: Record, http: HttpConfig): String? {
|
|
79
|
+
val location = http.locationTemplate?.takeIf { it.isNotBlank() }
|
|
80
|
+
if (record.event == RecordEvent.GEOFENCE) return http.geofenceTemplate?.takeIf { it.isNotBlank() } ?: location
|
|
81
|
+
return location
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Renders [template] for [record] and parses the result. Returns a [JSONObject] or [JSONArray], or null (and
|
|
86
|
+
* logs an error) if the rendered text is not a valid JSON object or array; callers then use the default shape.
|
|
87
|
+
*/
|
|
88
|
+
fun render(template: String, record: Record, sentAt: Long): Any? {
|
|
89
|
+
val text = renderText(template, record, sentAt)
|
|
90
|
+
val value = if (StrictJson.isValid(text)) parse(text) else null
|
|
91
|
+
if (value == null) {
|
|
92
|
+
Logger.e(
|
|
93
|
+
TAG,
|
|
94
|
+
"template for ${record.event.wire} record ${record.uuid} did not render to a JSON object or array; " +
|
|
95
|
+
"using the default shape. Template: ${template.take(200)}",
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
return value
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Substitutes every placeholder of [template] (no JSON validation). */
|
|
102
|
+
fun renderText(template: String, record: Record, sentAt: Long): String {
|
|
103
|
+
val unknown = LinkedHashSet<String>()
|
|
104
|
+
val text = PLACEHOLDER.replace(template) { match ->
|
|
105
|
+
val (open, name, close) = match.destructured
|
|
106
|
+
val resolver = RESOLVERS[name]
|
|
107
|
+
if (resolver == null) {
|
|
108
|
+
unknown += name
|
|
109
|
+
return@replace open + close
|
|
110
|
+
}
|
|
111
|
+
val literal = resolver(record, sentAt)
|
|
112
|
+
when {
|
|
113
|
+
literal != null -> open + literal + close
|
|
114
|
+
open.isNotEmpty() && close.isNotEmpty() -> NULL
|
|
115
|
+
else -> open + NULL + close
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (unknown.isNotEmpty()) {
|
|
119
|
+
Logger.w(TAG, "unknown template placeholder(s) ${unknown.joinToString { "'$it'" }} replaced by \"\"")
|
|
120
|
+
}
|
|
121
|
+
return text
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** The JSON literal for placeholder [name] (`null` for a null value), or null if the name is unknown. */
|
|
125
|
+
fun literal(name: String, record: Record, sentAt: Long): String? {
|
|
126
|
+
val resolver = RESOLVERS[name] ?: return null
|
|
127
|
+
return resolver(record, sentAt) ?: NULL
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private fun parse(text: String): Any? = try {
|
|
131
|
+
JSONTokener(text).nextValue().takeIf { it is JSONObject || it is JSONArray }
|
|
132
|
+
} catch (e: Exception) {
|
|
133
|
+
// e.g. a number out of range (org.json rejects infinities)
|
|
134
|
+
null
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** JSON string-content escaping, without the surrounding quotes. */
|
|
138
|
+
private fun string(value: String): String = JSONObject.quote(value).let { it.substring(1, it.length - 1) }
|
|
139
|
+
|
|
140
|
+
private fun number(value: Double?): String? = value?.takeIf { !it.isNaN() && !it.isInfinite() }?.toString()
|
|
141
|
+
|
|
142
|
+
private fun number(value: Float?): String? = number(value?.let { JsonUtil.floatToJson(it) })
|
|
143
|
+
}
|