@bricks-soft/capacitor-location-tracking 0.0.0-stage → 8.0.1
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 +107 -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 +60 -4
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
package com.brickssoft.locationtracking.bridge
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.content.ActivityNotFoundException
|
|
5
|
+
import android.content.Intent
|
|
6
|
+
import com.brickssoft.locationtracking.config.ConfigJson
|
|
7
|
+
import com.brickssoft.locationtracking.config.State
|
|
8
|
+
import com.brickssoft.locationtracking.core.Components
|
|
9
|
+
import com.brickssoft.locationtracking.core.ErrorCode
|
|
10
|
+
import com.brickssoft.locationtracking.core.Logger
|
|
11
|
+
import com.brickssoft.locationtracking.core.Subscription
|
|
12
|
+
import com.brickssoft.locationtracking.core.TrackingException
|
|
13
|
+
import com.brickssoft.locationtracking.model.BatteryOptimizationStatus
|
|
14
|
+
import com.brickssoft.locationtracking.model.BatteryOptimizationStatusJson
|
|
15
|
+
import com.brickssoft.locationtracking.model.DeviceInfoJson
|
|
16
|
+
import com.brickssoft.locationtracking.model.EventJson
|
|
17
|
+
import com.brickssoft.locationtracking.model.GeofenceJson
|
|
18
|
+
import com.brickssoft.locationtracking.model.HeartbeatStatusJson
|
|
19
|
+
import com.brickssoft.locationtracking.model.PermissionStatusJson
|
|
20
|
+
import com.brickssoft.locationtracking.model.PowerManagerInfoJson
|
|
21
|
+
import com.brickssoft.locationtracking.model.ProviderStateJson
|
|
22
|
+
import com.brickssoft.locationtracking.model.Record
|
|
23
|
+
import com.brickssoft.locationtracking.model.RecordJson
|
|
24
|
+
import com.brickssoft.locationtracking.model.SensorsJson
|
|
25
|
+
import com.brickssoft.locationtracking.permission.PermissionHost
|
|
26
|
+
import com.brickssoft.locationtracking.permission.PermissionState
|
|
27
|
+
import com.brickssoft.locationtracking.permission.PermissionType
|
|
28
|
+
import kotlinx.coroutines.CancellationException
|
|
29
|
+
import kotlinx.coroutines.suspendCancellableCoroutine
|
|
30
|
+
import kotlinx.coroutines.sync.Mutex
|
|
31
|
+
import kotlinx.coroutines.sync.withLock
|
|
32
|
+
import kotlinx.coroutines.withContext
|
|
33
|
+
import org.json.JSONArray
|
|
34
|
+
import org.json.JSONException
|
|
35
|
+
import org.json.JSONObject
|
|
36
|
+
import java.util.concurrent.atomic.AtomicBoolean
|
|
37
|
+
import kotlin.coroutines.resume
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The logic of every `LocationTracking` plugin method, testable without a Capacitor bridge: options come in as the
|
|
41
|
+
* call's JSONObject, results go out as the JS shape (null = `void`), and failures are thrown as
|
|
42
|
+
* [TrackingException] (the plugin maps them to `call.reject(message, code.name)`).
|
|
43
|
+
*
|
|
44
|
+
* Every method except [ALLOWED_BEFORE_READY] throws NOT_READY until [ready] has resolved in this process.
|
|
45
|
+
* Suspend methods run on the caller's coroutine (the plugin uses `Components.scope`); UI work (settings screens,
|
|
46
|
+
* permission requests, the email chooser) is moved to `dispatchers.main`.
|
|
47
|
+
*
|
|
48
|
+
* @param activityProvider the plugin's current activity, if any.
|
|
49
|
+
* @param readyFlag set once `ready()` resolved; process-wide by default.
|
|
50
|
+
*/
|
|
51
|
+
internal class PluginHandlers(
|
|
52
|
+
private val services: BridgeServices,
|
|
53
|
+
private val activityProvider: () -> Activity? = { null },
|
|
54
|
+
private val readyFlag: AtomicBoolean = processReady,
|
|
55
|
+
val watches: WatchRegistry = WatchRegistry(),
|
|
56
|
+
) {
|
|
57
|
+
constructor(components: Components) : this(ComponentServices(components))
|
|
58
|
+
|
|
59
|
+
/** Serializes permission requests (see [requestPermissions]). */
|
|
60
|
+
private val permissionRequests = Mutex()
|
|
61
|
+
|
|
62
|
+
/** True once `ready()` has resolved (in this process, unless a custom flag was injected). */
|
|
63
|
+
val isReady: Boolean get() = readyFlag.get()
|
|
64
|
+
|
|
65
|
+
// ---- lifecycle and config
|
|
66
|
+
|
|
67
|
+
/** `ready({config?, reset = true})` -> State. */
|
|
68
|
+
suspend fun ready(options: JSONObject): JSONObject {
|
|
69
|
+
gate("ready")
|
|
70
|
+
val config = OptionParsers.optObject(options, "config")
|
|
71
|
+
val reset = OptionParsers.optBoolean(options, "reset") ?: true
|
|
72
|
+
val state = services.engine.ready(config, reset)
|
|
73
|
+
readyFlag.set(true)
|
|
74
|
+
return stateJson(state)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** `setConfig({config})` -> State. */
|
|
78
|
+
suspend fun setConfig(options: JSONObject): JSONObject {
|
|
79
|
+
gate("setConfig")
|
|
80
|
+
return stateJson(services.engine.setConfig(OptionParsers.requireObject(options, "config")))
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** `reset({config?})` -> State. */
|
|
84
|
+
suspend fun reset(options: JSONObject): JSONObject {
|
|
85
|
+
gate("reset")
|
|
86
|
+
return stateJson(services.engine.reset(OptionParsers.optObject(options, "config")))
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
fun getState(): JSONObject {
|
|
90
|
+
gate("getState")
|
|
91
|
+
return stateJson(services.engine.state())
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
suspend fun start(): JSONObject {
|
|
95
|
+
gate("start")
|
|
96
|
+
return stateJson(services.engine.start())
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
suspend fun startGeofences(): JSONObject {
|
|
100
|
+
gate("startGeofences")
|
|
101
|
+
return stateJson(services.engine.startGeofences())
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
suspend fun stop(): JSONObject {
|
|
105
|
+
gate("stop")
|
|
106
|
+
return stateJson(services.engine.stop())
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** `changePace({isMoving})` -> void. */
|
|
110
|
+
suspend fun changePace(options: JSONObject): JSONObject? {
|
|
111
|
+
gate("changePace")
|
|
112
|
+
services.engine.changePace(OptionParsers.requireBoolean(options, "isMoving"))
|
|
113
|
+
return null
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ---- positions
|
|
117
|
+
|
|
118
|
+
/** `getCurrentPosition(options?)` -> Location. */
|
|
119
|
+
suspend fun getCurrentPosition(options: JSONObject): JSONObject {
|
|
120
|
+
gate("getCurrentPosition")
|
|
121
|
+
val record = services.positions.getCurrentPosition(OptionParsers.currentPositionOptions(options))
|
|
122
|
+
return RecordJson.toJson(record)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* `watchPosition(options, callback)`: registers [target] under [id] (the call's callback id) and starts the
|
|
127
|
+
* watch. Locations and errors go to [target] until [clearWatch] or [releaseAllWatches].
|
|
128
|
+
*/
|
|
129
|
+
fun watchPosition(id: String, options: JSONObject, target: WatchTarget) {
|
|
130
|
+
gate("watchPosition")
|
|
131
|
+
if (id.isBlank()) OptionParsers.invalid("watchPosition needs a callback id")
|
|
132
|
+
val watchOptions = OptionParsers.watchPositionOptions(options)
|
|
133
|
+
watches.add(id, target)
|
|
134
|
+
try {
|
|
135
|
+
services.positions.watchPosition(id, watchOptions) { record, error -> deliverWatch(id, record, error) }
|
|
136
|
+
} catch (e: Exception) {
|
|
137
|
+
watches.release(id)
|
|
138
|
+
throw e
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* `clearWatch({id})` -> void; unknown ids are ignored. The position watch is stopped first; the JS callback is
|
|
144
|
+
* released even if that fails.
|
|
145
|
+
*/
|
|
146
|
+
fun clearWatch(options: JSONObject): JSONObject? {
|
|
147
|
+
gate("clearWatch")
|
|
148
|
+
val id = OptionParsers.requireString(options, "id")
|
|
149
|
+
try {
|
|
150
|
+
services.positions.clearWatch(id)
|
|
151
|
+
} finally {
|
|
152
|
+
watches.release(id)
|
|
153
|
+
}
|
|
154
|
+
return null
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Releases every watch of this plugin instance (handleOnDestroy). */
|
|
158
|
+
fun releaseAllWatches() {
|
|
159
|
+
val ids = watches.releaseAll()
|
|
160
|
+
if (ids.isEmpty()) return
|
|
161
|
+
val positions = services.positions
|
|
162
|
+
for (id in ids) {
|
|
163
|
+
try {
|
|
164
|
+
positions.clearWatch(id)
|
|
165
|
+
} catch (e: Exception) {
|
|
166
|
+
Logger.w(TAG, "clearWatch($id) failed", e)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private fun deliverWatch(id: String, record: Record?, error: TrackingException?) {
|
|
172
|
+
val target = watches[id] ?: return
|
|
173
|
+
try {
|
|
174
|
+
when {
|
|
175
|
+
record != null -> target.deliver(RecordJson.toJson(record))
|
|
176
|
+
error != null -> target.fail(error.code, error.message ?: error.code.name)
|
|
177
|
+
}
|
|
178
|
+
} catch (e: Exception) {
|
|
179
|
+
Logger.e(TAG, "watch $id delivery failed", e)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ---- odometer
|
|
184
|
+
|
|
185
|
+
fun getOdometer(): JSONObject {
|
|
186
|
+
gate("getOdometer")
|
|
187
|
+
return odometerJson()
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** `setOdometer({odometer})`: meters, >= 0. */
|
|
191
|
+
fun setOdometer(options: JSONObject): JSONObject {
|
|
192
|
+
gate("setOdometer")
|
|
193
|
+
val value = OptionParsers.requireNumber(options, "odometer")
|
|
194
|
+
if (value < 0) OptionParsers.invalid("'odometer' must be >= 0")
|
|
195
|
+
services.odometer.set(value)
|
|
196
|
+
return odometerJson()
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
fun resetOdometer(): JSONObject {
|
|
200
|
+
gate("resetOdometer")
|
|
201
|
+
services.odometer.reset()
|
|
202
|
+
return odometerJson()
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private fun odometerJson(): JSONObject = JSONObject().put("odometer", services.odometer.value)
|
|
206
|
+
|
|
207
|
+
// ---- records
|
|
208
|
+
|
|
209
|
+
/** `getLocations({limit?})` -> `{locations}`, oldest first; no limit = all. */
|
|
210
|
+
suspend fun getLocations(options: JSONObject): JSONObject {
|
|
211
|
+
gate("getLocations")
|
|
212
|
+
val limit = OptionParsers.optInt(options, "limit", min = 0) ?: -1
|
|
213
|
+
return JSONObject().put("locations", RecordJson.toJsonArray(services.locationStore.list(limit)))
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
suspend fun getCount(): JSONObject {
|
|
217
|
+
gate("getCount")
|
|
218
|
+
return JSONObject().put("count", services.locationStore.count())
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* `insertLocation({location})` -> `{uuid}`. The record is queued (and handed to the upload policy) like any
|
|
223
|
+
* other record, but it does not count as tracking activity: no event, no runtime or heartbeat update.
|
|
224
|
+
* Round 2: after a successful insert the record goes to `recordHooks` (native companion listeners receive it
|
|
225
|
+
* in `onRecord`), because inserted records do not pass through the record sink. A failed insert rejects the
|
|
226
|
+
* call and is not dispatched: the caller receives the error and may retry.
|
|
227
|
+
*/
|
|
228
|
+
suspend fun insertLocation(options: JSONObject): JSONObject {
|
|
229
|
+
gate("insertLocation")
|
|
230
|
+
val input = OptionParsers.insertLocationInput(options)
|
|
231
|
+
val record = try {
|
|
232
|
+
services.recordFactory.fromExternal(input)
|
|
233
|
+
} catch (e: JSONException) {
|
|
234
|
+
throw TrackingException(ErrorCode.INVALID_ARGUMENT, e.message ?: "invalid location", e)
|
|
235
|
+
} catch (e: IllegalArgumentException) {
|
|
236
|
+
throw TrackingException(ErrorCode.INVALID_ARGUMENT, e.message ?: "invalid location", e)
|
|
237
|
+
}
|
|
238
|
+
services.locationStore.insert(record)
|
|
239
|
+
services.recordHooks.dispatch(record)
|
|
240
|
+
services.syncer.onRecordInserted(record)
|
|
241
|
+
return JSONObject().put("uuid", record.uuid)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
suspend fun destroyLocations(): JSONObject {
|
|
245
|
+
gate("destroyLocations")
|
|
246
|
+
return JSONObject().put("count", services.locationStore.deleteAll())
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** `destroyLocation({uuid})` -> `{deleted}`. */
|
|
250
|
+
suspend fun destroyLocation(options: JSONObject): JSONObject {
|
|
251
|
+
gate("destroyLocation")
|
|
252
|
+
val uuid = OptionParsers.requireString(options, "uuid")
|
|
253
|
+
return JSONObject().put("deleted", services.locationStore.delete(listOf(uuid)) > 0)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** `sync()` -> `{locations}`: the uploaded records. */
|
|
257
|
+
suspend fun sync(): JSONObject {
|
|
258
|
+
gate("sync")
|
|
259
|
+
return JSONObject().put("locations", RecordJson.toJsonArray(services.syncer.sync()))
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// ---- geofences
|
|
263
|
+
|
|
264
|
+
suspend fun addGeofence(options: JSONObject): JSONObject? {
|
|
265
|
+
gate("addGeofence")
|
|
266
|
+
val spec = OptionParsers.geofence(OptionParsers.requireObject(options, "geofence"))
|
|
267
|
+
services.geofences.add(listOf(spec))
|
|
268
|
+
return null
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** `addGeofences({geofences})`; an empty array is a no-op. */
|
|
272
|
+
suspend fun addGeofences(options: JSONObject): JSONObject? {
|
|
273
|
+
gate("addGeofences")
|
|
274
|
+
val specs = OptionParsers.geofences(options)
|
|
275
|
+
if (specs.isNotEmpty()) services.geofences.add(specs)
|
|
276
|
+
return null
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
suspend fun removeGeofence(options: JSONObject): JSONObject? {
|
|
280
|
+
gate("removeGeofence")
|
|
281
|
+
services.geofences.remove(listOf(OptionParsers.requireString(options, "identifier")))
|
|
282
|
+
return null
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* `removeGeofences({identifiers?})`: no `identifiers` (missing or null) removes all; an empty array removes
|
|
287
|
+
* nothing, so a computed list that happens to be empty never wipes every geofence.
|
|
288
|
+
*/
|
|
289
|
+
suspend fun removeGeofences(options: JSONObject): JSONObject? {
|
|
290
|
+
gate("removeGeofences")
|
|
291
|
+
val array = OptionParsers.optArray(options, "identifiers")
|
|
292
|
+
when {
|
|
293
|
+
array == null -> services.geofences.removeAll()
|
|
294
|
+
array.length() > 0 -> services.geofences.remove(identifiers(array))
|
|
295
|
+
}
|
|
296
|
+
return null
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
suspend fun getGeofences(): JSONObject {
|
|
300
|
+
gate("getGeofences")
|
|
301
|
+
return JSONObject().put("geofences", GeofenceJson.toJsonArray(services.geofences.list()))
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** `getGeofence({identifier})` -> `{geofence}` (null if unknown). */
|
|
305
|
+
suspend fun getGeofence(options: JSONObject): JSONObject {
|
|
306
|
+
gate("getGeofence")
|
|
307
|
+
val spec = services.geofences.get(OptionParsers.requireString(options, "identifier"))
|
|
308
|
+
return JSONObject().put("geofence", spec?.let { GeofenceJson.toJson(it) } ?: JSONObject.NULL)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
suspend fun geofenceExists(options: JSONObject): JSONObject {
|
|
312
|
+
gate("geofenceExists")
|
|
313
|
+
val spec = services.geofences.get(OptionParsers.requireString(options, "identifier"))
|
|
314
|
+
return JSONObject().put("exists", spec != null)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
private fun identifiers(array: JSONArray): List<String> = (0 until array.length()).map { i ->
|
|
318
|
+
(array.opt(i) as? String)?.takeIf { it.isNotBlank() }
|
|
319
|
+
?: OptionParsers.invalid("identifiers[$i] must be a non-empty string")
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// ---- heartbeat and device
|
|
323
|
+
|
|
324
|
+
suspend fun getHeartbeatStatus(): JSONObject {
|
|
325
|
+
gate("getHeartbeatStatus")
|
|
326
|
+
return HeartbeatStatusJson.toJson(services.heartbeat.status())
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
fun getProviderState(): JSONObject {
|
|
330
|
+
gate("getProviderState")
|
|
331
|
+
return ProviderStateJson.toJson(services.device.providerState())
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
fun isPowerSaveMode(): JSONObject {
|
|
335
|
+
gate("isPowerSaveMode")
|
|
336
|
+
return JSONObject().put("isPowerSaveMode", services.device.isPowerSaveMode())
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
fun getBatteryOptimizationStatus(): JSONObject {
|
|
340
|
+
gate("getBatteryOptimizationStatus")
|
|
341
|
+
val device = services.device
|
|
342
|
+
return BatteryOptimizationStatusJson.toJson(
|
|
343
|
+
BatteryOptimizationStatus(
|
|
344
|
+
isIgnoringBatteryOptimizations = device.isIgnoringBatteryOptimizations(),
|
|
345
|
+
canScheduleExactAlarms = device.canScheduleExactAlarms(),
|
|
346
|
+
isDeviceIdleMode = device.isDeviceIdleMode(),
|
|
347
|
+
),
|
|
348
|
+
)
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
fun getPowerManagerInfo(): JSONObject {
|
|
352
|
+
gate("getPowerManagerInfo")
|
|
353
|
+
return PowerManagerInfoJson.toJson(services.deviceSettings.powerManagerInfo())
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
fun getDeviceInfo(): JSONObject {
|
|
357
|
+
gate("getDeviceInfo")
|
|
358
|
+
return DeviceInfoJson.toJson(services.deviceInfo.deviceInfo())
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
fun getSensors(): JSONObject {
|
|
362
|
+
gate("getSensors")
|
|
363
|
+
return SensorsJson.toJson(services.deviceInfo.sensors())
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// ---- settings screens (main thread) -> {opened}
|
|
367
|
+
|
|
368
|
+
suspend fun openBatteryOptimizationSettings(): JSONObject =
|
|
369
|
+
openSettings("openBatteryOptimizationSettings") { services.deviceSettings.openBatteryOptimizationSettings(it) }
|
|
370
|
+
|
|
371
|
+
suspend fun openPowerManagerSettings(): JSONObject =
|
|
372
|
+
openSettings("openPowerManagerSettings") { services.deviceSettings.openPowerManagerSettings(it) }
|
|
373
|
+
|
|
374
|
+
suspend fun openLocationSettings(): JSONObject =
|
|
375
|
+
openSettings("openLocationSettings") { services.deviceSettings.openLocationSettings(it) }
|
|
376
|
+
|
|
377
|
+
suspend fun openAppSettings(): JSONObject =
|
|
378
|
+
openSettings("openAppSettings") { services.deviceSettings.openAppSettings(it) }
|
|
379
|
+
|
|
380
|
+
private suspend fun openSettings(method: String, open: (Activity?) -> Boolean): JSONObject {
|
|
381
|
+
gate(method)
|
|
382
|
+
val opened = withContext(services.dispatchers.main) { open(activity()) }
|
|
383
|
+
return JSONObject().put("opened", opened)
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// ---- permissions -> PermissionStatus
|
|
387
|
+
|
|
388
|
+
fun checkPermissions(): JSONObject {
|
|
389
|
+
gate("checkPermissions")
|
|
390
|
+
return permissionStatusJson(emptyMap())
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* `requestPermissions({permissions?})`: runs `PermissionManager.request` on the main thread through [host] and
|
|
395
|
+
* resolves with the resulting status (entries reported by the request win over `permissions.status()`).
|
|
396
|
+
* Requests are serialized: Android answers an overlapping request at once with an empty result, and Capacitor
|
|
397
|
+
* matches results to calls in FIFO order, so concurrent requests would receive each other's results.
|
|
398
|
+
*/
|
|
399
|
+
suspend fun requestPermissions(options: JSONObject, host: PermissionHost): JSONObject {
|
|
400
|
+
gate("requestPermissions")
|
|
401
|
+
val types = OptionParsers.permissionTypes(options)
|
|
402
|
+
if (types.isEmpty()) return permissionStatusJson(emptyMap())
|
|
403
|
+
val result = permissionRequests.withLock {
|
|
404
|
+
val rationale = services.configStore.config.value.backgroundPermissionRationale
|
|
405
|
+
withContext(services.dispatchers.main) {
|
|
406
|
+
suspendCancellableCoroutine { continuation ->
|
|
407
|
+
val settled = AtomicBoolean(false)
|
|
408
|
+
services.permissions.request(host, types, rationale) { status ->
|
|
409
|
+
if (settled.compareAndSet(false, true)) continuation.resume(status)
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return permissionStatusJson(result)
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
private fun permissionStatusJson(overrides: Map<PermissionType, PermissionState>): JSONObject =
|
|
418
|
+
PermissionStatusJson.toJson(services.permissions.status() + overrides)
|
|
419
|
+
|
|
420
|
+
// ---- log
|
|
421
|
+
|
|
422
|
+
/** `log({level, message})` -> void; written through [Logger] (logcat and the log file). */
|
|
423
|
+
fun log(options: JSONObject): JSONObject? {
|
|
424
|
+
gate("log")
|
|
425
|
+
val level = OptionParsers.logLevel(options)
|
|
426
|
+
val message = OptionParsers.optString(options, "message") ?: OptionParsers.invalid("'message' is required")
|
|
427
|
+
Logger.log(level, JS_TAG, message)
|
|
428
|
+
return null
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
suspend fun getLog(options: JSONObject): JSONObject {
|
|
432
|
+
gate("getLog")
|
|
433
|
+
return JSONObject().put("log", services.logStore.read(OptionParsers.logQuery(options)))
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
suspend fun destroyLog(): JSONObject? {
|
|
437
|
+
gate("destroyLog")
|
|
438
|
+
services.logStore.destroy()
|
|
439
|
+
return null
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/** `uploadLog({url, headers?, params?})` -> `{success, status}`. */
|
|
443
|
+
suspend fun uploadLog(options: JSONObject): JSONObject {
|
|
444
|
+
gate("uploadLog")
|
|
445
|
+
val o = OptionParsers.uploadLogOptions(options)
|
|
446
|
+
val result = services.logStore.upload(o.url, o.headers, o.paramsJson)
|
|
447
|
+
return JSONObject().put("success", result.success).put("status", result.status)
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/** `emailLog({email, subject?})` -> void: opens a chooser with the log attached; NO_ACTIVITY without UI. */
|
|
451
|
+
suspend fun emailLog(options: JSONObject): JSONObject? {
|
|
452
|
+
gate("emailLog")
|
|
453
|
+
val email = OptionParsers.requireString(options, "email")
|
|
454
|
+
val subject = OptionParsers.optString(options, "subject")
|
|
455
|
+
requireActivity()
|
|
456
|
+
val intent = services.logStore.prepareEmail(email, subject)
|
|
457
|
+
withContext(services.dispatchers.main) {
|
|
458
|
+
val chooser = Intent.createChooser(intent, subject?.takeIf { it.isNotBlank() } ?: EMAIL_CHOOSER_TITLE)
|
|
459
|
+
try {
|
|
460
|
+
requireActivity().startActivity(chooser)
|
|
461
|
+
} catch (e: ActivityNotFoundException) {
|
|
462
|
+
throw TrackingException(ErrorCode.UNAVAILABLE, "No app can send the log", e)
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return null
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// ---- events and lifecycle
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Forwards every bus event to [notify] as (JS event name, payload builder); cancel the result to stop. The
|
|
472
|
+
* payload is built only when [notify] calls the builder, so an event without JS listeners costs nothing and the
|
|
473
|
+
* emitting (engine) thread never serializes records.
|
|
474
|
+
*/
|
|
475
|
+
fun forwardEvents(notify: (name: String, payload: () -> JSONObject) -> Unit): Subscription =
|
|
476
|
+
services.events.subscribe { event ->
|
|
477
|
+
try {
|
|
478
|
+
notify(EventJson.name(event)) { EventJson.payload(event) }
|
|
479
|
+
} catch (e: Exception) {
|
|
480
|
+
Logger.e(TAG, "failed to forward ${event::class.simpleName}", e)
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/** handleOnResume: re-checks the provider state (catches changes made while the app was in the background). */
|
|
485
|
+
suspend fun onResume() {
|
|
486
|
+
try {
|
|
487
|
+
services.device.checkProviderState(RESUME_REASON)
|
|
488
|
+
} catch (e: CancellationException) {
|
|
489
|
+
throw e
|
|
490
|
+
} catch (e: Exception) {
|
|
491
|
+
Logger.w(TAG, "provider state check on resume failed", e)
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// ---- helpers
|
|
496
|
+
|
|
497
|
+
private fun gate(method: String) {
|
|
498
|
+
if (method !in ALLOWED_BEFORE_READY && !readyFlag.get()) {
|
|
499
|
+
throw TrackingException(ErrorCode.NOT_READY, "$method() requires ready() to have resolved first")
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
private fun stateJson(state: State): JSONObject = ConfigJson.stateToJson(state)
|
|
504
|
+
|
|
505
|
+
private fun activity(): Activity? = activityProvider()?.takeUnless { it.isFinishing || it.isDestroyed }
|
|
506
|
+
|
|
507
|
+
private fun requireActivity(): Activity =
|
|
508
|
+
activity() ?: throw TrackingException(ErrorCode.NO_ACTIVITY, "No activity is attached to the plugin")
|
|
509
|
+
|
|
510
|
+
companion object {
|
|
511
|
+
private const val TAG = "LT.Bridge"
|
|
512
|
+
|
|
513
|
+
/** Log tag of messages written by JS `log()`. */
|
|
514
|
+
const val JS_TAG = "LT.JS"
|
|
515
|
+
|
|
516
|
+
/** The `checkProviderState` reason used on handleOnResume. */
|
|
517
|
+
const val RESUME_REASON = "resume"
|
|
518
|
+
|
|
519
|
+
const val EMAIL_CHOOSER_TITLE = "Send location tracking log"
|
|
520
|
+
|
|
521
|
+
/** Methods that work before `ready()` (architecture §1, NOT_READY rule). */
|
|
522
|
+
val ALLOWED_BEFORE_READY: Set<String> = setOf(
|
|
523
|
+
"ready",
|
|
524
|
+
"getState",
|
|
525
|
+
"checkPermissions",
|
|
526
|
+
"requestPermissions",
|
|
527
|
+
"getDeviceInfo",
|
|
528
|
+
"getSensors",
|
|
529
|
+
"log",
|
|
530
|
+
"getLog",
|
|
531
|
+
"getProviderState",
|
|
532
|
+
"openBatteryOptimizationSettings",
|
|
533
|
+
"openPowerManagerSettings",
|
|
534
|
+
"openLocationSettings",
|
|
535
|
+
"openAppSettings",
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
/** Process-wide "ready() resolved" flag. */
|
|
539
|
+
internal val processReady = AtomicBoolean(false)
|
|
540
|
+
}
|
|
541
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
package com.brickssoft.locationtracking.bridge
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import com.brickssoft.locationtracking.core.Logger
|
|
5
|
+
import com.brickssoft.locationtracking.permission.PermissionHost
|
|
6
|
+
import com.brickssoft.locationtracking.permission.PermissionType
|
|
7
|
+
import com.getcapacitor.Plugin
|
|
8
|
+
import com.getcapacitor.annotation.CapacitorPlugin
|
|
9
|
+
import com.getcapacitor.util.PermissionHelper
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Starts a Capacitor permission request for plugin permission aliases and calls `onDone` from the plugin's
|
|
13
|
+
* `@PermissionCallback`. Implemented by `LocationTrackingPlugin` per call, because `requestPermissionForAliases`
|
|
14
|
+
* is protected.
|
|
15
|
+
*/
|
|
16
|
+
fun interface PermissionRequestLauncher {
|
|
17
|
+
fun launch(aliases: List<String>, onDone: () -> Unit)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* [PermissionHost] on top of the Capacitor plugin.
|
|
22
|
+
*
|
|
23
|
+
* [requestAliases] forwards the known aliases whose Android permissions are declared in the app manifest to
|
|
24
|
+
* [launcher]. It completes immediately (nothing to ask) when no alias is left, when there is no [launcher], or when
|
|
25
|
+
* the launch fails; otherwise [launcher] calls `onDone` from the permission callback, on the main thread.
|
|
26
|
+
* Undeclared permissions are skipped because Capacitor would reject the call without ever calling back.
|
|
27
|
+
*
|
|
28
|
+
* @param isRequestable whether an alias can be requested; default: all its permissions are in the app manifest.
|
|
29
|
+
*/
|
|
30
|
+
class PluginPermissionHost(
|
|
31
|
+
private val plugin: Plugin,
|
|
32
|
+
private val launcher: PermissionRequestLauncher? = null,
|
|
33
|
+
private val isRequestable: (alias: String) -> Boolean = { declaredInManifest(plugin, it) },
|
|
34
|
+
) : PermissionHost {
|
|
35
|
+
/** The plugin's activity, or null without a bridge or while it is finishing or destroyed. */
|
|
36
|
+
override val activity: Activity? get() = usableActivity(plugin)
|
|
37
|
+
|
|
38
|
+
override fun requestAliases(aliases: List<String>, onDone: () -> Unit) {
|
|
39
|
+
val known = aliases.filter { PermissionType.fromAlias(it) != null }.distinct()
|
|
40
|
+
if (known.size != aliases.distinct().size) Logger.w(TAG, "ignoring unknown permission aliases in $aliases")
|
|
41
|
+
val requestable = known.filter(isRequestable)
|
|
42
|
+
if (requestable.size != known.size) {
|
|
43
|
+
Logger.w(TAG, "not requesting ${known - requestable.toSet()}: permissions missing from the app manifest")
|
|
44
|
+
}
|
|
45
|
+
val l = launcher
|
|
46
|
+
if (requestable.isEmpty() || l == null) {
|
|
47
|
+
onDone()
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
l.launch(requestable, onDone)
|
|
52
|
+
} catch (e: Exception) {
|
|
53
|
+
Logger.e(TAG, "permission request for $requestable failed", e)
|
|
54
|
+
onDone()
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
internal companion object {
|
|
59
|
+
private const val TAG = "LT.PermissionHost"
|
|
60
|
+
|
|
61
|
+
/** The plugin's activity, or null without a bridge or while it is finishing or destroyed. */
|
|
62
|
+
fun usableActivity(plugin: Plugin): Activity? =
|
|
63
|
+
runCatching { plugin.activity }.getOrNull()?.takeUnless { it.isFinishing || it.isDestroyed }
|
|
64
|
+
|
|
65
|
+
/** True if every Android permission of [alias] (per the `@CapacitorPlugin` annotation) is in the manifest. */
|
|
66
|
+
fun declaredInManifest(plugin: Plugin, alias: String): Boolean {
|
|
67
|
+
val strings = plugin.javaClass.getAnnotation(CapacitorPlugin::class.java)
|
|
68
|
+
?.permissions
|
|
69
|
+
?.firstOrNull { it.alias == alias }
|
|
70
|
+
?.strings
|
|
71
|
+
?: return false
|
|
72
|
+
val context = runCatching { plugin.context }.getOrNull() ?: return false
|
|
73
|
+
return PermissionHelper.hasDefinedPermissions(context, strings)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|