@josuelmm/cordova-background-geolocation 3.1.0 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## [3.1.1](https://github.com/josuelmm/cordova-background-geolocation/tree/3.1.1) (2026-02-27)
4
+
5
+ ### Added
6
+
7
+ - **Browser / `ng serve` support** — The plugin uses `cordova/exec` and `cordova/channel`, which only exist in the Cordova runtime. To allow Angular (and other webpack-based) builds to succeed when running `ng serve` or building for browser, the package now ships:
8
+ - **Stub modules:** `www/cordova-exec-stub.js` and `www/cordova-channel-stub.js`. When running in the browser they avoid crashes; when running inside Cordova they delegate to the real `cordova.exec` and `cordova/channel`.
9
+ - **`browser` field in `package.json`** so bundlers (e.g. webpack) resolve `cordova/exec` and `cordova/channel` to these stubs. No app-side webpack config is required in normal setups.
10
+
11
+ ### Fixed
12
+
13
+ - **Angular types on Windows** — The emitted `.d.ts` in `angular/dist/` used `from '../www/BackgroundGeolocation'`, which resolves (relative to `dist/`) to `angular/www/`, a folder that does not exist in the published package. Some apps worked around this by creating a junction `angular/www` → `www`. The build now runs a post-step (`scripts/fix-angular-dts-paths.js`) that rewrites these paths to `../../www/BackgroundGeolocation` in the emitted declarations, so types resolve to the package root `www/` and the junction is no longer needed.
14
+
15
+ ### Documentation
16
+
17
+ - **docs/angular.md** — New section *"Build (ng serve / browser)"*: explains why the stubs exist, that webpack uses the `browser` field, and how to add resolve aliases in the app if a bundler does not respect it (e.g. "Can't resolve 'cordova/exec'"). Note that from 3.1.1 the junction workaround for types is unnecessary.
18
+
19
+ - **README.md** — Angular section and "New in" updated to mention 3.1.1 and browser/ng serve compatibility.
20
+
21
+ ### Changed
22
+
23
+ - Version bump to 3.1.1.
24
+
25
+ [Full Changelog](https://github.com/josuelmm/cordova-background-geolocation/compare/3.1.0...3.1.1)
26
+
27
+ ---
28
+
3
29
  ## [3.1.0](https://github.com/josuelmm/cordova-background-geolocation/tree/3.1.0) (2026-02-21)
4
30
 
5
31
  ### Added
package/README.md CHANGED
@@ -95,6 +95,22 @@ If your app’s merged manifest ends up with a `foregroundServiceType` other tha
95
95
  <string name="plugin_bgloc_content_authority">site.seelight.client.bgloc</string>
96
96
  ```
97
97
 
98
+ **Notification labels (showTime / showDistance):** If you use `showTime: true` or `showDistance: true`, the notification shows a line for elapsed time and one for distance. **By default the labels are in English** ("Time" and "Distance"). If you want Spanish or another language, add these optional strings in your app so the plugin uses them; if you don’t define them, English is used.
99
+
100
+ Example — English (default, optional in `res/values/strings.xml`):
101
+
102
+ ```xml
103
+ <string name="plugin_bgloc_notification_time_label">Time</string>
104
+ <string name="plugin_bgloc_notification_distance_label">Distance</string>
105
+ ```
106
+
107
+ Example — Spanish: add in `res/values-es/strings.xml` (or your locale folder):
108
+
109
+ ```xml
110
+ <string name="plugin_bgloc_notification_time_label">Tiempo</string>
111
+ <string name="plugin_bgloc_notification_distance_label">Distancia</string>
112
+ ```
113
+
98
114
  This makes your app enforce the correct foreground service type and defines the strings the plugin needs for the sync account.
99
115
 
100
116
  ---
@@ -252,7 +268,18 @@ BackgroundGeolocation.stop();
252
268
 
253
269
  ### 5. Sync queue (syncUrl): pending count, force sync, clear queue
254
270
 
255
- When you use `syncUrl`, locations that fail to post to `url` (or that are only queued for sync) are sent in batch to `syncUrl`. You can:
271
+ When you use `syncUrl`, locations that fail to post to `url` (or that are only queued for sync) are sent in batch to `syncUrl`.
272
+
273
+ **How sync sends data (Content-Type):** It depends on the `Content-Type` you set in `httpHeaders`. Many people assume “one request per location”; that is only true for form encoding.
274
+
275
+ | Content-Type | Sync to `syncUrl` |
276
+ |--------------|-------------------|
277
+ | **`application/json`** (default) | **One POST** with a JSON **array** of all locations in the batch. |
278
+ | **`application/x-www-form-urlencoded`** | **One POST per location** (same flat `key=value&...` as real-time to `url`). Same endpoint can handle both. |
279
+
280
+ So: with **JSON** you get one request per batch (e.g. 100 locations in one body). With **form-urlencoded** you get one request per location (one record per POST). For headers, retries, `postTemplate` and full behaviour see [HTTP posting](docs/http_posting.md) and [API](docs/api.md).
281
+
282
+ You can:
256
283
 
257
284
  - **Get pending count** — `getPendingSyncCount()` returns how many locations are waiting to be synced.
258
285
  - **Force sync now** — `forceSync()` sends all pending locations immediately (ignores `syncThreshold`). No-op if `sync: false`.
@@ -277,6 +304,8 @@ BackgroundGeolocation.clearSync().then(function () {
277
304
  });
278
305
  ```
279
306
 
307
+ More on sync (headers, retries, postTemplate): [HTTP posting](docs/http_posting.md). Full options and methods: [API](docs/api.md).
308
+
280
309
  ### 6. Other methods (summary)
281
310
 
282
311
  | Method | Description |
@@ -287,7 +316,7 @@ BackgroundGeolocation.clearSync().then(function () {
287
316
  | `deleteLocation(id, success, fail)` | Delete one location by id. |
288
317
  | `deleteAllLocations(success, fail)` | Delete all stored locations. |
289
318
  | `getCurrentLocation(success, fail, options)` | One-shot location (e.g. timeout, maximumAge). |
290
- | `getPluginVersion(success, fail)` | Plugin version string (e.g. "3.1.0"). |
319
+ | `getPluginVersion(success, fail)` | Plugin version string (e.g. "3.1.1"). |
291
320
  | `checkStatus(success, fail)` | Service status (isRunning, authorization, etc.). |
292
321
  | `showAppSettings()` / `openSettings()` | Open app settings. |
293
322
  | `showLocationSettings()` | Open system location settings. |
@@ -314,6 +343,10 @@ Subscribe with `BackgroundGeolocation.on(eventName, callback)`. Unsubscribe with
314
343
 
315
344
  Full event payloads and options: [Events](docs/events.md). Full API (all options, all methods): [API](docs/api.md).
316
345
 
346
+ ### New in 3.1.1
347
+
348
+ - **Browser / `ng serve` builds** — The plugin can now be bundled by webpack without "Can't resolve 'cordova/exec'" or "Can't resolve 'cordova/channel'". The package ships stub modules and a `browser` field so `ng serve` and browser builds succeed; on device/emulator the stubs delegate to the real Cordova API. See [docs/angular.md](docs/angular.md#build-ng-serve--browser).
349
+
317
350
  ### New in 3.1.0
318
351
 
319
352
  - **`getPendingSyncCount()`** — Number of locations pending to be synced. Use with `forceSync()` for “X pending” UI.
@@ -370,6 +403,10 @@ export class MyService {
370
403
 
371
404
  **You must import `BackgroundGeolocationModule`** in your `AppModule` (or feature module) so the service is provided and AOT builds work. Then inject `BackgroundGeolocationService` as in the example above. See [docs/angular.md](docs/angular.md) for the full snippet.
372
405
 
406
+ **`ng serve` / browser:** From 3.1.1 the plugin includes browser stubs so `ng serve` and web builds complete without "Can't resolve 'cordova/exec'" — see [docs/angular.md](docs/angular.md#build-ng-serve--browser).
407
+
408
+ **Lazy-loaded pages:** If you see **NG0202** or *"dependency at index N is invalid"* when opening a page that injects this service, use an app-defined token and inject by that token (the plugin token can be undefined in the lazy chunk). See [docs/angular.md](docs/angular.md) (Lazy-loaded modules).
409
+
373
410
  **Migrating from @awesome-cordova-plugins/background-geolocation:** there you inject a class named `BackgroundGeolocation`. In this package, `BackgroundGeolocation` is the **global plugin object**, not an injectable class. Use `BackgroundGeolocationService` instead (same API). See [docs/angular.md](docs/angular.md) for details.
374
411
 
375
412
  ### Summary
@@ -394,13 +431,16 @@ No extra wrapper (e.g. Awesome Cordova Plugins) is required.
394
431
 
395
432
  ## Documentation and changelog
396
433
 
397
- - **[Documentation](https://josuelmm.github.io/cordova-background-geolocation/)** — Full docs (API, options, examples).
398
- - **[API reference](docs/api.md)** — All `configure` options, every method (`configure`, `start`, `stop`, `getPendingSyncCount`, `forceSync`, `clearSync`, `getConfig`, `getLocations`, etc.), TypeScript types.
399
- - **[HTTP posting](docs/http_posting.md)** `url` vs `syncUrl`, headers, Content-Type (JSON vs form-urlencoded), sync batch behaviour, `getPendingSyncCount` / `forceSync` / `clearSync`.
400
- - **[Events](docs/events.md)** — All events (`location`, `error`, `stationary`, `activity`, `http_authorization`, etc.) and payloads.
401
- - **[Angular / Ionic](docs/angular.md)** Injectable service, module, same API.
402
- - **[Example](docs/example.md)** Full example with events and sync.
403
- - **[CHANGELOG](CHANGELOG.md)** Version history.
434
+ This README is the main entry point. For more detail, edge cases and examples use the docs below (and the [online documentation](https://josuelmm.github.io/cordova-background-geolocation/)).
435
+
436
+ | Doc | What you’ll find |
437
+ |-----|------------------|
438
+ | **[API reference](docs/api.md)** | Every `configure` option, every method (`configure`, `start`, `stop`, `getPendingSyncCount`, `forceSync`, `clearSync`, `getConfig`, `getLocations`, etc.), TypeScript types. |
439
+ | **[HTTP posting](docs/http_posting.md)** | `url` vs `syncUrl`, Content-Type (JSON = one POST with array; form-urlencoded = one POST per location), headers, retries, `postTemplate`, sync behaviour. |
440
+ | **[Events](docs/events.md)** | All events (`location`, `error`, `stationary`, `activity`, `http_authorization`, etc.) and payloads. |
441
+ | **[Angular / Ionic](docs/angular.md)** | Injectable service, module, lazy-loaded modules and token “must be defined”, `ng serve` / browser build. |
442
+ | **[Example](docs/example.md)** | Full example with events and sync. |
443
+ | **[CHANGELOG](CHANGELOG.md)** | Version history. |
404
444
 
405
445
  This project is based on [@mauron85/cordova-plugin-background-geolocation](https://github.com/mauron85/cordova-plugin-background-geolocation) and the original by [christocracy](https://github.com/christocracy). Maintained at [josuelmm/cordova-background-geolocation](https://github.com/josuelmm/cordova-background-geolocation). Issues and PRs welcome.
406
446
 
package/RELEASE.MD CHANGED
@@ -1,5 +1,16 @@
1
1
  In order to release a version the following actions are needed:
2
- 1. Update the version in the package.json, lock and plugin.xml files
3
- 2. Go to [releases and create a new release](https://github.com/josuelmm/cordova-background-geolocation/releases/new) in GitHub
4
- 3. Make sure to create a tag too with the version name (e.g. v3.1.0)
5
- 4. Click create. Github actions will publish the package to npm
2
+
3
+ 1. **Version and docs**
4
+ - Set the new version (e.g. `3.1.1`) in:
5
+ - `package.json` (`version` field)
6
+ - `plugin.xml` (root `<plugin>` attribute `version`)
7
+ - Add a release entry in `CHANGELOG.md` (date, Added/Fixed/Changed/Documentation).
8
+ - Update `README.md` if needed (e.g. "New in X.Y.Z", example version strings).
9
+
10
+ 2. **Lock file**
11
+ - Run `npm install` so `package-lock.json` stays in sync with `package.json`.
12
+
13
+ 3. **Git tag and release**
14
+ - Go to [Releases → Create a new release](https://github.com/josuelmm/cordova-background-geolocation/releases/new).
15
+ - Create a tag with the version name (e.g. `v3.1.1`).
16
+ - Publish the release. GitHub Actions will publish the package to npm.
@@ -116,6 +116,12 @@ public class ConfigMapper {
116
116
  if (jObject.has("enableWatchdog")) {
117
117
  config.setEnableWatchdog(jObject.getBoolean("enableWatchdog"));
118
118
  }
119
+ if (jObject.has("showTime")) {
120
+ config.setShowTime(jObject.getBoolean("showTime"));
121
+ }
122
+ if (jObject.has("showDistance")) {
123
+ config.setShowDistance(jObject.getBoolean("showDistance"));
124
+ }
119
125
 
120
126
  return config;
121
127
  }
@@ -151,6 +157,8 @@ public class ConfigMapper {
151
157
  json.put("httpHeaders", new JSONObject(config.getHttpHeaders()));
152
158
  json.put("maxLocations", config.getMaxLocations());
153
159
  json.put("enableWatchdog", Boolean.TRUE.equals(config.getEnableWatchdog()));
160
+ json.put("showTime", Boolean.TRUE.equals(config.getShowTime()));
161
+ json.put("showDistance", Boolean.TRUE.equals(config.getShowDistance()));
154
162
  LocationTemplate tpl = config.getTemplate();
155
163
  Object template = JSONObject.NULL;
156
164
  if (tpl instanceof HashMapLocationTemplate) {
@@ -69,6 +69,8 @@ public class Config implements Parcelable
69
69
  private Integer maxLocations;
70
70
  private LocationTemplate template;
71
71
  private Boolean enableWatchdog;
72
+ private Boolean showTime;
73
+ private Boolean showDistance;
72
74
 
73
75
  public Config () {
74
76
  }
@@ -104,6 +106,8 @@ public class Config implements Parcelable
104
106
  this.httpHeaders = CloneHelper.deepCopy(config.httpHeaders);
105
107
  this.maxLocations = config.maxLocations;
106
108
  this.enableWatchdog = config.enableWatchdog;
109
+ this.showTime = config.showTime;
110
+ this.showDistance = config.showDistance;
107
111
  if (config.template instanceof AbstractLocationTemplate) {
108
112
  this.template = ((AbstractLocationTemplate)config.template).clone();
109
113
  }
@@ -138,6 +142,8 @@ public class Config implements Parcelable
138
142
  setSyncEnabled((Boolean) in.readValue(null));
139
143
  setMaxLocations(in.readInt());
140
144
  setEnableWatchdog((Boolean) in.readValue(null));
145
+ setShowTime((Boolean) in.readValue(null));
146
+ setShowDistance((Boolean) in.readValue(null));
141
147
  Bundle bundle = in.readBundle();
142
148
  setHttpHeaders((HashMap<String, String>) bundle.getSerializable("httpHeaders"));
143
149
  setTemplate((LocationTemplate) bundle.getSerializable(AbstractLocationTemplate.BUNDLE_KEY));
@@ -175,6 +181,8 @@ public class Config implements Parcelable
175
181
  config.maxLocations = 10000;
176
182
  config.template = null;
177
183
  config.enableWatchdog = false;
184
+ config.showTime = false;
185
+ config.showDistance = false;
178
186
 
179
187
  return config;
180
188
  }
@@ -213,6 +221,8 @@ public class Config implements Parcelable
213
221
  out.writeValue(getSyncEnabled());
214
222
  out.writeInt(getMaxLocations());
215
223
  out.writeValue(getEnableWatchdog());
224
+ out.writeValue(getShowTime());
225
+ out.writeValue(getShowDistance());
216
226
  Bundle bundle = new Bundle();
217
227
  bundle.putSerializable("httpHeaders", getHttpHeaders());
218
228
  bundle.putSerializable(AbstractLocationTemplate.BUNDLE_KEY, (AbstractLocationTemplate) getTemplate());
@@ -609,6 +619,32 @@ public class Config implements Parcelable
609
619
  this.enableWatchdog = enableWatchdog;
610
620
  }
611
621
 
622
+ public boolean hasShowTime() {
623
+ return showTime != null;
624
+ }
625
+
626
+ @Nullable
627
+ public Boolean getShowTime() {
628
+ return showTime;
629
+ }
630
+
631
+ public void setShowTime(Boolean showTime) {
632
+ this.showTime = showTime;
633
+ }
634
+
635
+ public boolean hasShowDistance() {
636
+ return showDistance != null;
637
+ }
638
+
639
+ @Nullable
640
+ public Boolean getShowDistance() {
641
+ return showDistance;
642
+ }
643
+
644
+ public void setShowDistance(Boolean showDistance) {
645
+ this.showDistance = showDistance;
646
+ }
647
+
612
648
  @Override
613
649
  public String toString () {
614
650
  return new StringBuffer()
@@ -637,6 +673,8 @@ public class Config implements Parcelable
637
673
  .append(" httpHeaders=").append(getHttpHeaders().toString())
638
674
  .append(" maxLocations=").append(getMaxLocations())
639
675
  .append(" postTemplate=").append(hasTemplate() ? getTemplate().toString() : null)
676
+ .append(" showTime=").append(getShowTime())
677
+ .append(" showDistance=").append(getShowDistance())
640
678
  .append("]")
641
679
  .toString();
642
680
  }
@@ -744,6 +782,12 @@ public class Config implements Parcelable
744
782
  if (config2.hasTemplate()) {
745
783
  merger.setTemplate(config2.getTemplate());
746
784
  }
785
+ if (config2.hasShowTime()) {
786
+ merger.setShowTime(config2.getShowTime());
787
+ }
788
+ if (config2.hasShowDistance()) {
789
+ merger.setShowDistance(config2.getShowDistance());
790
+ }
747
791
 
748
792
  return merger;
749
793
  }
@@ -45,6 +45,8 @@ public final class SQLiteConfigurationContract {
45
45
  public static final String COLUMN_NAME_HEADERS = "http_headers";
46
46
  public static final String COLUMN_NAME_MAX_LOCATIONS = "max_locations";
47
47
  public static final String COLUMN_NAME_TEMPLATE = "template";
48
+ public static final String COLUMN_NAME_SHOW_TIME = "show_time";
49
+ public static final String COLUMN_NAME_SHOW_DISTANCE = "show_distance";
48
50
 
49
51
  public static final String SQL_CREATE_CONFIG_TABLE =
50
52
  "CREATE TABLE " + ConfigurationEntry.TABLE_NAME + " (" +
@@ -77,7 +79,9 @@ public final class SQLiteConfigurationContract {
77
79
  ConfigurationEntry.COLUMN_NAME_SYNC_ENABLED + INTEGER_TYPE + COMMA_SEP +
78
80
  ConfigurationEntry.COLUMN_NAME_HEADERS + TEXT_TYPE + COMMA_SEP +
79
81
  ConfigurationEntry.COLUMN_NAME_MAX_LOCATIONS + INTEGER_TYPE + COMMA_SEP +
80
- ConfigurationEntry.COLUMN_NAME_TEMPLATE + TEXT_TYPE +
82
+ ConfigurationEntry.COLUMN_NAME_TEMPLATE + TEXT_TYPE + COMMA_SEP +
83
+ ConfigurationEntry.COLUMN_NAME_SHOW_TIME + INTEGER_TYPE + COMMA_SEP +
84
+ ConfigurationEntry.COLUMN_NAME_SHOW_DISTANCE + INTEGER_TYPE +
81
85
  " )";
82
86
 
83
87
  public static final String SQL_DROP_CONFIG_TABLE =
@@ -61,7 +61,9 @@ public class SQLiteConfigurationDAO implements ConfigurationDAO {
61
61
  ConfigurationEntry.COLUMN_NAME_SYNC_ENABLED,
62
62
  ConfigurationEntry.COLUMN_NAME_HEADERS,
63
63
  ConfigurationEntry.COLUMN_NAME_MAX_LOCATIONS,
64
- ConfigurationEntry.COLUMN_NAME_TEMPLATE
64
+ ConfigurationEntry.COLUMN_NAME_TEMPLATE,
65
+ ConfigurationEntry.COLUMN_NAME_SHOW_TIME,
66
+ ConfigurationEntry.COLUMN_NAME_SHOW_DISTANCE
65
67
  };
66
68
 
67
69
  String whereClause = null;
@@ -142,6 +144,14 @@ public class SQLiteConfigurationDAO implements ConfigurationDAO {
142
144
  config.setHttpHeaders(new JSONObject(c.getString(c.getColumnIndex(ConfigurationEntry.COLUMN_NAME_HEADERS))));
143
145
  config.setMaxLocations(c.getInt(c.getColumnIndex(ConfigurationEntry.COLUMN_NAME_MAX_LOCATIONS)));
144
146
  config.setTemplate(LocationTemplateFactory.fromJSONString(c.getString(c.getColumnIndex(ConfigurationEntry.COLUMN_NAME_TEMPLATE))));
147
+ int idxShowTime = c.getColumnIndex(ConfigurationEntry.COLUMN_NAME_SHOW_TIME);
148
+ if (idxShowTime >= 0 && !c.isNull(idxShowTime)) {
149
+ config.setShowTime(c.getInt(idxShowTime) == 1);
150
+ }
151
+ int idxShowDistance = c.getColumnIndex(ConfigurationEntry.COLUMN_NAME_SHOW_DISTANCE);
152
+ if (idxShowDistance >= 0 && !c.isNull(idxShowDistance)) {
153
+ config.setShowDistance(c.getInt(idxShowDistance) == 1);
154
+ }
145
155
 
146
156
  return config;
147
157
  }
@@ -178,6 +188,8 @@ public class SQLiteConfigurationDAO implements ConfigurationDAO {
178
188
  values.put(ConfigurationEntry.COLUMN_NAME_HEADERS, new JSONObject(config.getHttpHeaders()).toString());
179
189
  values.put(ConfigurationEntry.COLUMN_NAME_MAX_LOCATIONS, config.getMaxLocations());
180
190
  values.put(ConfigurationEntry.COLUMN_NAME_TEMPLATE, config.hasTemplate() ? config.getTemplate().toString() : null);
191
+ values.put(ConfigurationEntry.COLUMN_NAME_SHOW_TIME, Boolean.TRUE.equals(config.getShowTime()) ? 1 : 0);
192
+ values.put(ConfigurationEntry.COLUMN_NAME_SHOW_DISTANCE, Boolean.TRUE.equals(config.getShowDistance()) ? 1 : 0);
181
193
 
182
194
  return values;
183
195
  }
@@ -21,7 +21,7 @@ import static com.marianhello.bgloc.data.sqlite.SQLiteLocationContract.LocationE
21
21
  public class SQLiteOpenHelper extends android.database.sqlite.SQLiteOpenHelper {
22
22
  private static final String TAG = SQLiteOpenHelper.class.getName();
23
23
  public static final String SQLITE_DATABASE_NAME = "cordova_bg_geolocation.db";
24
- public static final int DATABASE_VERSION = 18;
24
+ public static final int DATABASE_VERSION = 19;
25
25
 
26
26
  public static final String TEXT_TYPE = " TEXT";
27
27
  public static final String INTEGER_TYPE = " INTEGER";
@@ -132,6 +132,11 @@ public class SQLiteOpenHelper extends android.database.sqlite.SQLiteOpenHelper {
132
132
  case 17:
133
133
  alterSql.add("ALTER TABLE " + ConfigurationEntry.TABLE_NAME +
134
134
  " ADD COLUMN " + ConfigurationEntry.COLUMN_NAME_SYNC_ENABLED + INTEGER_TYPE);
135
+ case 18:
136
+ alterSql.add("ALTER TABLE " + ConfigurationEntry.TABLE_NAME +
137
+ " ADD COLUMN " + ConfigurationEntry.COLUMN_NAME_SHOW_TIME + INTEGER_TYPE);
138
+ alterSql.add("ALTER TABLE " + ConfigurationEntry.TABLE_NAME +
139
+ " ADD COLUMN " + ConfigurationEntry.COLUMN_NAME_SHOW_DISTANCE + INTEGER_TYPE);
135
140
 
136
141
  break; // DO NOT FORGET TO MOVE DOWN BREAK ON DB UPGRADE!!!
137
142
  default:
@@ -22,6 +22,7 @@ import android.content.ComponentName;
22
22
  import android.content.pm.PackageManager;
23
23
  import android.content.pm.ServiceInfo;
24
24
  import android.Manifest;
25
+ import android.location.Location;
25
26
  import android.net.ConnectivityManager;
26
27
  import android.net.NetworkInfo;
27
28
  import android.os.Binder;
@@ -67,6 +68,8 @@ import com.marianhello.logging.UncaughtExceptionLogger;
67
68
  import org.chromium.content.browser.ThreadUtils;
68
69
  import org.json.JSONException;
69
70
 
71
+ import java.util.Locale;
72
+
70
73
  import static com.marianhello.bgloc.service.LocationServiceIntentBuilder.containsCommand;
71
74
  import static com.marianhello.bgloc.service.LocationServiceIntentBuilder.containsMessage;
72
75
  import static com.marianhello.bgloc.service.LocationServiceIntentBuilder.getCommand;
@@ -156,6 +159,30 @@ public class LocationServiceImpl extends Service implements ProviderDelegate, Lo
156
159
  }
157
160
  };
158
161
 
162
+ /** Session start time for notification elapsed time (showTime). */
163
+ private volatile long mSessionStartTime = 0L;
164
+ /** Accumulated distance in meters for notification (showDistance). */
165
+ private volatile double mSessionDistanceMeters = 0.0;
166
+ private volatile double mLastLat = 0.0;
167
+ private volatile double mLastLon = 0.0;
168
+ private volatile boolean mHasLastLocation = false;
169
+ private static final long NOTIFICATION_UPDATE_INTERVAL_MS = 1000L;
170
+ private final Runnable mNotificationUpdateRunnable = new Runnable() {
171
+ @Override
172
+ public void run() {
173
+ if (!sIsRunning || !mIsInForeground || mConfig == null) {
174
+ return;
175
+ }
176
+ boolean showTime = Boolean.TRUE.equals(mConfig.getShowTime());
177
+ boolean showDistance = Boolean.TRUE.equals(mConfig.getShowDistance());
178
+ if (!showTime && !showDistance) {
179
+ return;
180
+ }
181
+ updateForegroundNotification();
182
+ mMainHandler.postDelayed(this, NOTIFICATION_UPDATE_INTERVAL_MS);
183
+ }
184
+ };
185
+
159
186
  private static LocationTransform sLocationTransform;
160
187
  private static LocationProviderFactory sLocationProviderFactory;
161
188
 
@@ -418,6 +445,12 @@ public class LocationServiceImpl extends Service implements ProviderDelegate, Lo
418
445
  mMainHandler.postDelayed(mWatchdogRunnable, WATCHDOG_INTERVAL_MS);
419
446
  }
420
447
 
448
+ mSessionStartTime = System.currentTimeMillis();
449
+ mSessionDistanceMeters = 0.0;
450
+ mLastLat = 0.0;
451
+ mLastLon = 0.0;
452
+ mHasLastLocation = false;
453
+
421
454
  ThreadUtils.runOnUiThreadBlocking(new Runnable() {
422
455
  @Override
423
456
  public void run() {
@@ -447,6 +480,7 @@ public class LocationServiceImpl extends Service implements ProviderDelegate, Lo
447
480
  }
448
481
 
449
482
  mMainHandler.removeCallbacks(mWatchdogRunnable);
483
+ mMainHandler.removeCallbacks(mNotificationUpdateRunnable);
450
484
 
451
485
  if (mWakeLock != null && mWakeLock.isHeld()) {
452
486
  try {
@@ -461,6 +495,7 @@ public class LocationServiceImpl extends Service implements ProviderDelegate, Lo
461
495
  mProvider.onStop();
462
496
  }
463
497
 
498
+ mIsInForeground = false;
464
499
  stopForeground(true);
465
500
  stopSelf();
466
501
 
@@ -530,9 +565,10 @@ public class LocationServiceImpl extends Service implements ProviderDelegate, Lo
530
565
  return;
531
566
  }
532
567
  Config config = getConfig();
568
+ String contentText = buildNotificationContentText(config);
533
569
  Notification notification = new NotificationHelper.NotificationFactory(this).getNotification(
534
570
  config.getNotificationTitle(),
535
- config.getNotificationText(),
571
+ contentText,
536
572
  config.getLargeNotificationIcon(),
537
573
  config.getSmallNotificationIcon(),
538
574
  config.getNotificationIconColor());
@@ -548,12 +584,14 @@ public class LocationServiceImpl extends Service implements ProviderDelegate, Lo
548
584
  super.startForeground(NOTIFICATION_ID, notification);
549
585
  }
550
586
  mIsInForeground = true;
587
+ scheduleNotificationUpdater();
551
588
  }
552
589
  }
553
590
 
554
591
  @Override
555
592
  public synchronized void stopForeground() {
556
593
  if (sIsRunning && mIsInForeground) {
594
+ mMainHandler.removeCallbacks(mNotificationUpdateRunnable);
557
595
  stopForeground(true);
558
596
  if (mProvider != null) {
559
597
  mProvider.onCommand(LocationProvider.CMD_SWITCH_MODE,
@@ -563,6 +601,70 @@ public class LocationServiceImpl extends Service implements ProviderDelegate, Lo
563
601
  }
564
602
  }
565
603
 
604
+ /** Resource names for optional app-localized notification labels (showTime / showDistance). */
605
+ private static final String RES_NOTIFICATION_TIME_LABEL = "plugin_bgloc_notification_time_label";
606
+ private static final String RES_NOTIFICATION_DISTANCE_LABEL = "plugin_bgloc_notification_distance_label";
607
+
608
+ private String getNotificationLabel(String resourceName, String defaultValue) {
609
+ Context app = getApplicationContext();
610
+ int id = app.getResources().getIdentifier(resourceName, "string", app.getPackageName());
611
+ return (id != 0) ? app.getString(id) : defaultValue;
612
+ }
613
+
614
+ private String buildNotificationContentText(Config config) {
615
+ String base = config.getNotificationText() != null ? config.getNotificationText() : "ENABLED";
616
+ if (Boolean.TRUE.equals(config.getShowTime())) {
617
+ String timeLabel = getNotificationLabel(RES_NOTIFICATION_TIME_LABEL, "Time");
618
+ base += "\n" + timeLabel + ": " + formatElapsed(mSessionStartTime);
619
+ }
620
+ if (Boolean.TRUE.equals(config.getShowDistance())) {
621
+ String distanceLabel = getNotificationLabel(RES_NOTIFICATION_DISTANCE_LABEL, "Distance");
622
+ base += "\n" + distanceLabel + ": " + formatDistance(mSessionDistanceMeters);
623
+ }
624
+ return base;
625
+ }
626
+
627
+ private static String formatElapsed(long startTimeMs) {
628
+ long elapsed = Math.max(0L, System.currentTimeMillis() - startTimeMs);
629
+ long s = (elapsed / 1000L) % 60L;
630
+ long m = (elapsed / 60000L) % 60L;
631
+ long h = elapsed / 3600000L;
632
+ return String.format(Locale.US, "%02d:%02d:%02d", h, m, s);
633
+ }
634
+
635
+ private static String formatDistance(double meters) {
636
+ return String.format(Locale.US, "%.2f km", meters / 1000.0);
637
+ }
638
+
639
+ private void updateForegroundNotification() {
640
+ if (!sIsRunning || !mIsInForeground || mConfig == null) {
641
+ return;
642
+ }
643
+ String contentText = buildNotificationContentText(mConfig);
644
+ Notification notification = new NotificationHelper.NotificationFactory(this).getNotification(
645
+ mConfig.getNotificationTitle(),
646
+ contentText,
647
+ mConfig.getLargeNotificationIcon(),
648
+ mConfig.getSmallNotificationIcon(),
649
+ mConfig.getNotificationIconColor());
650
+ NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
651
+ if (nm != null) {
652
+ nm.notify(NOTIFICATION_ID, notification);
653
+ }
654
+ }
655
+
656
+ private void scheduleNotificationUpdater() {
657
+ mMainHandler.removeCallbacks(mNotificationUpdateRunnable);
658
+ if (mConfig == null) {
659
+ return;
660
+ }
661
+ boolean showTime = Boolean.TRUE.equals(mConfig.getShowTime());
662
+ boolean showDistance = Boolean.TRUE.equals(mConfig.getShowDistance());
663
+ if ((showTime || showDistance) && sIsRunning && mIsInForeground) {
664
+ mMainHandler.postDelayed(mNotificationUpdateRunnable, NOTIFICATION_UPDATE_INTERVAL_MS);
665
+ }
666
+ }
667
+
566
668
  @Override
567
669
  public synchronized void configure(Config config) {
568
670
  if (mConfig == null) {
@@ -580,7 +682,7 @@ public class LocationServiceImpl extends Service implements ProviderDelegate, Lo
580
682
  public void run() {
581
683
  if (sIsRunning) {
582
684
  if (currentConfig.getStartForeground() == true && mConfig.getStartForeground() == false) {
583
- stopForeground(true);
685
+ stopForeground();
584
686
  }
585
687
 
586
688
  if (mConfig.getStartForeground() == true) {
@@ -589,15 +691,17 @@ public class LocationServiceImpl extends Service implements ProviderDelegate, Lo
589
691
  startForeground();
590
692
  } else {
591
693
  // was running in foreground, so just update existing notification
694
+ String contentText = buildNotificationContentText(mConfig);
592
695
  Notification notification = new NotificationHelper.NotificationFactory(LocationServiceImpl.this).getNotification(
593
696
  mConfig.getNotificationTitle(),
594
- mConfig.getNotificationText(),
697
+ contentText,
595
698
  mConfig.getLargeNotificationIcon(),
596
699
  mConfig.getSmallNotificationIcon(),
597
700
  mConfig.getNotificationIconColor());
598
701
 
599
702
  NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
600
703
  notificationManager.notify(NOTIFICATION_ID, notification);
704
+ scheduleNotificationUpdater();
601
705
  }
602
706
  }
603
707
  }
@@ -661,6 +765,26 @@ public class LocationServiceImpl extends Service implements ProviderDelegate, Lo
661
765
  @Override
662
766
  public void onLocation(BackgroundLocation location) {
663
767
  mLastLocationTime = System.currentTimeMillis();
768
+ if (Boolean.TRUE.equals(mConfig != null ? mConfig.getShowDistance() : null)) {
769
+ double lat = location.getLatitude();
770
+ double lon = location.getLongitude();
771
+ if (mHasLastLocation) {
772
+ float[] dist = new float[1];
773
+ Location.distanceBetween(mLastLat, mLastLon, lat, lon, dist);
774
+ mSessionDistanceMeters += (double) dist[0];
775
+ }
776
+ mLastLat = lat;
777
+ mLastLon = lon;
778
+ mHasLastLocation = true;
779
+ if (mIsInForeground && mConfig != null && (Boolean.TRUE.equals(mConfig.getShowTime()) || Boolean.TRUE.equals(mConfig.getShowDistance()))) {
780
+ mMainHandler.post(new Runnable() {
781
+ @Override
782
+ public void run() {
783
+ updateForegroundNotification();
784
+ }
785
+ });
786
+ }
787
+ }
664
788
  logger.debug("New location {}", location.toString());
665
789
 
666
790
  location = transformLocation(location);
@@ -6,4 +6,4 @@ export { BackgroundGeolocationService, BACKGROUND_GEOLOCATION_SERVICE, } from '.
6
6
  export { BackgroundGeolocationModule } from './background-geolocation.module';
7
7
  export { BackgroundGeolocationEvents } from './background-geolocation-events';
8
8
  export { BackgroundGeolocationLocationCode, BackgroundGeolocationNativeProvider, BackgroundGeolocationLocationProvider, BackgroundGeolocationAuthorizationStatus, BackgroundGeolocationLogLevel, BackgroundGeolocationProvider, BackgroundGeolocationAccuracy, BackgroundGeolocationMode, BackgroundGeolocationIOSActivity, } from './background-geolocation-enums';
9
- export type { BackgroundGeolocationConfig, BackgroundGeolocationResponse, ServiceStatus, BackgroundGeolocationLogEntry, } from '../www/BackgroundGeolocation';
9
+ export type { BackgroundGeolocationConfig, BackgroundGeolocationResponse, ServiceStatus, BackgroundGeolocationLogEntry, } from '../../www/BackgroundGeolocation';
package/package.json CHANGED
@@ -1,9 +1,13 @@
1
1
  {
2
2
  "name": "@josuelmm/cordova-background-geolocation",
3
- "version": "3.1.0",
3
+ "version": "3.1.1",
4
4
  "description": "Cordova Background Geolocation (fork actualizado)",
5
5
  "main": "./www/BackgroundGeolocation.js",
6
6
  "types": "./www/BackgroundGeolocation.d.ts",
7
+ "browser": {
8
+ "cordova/exec": "./www/cordova-exec-stub.js",
9
+ "cordova/channel": "./www/cordova-channel-stub.js"
10
+ },
7
11
  "exports": {
8
12
  ".": {
9
13
  "types": "./www/BackgroundGeolocation.d.ts",
@@ -27,10 +31,10 @@
27
31
  "typescript": "~5.5.4"
28
32
  },
29
33
  "scripts": {
30
- "build:angular": "npx ng-packagr -p angular/ng-package.json",
34
+ "build:angular": "npx ng-packagr -p angular/ng-package.json && node scripts/fix-angular-dts-paths.js",
31
35
  "prepack": "npm run build:angular",
32
36
  "pack": "npm pack",
33
- "build-package": "npm run build:angular && npm pack"
37
+ "build": "npm run build:angular && npm pack"
34
38
  },
35
39
  "cordova": {
36
40
  "id": "cordova-background-geolocation",
package/plugin.xml CHANGED
@@ -2,7 +2,7 @@
2
2
  <plugin xmlns="http://www.phonegap.com/ns/plugins/1.0"
3
3
  xmlns:android="http://schemas.android.com/apk/res/android"
4
4
  id="cordova-background-geolocation"
5
- version="3.1.0">
5
+ version="3.1.1">
6
6
  <name>cordova-background-geolocation</name>
7
7
  <description>Cordova Background Geolocation Plugin</description>
8
8
  <license>Apache-2.0</license>
@@ -214,6 +214,24 @@ export interface ConfigureOptions {
214
214
  */
215
215
  notificationText?: string;
216
216
 
217
+ /**
218
+ * When true, the foreground notification shows a live elapsed time (HH:mm:ss) since the session started.
219
+ * Requires startForeground. Updates every second.
220
+ *
221
+ * Platform: Android
222
+ * @default false
223
+ */
224
+ showTime?: boolean;
225
+
226
+ /**
227
+ * When true, the foreground notification shows accumulated distance (km) since the session started.
228
+ * Requires startForeground. Updates when each new location is received.
229
+ *
230
+ * Platform: Android
231
+ * @default false
232
+ */
233
+ showDistance?: boolean;
234
+
217
235
  /**
218
236
  * Title shown in the notification while locations are syncing to the server.
219
237
  * Use this (and notificationSyncText, etc.) to localize sync notifications.
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Stub for cordova/channel so the plugin can be bundled by webpack (ng serve / browser).
3
+ * When running inside Cordova (native), uses the real cordova channel.
4
+ * When running in browser, provides deviceready.subscribe() that runs the callback on load.
5
+ */
6
+ function getChannel() {
7
+ if (typeof window !== 'undefined' && window.cordova && typeof window.cordova.require === 'function') {
8
+ try {
9
+ return window.cordova.require('cordova/channel');
10
+ } catch (e) {}
11
+ }
12
+ return {
13
+ deviceready: {
14
+ subscribe: function (cb) {
15
+ if (typeof window !== 'undefined') {
16
+ if (document.readyState === 'complete') {
17
+ setTimeout(cb, 0);
18
+ } else {
19
+ window.addEventListener('load', cb);
20
+ }
21
+ }
22
+ }
23
+ }
24
+ };
25
+ }
26
+
27
+ module.exports = getChannel();
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Stub for cordova/exec so the plugin can be bundled by webpack (ng serve / browser).
3
+ * When running inside Cordova (native), delegates to the real cordova.exec.
4
+ * When running in browser, calls the failure callback so the app does not break.
5
+ */
6
+ function exec(success, fail, service, method, args) {
7
+ if (typeof window !== 'undefined' && window.cordova && typeof window.cordova.exec === 'function') {
8
+ return window.cordova.exec(success, fail, service, method, args || []);
9
+ }
10
+ if (typeof fail === 'function') {
11
+ fail({ message: 'Cordova is not available. Run on a device or emulator.' });
12
+ }
13
+ }
14
+
15
+ module.exports = exec;