@josuelmm/capacitor-background-geolocation 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/JosuelmmCapacitorBackgroundGeolocation.podspec +34 -0
  2. package/LICENSE +17 -0
  3. package/NOTICE.md +32 -0
  4. package/Package.swift +45 -0
  5. package/README.md +402 -0
  6. package/android/build.gradle +79 -0
  7. package/android/proguard-rules.pro +1 -0
  8. package/android/src/main/AndroidManifest.xml +83 -0
  9. package/android/src/main/java/com/evgenii/jsevaluator/HandlerWrapper.java +18 -0
  10. package/android/src/main/java/com/evgenii/jsevaluator/JavaScriptInterface.java +22 -0
  11. package/android/src/main/java/com/evgenii/jsevaluator/JsEvaluator.java +133 -0
  12. package/android/src/main/java/com/evgenii/jsevaluator/JsFunctionCallFormatter.java +37 -0
  13. package/android/src/main/java/com/evgenii/jsevaluator/WebViewWrapper.java +71 -0
  14. package/android/src/main/java/com/evgenii/jsevaluator/interfaces/CallJavaResultInterface.java +8 -0
  15. package/android/src/main/java/com/evgenii/jsevaluator/interfaces/HandlerWrapperInterface.java +5 -0
  16. package/android/src/main/java/com/evgenii/jsevaluator/interfaces/JsCallback.java +10 -0
  17. package/android/src/main/java/com/evgenii/jsevaluator/interfaces/JsEvaluatorInterface.java +18 -0
  18. package/android/src/main/java/com/evgenii/jsevaluator/interfaces/WebViewWrapperInterface.java +14 -0
  19. package/android/src/main/java/com/josuelmm/capacitor/backgroundgeolocation/BackgroundGeolocationPlugin.java +898 -0
  20. package/android/src/main/java/com/josuelmm/capacitor/backgroundgeolocation/ConfigMapper.java +303 -0
  21. package/android/src/main/java/com/josuelmm/capacitor/backgroundgeolocation/HeadlessTaskRegistry.java +34 -0
  22. package/android/src/main/java/com/josuelmm/capacitor/backgroundgeolocation/JsEvaluatorTaskRunner.java +63 -0
  23. package/android/src/main/java/com/marianhello/bgloc/BackgroundGeolocationFacade.java +699 -0
  24. package/android/src/main/java/com/marianhello/bgloc/BootCompletedReceiver.java +103 -0
  25. package/android/src/main/java/com/marianhello/bgloc/Config.java +1155 -0
  26. package/android/src/main/java/com/marianhello/bgloc/ConnectivityListener.java +5 -0
  27. package/android/src/main/java/com/marianhello/bgloc/HttpPostService.java +362 -0
  28. package/android/src/main/java/com/marianhello/bgloc/LocationManager.java +138 -0
  29. package/android/src/main/java/com/marianhello/bgloc/PluginDelegate.java +45 -0
  30. package/android/src/main/java/com/marianhello/bgloc/PluginException.java +38 -0
  31. package/android/src/main/java/com/marianhello/bgloc/PostLocationTask.java +238 -0
  32. package/android/src/main/java/com/marianhello/bgloc/ResourceResolver.java +55 -0
  33. package/android/src/main/java/com/marianhello/bgloc/data/AbstractLocationTemplate.java +69 -0
  34. package/android/src/main/java/com/marianhello/bgloc/data/ArrayListLocationTemplate.java +88 -0
  35. package/android/src/main/java/com/marianhello/bgloc/data/BackgroundActivity.java +108 -0
  36. package/android/src/main/java/com/marianhello/bgloc/data/BackgroundLocation.java +1088 -0
  37. package/android/src/main/java/com/marianhello/bgloc/data/ConfigJsonMapper.java +211 -0
  38. package/android/src/main/java/com/marianhello/bgloc/data/ConfigurationDAO.java +13 -0
  39. package/android/src/main/java/com/marianhello/bgloc/data/DAOFactory.java +17 -0
  40. package/android/src/main/java/com/marianhello/bgloc/data/HashMapLocationTemplate.java +82 -0
  41. package/android/src/main/java/com/marianhello/bgloc/data/LocationDAO.java +27 -0
  42. package/android/src/main/java/com/marianhello/bgloc/data/LocationTemplate.java +12 -0
  43. package/android/src/main/java/com/marianhello/bgloc/data/LocationTemplateFactory.java +71 -0
  44. package/android/src/main/java/com/marianhello/bgloc/data/LocationTransform.java +19 -0
  45. package/android/src/main/java/com/marianhello/bgloc/data/SessionLocationDAO.java +18 -0
  46. package/android/src/main/java/com/marianhello/bgloc/data/provider/ContentProviderLocationDAO.java +406 -0
  47. package/android/src/main/java/com/marianhello/bgloc/data/provider/LocationContentProvider.java +321 -0
  48. package/android/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteConfigurationContract.java +94 -0
  49. package/android/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteConfigurationDAO.java +227 -0
  50. package/android/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteLocationContract.java +122 -0
  51. package/android/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteLocationDAO.java +550 -0
  52. package/android/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteOpenHelper.java +189 -0
  53. package/android/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteSessionContract.java +74 -0
  54. package/android/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteSessionLocationDAO.java +169 -0
  55. package/android/src/main/java/com/marianhello/bgloc/driving/DrivingEventsDetector.java +265 -0
  56. package/android/src/main/java/com/marianhello/bgloc/headless/AbstractTaskRunner.java +15 -0
  57. package/android/src/main/java/com/marianhello/bgloc/headless/ActivityTask.java +48 -0
  58. package/android/src/main/java/com/marianhello/bgloc/headless/JsCallback.java +10 -0
  59. package/android/src/main/java/com/marianhello/bgloc/headless/LocationTask.java +60 -0
  60. package/android/src/main/java/com/marianhello/bgloc/headless/StationaryTask.java +25 -0
  61. package/android/src/main/java/com/marianhello/bgloc/headless/Task.java +8 -0
  62. package/android/src/main/java/com/marianhello/bgloc/headless/TaskRunner.java +5 -0
  63. package/android/src/main/java/com/marianhello/bgloc/headless/TaskRunnerFactory.java +8 -0
  64. package/android/src/main/java/com/marianhello/bgloc/http/UrlTemplateResolver.java +115 -0
  65. package/android/src/main/java/com/marianhello/bgloc/oem/BatteryOemHelper.java +214 -0
  66. package/android/src/main/java/com/marianhello/bgloc/provider/AbstractLocationProvider.java +218 -0
  67. package/android/src/main/java/com/marianhello/bgloc/provider/ActivityRecognitionLocationProvider.java +385 -0
  68. package/android/src/main/java/com/marianhello/bgloc/provider/DistanceFilterLocationProvider.java +685 -0
  69. package/android/src/main/java/com/marianhello/bgloc/provider/LocationProvider.java +32 -0
  70. package/android/src/main/java/com/marianhello/bgloc/provider/LocationProviderFactory.java +47 -0
  71. package/android/src/main/java/com/marianhello/bgloc/provider/ProviderDelegate.java +12 -0
  72. package/android/src/main/java/com/marianhello/bgloc/provider/RawLocationProvider.java +175 -0
  73. package/android/src/main/java/com/marianhello/bgloc/sensor/SensorFusionDetector.java +199 -0
  74. package/android/src/main/java/com/marianhello/bgloc/service/LocationService.java +16 -0
  75. package/android/src/main/java/com/marianhello/bgloc/service/LocationServiceImpl.java +1531 -0
  76. package/android/src/main/java/com/marianhello/bgloc/service/LocationServiceInfo.java +6 -0
  77. package/android/src/main/java/com/marianhello/bgloc/service/LocationServiceInfoImpl.java +41 -0
  78. package/android/src/main/java/com/marianhello/bgloc/service/LocationServiceIntentBuilder.java +203 -0
  79. package/android/src/main/java/com/marianhello/bgloc/service/LocationServiceProxy.java +156 -0
  80. package/android/src/main/java/com/marianhello/bgloc/sync/AccountHelper.java +39 -0
  81. package/android/src/main/java/com/marianhello/bgloc/sync/Authenticator.java +68 -0
  82. package/android/src/main/java/com/marianhello/bgloc/sync/AuthenticatorService.java +28 -0
  83. package/android/src/main/java/com/marianhello/bgloc/sync/BatchManager.java +311 -0
  84. package/android/src/main/java/com/marianhello/bgloc/sync/NotificationHelper.java +148 -0
  85. package/android/src/main/java/com/marianhello/bgloc/sync/SyncAdapter.java +301 -0
  86. package/android/src/main/java/com/marianhello/bgloc/sync/SyncService.java +68 -0
  87. package/android/src/main/java/com/marianhello/logging/DBLogReader.java +208 -0
  88. package/android/src/main/java/com/marianhello/logging/LogEntry.java +99 -0
  89. package/android/src/main/java/com/marianhello/logging/LoggerManager.java +70 -0
  90. package/android/src/main/java/com/marianhello/logging/UncaughtExceptionLogger.java +36 -0
  91. package/android/src/main/java/com/marianhello/utils/CloneHelper.java +22 -0
  92. package/android/src/main/java/com/marianhello/utils/Convert.java +56 -0
  93. package/android/src/main/java/com/marianhello/utils/TextUtils.java +72 -0
  94. package/android/src/main/java/com/marianhello/utils/ToneGenerator.java +68 -0
  95. package/android/src/main/java/org/apache/commons/io/Charsets.java +153 -0
  96. package/android/src/main/java/org/apache/commons/io/input/ReversedLinesFileReader.java +344 -0
  97. package/android/src/main/java/org/chromium/content/browser/ThreadUtils.java +134 -0
  98. package/android/src/main/java/ru/andremoniy/sqlbuilder/SqlExpression.java +398 -0
  99. package/android/src/main/java/ru/andremoniy/sqlbuilder/SqlSelectStatement.java +671 -0
  100. package/android/src/main/java/ru/andremoniy/sqlbuilder/SqlStatement.java +29 -0
  101. package/android/src/main/java/ru/andremoniy/utils/TextUtils.java +61 -0
  102. package/android/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  103. package/android/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  104. package/android/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  105. package/android/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  106. package/android/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  107. package/android/src/main/res/values/strings.xml +15 -0
  108. package/android/src/main/res/xml/authenticator.xml +7 -0
  109. package/android/src/main/res/xml/syncadapter.xml +9 -0
  110. package/dist/esm/definitions.d.ts +1052 -0
  111. package/dist/esm/definitions.js +142 -0
  112. package/dist/esm/definitions.js.map +1 -0
  113. package/dist/esm/index.d.ts +8 -0
  114. package/dist/esm/index.js +23 -0
  115. package/dist/esm/index.js.map +1 -0
  116. package/dist/esm/web.d.ts +92 -0
  117. package/dist/esm/web.js +242 -0
  118. package/dist/esm/web.js.map +1 -0
  119. package/dist/plugin.cjs.js +415 -0
  120. package/dist/plugin.cjs.js.map +1 -0
  121. package/dist/plugin.js +418 -0
  122. package/dist/plugin.js.map +1 -0
  123. package/ios/Sources/BackgroundGeolocationPlugin/BackgroundGeolocationPlugin-Bridging-Header.h +18 -0
  124. package/ios/Sources/BackgroundGeolocationPlugin/BackgroundGeolocationPlugin.m +52 -0
  125. package/ios/Sources/BackgroundGeolocationPlugin/BackgroundGeolocationPlugin.swift +750 -0
  126. package/ios/Tests/BackgroundGeolocationPluginTests/BackgroundGeolocationPluginTests.swift +12 -0
  127. package/ios/common/BackgroundGeolocation/CocoaLumberjack.h +1945 -0
  128. package/ios/common/BackgroundGeolocation/CocoaLumberjack.m +5255 -0
  129. package/ios/common/BackgroundGeolocation/FMDB.h +2357 -0
  130. package/ios/common/BackgroundGeolocation/FMDB.m +2672 -0
  131. package/ios/common/BackgroundGeolocation/FMDBLogger.h +42 -0
  132. package/ios/common/BackgroundGeolocation/FMDBLogger.m +264 -0
  133. package/ios/common/BackgroundGeolocation/INTULocationManager/INTUHeadingRequest.h +41 -0
  134. package/ios/common/BackgroundGeolocation/INTULocationManager/INTUHeadingRequest.m +68 -0
  135. package/ios/common/BackgroundGeolocation/INTULocationManager/INTULocationManager+Internal.h +33 -0
  136. package/ios/common/BackgroundGeolocation/INTULocationManager/INTULocationManager.h +178 -0
  137. package/ios/common/BackgroundGeolocation/INTULocationManager/INTULocationManager.m +1025 -0
  138. package/ios/common/BackgroundGeolocation/INTULocationManager/INTULocationRequest.h +103 -0
  139. package/ios/common/BackgroundGeolocation/INTULocationManager/INTULocationRequest.m +238 -0
  140. package/ios/common/BackgroundGeolocation/INTULocationManager/INTULocationRequestDefines.h +163 -0
  141. package/ios/common/BackgroundGeolocation/INTULocationManager/INTURequestIDGenerator.h +39 -0
  142. package/ios/common/BackgroundGeolocation/INTULocationManager/INTURequestIDGenerator.m +37 -0
  143. package/ios/common/BackgroundGeolocation/MAURAbstractLocationProvider.h +51 -0
  144. package/ios/common/BackgroundGeolocation/MAURAbstractLocationProvider.m +53 -0
  145. package/ios/common/BackgroundGeolocation/MAURActivity.h +23 -0
  146. package/ios/common/BackgroundGeolocation/MAURActivity.m +52 -0
  147. package/ios/common/BackgroundGeolocation/MAURActivityLocationProvider.h +18 -0
  148. package/ios/common/BackgroundGeolocation/MAURActivityLocationProvider.m +340 -0
  149. package/ios/common/BackgroundGeolocation/MAURBackgroundGeolocationFacade.h +88 -0
  150. package/ios/common/BackgroundGeolocation/MAURBackgroundGeolocationFacade.m +1193 -0
  151. package/ios/common/BackgroundGeolocation/MAURBackgroundSync.h +46 -0
  152. package/ios/common/BackgroundGeolocation/MAURBackgroundSync.m +283 -0
  153. package/ios/common/BackgroundGeolocation/MAURBackgroundTaskManager.h +25 -0
  154. package/ios/common/BackgroundGeolocation/MAURBackgroundTaskManager.m +105 -0
  155. package/ios/common/BackgroundGeolocation/MAURConfig.h +99 -0
  156. package/ios/common/BackgroundGeolocation/MAURConfig.m +636 -0
  157. package/ios/common/BackgroundGeolocation/MAURConfigurationContract.h +53 -0
  158. package/ios/common/BackgroundGeolocation/MAURConfigurationContract.m +54 -0
  159. package/ios/common/BackgroundGeolocation/MAURDistanceFilterLocationProvider.h +20 -0
  160. package/ios/common/BackgroundGeolocation/MAURDistanceFilterLocationProvider.m +550 -0
  161. package/ios/common/BackgroundGeolocation/MAURGeolocationOpenHelper.h +17 -0
  162. package/ios/common/BackgroundGeolocation/MAURGeolocationOpenHelper.m +124 -0
  163. package/ios/common/BackgroundGeolocation/MAURLocation.h +73 -0
  164. package/ios/common/BackgroundGeolocation/MAURLocation.m +392 -0
  165. package/ios/common/BackgroundGeolocation/MAURLocationContract.h +38 -0
  166. package/ios/common/BackgroundGeolocation/MAURLocationContract.m +39 -0
  167. package/ios/common/BackgroundGeolocation/MAURLocationManager.h +53 -0
  168. package/ios/common/BackgroundGeolocation/MAURLocationManager.m +305 -0
  169. package/ios/common/BackgroundGeolocation/MAURLogReader.h +26 -0
  170. package/ios/common/BackgroundGeolocation/MAURLogReader.m +122 -0
  171. package/ios/common/BackgroundGeolocation/MAURLogging.h +19 -0
  172. package/ios/common/BackgroundGeolocation/MAURPostLocationTask.h +53 -0
  173. package/ios/common/BackgroundGeolocation/MAURPostLocationTask.m +367 -0
  174. package/ios/common/BackgroundGeolocation/MAURProviderDelegate.h +52 -0
  175. package/ios/common/BackgroundGeolocation/MAURRawLocationProvider.h +18 -0
  176. package/ios/common/BackgroundGeolocation/MAURRawLocationProvider.m +138 -0
  177. package/ios/common/BackgroundGeolocation/MAURSQLiteConfigurationDAO.h +26 -0
  178. package/ios/common/BackgroundGeolocation/MAURSQLiteConfigurationDAO.m +335 -0
  179. package/ios/common/BackgroundGeolocation/MAURSQLiteHelper.h +57 -0
  180. package/ios/common/BackgroundGeolocation/MAURSQLiteHelper.m +93 -0
  181. package/ios/common/BackgroundGeolocation/MAURSQLiteLocationDAO.h +52 -0
  182. package/ios/common/BackgroundGeolocation/MAURSQLiteLocationDAO.m +520 -0
  183. package/ios/common/BackgroundGeolocation/MAURSQLiteOpenHelper.h +32 -0
  184. package/ios/common/BackgroundGeolocation/MAURSQLiteOpenHelper.m +276 -0
  185. package/ios/common/BackgroundGeolocation/MAURSensorFusionDetector.h +41 -0
  186. package/ios/common/BackgroundGeolocation/MAURSensorFusionDetector.m +137 -0
  187. package/ios/common/BackgroundGeolocation/MAURSessionLocationContract.h +29 -0
  188. package/ios/common/BackgroundGeolocation/MAURSessionLocationContract.m +31 -0
  189. package/ios/common/BackgroundGeolocation/MAURSessionLocationDAO.h +25 -0
  190. package/ios/common/BackgroundGeolocation/MAURSessionLocationDAO.m +153 -0
  191. package/ios/common/BackgroundGeolocation/MAURUncaughtExceptionLogger.h +20 -0
  192. package/ios/common/BackgroundGeolocation/MAURUncaughtExceptionLogger.m +62 -0
  193. package/ios/common/BackgroundGeolocation/MAURUrlTemplateResolver.h +31 -0
  194. package/ios/common/BackgroundGeolocation/MAURUrlTemplateResolver.m +107 -0
  195. package/ios/common/BackgroundGeolocation/Reachability.h +102 -0
  196. package/ios/common/BackgroundGeolocation/Reachability.m +475 -0
  197. package/ios/common/BackgroundGeolocation/SQLQueryBuilder/README.md +170 -0
  198. package/ios/common/BackgroundGeolocation/SQLQueryBuilder/ext/NSString+ZIMString.h +55 -0
  199. package/ios/common/BackgroundGeolocation/SQLQueryBuilder/ext/NSString+ZIMString.m +47 -0
  200. package/ios/common/BackgroundGeolocation/SQLQueryBuilder/sql/ZIMSqlDataManipulationCommand.h +27 -0
  201. package/ios/common/BackgroundGeolocation/SQLQueryBuilder/sql/ZIMSqlExpression.h +250 -0
  202. package/ios/common/BackgroundGeolocation/SQLQueryBuilder/sql/ZIMSqlExpression.m +259 -0
  203. package/ios/common/BackgroundGeolocation/SQLQueryBuilder/sql/ZIMSqlSelectStatement.h +360 -0
  204. package/ios/common/BackgroundGeolocation/SQLQueryBuilder/sql/ZIMSqlSelectStatement.m +427 -0
  205. package/ios/common/BackgroundGeolocation/SQLQueryBuilder/sql/ZIMSqlStatement.h +37 -0
  206. package/ios/common/BackgroundGeolocation/module.modulemap +16 -0
  207. package/package.json +82 -0
