@josuelmm/cordova-background-geolocation 4.2.3 → 4.5.2

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 (103) hide show
  1. package/.npmignore +11 -0
  2. package/CHANGELOG.md +261 -0
  3. package/README.md +306 -115
  4. package/android/CDVBackgroundGeolocation/src/main/java/com/marianhello/bgloc/cordova/ConfigMapper.java +34 -0
  5. package/android/CDVBackgroundGeolocation/src/main/java/com/tenforwardconsulting/bgloc/cordova/BackgroundGeolocationPlugin.java +61 -1
  6. package/android/common/src/main/AndroidManifest.xml +1 -1
  7. package/android/common/src/main/java/com/marianhello/bgloc/BootCompletedReceiver.java +20 -3
  8. package/android/common/src/main/java/com/marianhello/bgloc/Config.java +87 -1
  9. package/android/common/src/main/java/com/marianhello/bgloc/data/BackgroundLocation.java +94 -0
  10. package/android/common/src/main/java/com/marianhello/bgloc/data/ConfigJsonMapper.java +211 -0
  11. package/android/common/src/main/java/com/marianhello/bgloc/data/LocationTemplateFactory.java +6 -0
  12. package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteConfigurationContract.java +5 -1
  13. package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteConfigurationDAO.java +32 -1
  14. package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteLocationContract.java +12 -2
  15. package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteLocationDAO.java +33 -2
  16. package/android/common/src/main/java/com/marianhello/bgloc/data/sqlite/SQLiteOpenHelper.java +15 -1
  17. package/android/common/src/main/java/com/marianhello/bgloc/provider/AbstractLocationProvider.java +48 -1
  18. package/android/common/src/main/java/com/marianhello/bgloc/provider/ActivityRecognitionLocationProvider.java +105 -6
  19. package/android/common/src/main/java/com/marianhello/bgloc/provider/DistanceFilterLocationProvider.java +336 -250
  20. package/android/common/src/main/java/com/marianhello/bgloc/provider/RawLocationProvider.java +69 -19
  21. package/android/common/src/main/java/com/marianhello/bgloc/service/LocationServiceImpl.java +246 -21
  22. package/android/common/src/main/java/com/marianhello/bgloc/service/LocationServiceProxy.java +5 -2
  23. package/android/common/src/main/java/com/marianhello/bgloc/sync/BatchManager.java +46 -13
  24. package/ios/CDVBackgroundGeolocation/CDVBackgroundGeolocation.m +23 -1
  25. package/ios/common/BackgroundGeolocation/MAURActivityLocationProvider.m +208 -70
  26. package/ios/common/BackgroundGeolocation/MAURBackgroundGeolocationFacade.m +132 -5
  27. package/ios/common/BackgroundGeolocation/MAURBackgroundSync.m +20 -0
  28. package/ios/common/BackgroundGeolocation/MAURConfig.h +7 -0
  29. package/ios/common/BackgroundGeolocation/MAURConfig.m +37 -2
  30. package/ios/common/BackgroundGeolocation/MAURConfigurationContract.h +3 -0
  31. package/ios/common/BackgroundGeolocation/MAURConfigurationContract.m +3 -1
  32. package/ios/common/BackgroundGeolocation/MAURDistanceFilterLocationProvider.m +10 -1
  33. package/ios/common/BackgroundGeolocation/MAURGeolocationOpenHelper.m +15 -1
  34. package/ios/common/BackgroundGeolocation/MAURLocation.h +12 -0
  35. package/ios/common/BackgroundGeolocation/MAURLocation.m +33 -4
  36. package/ios/common/BackgroundGeolocation/MAURLocationContract.h +4 -0
  37. package/ios/common/BackgroundGeolocation/MAURLocationContract.m +5 -1
  38. package/ios/common/BackgroundGeolocation/MAURLocationManager.m +19 -1
  39. package/ios/common/BackgroundGeolocation/MAURPostLocationTask.h +9 -0
  40. package/ios/common/BackgroundGeolocation/MAURPostLocationTask.m +59 -1
  41. package/ios/common/BackgroundGeolocation/MAURRawLocationProvider.m +10 -1
  42. package/ios/common/BackgroundGeolocation/MAURSQLiteConfigurationDAO.m +54 -4
  43. package/ios/common/BackgroundGeolocation/MAURSQLiteLocationDAO.h +12 -0
  44. package/ios/common/BackgroundGeolocation/MAURSQLiteLocationDAO.m +125 -5
  45. package/package.json +31 -1
  46. package/plugin.xml +3 -10
  47. package/www/BackgroundGeolocation.d.ts +143 -3
  48. package/www/BackgroundGeolocation.js +11 -4
  49. package/CLAUDE.md +0 -56
  50. package/HISTORY.md +0 -871
  51. package/android/CDVBackgroundGeolocation/src/test/java/com/marianhello/ConfigMapperTest.java +0 -220
  52. package/android/common/src/androidTest/java/com/marianhello/bgloc/BackgroundGeolocationFacadeTest.java +0 -45
  53. package/android/common/src/androidTest/java/com/marianhello/bgloc/BatchManagerTest.java +0 -570
  54. package/android/common/src/androidTest/java/com/marianhello/bgloc/ConfigTest.java +0 -76
  55. package/android/common/src/androidTest/java/com/marianhello/bgloc/ContentProviderLocationDAOTest.java +0 -437
  56. package/android/common/src/androidTest/java/com/marianhello/bgloc/DBLogReaderTest.java +0 -95
  57. package/android/common/src/androidTest/java/com/marianhello/bgloc/LocationContentProviderTest.java +0 -159
  58. package/android/common/src/androidTest/java/com/marianhello/bgloc/LocationServiceProxyTest.java +0 -161
  59. package/android/common/src/androidTest/java/com/marianhello/bgloc/LocationServiceTest.java +0 -247
  60. package/android/common/src/androidTest/java/com/marianhello/bgloc/SQLiteConfigurationDAOTest.java +0 -200
  61. package/android/common/src/androidTest/java/com/marianhello/bgloc/SQLiteLocationDAOTest.java +0 -457
  62. package/android/common/src/androidTest/java/com/marianhello/bgloc/SQLiteLocationDAOThreadTest.java +0 -96
  63. package/android/common/src/androidTest/java/com/marianhello/bgloc/SQLiteOpenHelperTest.java +0 -225
  64. package/android/common/src/androidTest/java/com/marianhello/bgloc/TestPluginDelegate.java +0 -46
  65. package/android/common/src/androidTest/java/com/marianhello/bgloc/TestResourceResolver.java +0 -14
  66. package/android/common/src/androidTest/java/com/marianhello/bgloc/provider/MockLocationProvider.java +0 -50
  67. package/android/common/src/androidTest/java/com/marianhello/bgloc/provider/TestLocationProviderFactory.java +0 -17
  68. package/android/common/src/androidTest/java/com/marianhello/bgloc/sqlite/SQLiteOpenHelper10.java +0 -92
  69. package/android/common/src/androidTest/java/com/marianhello/bgloc/test/LocationProviderTestCase.java +0 -107
  70. package/android/common/src/androidTest/java/com/marianhello/bgloc/test/TestConstants.java +0 -5
  71. package/android/common/src/test/java/com/marianhello/backgroundgeolocation/ArrayListLocationTemplateTest.java +0 -82
  72. package/android/common/src/test/java/com/marianhello/backgroundgeolocation/BackgroundLocationTest.java +0 -128
  73. package/android/common/src/test/java/com/marianhello/backgroundgeolocation/ConfigTest.java +0 -191
  74. package/android/common/src/test/java/com/marianhello/backgroundgeolocation/DBLogReaderTest.java +0 -37
  75. package/android/common/src/test/java/com/marianhello/backgroundgeolocation/HashMapLocationTemplateTest.java +0 -216
  76. package/android/common/src/test/java/com/marianhello/backgroundgeolocation/HttpPostServiceTest.java +0 -223
  77. package/android/common/src/test/java/com/marianhello/backgroundgeolocation/LocationTemplateFactoryTest.java +0 -50
  78. package/android/common/src/test/java/com/marianhello/backgroundgeolocation/PostLocationTaskTest.java +0 -180
  79. package/android/common/src/test/java/com/marianhello/backgroundgeolocation/TestHelper.java +0 -16
  80. package/ios/common/BackgroundGeolocation/SOMotionDetector/CHANGELOG.md +0 -2
  81. package/ios/common/BackgroundGeolocation/SOMotionDetector/LICENSE +0 -21
  82. package/ios/common/BackgroundGeolocation/SOMotionDetector/README.md +0 -135
  83. package/ios/common/BackgroundGeolocation/SOMotionDetector/SOLocationManager.h +0 -80
  84. package/ios/common/BackgroundGeolocation/SOMotionDetector/SOLocationManager.m +0 -147
  85. package/ios/common/BackgroundGeolocation/SOMotionDetector/SOMotionActivity.h +0 -30
  86. package/ios/common/BackgroundGeolocation/SOMotionDetector/SOMotionActivity.m +0 -42
  87. package/ios/common/BackgroundGeolocation/SOMotionDetector/SOMotionDetector.h +0 -99
  88. package/ios/common/BackgroundGeolocation/SOMotionDetector/SOMotionDetector.m +0 -327
  89. package/ios/common/BackgroundGeolocation/SOMotionDetector/SOStepDetector.h +0 -44
  90. package/ios/common/BackgroundGeolocation/SOMotionDetector/SOStepDetector.m +0 -94
  91. package/ios/common/BackgroundGeolocationTests/Info.plist +0 -24
  92. package/ios/common/BackgroundGeolocationTests/MAURBackgroundLocationTest.m +0 -185
  93. package/ios/common/BackgroundGeolocationTests/MAURConfigTest.m +0 -161
  94. package/ios/common/BackgroundGeolocationTests/MAURGeolocationOpenHelperTest.m +0 -102
  95. package/ios/common/BackgroundGeolocationTests/MAURLocationTest.m +0 -216
  96. package/ios/common/BackgroundGeolocationTests/MAURLocationUploaderTest.m +0 -55
  97. package/ios/common/BackgroundGeolocationTests/MAURLogReaderTest.m +0 -43
  98. package/ios/common/BackgroundGeolocationTests/MAURSQLiteConfigurationDAOTest.m +0 -102
  99. package/ios/common/BackgroundGeolocationTests/MAURSQLiteHelperTest.m +0 -41
  100. package/ios/common/BackgroundGeolocationTests/MAURSQLiteLocationDAOTests.m +0 -240
  101. package/ios/common/BackgroundGeolocationTests/MAURSQLiteLocationDAOThreadTest.m +0 -84
  102. package/ios/common/BackgroundGeolocationTests/MAURSQLiteOpenHelperTest.m +0 -144
  103. package/ios/common/scripts/xcode-refactor.js +0 -184
