@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,115 @@
|
|
|
1
|
+
package com.brickssoft.locationtracking.service
|
|
2
|
+
|
|
3
|
+
import android.Manifest
|
|
4
|
+
import android.app.Activity
|
|
5
|
+
import android.app.ActivityManager
|
|
6
|
+
import android.app.Application
|
|
7
|
+
import android.content.Context
|
|
8
|
+
import android.content.pm.PackageManager
|
|
9
|
+
import android.os.Build
|
|
10
|
+
import androidx.core.content.ContextCompat
|
|
11
|
+
import androidx.lifecycle.Lifecycle
|
|
12
|
+
import androidx.lifecycle.LifecycleOwner
|
|
13
|
+
import com.brickssoft.locationtracking.core.Clock
|
|
14
|
+
import com.brickssoft.locationtracking.permission.ActivityLifecycleAdapter
|
|
15
|
+
import com.brickssoft.locationtracking.permission.CurrentActivityTracker
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Predicts, before `startForegroundService()`, whether Android 14+ would refuse a location foreground service because
|
|
19
|
+
* the app would not get while-in-use location access.
|
|
20
|
+
*
|
|
21
|
+
* On Android 14 (API 34) and later, `startForeground()` with type `location` throws `SecurityException` when the app
|
|
22
|
+
* has only while-in-use location permission (no `ACCESS_BACKGROUND_LOCATION`) and is not in a state that grants
|
|
23
|
+
* while-in-use access. The main such state is a visible activity; Android also grants it for 10 s after an activity of
|
|
24
|
+
* the app was started or finished. A boot, alarm or heartbeat start from the background therefore fails.
|
|
25
|
+
*
|
|
26
|
+
* [refusalReason] returns a reason (start refused) only when all of these are true:
|
|
27
|
+
* - API level ≥ 34;
|
|
28
|
+
* - `ACCESS_BACKGROUND_LOCATION` is not granted;
|
|
29
|
+
* - the process importance (`ActivityManager.getMyMemoryState`) is worse than `IMPORTANCE_VISIBLE`. An importance of
|
|
30
|
+
* `IMPORTANCE_FOREGROUND_SERVICE` (another foreground service of the app runs, for example a companion plugin's) is
|
|
31
|
+
* treated as possibly visible, because the activity state cannot be told from it;
|
|
32
|
+
* - the activity that `CurrentActivityTracker` remembers (the plugin seeds it with its activity at load) is not in the
|
|
33
|
+
* `STARTED` lifecycle state or later;
|
|
34
|
+
* - no activity is started according to the lifecycle callbacks registered by this object (they see only events after
|
|
35
|
+
* this object was created), and no activity was started, stopped or destroyed during the last [GRACE_MS] ms. This
|
|
36
|
+
* covers Android's 10 s grace period with a margin.
|
|
37
|
+
*
|
|
38
|
+
* When the check cannot be evaluated (an exception), it does not refuse. Every uncertain case lets the start proceed:
|
|
39
|
+
* a start that Android then refuses is still handled by the service (`onServiceStartFailed`).
|
|
40
|
+
*
|
|
41
|
+
* Other Android exemptions (a notification tap, a PendingIntent from a visible app) are not modelled; a start in such a
|
|
42
|
+
* state without a visible activity is refused here.
|
|
43
|
+
*/
|
|
44
|
+
internal class WhileInUseStartCheck(context: Context, private val clock: Clock) {
|
|
45
|
+
private val context: Context = context.applicationContext ?: context
|
|
46
|
+
|
|
47
|
+
@Volatile
|
|
48
|
+
private var startedActivities = 0
|
|
49
|
+
|
|
50
|
+
/** `clock.elapsedRealtime()` of the last activity start, stop or destroy seen; null if none was seen. */
|
|
51
|
+
@Volatile
|
|
52
|
+
private var lastActivityChangeAt: Long? = null
|
|
53
|
+
|
|
54
|
+
private val callbacks = object : ActivityLifecycleAdapter() {
|
|
55
|
+
override fun onActivityStarted(activity: Activity) {
|
|
56
|
+
startedActivities++
|
|
57
|
+
lastActivityChangeAt = clock.elapsedRealtime()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
override fun onActivityStopped(activity: Activity) {
|
|
61
|
+
startedActivities = (startedActivities - 1).coerceAtLeast(0)
|
|
62
|
+
lastActivityChangeAt = clock.elapsedRealtime()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
override fun onActivityDestroyed(activity: Activity) {
|
|
66
|
+
lastActivityChangeAt = clock.elapsedRealtime()
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
init {
|
|
71
|
+
(this.context as? Application)?.registerActivityLifecycleCallbacks(callbacks)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Why a location foreground service started now would be refused, or null if it may be allowed. */
|
|
75
|
+
fun refusalReason(): String? {
|
|
76
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) return null
|
|
77
|
+
return try {
|
|
78
|
+
when {
|
|
79
|
+
granted(Manifest.permission.ACCESS_BACKGROUND_LOCATION) -> null
|
|
80
|
+
processMayBeVisible() -> null
|
|
81
|
+
rememberedActivityStarted() -> null
|
|
82
|
+
startedActivities > 0 -> null
|
|
83
|
+
activityChangedRecently() -> null
|
|
84
|
+
else -> "Android 14+: no visible activity and no background location permission"
|
|
85
|
+
}
|
|
86
|
+
} catch (e: Exception) {
|
|
87
|
+
null
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private fun granted(permission: String): Boolean =
|
|
92
|
+
ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED
|
|
93
|
+
|
|
94
|
+
private fun processMayBeVisible(): Boolean {
|
|
95
|
+
val info = ActivityManager.RunningAppProcessInfo()
|
|
96
|
+
ActivityManager.getMyMemoryState(info)
|
|
97
|
+
return info.importance <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private fun rememberedActivityStarted(): Boolean {
|
|
101
|
+
val owner = CurrentActivityTracker.attach(context).current() as? LifecycleOwner ?: return false
|
|
102
|
+
return owner.lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private fun activityChangedRecently(): Boolean {
|
|
106
|
+
val at = lastActivityChangeAt ?: return false
|
|
107
|
+
val age = clock.elapsedRealtime() - at
|
|
108
|
+
return age in 0 until GRACE_MS
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
companion object {
|
|
112
|
+
/** Android grants while-in-use access for 10 s after an activity start or finish; 15 s adds a margin. */
|
|
113
|
+
const val GRACE_MS = 15_000L
|
|
114
|
+
}
|
|
115
|
+
}
|
package/android/src/main/java/com/brickssoft/locationtracking/settings/DefaultDeviceSettings.kt
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
package com.brickssoft.locationtracking.settings
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.content.Intent
|
|
6
|
+
import android.net.Uri
|
|
7
|
+
import android.os.Build
|
|
8
|
+
import android.provider.Settings
|
|
9
|
+
import com.brickssoft.locationtracking.core.Logger
|
|
10
|
+
import com.brickssoft.locationtracking.model.PowerManagerInfo
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Opens system and OEM settings screens. Screens start from the given Activity when it is usable, otherwise
|
|
14
|
+
* from the application context with `FLAG_ACTIVITY_NEW_TASK`. Any failure to start (ActivityNotFoundException,
|
|
15
|
+
* SecurityException, ...) returns false.
|
|
16
|
+
*
|
|
17
|
+
* The battery-optimization screen is the settings LIST (`ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS`); the
|
|
18
|
+
* plugin never uses `ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS`.
|
|
19
|
+
*/
|
|
20
|
+
class DefaultDeviceSettings(context: Context) : DeviceSettings {
|
|
21
|
+
private val appContext: Context = context.applicationContext ?: context
|
|
22
|
+
|
|
23
|
+
override fun openBatteryOptimizationSettings(activity: Activity?): Boolean =
|
|
24
|
+
launch(Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS), activity, "battery-optimization settings")
|
|
25
|
+
|
|
26
|
+
override fun powerManagerInfo(): PowerManagerInfo {
|
|
27
|
+
val manufacturer = manufacturer()
|
|
28
|
+
return PowerManagerInfo(
|
|
29
|
+
manufacturer = manufacturer,
|
|
30
|
+
available = OemPowerManagers.launchableIntents(appContext, manufacturer).isNotEmpty(),
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
override fun openPowerManagerSettings(activity: Activity?): Boolean {
|
|
35
|
+
val manufacturer = manufacturer()
|
|
36
|
+
val intents = OemPowerManagers.launchableIntents(appContext, manufacturer)
|
|
37
|
+
if (intents.isEmpty()) {
|
|
38
|
+
Logger.i(TAG, "no power-manager screen found for manufacturer '$manufacturer'")
|
|
39
|
+
return false
|
|
40
|
+
}
|
|
41
|
+
// Several candidates can resolve; fall through to the next one if a screen refuses to start.
|
|
42
|
+
return intents.any { intent -> launch(intent, activity, "power-manager screen ${describe(intent)}") }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
override fun openLocationSettings(activity: Activity?): Boolean =
|
|
46
|
+
launch(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS), activity, "location settings")
|
|
47
|
+
|
|
48
|
+
override fun openAppSettings(activity: Activity?): Boolean = launch(
|
|
49
|
+
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.fromParts("package", appContext.packageName, null)),
|
|
50
|
+
activity,
|
|
51
|
+
"app settings",
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
private fun launch(intent: Intent, activity: Activity?, what: String): Boolean {
|
|
55
|
+
val host = activity?.takeIf { !it.isFinishing && !it.isDestroyed }
|
|
56
|
+
return try {
|
|
57
|
+
if (host != null) {
|
|
58
|
+
host.startActivity(intent)
|
|
59
|
+
} else {
|
|
60
|
+
appContext.startActivity(Intent(intent).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
|
|
61
|
+
}
|
|
62
|
+
Logger.d(TAG, "opened $what")
|
|
63
|
+
true
|
|
64
|
+
} catch (e: RuntimeException) {
|
|
65
|
+
// ActivityNotFoundException, SecurityException, or an OEM ROM rejecting the intent
|
|
66
|
+
Logger.w(TAG, "cannot open $what: ${e.javaClass.simpleName}", e)
|
|
67
|
+
false
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private fun manufacturer(): String = Build.MANUFACTURER.orEmpty()
|
|
72
|
+
|
|
73
|
+
private fun describe(intent: Intent): String =
|
|
74
|
+
intent.component?.flattenToShortString() ?: "${intent.`package`}/${intent.action}"
|
|
75
|
+
|
|
76
|
+
private companion object {
|
|
77
|
+
const val TAG = "LT.DeviceSettings"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
package com.brickssoft.locationtracking.settings
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import com.brickssoft.locationtracking.model.PowerManagerInfo
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Opens system and OEM settings screens. Every `open*` returns false instead of throwing when the screen
|
|
8
|
+
* cannot be opened (ActivityNotFoundException, SecurityException).
|
|
9
|
+
*/
|
|
10
|
+
interface DeviceSettings {
|
|
11
|
+
/** Opens the battery-optimization list (`ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS`). */
|
|
12
|
+
fun openBatteryOptimizationSettings(activity: Activity?): Boolean
|
|
13
|
+
|
|
14
|
+
fun powerManagerInfo(): PowerManagerInfo
|
|
15
|
+
|
|
16
|
+
fun openPowerManagerSettings(activity: Activity?): Boolean
|
|
17
|
+
|
|
18
|
+
fun openLocationSettings(activity: Activity?): Boolean
|
|
19
|
+
|
|
20
|
+
fun openAppSettings(activity: Activity?): Boolean
|
|
21
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
package com.brickssoft.locationtracking.settings
|
|
2
|
+
|
|
3
|
+
import android.content.ComponentName
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.content.Intent
|
|
6
|
+
import android.content.pm.PackageManager
|
|
7
|
+
import android.net.Uri
|
|
8
|
+
import com.brickssoft.locationtracking.core.Logger
|
|
9
|
+
import java.util.Locale
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Known OEM power-manager / auto-start / background-activity screens, picked by `Build.MANUFACTURER`
|
|
13
|
+
* (case-insensitive). Every package listed here is declared in the plugin manifest's `<queries>`, so
|
|
14
|
+
* `resolveActivity` can see it on Android 11+. All entries are best-effort: OEMs move these screens between
|
|
15
|
+
* ROM versions, which is why each maker has several candidates, tried in order.
|
|
16
|
+
*/
|
|
17
|
+
internal object OemPowerManagers {
|
|
18
|
+
private const val TAG = "LT.DeviceSettings"
|
|
19
|
+
|
|
20
|
+
/** Value put into an extra of an [OemScreen] intent. */
|
|
21
|
+
enum class ExtraValue { PACKAGE_NAME, APP_LABEL }
|
|
22
|
+
|
|
23
|
+
/** One candidate screen: an explicit component (or an action scoped to [packageName]). */
|
|
24
|
+
data class OemScreen(
|
|
25
|
+
val packageName: String,
|
|
26
|
+
val className: String?,
|
|
27
|
+
val action: String? = null,
|
|
28
|
+
val dataUri: String? = null,
|
|
29
|
+
val extras: Map<String, ExtraValue> = emptyMap(),
|
|
30
|
+
) {
|
|
31
|
+
fun toIntent(context: Context): Intent {
|
|
32
|
+
val intent = if (className != null) {
|
|
33
|
+
Intent().setComponent(ComponentName(packageName, className))
|
|
34
|
+
} else {
|
|
35
|
+
Intent().setPackage(packageName)
|
|
36
|
+
}
|
|
37
|
+
action?.let { intent.setAction(it) }
|
|
38
|
+
dataUri?.let { intent.setData(Uri.parse(it)) }
|
|
39
|
+
extras.forEach { (key, value) ->
|
|
40
|
+
when (value) {
|
|
41
|
+
ExtraValue.PACKAGE_NAME -> intent.putExtra(key, context.packageName)
|
|
42
|
+
ExtraValue.APP_LABEL -> intent.putExtra(key, appLabel(context))
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return intent
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private class Family(val manufacturers: Set<String>, val screens: List<OemScreen>)
|
|
50
|
+
|
|
51
|
+
private const val HUAWEI = "com.huawei.systemmanager"
|
|
52
|
+
private const val HONOR = "com.hihonor.systemmanager"
|
|
53
|
+
private const val MIUI_SECURITY = "com.miui.securitycenter"
|
|
54
|
+
private const val MIUI_POWER = "com.miui.powerkeeper"
|
|
55
|
+
private const val COLOROS_SAFE = "com.coloros.safecenter"
|
|
56
|
+
private const val OPPO_SAFE = "com.oppo.safe"
|
|
57
|
+
private const val COLOROS_GUARD = "com.coloros.oppoguardelf"
|
|
58
|
+
private const val IQOO = "com.iqoo.secure"
|
|
59
|
+
private const val VIVO_PERMISSIONS = "com.vivo.permissionmanager"
|
|
60
|
+
private const val SAMSUNG_LOOL = "com.samsung.android.lool"
|
|
61
|
+
private const val SAMSUNG_SM = "com.samsung.android.sm"
|
|
62
|
+
private const val ONEPLUS = "com.oneplus.security"
|
|
63
|
+
private const val ASUS = "com.asus.mobilemanager"
|
|
64
|
+
private const val LETV = "com.letv.android.letvsafe"
|
|
65
|
+
private const val HTC = "com.htc.pitroad"
|
|
66
|
+
private const val MEIZU = "com.meizu.safe"
|
|
67
|
+
private const val NOKIA = "com.evenwell.powersaving.g3"
|
|
68
|
+
|
|
69
|
+
private val huaweiScreens = listOf(
|
|
70
|
+
OemScreen(HUAWEI, "$HUAWEI.startupmgr.ui.StartupNormalAppListActivity"),
|
|
71
|
+
OemScreen(HUAWEI, "$HUAWEI.optimize.process.ProtectActivity"),
|
|
72
|
+
OemScreen(HUAWEI, "$HUAWEI.appcontrol.activity.StartupAppControlActivity"),
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
private val honorScreens = listOf(
|
|
76
|
+
OemScreen(HONOR, "$HONOR.startupmgr.ui.StartupNormalAppListActivity"),
|
|
77
|
+
OemScreen(HONOR, "$HONOR.optimize.process.ProtectActivity"),
|
|
78
|
+
OemScreen(HONOR, "$HONOR.appcontrol.activity.StartupAppControlActivity"),
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
private val xiaomiScreens = listOf(
|
|
82
|
+
OemScreen(MIUI_SECURITY, "com.miui.permcenter.autostart.AutoStartManagementActivity"),
|
|
83
|
+
OemScreen(
|
|
84
|
+
MIUI_POWER,
|
|
85
|
+
"$MIUI_POWER.ui.HiddenAppsConfigActivity",
|
|
86
|
+
extras = mapOf("package_name" to ExtraValue.PACKAGE_NAME, "package_label" to ExtraValue.APP_LABEL),
|
|
87
|
+
),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
private val colorOsScreens = listOf(
|
|
91
|
+
OemScreen(COLOROS_SAFE, "$COLOROS_SAFE.permission.startup.StartupAppListActivity"),
|
|
92
|
+
OemScreen(COLOROS_SAFE, "$COLOROS_SAFE.startupapp.StartupAppListActivity"),
|
|
93
|
+
OemScreen(OPPO_SAFE, "$OPPO_SAFE.permission.startup.StartupAppListActivity"),
|
|
94
|
+
OemScreen(COLOROS_GUARD, "com.coloros.powermanager.fuelgaue.PowerUsageModelActivity"),
|
|
95
|
+
OemScreen(COLOROS_GUARD, "com.coloros.powermanager.fuelgaue.PowerSaverModeActivity"),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
private val vivoScreens = listOf(
|
|
99
|
+
OemScreen(IQOO, "$IQOO.ui.phoneoptimize.AddWhiteListActivity"),
|
|
100
|
+
OemScreen(IQOO, "$IQOO.ui.phoneoptimize.BgStartUpManager"),
|
|
101
|
+
OemScreen(VIVO_PERMISSIONS, "$VIVO_PERMISSIONS.activity.BgStartUpManagerActivity"),
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
private val samsungScreens = listOf(
|
|
105
|
+
OemScreen(SAMSUNG_LOOL, "com.samsung.android.sm.ui.battery.BatteryActivity"),
|
|
106
|
+
OemScreen(SAMSUNG_LOOL, "com.samsung.android.sm.battery.ui.BatteryActivity"),
|
|
107
|
+
OemScreen(SAMSUNG_SM, "com.samsung.android.sm.ui.battery.BatteryActivity"),
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
private val onePlusScreens = listOf(
|
|
111
|
+
OemScreen(ONEPLUS, "$ONEPLUS.chainlaunch.view.ChainLaunchAppListActivity"),
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
private val asusScreens = listOf(
|
|
115
|
+
OemScreen(ASUS, "$ASUS.entry.FunctionActivity", dataUri = "mobilemanager://function/entry/AutoStart"),
|
|
116
|
+
OemScreen(ASUS, "$ASUS.autostart.AutoStartActivity"),
|
|
117
|
+
OemScreen(ASUS, "$ASUS.MainActivity"),
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
private val letvScreens = listOf(
|
|
121
|
+
OemScreen(LETV, "$LETV.AutobootManageActivity"),
|
|
122
|
+
OemScreen(LETV, "$LETV.BackgroundAppManageActivity"),
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
private val htcScreens = listOf(
|
|
126
|
+
OemScreen(HTC, "$HTC.landingpage.activity.LandingPageActivity"),
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
private val meizuScreens = listOf(
|
|
130
|
+
OemScreen(MEIZU, "$MEIZU.permission.SmartBGActivity"),
|
|
131
|
+
OemScreen(
|
|
132
|
+
MEIZU,
|
|
133
|
+
className = null,
|
|
134
|
+
action = "com.meizu.safe.security.SHOW_APPSEC",
|
|
135
|
+
extras = mapOf("packageName" to ExtraValue.PACKAGE_NAME),
|
|
136
|
+
),
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
private val nokiaScreens = listOf(
|
|
140
|
+
OemScreen(NOKIA, "$NOKIA.exception.PowerSaverExceptionActivity"),
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
private val families = listOf(
|
|
144
|
+
Family(setOf("huawei"), huaweiScreens),
|
|
145
|
+
// Honor phones from before the 2020 split still ship Huawei's system manager.
|
|
146
|
+
Family(setOf("honor"), honorScreens + huaweiScreens),
|
|
147
|
+
Family(setOf("xiaomi", "redmi", "poco"), xiaomiScreens),
|
|
148
|
+
Family(setOf("oppo", "realme"), colorOsScreens),
|
|
149
|
+
// OxygenOS 12+ is built on ColorOS.
|
|
150
|
+
Family(setOf("oneplus"), onePlusScreens + colorOsScreens),
|
|
151
|
+
Family(setOf("vivo", "iqoo"), vivoScreens),
|
|
152
|
+
Family(setOf("samsung"), samsungScreens),
|
|
153
|
+
Family(setOf("asus"), asusScreens),
|
|
154
|
+
Family(setOf("letv", "leeco", "lemobile"), letvScreens),
|
|
155
|
+
Family(setOf("htc"), htcScreens),
|
|
156
|
+
Family(setOf("meizu"), meizuScreens),
|
|
157
|
+
Family(setOf("hmd global", "nokia"), nokiaScreens),
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
/** Every package the table can target (all must be in the manifest `<queries>`). */
|
|
161
|
+
val allPackages: Set<String> get() = families.flatMap { f -> f.screens.map { it.packageName } }.toSet()
|
|
162
|
+
|
|
163
|
+
/** Candidate screens for [manufacturer], in the order they should be tried. */
|
|
164
|
+
fun screensFor(manufacturer: String?): List<OemScreen> {
|
|
165
|
+
val name = manufacturer?.trim()?.lowercase(Locale.ROOT).orEmpty()
|
|
166
|
+
if (name.isEmpty()) return emptyList()
|
|
167
|
+
return families
|
|
168
|
+
.filter { family -> family.manufacturers.any { name == it || name.startsWith("$it ") } }
|
|
169
|
+
.flatMap { it.screens }
|
|
170
|
+
.distinct()
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Intents for [manufacturer]'s screens that resolve to an exported activity we are allowed to start. */
|
|
174
|
+
fun launchableIntents(context: Context, manufacturer: String?): List<Intent> =
|
|
175
|
+
screensFor(manufacturer).map { it.toIntent(context) }.filter { isLaunchable(context, it) }
|
|
176
|
+
|
|
177
|
+
private fun isLaunchable(context: Context, intent: Intent): Boolean {
|
|
178
|
+
val info = try {
|
|
179
|
+
@Suppress("DEPRECATION")
|
|
180
|
+
// MATCH_DEFAULT_ONLY: startActivity adds CATEGORY_DEFAULT to the package-scoped (implicit) intents.
|
|
181
|
+
context.packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)
|
|
182
|
+
} catch (e: RuntimeException) {
|
|
183
|
+
Logger.w(TAG, "resolveActivity failed for $intent", e)
|
|
184
|
+
null
|
|
185
|
+
}
|
|
186
|
+
val activity = info?.activityInfo ?: return false
|
|
187
|
+
if (!activity.exported) return false
|
|
188
|
+
val permission = activity.permission ?: return true
|
|
189
|
+
return context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private fun appLabel(context: Context): String =
|
|
193
|
+
try {
|
|
194
|
+
context.applicationInfo.loadLabel(context.packageManager).toString()
|
|
195
|
+
} catch (e: RuntimeException) {
|
|
196
|
+
context.packageName
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<!-- Monochrome location pin used as the default foreground-service notification icon. -->
|
|
3
|
+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
4
|
+
android:width="24dp"
|
|
5
|
+
android:height="24dp"
|
|
6
|
+
android:viewportWidth="24"
|
|
7
|
+
android:viewportHeight="24">
|
|
8
|
+
<path
|
|
9
|
+
android:fillColor="#FFFFFFFF"
|
|
10
|
+
android:pathData="M12,2C8.13,2 5,5.13 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13c0,-3.87 -3.13,-7 -7,-7zM12,11.5c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5z" />
|
|
11
|
+
</vector>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<resources>
|
|
3
|
+
<string name="lt_notification_text">Location tracking is active</string>
|
|
4
|
+
<string name="lt_notification_channel_name">Location tracking</string>
|
|
5
|
+
<string name="lt_resume_notification_text">Location tracking is paused. Tap to resume.</string>
|
|
6
|
+
<string name="lt_resume_channel_name">Paused location tracking</string>
|
|
7
|
+
<string name="lt_bg_rationale_title">Allow background location</string>
|
|
8
|
+
<string name="lt_bg_rationale_message">To keep tracking your location while the app is closed or not in use, select \"Allow all the time\" on the next screen.</string>
|
|
9
|
+
<string name="lt_bg_rationale_positive">Change to \"Allow all the time\"</string>
|
|
10
|
+
<string name="lt_bg_rationale_negative">Not now</string>
|
|
11
|
+
</resources>
|