@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
package/android/src/main/java/com/brickssoft/locationtracking/device/DefaultDeviceInfoProvider.kt
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
package com.brickssoft.locationtracking.device
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.hardware.Sensor
|
|
5
|
+
import android.hardware.SensorManager
|
|
6
|
+
import android.os.Build
|
|
7
|
+
import androidx.core.content.ContextCompat
|
|
8
|
+
import com.brickssoft.locationtracking.BuildConfig
|
|
9
|
+
import com.brickssoft.locationtracking.core.Logger
|
|
10
|
+
import com.brickssoft.locationtracking.model.DeviceInfo
|
|
11
|
+
import com.brickssoft.locationtracking.model.ProviderKind
|
|
12
|
+
import com.brickssoft.locationtracking.model.Sensors
|
|
13
|
+
import com.brickssoft.locationtracking.provider.DefaultProviderFactory
|
|
14
|
+
import com.brickssoft.locationtracking.provider.ProviderFactory
|
|
15
|
+
import com.brickssoft.locationtracking.provider.ProviderPackaging
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Default [DeviceInfoProvider]: build properties, plugin version, GMS/HMS availability and the active backend
|
|
19
|
+
* (from [providers]), the providers packaged in the APK ([ProviderPackaging]), and which motion sensors the device has.
|
|
20
|
+
* If the provider factory fails, both SDKs read as unavailable and the backend as `android` (the fallback).
|
|
21
|
+
*
|
|
22
|
+
* The constructor does not touch [providers].
|
|
23
|
+
*/
|
|
24
|
+
class DefaultDeviceInfoProvider(
|
|
25
|
+
private val context: Context,
|
|
26
|
+
private val providers: Lazy<ProviderFactory>,
|
|
27
|
+
classPresent: (String) -> Boolean = DefaultProviderFactory.reflectiveClassPresent(context.classLoader),
|
|
28
|
+
receiverDeclared: (String) -> Boolean = ProviderPackaging.receiverDeclaredIn(context),
|
|
29
|
+
providersMetaData: () -> String? = ProviderPackaging.providersMetaDataIn(context),
|
|
30
|
+
) : DeviceInfoProvider {
|
|
31
|
+
private val packaging = ProviderPackaging(classPresent, receiverDeclared, providersMetaData)
|
|
32
|
+
|
|
33
|
+
override fun deviceInfo(): DeviceInfo {
|
|
34
|
+
// DeviceInfo is diagnostic: a broken provider factory must not hide the build properties.
|
|
35
|
+
val factory = try {
|
|
36
|
+
providers.value
|
|
37
|
+
} catch (e: Exception) {
|
|
38
|
+
Logger.e(TAG, "provider factory unavailable", e)
|
|
39
|
+
null
|
|
40
|
+
}
|
|
41
|
+
val backend = try {
|
|
42
|
+
factory?.kind
|
|
43
|
+
} catch (e: Exception) {
|
|
44
|
+
Logger.e(TAG, "provider kind unavailable", e)
|
|
45
|
+
null
|
|
46
|
+
}
|
|
47
|
+
return DeviceInfo(
|
|
48
|
+
platform = "android",
|
|
49
|
+
manufacturer = Build.MANUFACTURER.orEmpty(),
|
|
50
|
+
model = Build.MODEL.orEmpty(),
|
|
51
|
+
brand = Build.BRAND.orEmpty(),
|
|
52
|
+
osVersion = Build.VERSION.RELEASE.orEmpty(),
|
|
53
|
+
sdkInt = Build.VERSION.SDK_INT,
|
|
54
|
+
pluginVersion = BuildConfig.PLUGIN_VERSION,
|
|
55
|
+
gmsAvailable = factory != null && isAvailable(factory, ProviderKind.GMS),
|
|
56
|
+
hmsAvailable = factory != null && isAvailable(factory, ProviderKind.HMS),
|
|
57
|
+
backend = backend ?: ProviderKind.ANDROID,
|
|
58
|
+
packagedProviders = packagedProviders(),
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
override fun sensors(): Sensors {
|
|
63
|
+
val manager = try {
|
|
64
|
+
ContextCompat.getSystemService(context, SensorManager::class.java)
|
|
65
|
+
} catch (e: Exception) {
|
|
66
|
+
Logger.w(TAG, "SensorManager unavailable", e)
|
|
67
|
+
null
|
|
68
|
+
}
|
|
69
|
+
fun has(type: Int): Boolean = try {
|
|
70
|
+
manager?.getDefaultSensor(type) != null
|
|
71
|
+
} catch (e: Exception) {
|
|
72
|
+
Logger.w(TAG, "getDefaultSensor($type) failed", e)
|
|
73
|
+
false
|
|
74
|
+
}
|
|
75
|
+
return Sensors(
|
|
76
|
+
accelerometer = has(Sensor.TYPE_ACCELEROMETER),
|
|
77
|
+
gyroscope = has(Sensor.TYPE_GYROSCOPE),
|
|
78
|
+
magnetometer = has(Sensor.TYPE_MAGNETIC_FIELD),
|
|
79
|
+
significantMotion = has(Sensor.TYPE_SIGNIFICANT_MOTION),
|
|
80
|
+
stepCounter = has(Sensor.TYPE_STEP_COUNTER),
|
|
81
|
+
stepDetector = has(Sensor.TYPE_STEP_DETECTOR),
|
|
82
|
+
barometer = has(Sensor.TYPE_PRESSURE),
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private fun isAvailable(factory: ProviderFactory, kind: ProviderKind): Boolean = try {
|
|
87
|
+
factory.isAvailable(kind)
|
|
88
|
+
} catch (e: Exception) {
|
|
89
|
+
Logger.w(TAG, "isAvailable(${kind.wire}) failed", e)
|
|
90
|
+
false
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The providers packaged in the APK ([ProviderPackaging]: allowed by the app's `PROVIDERS` meta-data, SDK classes
|
|
95
|
+
* present, receivers declared), in the order `gms`, `hms`. Read at runtime, so a flavored app reports only the
|
|
96
|
+
* provider its flavor declares, even when another library brings the other SDK's classes.
|
|
97
|
+
*/
|
|
98
|
+
private fun packagedProviders(): List<String> =
|
|
99
|
+
listOf(ProviderKind.GMS, ProviderKind.HMS).filter(packaging::isPackaged).map { it.wire }
|
|
100
|
+
|
|
101
|
+
internal companion object {
|
|
102
|
+
private const val TAG = "LT.DeviceInfo"
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
package com.brickssoft.locationtracking.device
|
|
2
|
+
|
|
3
|
+
import android.Manifest
|
|
4
|
+
import android.annotation.SuppressLint
|
|
5
|
+
import android.app.AlarmManager
|
|
6
|
+
import android.content.BroadcastReceiver
|
|
7
|
+
import android.content.Context
|
|
8
|
+
import android.content.Intent
|
|
9
|
+
import android.content.IntentFilter
|
|
10
|
+
import android.content.pm.PackageManager
|
|
11
|
+
import android.location.LocationManager
|
|
12
|
+
import android.net.ConnectivityManager
|
|
13
|
+
import android.net.Network
|
|
14
|
+
import android.net.NetworkCapabilities
|
|
15
|
+
import android.os.Build
|
|
16
|
+
import android.os.PowerManager
|
|
17
|
+
import androidx.core.content.ContextCompat
|
|
18
|
+
import androidx.core.location.LocationManagerCompat
|
|
19
|
+
import com.brickssoft.locationtracking.config.ConfigStore
|
|
20
|
+
import com.brickssoft.locationtracking.core.Clock
|
|
21
|
+
import com.brickssoft.locationtracking.core.EventBus
|
|
22
|
+
import com.brickssoft.locationtracking.core.Logger
|
|
23
|
+
import com.brickssoft.locationtracking.core.TrackingEvent
|
|
24
|
+
import com.brickssoft.locationtracking.model.BatterySnapshot
|
|
25
|
+
import com.brickssoft.locationtracking.model.Connectivity
|
|
26
|
+
import com.brickssoft.locationtracking.model.ProviderState
|
|
27
|
+
import com.brickssoft.locationtracking.model.RecordEvent
|
|
28
|
+
import com.brickssoft.locationtracking.permission.PermissionManager
|
|
29
|
+
import com.brickssoft.locationtracking.processing.RecordFactory
|
|
30
|
+
import com.brickssoft.locationtracking.provider.ProviderFactory
|
|
31
|
+
import com.brickssoft.locationtracking.record.RecordSink
|
|
32
|
+
import kotlinx.coroutines.CoroutineScope
|
|
33
|
+
import kotlinx.coroutines.delay
|
|
34
|
+
import kotlinx.coroutines.launch
|
|
35
|
+
import kotlinx.coroutines.sync.Mutex
|
|
36
|
+
import kotlinx.coroutines.sync.withLock
|
|
37
|
+
import java.util.concurrent.atomic.AtomicBoolean
|
|
38
|
+
import java.util.concurrent.atomic.AtomicReference
|
|
39
|
+
import kotlin.coroutines.cancellation.CancellationException
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Default [DeviceMonitor].
|
|
43
|
+
*
|
|
44
|
+
* [start] registers (once) a receiver for location provider/mode changes, power-save and device-idle changes, and
|
|
45
|
+
* a default-network callback, then schedules a provider check. Provider broadcasts schedule a debounced
|
|
46
|
+
* [checkProviderState] on [scope]. Connectivity and power-save changes are emitted as
|
|
47
|
+
* [TrackingEvent.ConnectivityChange] / [TrackingEvent.PowerSaveChange], deduplicated against the last published
|
|
48
|
+
* value (seeded at registration, so registering alone emits nothing). Device-idle changes are only logged; the
|
|
49
|
+
* heartbeat scheduler listens for them itself.
|
|
50
|
+
*
|
|
51
|
+
* [checkProviderState] diffs the current [ProviderState] against the persisted `runtime.providerState`. The first
|
|
52
|
+
* observation is persisted silently. A later difference is emitted as [TrackingEvent.ProviderChange] and, while
|
|
53
|
+
* tracking is enabled, submitted as a `providerchange` record with the last known location; the new state is
|
|
54
|
+
* persisted after the record was handed to the sink, so a failed submit is retried by the next check.
|
|
55
|
+
*
|
|
56
|
+
* The constructor does not touch the lazy parameters or any system service.
|
|
57
|
+
*/
|
|
58
|
+
class DefaultDeviceMonitor(
|
|
59
|
+
private val context: Context,
|
|
60
|
+
private val configStore: ConfigStore,
|
|
61
|
+
private val permissions: PermissionManager,
|
|
62
|
+
private val providers: Lazy<ProviderFactory>,
|
|
63
|
+
private val events: EventBus,
|
|
64
|
+
@Suppress("unused") private val clock: Clock,
|
|
65
|
+
private val recordFactory: Lazy<RecordFactory>,
|
|
66
|
+
private val recordSink: Lazy<RecordSink>,
|
|
67
|
+
private val scope: CoroutineScope,
|
|
68
|
+
) : DeviceMonitor {
|
|
69
|
+
private val receiverRegistered = AtomicBoolean(false)
|
|
70
|
+
private val networkCallbackRegistered = AtomicBoolean(false)
|
|
71
|
+
private val providerCheckPending = AtomicBoolean(false)
|
|
72
|
+
private val providerMutex = Mutex()
|
|
73
|
+
private val lastConnectivity = AtomicReference<Connectivity?>(null)
|
|
74
|
+
private val lastPowerSave = AtomicReference<Boolean?>(null)
|
|
75
|
+
|
|
76
|
+
private val locationManager: LocationManager? by lazy { systemService(LocationManager::class.java) }
|
|
77
|
+
private val connectivityManager: ConnectivityManager? by lazy { systemService(ConnectivityManager::class.java) }
|
|
78
|
+
private val powerManager: PowerManager? by lazy { systemService(PowerManager::class.java) }
|
|
79
|
+
private val alarmManager: AlarmManager? by lazy { systemService(AlarmManager::class.java) }
|
|
80
|
+
|
|
81
|
+
private val receiver = object : BroadcastReceiver() {
|
|
82
|
+
override fun onReceive(context: Context, intent: Intent) {
|
|
83
|
+
when (intent.action) {
|
|
84
|
+
LocationManager.PROVIDERS_CHANGED_ACTION,
|
|
85
|
+
LocationManager.MODE_CHANGED_ACTION,
|
|
86
|
+
-> scheduleProviderCheck(REASON_PROVIDERS_CHANGED)
|
|
87
|
+
PowerManager.ACTION_POWER_SAVE_MODE_CHANGED -> publishPowerSave(isPowerSaveMode())
|
|
88
|
+
PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED -> Logger.d(TAG, "device idle mode: ${isDeviceIdleMode()}")
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Tracks the default network. Callbacks of one registration arrive serially on one thread. */
|
|
94
|
+
private val networkCallback = object : ConnectivityManager.NetworkCallback() {
|
|
95
|
+
@Volatile
|
|
96
|
+
private var current: Network? = null
|
|
97
|
+
|
|
98
|
+
/** Connectivity of [current] from its capabilities, before [isBlocked] is applied. */
|
|
99
|
+
@Volatile
|
|
100
|
+
private var fromCapabilities: Connectivity? = null
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Whether this app's traffic is blocked (Data Saver, background restrictions, Doze); reported on API 29+.
|
|
104
|
+
* Kept across network switches: API 29+ reports the new network's status right after its capabilities.
|
|
105
|
+
*/
|
|
106
|
+
@Volatile
|
|
107
|
+
private var isBlocked = false
|
|
108
|
+
|
|
109
|
+
override fun onAvailable(network: Network) {
|
|
110
|
+
if (network != current) fromCapabilities = null
|
|
111
|
+
current = network
|
|
112
|
+
// From API 26 onAvailable is always followed by onCapabilitiesChanged, and querying the capabilities
|
|
113
|
+
// here is racy. Before that, the query is the only way to learn the new network's state.
|
|
114
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
|
115
|
+
capabilitiesOf(network)?.let { update(DeviceStateMapping.connectivityOf(it)) }
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
|
|
120
|
+
current = network
|
|
121
|
+
update(DeviceStateMapping.connectivityOf(networkCapabilities))
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
override fun onBlockedStatusChanged(network: Network, blocked: Boolean) {
|
|
125
|
+
val active = current
|
|
126
|
+
if (active != null && active != network) return
|
|
127
|
+
isBlocked = blocked
|
|
128
|
+
fromCapabilities?.let { publishConnectivity(effective(it)) }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
override fun onLost(network: Network) {
|
|
132
|
+
val active = current
|
|
133
|
+
// A default-network callback can report the old network as lost after the new one became available.
|
|
134
|
+
if (active != null && active != network) return
|
|
135
|
+
current = null
|
|
136
|
+
fromCapabilities = null
|
|
137
|
+
publishConnectivity(DeviceStateMapping.DISCONNECTED)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private fun update(value: Connectivity) {
|
|
141
|
+
fromCapabilities = value
|
|
142
|
+
publishConnectivity(effective(value))
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private fun effective(value: Connectivity): Connectivity =
|
|
146
|
+
if (isBlocked && value.connected) value.copy(connected = false) else value
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
override fun start() {
|
|
150
|
+
if (receiverRegistered.compareAndSet(false, true)) {
|
|
151
|
+
lastPowerSave.set(isPowerSaveMode())
|
|
152
|
+
val filter = IntentFilter().apply {
|
|
153
|
+
addAction(LocationManager.PROVIDERS_CHANGED_ACTION)
|
|
154
|
+
addAction(LocationManager.MODE_CHANGED_ACTION)
|
|
155
|
+
addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)
|
|
156
|
+
addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
registerNotExported(receiver, filter)
|
|
160
|
+
// Catches changes made while no receiver was listening (service start, cold start).
|
|
161
|
+
scheduleProviderCheck(REASON_START)
|
|
162
|
+
} catch (e: Exception) {
|
|
163
|
+
receiverRegistered.set(false)
|
|
164
|
+
Logger.e(TAG, "failed to register the device receiver", e)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (networkCallbackRegistered.compareAndSet(false, true)) {
|
|
168
|
+
lastConnectivity.set(connectivity())
|
|
169
|
+
val cm = connectivityManager
|
|
170
|
+
try {
|
|
171
|
+
checkNotNull(cm) { "no ConnectivityManager" }.registerDefaultNetworkCallback(networkCallback)
|
|
172
|
+
} catch (e: Exception) {
|
|
173
|
+
networkCallbackRegistered.set(false)
|
|
174
|
+
Logger.e(TAG, "failed to register the network callback", e)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
Logger.d(TAG, "started (receiver=${receiverRegistered.get()}, network=${networkCallbackRegistered.get()})")
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
override suspend fun checkProviderState(reason: String) {
|
|
181
|
+
providerMutex.withLock {
|
|
182
|
+
try {
|
|
183
|
+
val current = providerState()
|
|
184
|
+
val runtime = configStore.runtime.value
|
|
185
|
+
val previous = runtime.providerState
|
|
186
|
+
if (current == previous) {
|
|
187
|
+
Logger.v(TAG, "provider state unchanged ($reason)")
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
if (previous == null) {
|
|
191
|
+
configStore.updateRuntime { it.copy(providerState = current) }
|
|
192
|
+
Logger.i(TAG, "provider state first observed ($reason): $current")
|
|
193
|
+
return
|
|
194
|
+
}
|
|
195
|
+
Logger.i(TAG, "providerchange ($reason): $previous -> $current")
|
|
196
|
+
events.emit(TrackingEvent.ProviderChange(current))
|
|
197
|
+
if (runtime.enabled) {
|
|
198
|
+
val record = recordFactory.value.create(
|
|
199
|
+
RecordEvent.PROVIDERCHANGE,
|
|
200
|
+
runtime.lastLocation,
|
|
201
|
+
provider = current,
|
|
202
|
+
)
|
|
203
|
+
recordSink.value.submit(record)
|
|
204
|
+
}
|
|
205
|
+
configStore.updateRuntime { it.copy(providerState = current) }
|
|
206
|
+
} catch (e: CancellationException) {
|
|
207
|
+
throw e
|
|
208
|
+
} catch (e: Exception) {
|
|
209
|
+
// The state stays unpersisted, so the next check reports the change again.
|
|
210
|
+
Logger.e(TAG, "provider state check failed ($reason)", e)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
override fun providerState(): ProviderState {
|
|
216
|
+
val lm = locationManager
|
|
217
|
+
val foreground = permissions.hasForegroundLocation()
|
|
218
|
+
val background = foreground && permissions.hasBackgroundLocation()
|
|
219
|
+
val fine = ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) ==
|
|
220
|
+
PackageManager.PERMISSION_GRANTED
|
|
221
|
+
return ProviderState(
|
|
222
|
+
enabled = lm != null && safely("isLocationEnabled", false) { LocationManagerCompat.isLocationEnabled(lm) },
|
|
223
|
+
gps = isProviderEnabled(lm, LocationManager.GPS_PROVIDER),
|
|
224
|
+
network = isProviderEnabled(lm, LocationManager.NETWORK_PROVIDER),
|
|
225
|
+
permission = DeviceStateMapping.permissionLevel(foreground, background),
|
|
226
|
+
accuracy = DeviceStateMapping.accuracyLevel(foreground, fine),
|
|
227
|
+
backend = providers.value.kind,
|
|
228
|
+
)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
override fun connectivity(): Connectivity {
|
|
232
|
+
val cm = connectivityManager ?: return DeviceStateMapping.DISCONNECTED
|
|
233
|
+
return safely("connectivity", DeviceStateMapping.DISCONNECTED) {
|
|
234
|
+
val network = cm.activeNetwork
|
|
235
|
+
if (network == null) {
|
|
236
|
+
DeviceStateMapping.DISCONNECTED
|
|
237
|
+
} else {
|
|
238
|
+
DeviceStateMapping.connectivityOf(capabilitiesOf(network))
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
@SuppressLint("UnspecifiedRegisterReceiverFlag")
|
|
244
|
+
override fun battery(): BatterySnapshot = safely("battery", BatterySnapshot.UNKNOWN) {
|
|
245
|
+
// A null receiver only reads the sticky broadcast and registers nothing, so no export flag is needed
|
|
246
|
+
// (ACTION_BATTERY_CHANGED is a protected broadcast).
|
|
247
|
+
DeviceStateMapping.batteryOf(context.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED)))
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
override fun isPowerSaveMode(): Boolean = safely("isPowerSaveMode", false) { powerManager?.isPowerSaveMode == true }
|
|
251
|
+
|
|
252
|
+
override fun isDeviceIdleMode(): Boolean =
|
|
253
|
+
safely("isDeviceIdleMode", false) { powerManager?.isDeviceIdleMode == true }
|
|
254
|
+
|
|
255
|
+
override fun isIgnoringBatteryOptimizations(): Boolean = safely("isIgnoringBatteryOptimizations", false) {
|
|
256
|
+
powerManager?.isIgnoringBatteryOptimizations(context.packageName) == true
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
override fun canScheduleExactAlarms(): Boolean {
|
|
260
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return true
|
|
261
|
+
return safely("canScheduleExactAlarms", false) { alarmManager?.canScheduleExactAlarms() == true }
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Runs one [checkProviderState] [PROVIDER_CHECK_DEBOUNCE_MS] after the first of a burst of triggers. One user
|
|
266
|
+
* toggle can send several broadcasts (one per provider, plus MODE_CHANGED) with intermediate states in between;
|
|
267
|
+
* the delay lets them settle so a single toggle yields a single providerchange. A trigger that arrives after
|
|
268
|
+
* the check has begun schedules another one, so the final state is always observed.
|
|
269
|
+
*/
|
|
270
|
+
private fun scheduleProviderCheck(reason: String) {
|
|
271
|
+
if (!providerCheckPending.compareAndSet(false, true)) return
|
|
272
|
+
val job = scope.launch {
|
|
273
|
+
delay(PROVIDER_CHECK_DEBOUNCE_MS)
|
|
274
|
+
providerCheckPending.set(false)
|
|
275
|
+
checkProviderState(reason)
|
|
276
|
+
}
|
|
277
|
+
// If the scope is no longer active the body never runs; don't leave the flag stuck.
|
|
278
|
+
job.invokeOnCompletion { cause -> if (cause != null) providerCheckPending.set(false) }
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
private fun publishConnectivity(value: Connectivity) {
|
|
282
|
+
if (lastConnectivity.getAndSet(value) == value) return
|
|
283
|
+
Logger.i(TAG, "connectivity: connected=${value.connected}, type=${value.type.wire}")
|
|
284
|
+
events.emit(TrackingEvent.ConnectivityChange(value))
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
private fun publishPowerSave(value: Boolean) {
|
|
288
|
+
if (lastPowerSave.getAndSet(value) == value) return
|
|
289
|
+
Logger.i(TAG, "power save mode: $value")
|
|
290
|
+
events.emit(TrackingEvent.PowerSaveChange(value))
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Registers [receiver] with RECEIVER_NOT_EXPORTED; system broadcasts still reach it. Below API 33 androidx
|
|
295
|
+
* emulates the flag with the `<applicationId>.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION` its manifest declares.
|
|
296
|
+
* If an app strips that permission, fall back to a plain registration: safe, because every action this
|
|
297
|
+
* monitor listens to is a protected broadcast that only the system can send.
|
|
298
|
+
*/
|
|
299
|
+
@SuppressLint("UnspecifiedRegisterReceiverFlag")
|
|
300
|
+
private fun registerNotExported(receiver: BroadcastReceiver, filter: IntentFilter) {
|
|
301
|
+
try {
|
|
302
|
+
ContextCompat.registerReceiver(context, receiver, filter, ContextCompat.RECEIVER_NOT_EXPORTED)
|
|
303
|
+
} catch (e: RuntimeException) {
|
|
304
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) throw e
|
|
305
|
+
Logger.w(TAG, "RECEIVER_NOT_EXPORTED emulation unavailable; registering without it", e)
|
|
306
|
+
context.registerReceiver(receiver, filter)
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
private fun capabilitiesOf(network: Network): NetworkCapabilities? =
|
|
311
|
+
safely("getNetworkCapabilities", null) { connectivityManager?.getNetworkCapabilities(network) }
|
|
312
|
+
|
|
313
|
+
private fun isProviderEnabled(lm: LocationManager?, provider: String): Boolean =
|
|
314
|
+
lm != null && safely("isProviderEnabled($provider)", false) { lm.isProviderEnabled(provider) }
|
|
315
|
+
|
|
316
|
+
private fun <T> systemService(type: Class<T>): T? = safely("getSystemService(${type.simpleName})", null) {
|
|
317
|
+
ContextCompat.getSystemService(context, type)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
private inline fun <T> safely(what: String, fallback: T, block: () -> T): T = try {
|
|
321
|
+
block()
|
|
322
|
+
} catch (e: Exception) {
|
|
323
|
+
Logger.w(TAG, "$what failed", e)
|
|
324
|
+
fallback
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
internal companion object {
|
|
328
|
+
private const val TAG = "LT.DeviceMonitor"
|
|
329
|
+
private const val REASON_PROVIDERS_CHANGED = "providers_changed"
|
|
330
|
+
private const val REASON_START = "start"
|
|
331
|
+
|
|
332
|
+
/** Settle time between the first provider trigger of a burst and the check. */
|
|
333
|
+
const val PROVIDER_CHECK_DEBOUNCE_MS = 1_000L
|
|
334
|
+
}
|
|
335
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
package com.brickssoft.locationtracking.device
|
|
2
|
+
|
|
3
|
+
import com.brickssoft.locationtracking.model.BatterySnapshot
|
|
4
|
+
import com.brickssoft.locationtracking.model.Connectivity
|
|
5
|
+
import com.brickssoft.locationtracking.model.DeviceInfo
|
|
6
|
+
import com.brickssoft.locationtracking.model.ProviderState
|
|
7
|
+
import com.brickssoft.locationtracking.model.Sensors
|
|
8
|
+
|
|
9
|
+
/** Device state: location providers, connectivity, battery, power and idle modes. */
|
|
10
|
+
interface DeviceMonitor {
|
|
11
|
+
/** Idempotent: PROVIDERS_CHANGED, NetworkCallback, POWER_SAVE_MODE_CHANGED, DEVICE_IDLE_MODE_CHANGED. */
|
|
12
|
+
fun start()
|
|
13
|
+
|
|
14
|
+
/** Diffs against runtime.providerState -> ProviderChange event (+ providerchange record if enabled). */
|
|
15
|
+
suspend fun checkProviderState(reason: String)
|
|
16
|
+
|
|
17
|
+
fun providerState(): ProviderState
|
|
18
|
+
|
|
19
|
+
fun connectivity(): Connectivity
|
|
20
|
+
|
|
21
|
+
fun battery(): BatterySnapshot
|
|
22
|
+
|
|
23
|
+
fun isPowerSaveMode(): Boolean
|
|
24
|
+
|
|
25
|
+
fun isDeviceIdleMode(): Boolean
|
|
26
|
+
|
|
27
|
+
fun isIgnoringBatteryOptimizations(): Boolean
|
|
28
|
+
|
|
29
|
+
fun canScheduleExactAlarms(): Boolean
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface DeviceInfoProvider {
|
|
33
|
+
fun deviceInfo(): DeviceInfo
|
|
34
|
+
|
|
35
|
+
fun sensors(): Sensors
|
|
36
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
package com.brickssoft.locationtracking.device
|
|
2
|
+
|
|
3
|
+
import android.content.Intent
|
|
4
|
+
import android.net.NetworkCapabilities
|
|
5
|
+
import android.os.BatteryManager
|
|
6
|
+
import com.brickssoft.locationtracking.model.AccuracyLevel
|
|
7
|
+
import com.brickssoft.locationtracking.model.BatterySnapshot
|
|
8
|
+
import com.brickssoft.locationtracking.model.Connectivity
|
|
9
|
+
import com.brickssoft.locationtracking.model.ConnectivityType
|
|
10
|
+
import com.brickssoft.locationtracking.model.PermissionLevel
|
|
11
|
+
|
|
12
|
+
/** Pure mappings from Android system values to the plugin's device models. */
|
|
13
|
+
internal object DeviceStateMapping {
|
|
14
|
+
/** No network at all. */
|
|
15
|
+
val DISCONNECTED = Connectivity(connected = false, type = ConnectivityType.NONE)
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Maps a network's capabilities to [Connectivity].
|
|
19
|
+
*
|
|
20
|
+
* The type comes from the first matching transport: Wi-Fi, cellular, ethernet, otherwise OTHER (VPN,
|
|
21
|
+
* Bluetooth, ...). `connected` needs NET_CAPABILITY_INTERNET; VALIDATED is not required because validation
|
|
22
|
+
* can lag or be blocked (for example where the platform's connectivity check host is unreachable).
|
|
23
|
+
* A network behind a captive portal counts as not connected, so that logging in later reads as a change.
|
|
24
|
+
*/
|
|
25
|
+
fun connectivityOf(caps: NetworkCapabilities?): Connectivity {
|
|
26
|
+
if (caps == null) return DISCONNECTED
|
|
27
|
+
val type = when {
|
|
28
|
+
caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> ConnectivityType.WIFI
|
|
29
|
+
caps.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> ConnectivityType.CELLULAR
|
|
30
|
+
caps.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> ConnectivityType.ETHERNET
|
|
31
|
+
else -> ConnectivityType.OTHER
|
|
32
|
+
}
|
|
33
|
+
val connected = caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) &&
|
|
34
|
+
!caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL)
|
|
35
|
+
return Connectivity(connected, type)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Parses an `ACTION_BATTERY_CHANGED` intent. Level is `level / scale` in 0..1, or -1 when unknown.
|
|
40
|
+
* Charging means plugged in (AC, USB, wireless, dock) or reporting CHARGING; FULL counts as charging only
|
|
41
|
+
* when the plug state is missing from the intent.
|
|
42
|
+
*/
|
|
43
|
+
fun batteryOf(intent: Intent?): BatterySnapshot {
|
|
44
|
+
if (intent == null) return BatterySnapshot.UNKNOWN
|
|
45
|
+
val level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
|
|
46
|
+
val scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
|
|
47
|
+
val fraction = if (level >= 0 && scale > 0) (level.toFloat() / scale).coerceIn(0f, 1f) else -1f
|
|
48
|
+
val status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
|
|
49
|
+
val plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1)
|
|
50
|
+
val isCharging = plugged > 0 ||
|
|
51
|
+
status == BatteryManager.BATTERY_STATUS_CHARGING ||
|
|
52
|
+
(plugged < 0 && status == BatteryManager.BATTERY_STATUS_FULL)
|
|
53
|
+
return BatterySnapshot(fraction, isCharging)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Foreground access is required for any level; background access on top of it means ALWAYS. */
|
|
57
|
+
fun permissionLevel(foreground: Boolean, background: Boolean): PermissionLevel = when {
|
|
58
|
+
!foreground -> PermissionLevel.DENIED
|
|
59
|
+
background -> PermissionLevel.ALWAYS
|
|
60
|
+
else -> PermissionLevel.WHEN_IN_USE
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** PRECISE with fine location, APPROXIMATE with foreground access but no fine location, else NONE. */
|
|
64
|
+
fun accuracyLevel(foreground: Boolean, fine: Boolean): AccuracyLevel = when {
|
|
65
|
+
!foreground -> AccuracyLevel.NONE
|
|
66
|
+
fine -> AccuracyLevel.PRECISE
|
|
67
|
+
else -> AccuracyLevel.APPROXIMATE
|
|
68
|
+
}
|
|
69
|
+
}
|