@@ -188,12 +188,22 @@ public class BatchManager {
188
188
  }
189
189
 
190
190
  private void writeValue(Object value) throws IOException {
191
- if (value instanceof String ) {
191
+ if (value == null || value == JSONObject.NULL) {
192
+ writer.nullValue();
193
+ } else if (value instanceof String ) {
192
194
  writer.value((String) value);
193
195
  } else if (value instanceof Map) {
194
196
  writeMap((Map) value);
195
197
  } else if (value instanceof List) {
196
198
  writeList((List) value);
199
+ // v4.5.1 — handle JSONArray / JSONObject so that placeholders that resolve to
200
+ // structured data (@events → JSONArray of events) are written as real JSON arrays /
201
+ // objects, not as escaped strings. Without this, a fix coming out of the sync queue
202
+ // serialised "events" as "[{\"type\":\"hardBrake\"}]" literal.
203
+ } else if (value instanceof org.json.JSONArray) {
204
+ writeJsonArray((org.json.JSONArray) value);
205
+ } else if (value instanceof org.json.JSONObject) {
206
+ writeJsonObject((org.json.JSONObject) value);
197
207
  } else if (Integer.class.isInstance(value)) {
198
208
  writer.value((Integer) value);
199
209
  } else if (Double.class.isInstance(value)) {
@@ -204,13 +214,44 @@ public class BatchManager {
204
214
  writer.value((Long) value);
205
215
  } else if (Boolean.class.isInstance(value)) {
206
216
  writer.value((Boolean) value);
207
- } else if (value == JSONObject.NULL) {
208
- writer.nullValue();
209
217
  } else {
210
218
  writer.value(String.valueOf(value));
211
219
  }
212
220
  }
213
221
 
222
+ private void writeJsonArray(org.json.JSONArray arr) throws IOException {
223
+ writer.beginArray();
224
+ for (int i = 0; i < arr.length(); i++) {
225
+ Object v = arr.opt(i);
226
+ writeValue(v == null ? JSONObject.NULL : v);
227
+ }
228
+ writer.endArray();
229
+ }
230
+
231
+ private void writeJsonObject(org.json.JSONObject obj) throws IOException {
232
+ writer.beginObject();
233
+ Iterator<String> keys = obj.keys();
234
+ while (keys.hasNext()) {
235
+ String k = keys.next();
236
+ writer.name(k);
237
+ writeValue(obj.opt(k));
238
+ }
239
+ writer.endObject();
240
+ }
241
+
242
+ /** v4.5.1 — resolve a template string. If it is a placeholder ("@foo") and the location
243
+ * has no value, return JSONObject.NULL so the writer emits `null` instead of the literal
244
+ * "@foo" string. */
245
+ private Object resolveTemplateValue(Object value) {
246
+ if (value instanceof String) {
247
+ String s = (String) value;
248
+ Object resolved = location.getValueForKey(s);
249
+ if (resolved != null) return resolved;
250
+ if (s.startsWith("@")) return JSONObject.NULL;
251
+ }
252
+ return value;
253
+ }
254
+
214
255
  public void writeMap(Map values) throws IOException {
215
256
  writer.beginObject();
216
257
  Iterator<?> it = values.entrySet().iterator();
@@ -218,12 +259,8 @@ public class BatchManager {
218
259
  Map.Entry<String, Object> pair = (Map.Entry) it.next();
219
260
  String key = pair.getKey();
220
261
  Object value = pair.getValue();
221
- Object locationValue = null;
222
- if (value instanceof String) {
223
- locationValue = location.getValueForKey((String)value);
224
- }
225
262
  writer.name(key);
226
- writeValue(locationValue != null ? locationValue : value);
263
+ writeValue(resolveTemplateValue(value));
227
264
  }
228
265
  writer.endObject();
229
266
  }
@@ -233,11 +270,7 @@ public class BatchManager {
233
270
  Iterator<?> it = values.iterator();
234
271
  while (it.hasNext()) {
235
272
  Object value = it.next();
236
- Object locationValue = null;
237
- if (value instanceof String) {
238
- locationValue = location.getValueForKey((String) value);
239
- }
240
- writeValue(locationValue != null ? locationValue : value);
273
+ writeValue(resolveTemplateValue(value));
241
274
  }
242
275
  writer.endArray();
243
276
  }
@@ -135,6 +135,28 @@ static NSString * const TAG = @"CDVBackgroundGeolocation";
135
135
  callbackId:command.callbackId];
136
136
  }
