@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.
Files changed (168) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2759 -2
  3. package/android/build.gradle +107 -0
  4. package/android/consumer-rules.pro +42 -0
  5. package/android/src/main/AndroidManifest.xml +52 -0
  6. package/android/src/main/java/com/brickssoft/locationtracking/LocationTrackingPlugin.kt +431 -0
  7. package/android/src/main/java/com/brickssoft/locationtracking/api/LocationTrackingListener.kt +52 -0
  8. package/android/src/main/java/com/brickssoft/locationtracking/api/LocationTrackingNative.kt +267 -0
  9. package/android/src/main/java/com/brickssoft/locationtracking/api/NativeListeners.kt +270 -0
  10. package/android/src/main/java/com/brickssoft/locationtracking/api/NativeThread.kt +57 -0
  11. package/android/src/main/java/com/brickssoft/locationtracking/bridge/BridgeServices.kt +64 -0
  12. package/android/src/main/java/com/brickssoft/locationtracking/bridge/CallResponder.kt +41 -0
  13. package/android/src/main/java/com/brickssoft/locationtracking/bridge/OptionParsers.kt +281 -0
  14. package/android/src/main/java/com/brickssoft/locationtracking/bridge/PluginHandlers.kt +541 -0
  15. package/android/src/main/java/com/brickssoft/locationtracking/bridge/PluginPermissionHost.kt +76 -0
  16. package/android/src/main/java/com/brickssoft/locationtracking/bridge/WatchRegistry.kt +84 -0
  17. package/android/src/main/java/com/brickssoft/locationtracking/config/Config.kt +231 -0
  18. package/android/src/main/java/com/brickssoft/locationtracking/config/ConfigJson.kt +574 -0
  19. package/android/src/main/java/com/brickssoft/locationtracking/config/ConfigStore.kt +31 -0
  20. package/android/src/main/java/com/brickssoft/locationtracking/config/ConfigValidator.kt +170 -0
  21. package/android/src/main/java/com/brickssoft/locationtracking/config/RuntimeStateJson.kt +73 -0
  22. package/android/src/main/java/com/brickssoft/locationtracking/config/SharedPrefsConfigStore.kt +135 -0
  23. package/android/src/main/java/com/brickssoft/locationtracking/config/State.kt +45 -0
  24. package/android/src/main/java/com/brickssoft/locationtracking/core/AppDispatchers.kt +21 -0
  25. package/android/src/main/java/com/brickssoft/locationtracking/core/Clock.kt +41 -0
  26. package/android/src/main/java/com/brickssoft/locationtracking/core/Components.kt +217 -0
  27. package/android/src/main/java/com/brickssoft/locationtracking/core/Constants.kt +78 -0
  28. package/android/src/main/java/com/brickssoft/locationtracking/core/Errors.kt +27 -0
  29. package/android/src/main/java/com/brickssoft/locationtracking/core/EventBus.kt +39 -0
  30. package/android/src/main/java/com/brickssoft/locationtracking/core/ForceStopProbe.kt +63 -0
  31. package/android/src/main/java/com/brickssoft/locationtracking/core/Iso8601.kt +63 -0
  32. package/android/src/main/java/com/brickssoft/locationtracking/core/Logger.kt +68 -0
  33. package/android/src/main/java/com/brickssoft/locationtracking/core/RecordHooks.kt +47 -0
  34. package/android/src/main/java/com/brickssoft/locationtracking/core/TrackingEvent.kt +64 -0
  35. package/android/src/main/java/com/brickssoft/locationtracking/data/SqlSupport.kt +79 -0
  36. package/android/src/main/java/com/brickssoft/locationtracking/data/SqliteGeofenceStore.kt +144 -0
  37. package/android/src/main/java/com/brickssoft/locationtracking/data/SqliteLocationStore.kt +215 -0
  38. package/android/src/main/java/com/brickssoft/locationtracking/data/Stores.kt +45 -0
  39. package/android/src/main/java/com/brickssoft/locationtracking/data/TrackingDatabase.kt +111 -0
  40. package/android/src/main/java/com/brickssoft/locationtracking/device/DefaultDeviceInfoProvider.kt +104 -0
  41. package/android/src/main/java/com/brickssoft/locationtracking/device/DefaultDeviceMonitor.kt +335 -0
  42. package/android/src/main/java/com/brickssoft/locationtracking/device/DeviceMonitor.kt +36 -0
  43. package/android/src/main/java/com/brickssoft/locationtracking/device/DeviceStateMapping.kt +69 -0
  44. package/android/src/main/java/com/brickssoft/locationtracking/engine/DefaultTrackingEngine.kt +1281 -0
  45. package/android/src/main/java/com/brickssoft/locationtracking/engine/ElasticDistance.kt +68 -0
  46. package/android/src/main/java/com/brickssoft/locationtracking/engine/LocationRequests.kt +74 -0
  47. package/android/src/main/java/com/brickssoft/locationtracking/engine/MotionStateMachine.kt +291 -0
  48. package/android/src/main/java/com/brickssoft/locationtracking/engine/StationaryRegion.kt +41 -0
  49. package/android/src/main/java/com/brickssoft/locationtracking/engine/TrackingEngine.kt +60 -0
  50. package/android/src/main/java/com/brickssoft/locationtracking/engine/TrackingTimers.kt +90 -0
  51. package/android/src/main/java/com/brickssoft/locationtracking/geofence/DefaultGeofenceManager.kt +899 -0
  52. package/android/src/main/java/com/brickssoft/locationtracking/geofence/DwellTracker.kt +80 -0
  53. package/android/src/main/java/com/brickssoft/locationtracking/geofence/GeofenceManager.kt +33 -0
  54. package/android/src/main/java/com/brickssoft/locationtracking/geofence/PolygonMath.kt +194 -0
  55. package/android/src/main/java/com/brickssoft/locationtracking/heartbeat/DefaultHeartbeatScheduler.kt +713 -0
  56. package/android/src/main/java/com/brickssoft/locationtracking/heartbeat/HeartbeatAlarmReceiver.kt +69 -0
  57. package/android/src/main/java/com/brickssoft/locationtracking/heartbeat/HeartbeatAlarms.kt +100 -0
  58. package/android/src/main/java/com/brickssoft/locationtracking/heartbeat/HeartbeatScheduler.kt +25 -0
  59. package/android/src/main/java/com/brickssoft/locationtracking/heartbeat/HeartbeatWindow.kt +179 -0
  60. package/android/src/main/java/com/brickssoft/locationtracking/http/AuthorizationManager.kt +213 -0
  61. package/android/src/main/java/com/brickssoft/locationtracking/http/BodyBuilder.kt +73 -0
  62. package/android/src/main/java/com/brickssoft/locationtracking/http/HttpSupport.kt +118 -0
  63. package/android/src/main/java/com/brickssoft/locationtracking/http/HttpSyncer.kt +18 -0
  64. package/android/src/main/java/com/brickssoft/locationtracking/http/OkHttpSyncer.kt +551 -0
  65. package/android/src/main/java/com/brickssoft/locationtracking/http/StrictJson.kt +135 -0
  66. package/android/src/main/java/com/brickssoft/locationtracking/http/SyncPolicy.kt +109 -0
  67. package/android/src/main/java/com/brickssoft/locationtracking/http/TemplateRenderer.kt +143 -0
  68. package/android/src/main/java/com/brickssoft/locationtracking/logging/FileLogger.kt +461 -0
  69. package/android/src/main/java/com/brickssoft/locationtracking/logging/LogFileProvider.kt +9 -0
  70. package/android/src/main/java/com/brickssoft/locationtracking/logging/LogFormatter.kt +148 -0
  71. package/android/src/main/java/com/brickssoft/locationtracking/logging/LogStore.kt +30 -0
  72. package/android/src/main/java/com/brickssoft/locationtracking/model/DeviceModels.kt +176 -0
  73. package/android/src/main/java/com/brickssoft/locationtracking/model/GeofenceSpec.kt +25 -0
  74. package/android/src/main/java/com/brickssoft/locationtracking/model/ModelJson.kt +550 -0
  75. package/android/src/main/java/com/brickssoft/locationtracking/model/Record.kt +106 -0
  76. package/android/src/main/java/com/brickssoft/locationtracking/model/TrackedLocation.kt +53 -0
  77. package/android/src/main/java/com/brickssoft/locationtracking/permission/BackgroundRationaleDialog.kt +85 -0
  78. package/android/src/main/java/com/brickssoft/locationtracking/permission/CurrentActivityTracker.kt +72 -0
  79. package/android/src/main/java/com/brickssoft/locationtracking/permission/DefaultPermissionManager.kt +333 -0
  80. package/android/src/main/java/com/brickssoft/locationtracking/permission/PermissionManager.kt +54 -0
  81. package/android/src/main/java/com/brickssoft/locationtracking/position/DefaultPositionService.kt +277 -0
  82. package/android/src/main/java/com/brickssoft/locationtracking/position/PositionService.kt +39 -0
  83. package/android/src/main/java/com/brickssoft/locationtracking/position/PositionWatch.kt +115 -0
  84. package/android/src/main/java/com/brickssoft/locationtracking/processing/DefaultLocationProcessor.kt +93 -0
  85. package/android/src/main/java/com/brickssoft/locationtracking/processing/DefaultOdometer.kt +99 -0
  86. package/android/src/main/java/com/brickssoft/locationtracking/processing/DefaultRecordFactory.kt +166 -0
  87. package/android/src/main/java/com/brickssoft/locationtracking/processing/Geo.kt +56 -0
  88. package/android/src/main/java/com/brickssoft/locationtracking/processing/KalmanFilter.kt +72 -0
  89. package/android/src/main/java/com/brickssoft/locationtracking/processing/Processing.kt +48 -0
  90. package/android/src/main/java/com/brickssoft/locationtracking/provider/Backends.kt +187 -0
  91. package/android/src/main/java/com/brickssoft/locationtracking/provider/DefaultProviderFactory.kt +169 -0
  92. package/android/src/main/java/com/brickssoft/locationtracking/provider/ProviderPackaging.kt +135 -0
  93. package/android/src/main/java/com/brickssoft/locationtracking/provider/android/AndroidActivityBackend.kt +25 -0
  94. package/android/src/main/java/com/brickssoft/locationtracking/provider/android/AndroidGeofenceBackend.kt +181 -0
  95. package/android/src/main/java/com/brickssoft/locationtracking/provider/android/AndroidGeofenceReceiver.kt +115 -0
  96. package/android/src/main/java/com/brickssoft/locationtracking/provider/android/AndroidLocationBackend.kt +265 -0
  97. package/android/src/main/java/com/brickssoft/locationtracking/provider/android/AndroidProviderBundle.kt +31 -0
  98. package/android/src/main/java/com/brickssoft/locationtracking/provider/android/LocationProviders.kt +98 -0
  99. package/android/src/main/java/com/brickssoft/locationtracking/provider/gms/GmsActivityBackend.kt +80 -0
  100. package/android/src/main/java/com/brickssoft/locationtracking/provider/gms/GmsActivityReceiver.kt +62 -0
  101. package/android/src/main/java/com/brickssoft/locationtracking/provider/gms/GmsErrors.kt +72 -0
  102. package/android/src/main/java/com/brickssoft/locationtracking/provider/gms/GmsGeofenceBackend.kt +152 -0
  103. package/android/src/main/java/com/brickssoft/locationtracking/provider/gms/GmsGeofenceReceiver.kt +78 -0
  104. package/android/src/main/java/com/brickssoft/locationtracking/provider/gms/GmsLocationBackend.kt +176 -0
  105. package/android/src/main/java/com/brickssoft/locationtracking/provider/gms/GmsProviderBundle.kt +62 -0
  106. package/android/src/main/java/com/brickssoft/locationtracking/provider/gms/GmsReceiverSupport.kt +88 -0
  107. package/android/src/main/java/com/brickssoft/locationtracking/provider/gms/GmsTasks.kt +55 -0
  108. package/android/src/main/java/com/brickssoft/locationtracking/provider/hms/HmsActivityBackend.kt +71 -0
  109. package/android/src/main/java/com/brickssoft/locationtracking/provider/hms/HmsActivityReceiver.kt +57 -0
  110. package/android/src/main/java/com/brickssoft/locationtracking/provider/hms/HmsErrors.kt +76 -0
  111. package/android/src/main/java/com/brickssoft/locationtracking/provider/hms/HmsGeofenceBackend.kt +116 -0
  112. package/android/src/main/java/com/brickssoft/locationtracking/provider/hms/HmsGeofenceReceiver.kt +67 -0
  113. package/android/src/main/java/com/brickssoft/locationtracking/provider/hms/HmsIntents.kt +83 -0
  114. package/android/src/main/java/com/brickssoft/locationtracking/provider/hms/HmsLocationBackend.kt +197 -0
  115. package/android/src/main/java/com/brickssoft/locationtracking/provider/hms/HmsProviderBundle.kt +55 -0
  116. package/android/src/main/java/com/brickssoft/locationtracking/provider/hms/HmsTasks.kt +44 -0
  117. package/android/src/main/java/com/brickssoft/locationtracking/record/RecordSink.kt +95 -0
  118. package/android/src/main/java/com/brickssoft/locationtracking/service/BootCountStore.kt +44 -0
  119. package/android/src/main/java/com/brickssoft/locationtracking/service/BootReceiver.kt +175 -0
  120. package/android/src/main/java/com/brickssoft/locationtracking/service/DefaultServiceController.kt +196 -0
  121. package/android/src/main/java/com/brickssoft/locationtracking/service/LocationTrackingService.kt +319 -0
  122. package/android/src/main/java/com/brickssoft/locationtracking/service/NotificationActionReceiver.kt +42 -0
  123. package/android/src/main/java/com/brickssoft/locationtracking/service/NotificationFactory.kt +338 -0
  124. package/android/src/main/java/com/brickssoft/locationtracking/service/ServiceCommands.kt +204 -0
  125. package/android/src/main/java/com/brickssoft/locationtracking/service/ServiceController.kt +22 -0
  126. package/android/src/main/java/com/brickssoft/locationtracking/service/ServiceDeps.kt +61 -0
  127. package/android/src/main/java/com/brickssoft/locationtracking/service/WhileInUseStartCheck.kt +115 -0
  128. package/android/src/main/java/com/brickssoft/locationtracking/settings/DefaultDeviceSettings.kt +79 -0
  129. package/android/src/main/java/com/brickssoft/locationtracking/settings/DeviceSettings.kt +21 -0
  130. package/android/src/main/java/com/brickssoft/locationtracking/settings/OemPowerManagers.kt +198 -0
  131. package/android/src/main/res/drawable/lt_ic_notification.xml +11 -0
  132. package/android/src/main/res/values/lt_strings.xml +11 -0
  133. package/android/src/main/res/xml/lt_file_paths.xml +5 -0
  134. package/dist/esm/definitions.d.ts +719 -0
  135. package/dist/esm/definitions.js +2 -0
  136. package/dist/esm/definitions.js.map +1 -0
  137. package/dist/esm/events.d.ts +50 -0
  138. package/dist/esm/events.js +52 -0
  139. package/dist/esm/events.js.map +1 -0
  140. package/dist/esm/index.d.ts +3 -0
  141. package/dist/esm/index.js +5 -0
  142. package/dist/esm/index.js.map +1 -0
  143. package/dist/esm/plugin.d.ts +3 -0
  144. package/dist/esm/plugin.js +10 -0
  145. package/dist/esm/plugin.js.map +1 -0
  146. package/dist/esm/web/config.d.ts +19 -0
  147. package/dist/esm/web/config.js +243 -0
  148. package/dist/esm/web/config.js.map +1 -0
  149. package/dist/esm/web/device.d.ts +9 -0
  150. package/dist/esm/web/device.js +90 -0
  151. package/dist/esm/web/device.js.map +1 -0
  152. package/dist/esm/web/errors.d.ts +14 -0
  153. package/dist/esm/web/errors.js +19 -0
  154. package/dist/esm/web/errors.js.map +1 -0
  155. package/dist/esm/web/geolocation.d.ts +26 -0
  156. package/dist/esm/web/geolocation.js +117 -0
  157. package/dist/esm/web/geolocation.js.map +1 -0
  158. package/dist/esm/web/location.d.ts +7 -0
  159. package/dist/esm/web/location.js +70 -0
  160. package/dist/esm/web/location.js.map +1 -0
  161. package/dist/esm/web.d.ts +164 -0
  162. package/dist/esm/web.js +328 -0
  163. package/dist/esm/web.js.map +1 -0
  164. package/dist/plugin.cjs.js +945 -0
  165. package/dist/plugin.cjs.js.map +1 -0
  166. package/dist/plugin.js +948 -0
  167. package/dist/plugin.js.map +1 -0
  168. package/package.json +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
+ }
@@ -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>
@@ -0,0 +1,5 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <paths>
3
+ <files-path name="lt_logs" path="location-tracking-logs/" />
4
+ <cache-path name="lt_logs_cache" path="location-tracking-logs/" />
5
+ </paths>