@@ -0,0 +1,685 @@
1
+ package com.marianhello.bgloc.provider;
2
+
3
+ import android.app.AlarmManager;
4
+ import android.app.PendingIntent;
5
+ import android.content.BroadcastReceiver;
6
+ import android.content.Context;
7
+ import android.content.Intent;
8
+ import android.content.IntentFilter;
9
+ import android.location.Location;
10
+ import android.location.LocationListener;
11
+ import android.location.LocationManager;
12
+ import android.os.Build;
13
+ import android.os.Bundle;
14
+ import android.os.Looper;
15
+
16
+ import com.google.android.gms.common.ConnectionResult;
17
+ import com.google.android.gms.common.GoogleApiAvailability;
18
+ import com.google.android.gms.location.FusedLocationProviderClient;
19
+ import com.google.android.gms.location.LocationCallback;
20
+ import com.google.android.gms.location.LocationRequest;
21
+ import com.google.android.gms.location.LocationResult;
22
+ import com.google.android.gms.location.LocationServices;
23
+ import com.google.android.gms.location.Priority;
24
+
25
+ import com.marianhello.bgloc.Config;
26
+ import com.marianhello.utils.ToneGenerator.Tone;
27
+
28
+ import java.util.List;
29
+
30
+ import static java.lang.Math.abs;
31
+ import static java.lang.Math.pow;
32
+ import static java.lang.Math.round;
33
+
34
+
35
+ /**
36
+ * v4.5.2 — Distance-filter provider with a runtime-chosen backend:
37
+ * <ul>
38
+ * <li><b>Fused path</b> (Play Services available): {@link FusedLocationProviderClient}
39
+ * + {@link LocationCallback}. Better fused GPS+Network blending and battery.</li>
40
+ * <li><b>Legacy path</b> (Play Services missing — Huawei/HMS, AOSP, China ROMs):
41
+ * {@link android.location.LocationManager} + {@link LocationListener}. Preserves the
42
+ * original DISTANCE_FILTER behavior so the plugin works on every Android device.</li>
43
+ * </ul>
44
+ *
45
+ * Stationary detection in both paths relies on the existing alarm-driven polling
46
+ * (no geofencing / proximity alerts), per product decision.
47
+ */
48
+ public class DistanceFilterLocationProvider extends AbstractLocationProvider implements LocationListener {
49
+
50
+ private static final String TAG = DistanceFilterLocationProvider.class.getSimpleName();
51
+ private static final String P_NAME = "com.marianhello.bgloc";
52
+
53
+ private static final String STATIONARY_ALARM_ACTION = P_NAME + ".STATIONARY_ALARM_ACTION";
54
+ private static final String SINGLE_LOCATION_UPDATE_ACTION = P_NAME + ".SINGLE_LOCATION_UPDATE_ACTION";
55
+ private static final String STATIONARY_LOCATION_MONITOR_ACTION = P_NAME + ".STATIONARY_LOCATION_MONITOR_ACTION";
56
+
57
+ // v4.5.1: defaults — overridable per-config via config.stationaryTimeout / stationaryPollInterval / stationaryPollFast
58
+ private static final long DEFAULT_STATIONARY_TIMEOUT = 5 * 1000 * 60;
59
+ private static final long DEFAULT_STATIONARY_LOCATION_POLLING_INTERVAL_LAZY = 3 * 1000 * 60;
60
+ private static final long DEFAULT_STATIONARY_LOCATION_POLLING_INTERVAL_AGGRESSIVE = 1 * 1000 * 60;
61
+ private static final int MAX_STATIONARY_ACQUISITION_ATTEMPTS = 5;
62
+ private static final int MAX_SPEED_ACQUISITION_ATTEMPTS = 3;
63
+
64
+ // v4.5.2 — Aggressive interval used while acquiring stationary location or speed (FLP path).
65
+ private static final long ACQUISITION_INTERVAL_MS = 1000L;
66
+
67
+ private Boolean isMoving = false;
68
+ private Boolean isAcquiringStationaryLocation = false;
69
+ private Boolean isAcquiringSpeed = false;
70
+ private Integer locationAcquisitionAttempts = 0;
71
+
72
+ private Location lastLocation;
73
+ private Location stationaryLocation;
74
+ private float stationaryRadius;
75
+ private PendingIntent stationaryAlarmPI;
76
+ private PendingIntent stationaryLocationPollingPI;
77
+ private PendingIntent singleUpdatePI; // legacy path only
78
+ private long stationaryLocationPollingInterval;
79
+
80
+ private Integer scaledDistanceFilter;
81
+
82
+ private FusedLocationProviderClient fusedClient; // null on the legacy path
83
+ private LocationManager locationManager; // always non-null after onCreate
84
+ private AlarmManager alarmManager;
85
+ private boolean usingFused = false;
86
+
87
+ private boolean isStarted = false;
88
+
89
+ /** v4.5.1: read overrides from {@link com.marianhello.bgloc.Config}; fall back to defaults. */
90
+ private long getStationaryTimeout() {
91
+ Integer v = mConfig != null ? mConfig.getStationaryTimeout() : null;
92
+ return v != null ? v.longValue() : DEFAULT_STATIONARY_TIMEOUT;
93
+ }
94
+ private long getStationaryPollLazy() {
95
+ Integer v = mConfig != null ? mConfig.getStationaryPollInterval() : null;
96
+ return v != null ? v.longValue() : DEFAULT_STATIONARY_LOCATION_POLLING_INTERVAL_LAZY;
97
+ }
98
+ private long getStationaryPollFast() {
99
+ Integer v = mConfig != null ? mConfig.getStationaryPollFast() : null;
100
+ return v != null ? v.longValue() : DEFAULT_STATIONARY_LOCATION_POLLING_INTERVAL_AGGRESSIVE;
101
+ }
102
+
103
+ public DistanceFilterLocationProvider(Context context) {
104
+ super(context);
105
+ PROVIDER_ID = Config.DISTANCE_FILTER_PROVIDER;
106
+ }
107
+
108
+ // ====== FLP path callbacks ======
109
+
110
+ private final LocationCallback fusedCallback = new LocationCallback() {
111
+ @Override
112
+ public void onLocationResult(LocationResult result) {
113
+ if (result == null) return;
114
+ List<Location> locations = result.getLocations();
115
+ if (locations == null) return;
116
+ for (Location location : locations) {
117
+ handleNewLocation(location);
118
+ }
119
+ }
120
+ };
121
+
122
+ /** FLP one-shot callback used by the stationary polling alarm. */
123
+ private final LocationCallback fusedPollCallback = new LocationCallback() {
124
+ @Override
125
+ public void onLocationResult(LocationResult result) {
126
+ if (result == null || fusedClient == null) return;
127
+ try {
128
+ fusedClient.removeLocationUpdates(this);
129
+ } catch (Exception ignored) { /* fire-and-forget */ }
130
+ Location loc = result.getLastLocation();
131
+ if (loc != null) {
132
+ logger.debug("Stationary monitor single update: {}", loc);
133
+ onPollStationaryLocation(loc);
134
+ }
135
+ }
136
+ };
137
+
138
+ // ====== Lifecycle ======
139
+
140
+ @Override
141
+ public void onCreate() {
142
+ super.onCreate();
143
+
144
+ locationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
145
+ alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
146
+
147
+ // v4.5.2 — pick the location backend at runtime. Play Services missing
148
+ // (Huawei/HMS, AOSP, China ROMs) → use the OS LocationManager so the
149
+ // provider still works.
150
+ int gps = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(mContext);
151
+ usingFused = (gps == ConnectionResult.SUCCESS);
152
+ if (usingFused) {
153
+ fusedClient = LocationServices.getFusedLocationProviderClient(mContext);
154
+ logger.info("DISTANCE_FILTER_PROVIDER using FusedLocationProviderClient (Play Services available).");
155
+ } else {
156
+ logger.info("DISTANCE_FILTER_PROVIDER falling back to LocationManager (Play Services unavailable, code={}).", gps);
157
+ }
158
+
159
+ int updateCurrentFlag = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
160
+ ? PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
161
+ : PendingIntent.FLAG_UPDATE_CURRENT;
162
+ // v4.5.2: singleUpdatePI must be MUTABLE on API 31+ because
163
+ // LocationManager.requestSingleUpdate() fills the resulting Location
164
+ // into the intent's extras at delivery time. FLAG_IMMUTABLE blocks that
165
+ // population, so the receiver would never see the fix.
166
+ int singleUpdateFlag = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
167
+ ? PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE
168
+ : PendingIntent.FLAG_CANCEL_CURRENT;
169
+
170
+ Intent stationaryAlarmIntent = new Intent(mContext, StationaryAlarmReceiver.class);
171
+ stationaryAlarmIntent.setAction(STATIONARY_ALARM_ACTION);
172
+ stationaryAlarmPI = PendingIntent.getBroadcast(mContext, 9000, stationaryAlarmIntent, updateCurrentFlag);
173
+ registerReceiver(stationaryAlarmReceiver, new IntentFilter(STATIONARY_ALARM_ACTION));
174
+
175
+ Intent stationaryLocationMonitorIntent = new Intent(mContext, StationaryLocationMonitorReceiver.class);
176
+ stationaryLocationMonitorIntent.setAction(STATIONARY_LOCATION_MONITOR_ACTION);
177
+ stationaryLocationPollingPI = PendingIntent.getBroadcast(mContext, 9002, stationaryLocationMonitorIntent, updateCurrentFlag);
178
+ registerReceiver(stationaryLocationMonitorReceiver, new IntentFilter(STATIONARY_LOCATION_MONITOR_ACTION));
179
+
180
+ // Legacy single-update PI + receiver (only used when usingFused == false).
181
+ if (!usingFused) {
182
+ Intent singleLocationUpdateIntent = new Intent(mContext, SingleUpdateReceiver.class);
183
+ singleLocationUpdateIntent.setAction(SINGLE_LOCATION_UPDATE_ACTION);
184
+ singleUpdatePI = PendingIntent.getBroadcast(mContext, 9003, singleLocationUpdateIntent, singleUpdateFlag);
185
+ registerReceiver(singleUpdateReceiver, new IntentFilter(SINGLE_LOCATION_UPDATE_ACTION));
186
+ }
187
+ }
188
+
189
+ @Override
190
+ public void onStart() {
191
+ if (isStarted) {
192
+ return;
193
+ }
194
+ if (locationManager == null) {
195
+ logger.error("LocationManager is null");
196
+ return;
197
+ }
198
+ if (alarmManager == null) {
199
+ logger.error("AlarmManager is null");
200
+ return;
201
+ }
202
+ if (mConfig == null) {
203
+ logger.warn("DistanceFilterLocationProvider started without config");
204
+ return;
205
+ }
206
+
207
+ logger.info("Start recording (path={})", usingFused ? "fused" : "legacy");
208
+ scaledDistanceFilter = mConfig.getDistanceFilter();
209
+ isStarted = true;
210
+ setPace(false);
211
+ }
212
+
213
+ @Override
214
+ public void onStop() {
215
+ if (!isStarted) {
216
+ return;
217
+ }
218
+ try {
219
+ unsubscribeLocationUpdates();
220
+ if (alarmManager != null) {
221
+ if (stationaryAlarmPI != null) alarmManager.cancel(stationaryAlarmPI);
222
+ if (stationaryLocationPollingPI != null) alarmManager.cancel(stationaryLocationPollingPI);
223
+ }
224
+ } catch (SecurityException ignored) {
225
+ } finally {
226
+ isStarted = false;
227
+ }
228
+ }
229
+
230
+ @Override
231
+ public void onCommand(int commandId, int arg1) {
232
+ switch(commandId) {
233
+ case CMD_SWITCH_MODE:
234
+ setPace(arg1 == BACKGROUND_MODE ? false : true);
235
+ return;
236
+ }
237
+ }
238
+
239
+ @Override
240
+ public void onConfigure(Config config) {
241
+ super.onConfigure(config);
242
+ if (isStarted) {
243
+ onStop();
244
+ onStart();
245
+ }
246
+ }
247
+
248
+ @Override
249
+ public boolean isStarted() {
250
+ return isStarted;
251
+ }
252
+
253
+ /** GPS first, fall back to Network — used only by the legacy LocationManager path. */
254
+ private String pickProvider() {
255
+ if (locationManager == null) return null;
256
+ if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
257
+ return LocationManager.GPS_PROVIDER;
258
+ }
259
+ if (locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
260
+ return LocationManager.NETWORK_PROVIDER;
261
+ }
262
+ return null;
263
+ }
264
+
265
+ /** Cheap check used to decide whether to emit SERVICE_ERROR when subscribing. */
266
+ private boolean anyProviderEnabled() {
267
+ if (locationManager == null) return true;
268
+ boolean gpsOn = false, netOn = false;
269
+ try { gpsOn = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER); }
270
+ catch (Exception ignored) { /* may throw on devices with no GPS hardware */ }
271
+ try { netOn = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER); }
272
+ catch (Exception ignored) { }
273
+ return gpsOn || netOn;
274
+ }
275
+
276
+ /** Translate the plugin's desiredAccuracy buckets into FLP priorities (FLP path only). */
277
+ private int translatePriority(Integer accuracy) {
278
+ if (accuracy == null) {
279
+ return Priority.PRIORITY_BALANCED_POWER_ACCURACY;
280
+ }
281
+ if (accuracy >= 10000) {
282
+ return Priority.PRIORITY_PASSIVE;
283
+ }
284
+ if (accuracy >= 1000) {
285
+ return Priority.PRIORITY_LOW_POWER;
286
+ }
287
+ if (accuracy >= 100) {
288
+ return Priority.PRIORITY_BALANCED_POWER_ACCURACY;
289
+ }
290
+ return Priority.PRIORITY_HIGH_ACCURACY;
291
+ }
292
+
293
+ private void unsubscribeLocationUpdates() {
294
+ try {
295
+ if (usingFused && fusedClient != null) {
296
+ fusedClient.removeLocationUpdates(fusedCallback);
297
+ fusedClient.removeLocationUpdates(fusedPollCallback);
298
+ }
299
+ if (locationManager != null) {
300
+ locationManager.removeUpdates(this);
301
+ }
302
+ } catch (SecurityException ignored) {
303
+ }
304
+ }
305
+
306
+ /**
307
+ * @param value true → aggressive moving tracking, false → stationary monitoring
308
+ */
309
+ private void setPace(Boolean value) {
310
+ if (!isStarted) {
311
+ return;
312
+ }
313
+ if (mConfig == null) {
314
+ return;
315
+ }
316
+
317
+ logger.info("Setting pace: {}", value);
318
+
319
+ Boolean wasMoving = isMoving;
320
+ isMoving = value;
321
+ isAcquiringStationaryLocation = false;
322
+ isAcquiringSpeed = false;
323
+ stationaryLocation = null;
324
+
325
+ try {
326
+ unsubscribeLocationUpdates();
327
+
328
+ if (isMoving) {
329
+ if (!wasMoving) {
330
+ isAcquiringSpeed = true;
331
+ }
332
+ } else {
333
+ isAcquiringStationaryLocation = true;
334
+ }
335
+
336
+ if (!anyProviderEnabled()) {
337
+ handleServiceError("No location provider available (GPS and Network disabled).");
338
+ }
339
+
340
+ if (usingFused) {
341
+ subscribeFused();
342
+ } else {
343
+ subscribeLegacy();
344
+ }
345
+ } catch (SecurityException e) {
346
+ logger.error("Security exception: {}", e.getMessage());
347
+ this.handleSecurityException(e);
348
+ }
349
+ }
350
+
351
+ private void subscribeFused() {
352
+ if (fusedClient == null) return;
353
+ LocationRequest request;
354
+ if (isAcquiringSpeed || isAcquiringStationaryLocation) {
355
+ locationAcquisitionAttempts = 0;
356
+ request = new LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, ACQUISITION_INTERVAL_MS)
357
+ .setMinUpdateIntervalMillis(ACQUISITION_INTERVAL_MS)
358
+ .setWaitForAccurateLocation(false)
359
+ .build();
360
+ } else {
361
+ int priority = translatePriority(mConfig.getDesiredAccuracy());
362
+ long interval = mConfig.getInterval();
363
+ LocationRequest.Builder b = new LocationRequest.Builder(priority, interval)
364
+ .setMinUpdateIntervalMillis(Math.min(interval, 1000L))
365
+ .setWaitForAccurateLocation(false);
366
+ if (scaledDistanceFilter != null && scaledDistanceFilter > 0) {
367
+ b.setMinUpdateDistanceMeters(scaledDistanceFilter.floatValue());
368
+ }
369
+ request = b.build();
370
+ }
371
+ try {
372
+ fusedClient.requestLocationUpdates(request, fusedCallback, Looper.getMainLooper());
373
+ } catch (SecurityException e) {
374
+ this.handleSecurityException(e);
375
+ }
376
+ }
377
+
378
+ private void subscribeLegacy() {
379
+ if (locationManager == null) return;
380
+ try {
381
+ if (isAcquiringSpeed || isAcquiringStationaryLocation) {
382
+ locationAcquisitionAttempts = 0;
383
+ // Burst: subscribe to every non-passive provider for fastest lock.
384
+ List<String> matchingProviders = locationManager.getAllProviders();
385
+ for (String provider : matchingProviders) {
386
+ if (!LocationManager.PASSIVE_PROVIDER.equals(provider)) {
387
+ logger.info("Requesting location updates from provider {}", provider);
388
+ locationManager.requestLocationUpdates(provider, 0, 0, this);
389
+ }
390
+ }
391
+ } else {
392
+ // v4.5.2 — subscribe to GPS AND Network simultaneously when both
393
+ // are available. The previous version only used GPS-or-Network
394
+ // (excluyente), which on cheap/vehicular Androids could leave the
395
+ // app waiting for a GPS fix while a quick Network fix was available.
396
+ boolean gpsOn = false, netOn = false;
397
+ try { gpsOn = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER); }
398
+ catch (Exception ignored) { }
399
+ try { netOn = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER); }
400
+ catch (Exception ignored) { }
401
+ if (!gpsOn && !netOn) {
402
+ logger.warn("No location provider available (GPS and Network disabled)");
403
+ return;
404
+ }
405
+ long interval = mConfig.getInterval();
406
+ int distance = scaledDistanceFilter != null ? scaledDistanceFilter : 0;
407
+ if (gpsOn) {
408
+ logger.info("Requesting location updates from GPS");
409
+ locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, interval, distance, this);
410
+ }
411
+ if (netOn) {
412
+ logger.info("Requesting location updates from Network");
413
+ locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, interval, distance, this);
414
+ }
415
+ }
416
+ } catch (SecurityException e) {
417
+ this.handleSecurityException(e);
418
+ }
419
+ }
420
+
421
+ // ====== LocationListener (legacy path) ======
422
+
423
+ @Override
424
+ public void onLocationChanged(Location location) {
425
+ handleNewLocation(location);
426
+ }
427
+
428
+ @Override
429
+ public void onStatusChanged(String provider, int status, Bundle extras) {
430
+ logger.debug("Provider {} status changed: {}", provider, status);
431
+ }
432
+
433
+ @Override
434
+ public void onProviderEnabled(String provider) {
435
+ logger.debug("Provider {} was enabled", provider);
436
+ }
437
+
438
+ @Override
439
+ public void onProviderDisabled(String provider) {
440
+ logger.warn("Provider {} was disabled", provider);
441
+ // v4.5.2: surface as an error so JS layer can prompt the user to enable
442
+ // location services. Only when no fallback provider is left.
443
+ if (locationManager != null && pickProvider() == null) {
444
+ handleServiceError("Location provider '" + provider + "' disabled and no fallback available.");
445
+ }
446
+ }
447
+
448
+ // ====== State machine (path-agnostic) ======
449
+
450
+ /**
451
+ * Same logic as the legacy {@code onLocationChanged}: handles the moving / stationary /
452
+ * acquisition state machine. Called from both the FLP callback and the LocationListener.
453
+ */
454
+ private void handleNewLocation(Location location) {
455
+ if (location == null) return;
456
+ logger.debug("Location change: {} isMoving={}", location.toString(), isMoving);
457
+
458
+ if (!isMoving && !isAcquiringStationaryLocation && stationaryLocation==null) {
459
+ // Perhaps our GPS signal was interrupted, re-acquire a stationary location now.
460
+ setPace(false);
461
+ }
462
+
463
+ showDebugToast("mv:" + isMoving + ",acy:" + location.getAccuracy() + ",v:" + location.getSpeed() + ",df:" + scaledDistanceFilter);
464
+
465
+ if (isAcquiringStationaryLocation) {
466
+ if (stationaryLocation == null || stationaryLocation.getAccuracy() > location.getAccuracy()) {
467
+ stationaryLocation = location;
468
+ }
469
+ if (++locationAcquisitionAttempts == MAX_STATIONARY_ACQUISITION_ATTEMPTS) {
470
+ isAcquiringStationaryLocation = false;
471
+ enterStationary(stationaryLocation);
472
+ handleStationary(stationaryLocation, stationaryRadius);
473
+ return;
474
+ } else {
475
+ playDebugTone(Tone.BEEP);
476
+ return;
477
+ }
478
+ } else if (isAcquiringSpeed) {
479
+ if (++locationAcquisitionAttempts == MAX_SPEED_ACQUISITION_ATTEMPTS) {
480
+ playDebugTone(Tone.DOODLY_DOO);
481
+ isAcquiringSpeed = false;
482
+ scaledDistanceFilter = calculateDistanceFilter(location.getSpeed());
483
+ setPace(true);
484
+ } else {
485
+ playDebugTone(Tone.BEEP);
486
+ return;
487
+ }
488
+ } else if (isMoving) {
489
+ playDebugTone(Tone.BEEP);
490
+
491
+ if ((location.getSpeed() >= 1) && (location.getAccuracy() <= mConfig.getStationaryRadius())) {
492
+ resetStationaryAlarm();
493
+ }
494
+ Integer newDistanceFilter = calculateDistanceFilter(location.getSpeed());
495
+ if (newDistanceFilter != scaledDistanceFilter.intValue()) {
496
+ logger.info("Updating distanceFilter: new={} old={}", newDistanceFilter, scaledDistanceFilter);
497
+ scaledDistanceFilter = newDistanceFilter;
498
+ setPace(true);
499
+ }
500
+ if (lastLocation != null && location.distanceTo(lastLocation) < mConfig.getDistanceFilter()) {
501
+ return;
502
+ }
503
+ } else if (stationaryLocation != null) {
504
+ return;
505
+ }
506
+ lastLocation = location;
507
+ handleLocation(location);
508
+ }
509
+
510
+ public void resetStationaryAlarm() {
511
+ if (alarmManager == null || stationaryAlarmPI == null) return;
512
+ alarmManager.cancel(stationaryAlarmPI);
513
+ alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + getStationaryTimeout(), stationaryAlarmPI);
514
+ }
515
+
516
+ private Integer calculateDistanceFilter(Float speed) {
517
+ Double newDistanceFilter = (double) mConfig.getDistanceFilter();
518
+ if (speed < 100) {
519
+ float roundedDistanceFilter = (round(speed / 5) * 5);
520
+ newDistanceFilter = pow(roundedDistanceFilter, 2) + (double) mConfig.getDistanceFilter();
521
+ }
522
+ return (newDistanceFilter.intValue() < 1000) ? newDistanceFilter.intValue() : 1000;
523
+ }
524
+
525
+ /**
526
+ * v4.5.2 — Stop active updates and start the polling-based stationary monitor.
527
+ * The previous version also called {@code addProximityAlert} (geofence); that
528
+ * path has been removed per product decision (no geofencing).
529
+ */
530
+ private void enterStationary(Location location) {
531
+ if (location == null || mConfig == null) return;
532
+ try {
533
+ unsubscribeLocationUpdates();
534
+
535
+ float radius = mConfig.getStationaryRadius();
536
+ float proximityRadius = (location.getAccuracy() < radius) ? radius : location.getAccuracy();
537
+ stationaryLocation = location;
538
+ this.stationaryRadius = proximityRadius;
539
+
540
+ logger.info("enterStationary: lat={} lon={} acy={} radius={}", location.getLatitude(), location.getLongitude(), location.getAccuracy(), proximityRadius);
541
+
542
+ startPollingStationaryLocation(getStationaryPollLazy());
543
+ } catch (SecurityException e) {
544
+ logger.error("Security exception: {}", e.getMessage());
545
+ this.handleSecurityException(e);
546
+ }
547
+ }
548
+
549
+ /** Engage aggressive geolocation after stationary exit. */
550
+ public void onExitStationaryRegion(Location location) {
551
+ if (location == null || alarmManager == null) return;
552
+
553
+ playDebugTone(Tone.BEEP_BEEP_BEEP);
554
+
555
+ logger.info("Exited stationary: lat={} long={} acy={}", location.getLatitude(), location.getLongitude(), location.getAccuracy());
556
+
557
+ try {
558
+ alarmManager.cancel(stationaryLocationPollingPI);
559
+ this.setPace(true);
560
+ } catch (SecurityException e) {
561
+ logger.error("Security exception: {}", e.getMessage());
562
+ this.handleSecurityException(e);
563
+ }
564
+ }
565
+
566
+ public void startPollingStationaryLocation(long interval) {
567
+ if (alarmManager == null || stationaryLocationPollingPI == null) return;
568
+ stationaryLocationPollingInterval = interval;
569
+ alarmManager.cancel(stationaryLocationPollingPI);
570
+ long start = System.currentTimeMillis() + 60_000;
571
+ alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, start, interval, stationaryLocationPollingPI);
572
+ }
573
+
574
+ public void onPollStationaryLocation(Location location) {
575
+ if (location == null || mConfig == null) return;
576
+
577
+ float radius = mConfig.getStationaryRadius();
578
+ if (isMoving) {
579
+ return;
580
+ }
581
+ playDebugTone(Tone.BEEP);
582
+
583
+ float distance = 0.0f;
584
+ if (stationaryLocation != null) {
585
+ distance = abs(location.distanceTo(stationaryLocation) - stationaryLocation.getAccuracy() - location.getAccuracy());
586
+ }
587
+
588
+ showDebugToast("Stationary exit in " + (radius - distance) + "m");
589
+
590
+ logger.info("Distance from stationary location: {}", distance);
591
+ if (distance > radius) {
592
+ onExitStationaryRegion(location);
593
+ } else if (distance > 0) {
594
+ startPollingStationaryLocation(getStationaryPollFast());
595
+ } else if (stationaryLocationPollingInterval != getStationaryPollLazy()) {
596
+ startPollingStationaryLocation(getStationaryPollLazy());
597
+ }
598
+ }
599
+
600
+ // ====== Receivers ======
601
+
602
+ private class StationaryAlarmReceiver extends BroadcastReceiver {
603
+ @Override
604
+ public void onReceive(Context context, Intent intent) {
605
+ logger.info("stationaryAlarm fired");
606
+ setPace(false);
607
+ }
608
+ }
609
+ private BroadcastReceiver stationaryAlarmReceiver = new StationaryAlarmReceiver();
610
+
611
+ /**
612
+ * Triggered by the inexact repeating alarm to poll a single fresh location while
613
+ * inside the stationary region. Uses FLP one-shot when available, otherwise falls
614
+ * back to {@link LocationManager#requestSingleUpdate(String, PendingIntent)}.
615
+ */
616
+ private class StationaryLocationMonitorReceiver extends BroadcastReceiver {
617
+ @Override
618
+ public void onReceive(Context context, Intent intent) {
619
+ logger.info("Stationary location monitor fired");
620
+ playDebugTone(Tone.DIALTONE);
621
+
622
+ if (usingFused && fusedClient != null) {
623
+ LocationRequest oneShot = new LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 0L)
624
+ .setMaxUpdates(1)
625
+ .setWaitForAccurateLocation(false)
626
+ .build();
627
+ try {
628
+ fusedClient.requestLocationUpdates(oneShot, fusedPollCallback, Looper.getMainLooper());
629
+ } catch (SecurityException e) {
630
+ logger.error("Security exception (FLP one-shot): {}", e.getMessage());
631
+ } catch (IllegalArgumentException e) {
632
+ logger.warn("FLP one-shot failed: {}", e.getMessage());
633
+ }
634
+ } else if (locationManager != null && singleUpdatePI != null) {
635
+ String provider = pickProvider();
636
+ if (provider == null) {
637
+ logger.warn("Stationary monitor: no provider available");
638
+ return;
639
+ }
640
+ try {
641
+ locationManager.requestSingleUpdate(provider, singleUpdatePI);
642
+ } catch (SecurityException e) {
643
+ logger.error("Security exception (single update): {}", e.getMessage());
644
+ } catch (IllegalArgumentException e) {
645
+ logger.warn("requestSingleUpdate failed: {}", e.getMessage());
646
+ }
647
+ }
648
+ }
649
+ }
650
+ private BroadcastReceiver stationaryLocationMonitorReceiver = new StationaryLocationMonitorReceiver();
651
+
652
+ /** Legacy single-update receiver — feeds {@link #onPollStationaryLocation(Location)}. */
653
+ private class SingleUpdateReceiver extends BroadcastReceiver {
654
+ @Override
655
+ public void onReceive(Context context, Intent intent) {
656
+ Bundle extras = intent.getExtras();
657
+ if (extras == null) return;
658
+ Location location = extras.getParcelable(LocationManager.KEY_LOCATION_CHANGED);
659
+ if (location != null) {
660
+ logger.debug("Single location update: {}", location);
661
+ onPollStationaryLocation(location);
662
+ }
663
+ }
664
+ }
665
+ private BroadcastReceiver singleUpdateReceiver = new SingleUpdateReceiver();
666
+
667
+ @Override
668
+ public void onDestroy() {
669
+ logger.info("Destroying DistanceFilterLocationProvider");
670
+
671
+ this.onStop();
672
+ if (alarmManager != null) {
673
+ if (stationaryAlarmPI != null) alarmManager.cancel(stationaryAlarmPI);
674
+ if (stationaryLocationPollingPI != null) alarmManager.cancel(stationaryLocationPollingPI);
675
+ }
676
+
677
+ try { unregisterReceiver(stationaryAlarmReceiver); } catch (Exception ignored) { }
678
+ try { unregisterReceiver(stationaryLocationMonitorReceiver); } catch (Exception ignored) { }
679
+ if (!usingFused) {
680
+ try { unregisterReceiver(singleUpdateReceiver); } catch (Exception ignored) { }
681
+ }
682
+
683
+ super.onDestroy();
684
+ }
685
+ }