137
137
 
138
+ // v4.5: runtime permission helpers — paridad de API con Android. iOS no expone gates
139
+ // separados para background location / activity recognition / notifications, así que
140
+ // resolvemos siempre con notRequired:YES.
141
+ - (void) requestBackgroundLocationPermission:(CDVInvokedUrlCommand *)command
142
+ {
143
+ [self sendNotRequiredPermissionResult:command];
144
+ }
145
+ - (void) requestActivityRecognitionPermission:(CDVInvokedUrlCommand *)command
146
+ {
147
+ [self sendNotRequiredPermissionResult:command];
148
+ }
149
+ - (void) requestNotificationPermission:(CDVInvokedUrlCommand *)command
150
+ {
151
+ [self sendNotRequiredPermissionResult:command];
152
+ }
153
+ - (void) sendNotRequiredPermissionResult:(CDVInvokedUrlCommand *)command
154
+ {
155
+ NSDictionary *r = @{ @"granted": @YES, @"notRequired": @YES };
156
+ [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:r]
157
+ callbackId:command.callbackId];
158
+ }
159
+
138
160
  // v4.1 GPS-derived sensor-like events
139
161
  - (void) sendDrivingEventN:(NSString *)name note:(NSNotification *)note
140
162
  {
@@ -513,7 +535,7 @@ static NSString * const TAG = @"CDVBackgroundGeolocation";
513
535
  - (void) getPluginVersion:(CDVInvokedUrlCommand*)command
514
536
  {
515
537
  NSLog(@"%@ #%@", TAG, @"getPluginVersion");
516
- NSString *version = @"4.2.3"; // keep in sync with plugin.xml and Android PLUGIN_VERSION
538
+ NSString *version = @"4.5.2"; // keep in sync with plugin.xml and Android PLUGIN_VERSION
517
539
  CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:version];
518
540
  [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
519
541
  }
@@ -5,43 +5,59 @@
5
5
  // Created by Marian Hello on 14/09/2016.
6
6
  // Copyright © 2016 mauron85. All rights reserved.
7
7
  //
8
+ // v4.5.2 — refactored to use CoreMotion's CMMotionActivityManager directly.
9
+ // The SOMotionDetector dependency (sources + plugin.xml entries) was removed
10
+ // in this version.
11
+ //
8
12
 
9
13
  #import <Foundation/Foundation.h>
14
+ #import <CoreMotion/CoreMotion.h>
10
15
  #import "MAURActivityLocationProvider.h"
11
16
  #import "MAURActivity.h"
12
- #import "SOMotionDetector.h"
13
17
  #import "MAURLocationManager.h"
14
18
  #import "MAURLogging.h"
15
19
 
16
- #define SYSTEM_VERSION_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedSame)
17
- #define SYSTEM_VERSION_GREATER_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedDescending)
18
- #define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
19
- #define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
20
- #define SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedDescending)
21
-
22
20
  static NSString * const TAG = @"ActivityLocationProvider";
23
21
  static NSString * const Domain = @"com.marianhello";
24
22
 
25
- @interface MAURActivityLocationProvider () <SOMotionDetectorDelegate>
26
- @end
23
+ // Local motion-type enum (replaces the legacy SOMotionType used previously).
24
+ typedef NS_ENUM(NSUInteger, MAURMotionType) {
25
+ MAURMotionTypeUnknown = 0,
26
+ MAURMotionTypeNotMoving,
27
+ MAURMotionTypeWalking,
28
+ MAURMotionTypeRunning,
29
+ MAURMotionTypeAutomotive,
30
+ MAURMotionTypeCycling
31
+ };
27
32
 
28
33
  @implementation MAURActivityLocationProvider {
29
34
  BOOL isStarted;
30
35
  BOOL isTracking;
31
- SOMotionType lastMotionType;
36
+ BOOL motionAvailable;
37
+ BOOL motionPermissionErrorEmitted;
38
+ MAURMotionType lastMotionType;
32
39
 
33
40
  MAURLocationManager *locationManager;
41
+ CMMotionActivityManager *activityManager;
42
+ NSOperationQueue *activityQueue;
43
+
44
+ // v4.5.2: cache of active config so motion callbacks can read
45
+ // activityConfidenceThreshold without re-fetching.
46
+ MAURConfig *currentConfig;
34
47
  }
35
48
 
36
49
  - (instancetype) init
37
50
  {
38
51
  self = [super init];
39
-
52
+
40
53
  if (self) {
41
54
  isStarted = NO;
42
55
  isTracking = NO;
56
+ motionAvailable = NO;
57
+ motionPermissionErrorEmitted = NO;
58
+ lastMotionType = MAURMotionTypeUnknown;
43
59
  }
44
-
60
+
45
61
  return self;
46
62
  }
47
63
 
@@ -49,52 +65,197 @@ static NSString * const Domain = @"com.marianhello";
49
65
  locationManager = [MAURLocationManager sharedInstance];
50
66
  locationManager.delegate = self;
51
67
 
52
- SOMotionDetector *motionDetector = [SOMotionDetector sharedInstance];
53
- motionDetector.delegate = self;
54
- if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"7.0")) {
55
- motionDetector.useM7IfAvailable = YES; //Use M7 chip if available, otherwise use lib's algorithm
68
+ // v4.5.2 CoreMotion direct. Without CMMotionActivityManager support the
69
+ // provider cannot drive STILL/ACTIVE transitions; emit a clear error so the
70
+ // host app knows to fall back to DISTANCE_FILTER or RAW.
71
+ motionAvailable = [CMMotionActivityManager isActivityAvailable];
72
+ if (!motionAvailable) {
73
+ DDLogError(@"%@ CMMotionActivityManager unavailable on this device; ACTIVITY_PROVIDER will be inert.", TAG);
74
+ NSError *err = [NSError errorWithDomain:Domain
75
+ code:MAURBGServiceError
76
+ userInfo:@{ NSLocalizedDescriptionKey: @"CMMotionActivityManager unavailable on this device." }];
77
+ if (self.delegate && [self.delegate respondsToSelector:@selector(onError:)]) {
78
+ [self.delegate onError:err];
79
+ }
80
+ return;
56
81
  }
82
+
83
+ activityManager = [[CMMotionActivityManager alloc] init];
84
+ activityQueue = [[NSOperationQueue alloc] init];
85
+ activityQueue.name = @"MAURActivityRecognitionQueue";
86
+ activityQueue.maxConcurrentOperationCount = 1;
57
87
  }
58
88
 
59
89
  - (BOOL) onConfigure:(MAURConfig*)config error:(NSError * __autoreleasing *)outError
60
90
  {
61
91
  DDLogVerbose(@"%@ configure", TAG);
62
-
92
+
93
+ currentConfig = config;
94
+
63
95
  locationManager.pausesLocationUpdatesAutomatically = [config pauseLocationUpdates];
64
96
  locationManager.activityType = [config decodeActivityType];
65
97
  locationManager.distanceFilter = config.distanceFilter.integerValue; // meters
66
98
  locationManager.desiredAccuracy = [config decodeDesiredAccuracy];
67
- [SOMotionDetector sharedInstance].activityDetectionInterval = config.activitiesInterval.intValue / 1000;
68
-
99
+
69
100
  return YES;
70
101
  }
71
102
 
72
103
  - (BOOL) onStart:(NSError * __autoreleasing *)outError
73
104
  {
74
105
  DDLogInfo(@"%@ will start", TAG);
75
-
76
- if (!isStarted) {
77
- [[SOMotionDetector sharedInstance] startDetection];
106
+
107
+ if (isStarted) {
108
+ return YES;
109
+ }
110
+
111
+ if (!motionAvailable || activityManager == nil) {
112
+ // No motion service: degrade gracefully — keep emitting raw location
113
+ // changes via the manager, but the STILL/ACTIVE state machine is off.
78
114
  [self startTracking];
79
115
  isStarted = YES;
116
+ return YES;
117
+ }
118
+
119
+ // iOS 11+: surface motion-permission denial up-front so the host app can
120
+ // re-prompt. Older iOS versions deliver permission errors via the handler.
121
+ if (@available(iOS 11.0, *)) {
122
+ CMAuthorizationStatus authStatus = [CMMotionActivityManager authorizationStatus];
123
+ if (authStatus == CMAuthorizationStatusDenied || authStatus == CMAuthorizationStatusRestricted) {
124
+ if (!motionPermissionErrorEmitted) {
125
+ NSError *err = [NSError errorWithDomain:Domain
126
+ code:MAURBGServiceError
127
+ userInfo:@{ NSLocalizedDescriptionKey: @"Motion & Fitness permission denied; ACTIVITY_PROVIDER cannot detect STILL/ACTIVE." }];
128
+ if (self.delegate && [self.delegate respondsToSelector:@selector(onError:)]) {
129
+ [self.delegate onError:err];
130
+ }
131
+ motionPermissionErrorEmitted = YES;
132
+ }
133
+ // Still start raw tracking so locations flow.
134
+ [self startTracking];
135
+ isStarted = YES;
136
+ return YES;
137
+ }
80
138
  }
81
-
139
+
140
+ __weak typeof(self) weakSelf = self;
141
+ [activityManager startActivityUpdatesToQueue:activityQueue
142
+ withHandler:^(CMMotionActivity * _Nullable activity) {
143
+ typeof(self) strongSelf = weakSelf;
144
+ if (strongSelf == nil || activity == nil) return;
145
+ [strongSelf handleActivityUpdate:activity];
146
+ }];
147
+
148
+ // v4.5.2 — start tracking immediately. Without this, if the user opens the
149
+ // app while already still, CoreMotion fires STILL first and `handleActivityUpdate`
150
+ // never calls startTracking (its rule is "ACTIVE → start"), so no fix is ever
151
+ // produced and the initial stationary is never emitted. Mirrors the legacy
152
+ // SOMotionDetector behavior. If CoreMotion subsequently confirms STILL, the
153
+ // first incoming fix will trigger `onStationaryChanged` + `stopTracking`, so
154
+ // battery cost stays bounded.
155
+ [self startTracking];
156
+
157
+ isStarted = YES;
82
158
  return YES;
83
159
  }
84
160
 
85
161
  - (BOOL) onStop:(NSError * __autoreleasing *)outError
86
162
  {
87
163
  DDLogInfo(@"%@ will stop", TAG);
88
-
89
- if (isStarted) {
90
- [[SOMotionDetector sharedInstance] stopDetection];
91
- [self stopTracking];
92
- isStarted = NO;
164
+
165
+ if (!isStarted) {
166
+ return YES;
167
+ }
168
+
169
+ if (activityManager != nil) {
170
+ [activityManager stopActivityUpdates];
93
171
  }
94
-
172
+ [self stopTracking];
173
+ isStarted = NO;
174
+
95
175
  return YES;
96
176
  }
97
177
 
178
+ #pragma mark - CMMotionActivity → MAURActivity bridge
179
+
180
+ - (void) handleActivityUpdate:(CMMotionActivity *)activity
181
+ {
182
+ // CMMotionActivityConfidence: Low=0, Medium=1, High=2 → normalize to 0-100
183
+ // so activityConfidenceThreshold means the same thing as on Android.
184
+ int confidence;
185
+ switch (activity.confidence) {
186
+ case CMMotionActivityConfidenceLow: confidence = 20; break;
187
+ case CMMotionActivityConfidenceMedium: confidence = 40; break;
188
+ case CMMotionActivityConfidenceHigh: confidence = 80; break;
189
+ default: confidence = 0; break;
190
+ }
191
+
192
+ NSNumber *thresholdN = currentConfig.activityConfidenceThreshold;
193
+ if (thresholdN != nil && confidence < thresholdN.intValue) {
194
+ DDLogDebug(@"%@ ignoring low-confidence activity confidence=%d threshold=%@", TAG, confidence, thresholdN);
195
+ return;
196
+ }
197
+
198
+ MAURMotionType motionType;
199
+ NSString *typeStr;
200
+ if (activity.automotive) {
201
+ motionType = MAURMotionTypeAutomotive;
202
+ typeStr = @"IN_VEHICLE";
203
+ } else if (activity.cycling) {
204
+ motionType = MAURMotionTypeCycling;
205
+ typeStr = @"ON_BICYCLE";
206
+ } else if (activity.running) {
207
+ motionType = MAURMotionTypeRunning;
208
+ typeStr = @"RUNNING";
209
+ } else if (activity.walking) {
210
+ motionType = MAURMotionTypeWalking;
211
+ typeStr = @"WALKING";
212
+ } else if (activity.stationary) {
213
+ motionType = MAURMotionTypeNotMoving;
214
+ typeStr = @"STILL";
215
+ } else {
216
+ // CoreMotion fired "unknown" or no specific motion flag set. Do NOT
217
+ // collapse this to NotMoving — under uncertainty we keep the current
218
+ // tracking state (legacy behavior was to stop on UNKNOWN, which paused
219
+ // GPS unexpectedly during low-confidence motion gaps).
220
+ motionType = MAURMotionTypeUnknown;
221
+ typeStr = @"UNKNOWN";
222
+ }
223
+
224
+ // Hop to main queue: location manager + delegate are main-thread-affine.
225
+ dispatch_async(dispatch_get_main_queue(), ^{
226
+ // UNKNOWN must not perturb the state machine: no emit, no lastMotionType
227
+ // mutation, no tracking change. Otherwise a sequence STILL → UNKNOWN
228
+ // would lose the STILL state and the next fix would be delivered as a
229
+ // regular location instead of stationary.
230
+ if (motionType == MAURMotionTypeUnknown) {
231
+ DDLogDebug(@"%@ ignoring UNKNOWN activity (state preserved, confidence=%d)", TAG, confidence);
232
+ return;
233
+ }
234
+
235
+ BOOL changed = (motionType != self->lastMotionType);
236
+ self->lastMotionType = motionType;
237
+
238
+ if (changed) {
239
+ DDLogDebug(@"%@ activityTypeChanged: %@ confidence=%d", TAG, typeStr, confidence);
240
+ MAURActivity *act = [[MAURActivity alloc] init];
241
+ act.type = typeStr;
242
+ act.confidence = [NSNumber numberWithInt:confidence];
243
+ if (super.delegate && [super.delegate respondsToSelector:@selector(onActivityChanged:)]) {
244
+ [super.delegate onActivityChanged:act];
245
+ }
246
+ }
247
+
248
+ // Tracking control:
249
+ // - ACTIVE motion (walking, running, automotive, cycling) → ensure tracking is on.
250
+ // - STILL → leave tracking running; it will be stopped after the next fix (legacy).
251
+ if (motionType != MAURMotionTypeNotMoving) {
252
+ [self startTracking];
253
+ }
254
+ });
255
+ }
256
+
257
+ #pragma mark - Location plumbing
258
+
98
259
  - (void) startTracking
99
260
  {
100
261
  if (isTracking) {
@@ -105,7 +266,9 @@ static NSString * const Domain = @"com.marianhello";
105
266
  if ([locationManager start:&error]) {
106
267
  isTracking = YES;
107
268
  } else {
108
- [self.delegate onError:error];
269
+ if (self.delegate && [self.delegate respondsToSelector:@selector(onError:)]) {
270
+ [self.delegate onError:error];
271
+ }
109
272
  }
110
273
  }
111
274
 
@@ -129,9 +292,13 @@ static NSString * const Domain = @"com.marianhello";
129
292
 
130
293
  - (void) onLocationsChanged:(NSArray*)locations
131
294
  {
132
- if (lastMotionType == MotionTypeNotMoving) {
295
+ // v4.5.2: while NotMoving we only emit the stationary fix; the previous
296
+ // code fell through and also delivered each location as onLocationChanged,
297
+ // which produced phantom "moving" rows during a STILL window.
298
+ if (lastMotionType == MAURMotionTypeNotMoving) {
133
299
  [self stopTracking];
134
300
  [self.delegate onStationaryChanged:[MAURLocation fromCLLocation:[locations lastObject]]];
301
+ return;
135
302
  }
136
303
 
137
304
  for (CLLocation *location in locations) {
@@ -140,45 +307,6 @@ static NSString * const Domain = @"com.marianhello";
140
307
  }
141
308
  }
142
309
 
143
- - (void)motionDetector:(SOMotionDetector *)motionDetector activityTypeChanged:(SOMotionActivity *)motionActivity;
144
- {
145
- int confidence = motionActivity.confidence;
146
- SOMotionType motionType = motionActivity.motionType;
147
- lastMotionType = motionType;
148
-
149
- if (motionType != MotionTypeNotMoving) {
150
- [self startTracking];
151
- } else {
152
- // we delay tracking stop after location is found
153
- }
154
-
155
- NSString *type;
156
- switch (motionType) {
157
- case MotionTypeNotMoving:
158
- type = @"STILL";
159
- break;
160
- case MotionTypeWalking:
161
- type = @"WALKING";
162
- break;
163
- case MotionTypeRunning:
164
- type = @"RUNNING";
165
- break;
166
- case MotionTypeAutomotive:
167
- type = @"IN_VEHICLE";
168
- break;
169
- case MotionTypeUnknown:
170
- type = @"UNKNOWN";
171
- break;
172
- }
173
-
174
- DDLogDebug(@"%@ activityTypeChanged: %@", TAG, type);
175
- MAURActivity *activity = [[MAURActivity alloc] init];
176
- activity.type = type;
177
- activity.confidence = [NSNumber numberWithInt:confidence];
178
-
179
- [super.delegate onActivityChanged:activity];
180
- }
181
-
182
310
  - (void) onError:(NSError*)error
183
311
  {
184
312
  [self.delegate onError:error];
@@ -197,6 +325,16 @@ static NSString * const Domain = @"com.marianhello";
197
325
  - (void) onDestroy {
198
326
  DDLogInfo(@"Destroying %@ ", TAG);
199
327
  [self onStop:nil];
328
+
329
+ // v4.5.2: MAURLocationManager is a singleton shared with the other providers
330
+ // (RAW, DISTANCE). Release the delegate slot so a subsequent provider swap
331
+ // does not leave this destroyed instance as the active delegate.
332
+ if (locationManager != nil && locationManager.delegate == self) {
333
+ locationManager.delegate = nil;
334
+ }
335
+
336
+ activityManager = nil;
337
+ activityQueue = nil;
200
338
  }
201
339
 
202
340
  @end