From 9c74d70da35a3886fcbbe0daac55b06dc2512989 Mon Sep 17 00:00:00 2001 From: HarelM Date: Wed, 26 Oct 2022 22:35:10 +0300 Subject: [PATCH] Android and typescript support. --- README.md | 5 +- .../BackgroundGeolocation.java | 31 ++------ .../BackgroundGeolocationService.java | 56 ++++++++++++++- definitions.d.ts | 70 +++++++++++++++++++ 4 files changed, 134 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 00bedb0..385b474 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,10 @@ BackgroundGeolocation.addWatcher( // The minimum number of metres between subsequent locations. Defaults // to 0. - distanceFilter: 50 + distanceFilter: 50, + + // A file URL to use to log the locations received, can be good for app crashes and debug + file: "file://path/to/file.txt" }, function callback(location, error) { if (error) { diff --git a/android/src/main/java/com/equimaps/capacitor_background_geolocation/BackgroundGeolocation.java b/android/src/main/java/com/equimaps/capacitor_background_geolocation/BackgroundGeolocation.java index 6cf75cd..d5e1ccd 100644 --- a/android/src/main/java/com/equimaps/capacitor_background_geolocation/BackgroundGeolocation.java +++ b/android/src/main/java/com/equimaps/capacitor_background_geolocation/BackgroundGeolocation.java @@ -19,7 +19,6 @@ import android.os.IBinder; import android.provider.Settings; -import com.getcapacitor.JSObject; import com.getcapacitor.Logger; import com.getcapacitor.NativePlugin; import com.getcapacitor.Plugin; @@ -57,7 +56,7 @@ private void fetchLastLocation(PluginCall call) { @Override public void onSuccess(Location location) { if (location != null) { - call.resolve(formatLocation(location)); + call.resolve(BackgroundGeolocationService.formatLocation(location)); } } } @@ -141,7 +140,8 @@ public void addWatcher(final PluginCall call) { service.addWatcher( call.getCallbackId(), backgroundNotification, - call.getFloat("distanceFilter", 0f) + call.getFloat("distanceFilter", 0f), + call.getString("file", null) ); } @@ -212,28 +212,7 @@ private static Boolean isLocationEnabled(Context context) } } - private static JSObject formatLocation(Location location) { - JSObject obj = new JSObject(); - obj.put("latitude", location.getLatitude()); - obj.put("longitude", location.getLongitude()); - // The docs state that all Location objects have an accuracy, but then why is there a - // hasAccuracy method? Better safe than sorry. - obj.put("accuracy", location.hasAccuracy() ? location.getAccuracy() : JSONObject.NULL); - obj.put("altitude", location.hasAltitude() ? location.getAltitude() : JSONObject.NULL); - if (Build.VERSION.SDK_INT >= 26 && location.hasVerticalAccuracy()) { - obj.put("altitudeAccuracy", location.getVerticalAccuracyMeters()); - } else { - obj.put("altitudeAccuracy", JSONObject.NULL); - } - // In addition to mocking locations in development, Android allows the - // installation of apps which have the power to simulate location - // readings in other apps. - obj.put("simulated", location.isFromMockProvider()); - obj.put("speed", location.hasSpeed() ? location.getSpeed() : JSONObject.NULL); - obj.put("bearing", location.hasBearing() ? location.getBearing() : JSONObject.NULL); - obj.put("time", location.getTime()); - return obj; - } + // Sends messages to the service. private BackgroundGeolocationService.LocalBinder service = null; @@ -254,7 +233,7 @@ public void onReceive(Context context, Intent intent) { } return; } - call.success(formatLocation(location)); + call.success(BackgroundGeolocationService.formatLocation(location)); } } diff --git a/android/src/main/java/com/equimaps/capacitor_background_geolocation/BackgroundGeolocationService.java b/android/src/main/java/com/equimaps/capacitor_background_geolocation/BackgroundGeolocationService.java index 6c3813e..6442461 100644 --- a/android/src/main/java/com/equimaps/capacitor_background_geolocation/BackgroundGeolocationService.java +++ b/android/src/main/java/com/equimaps/capacitor_background_geolocation/BackgroundGeolocationService.java @@ -5,8 +5,10 @@ import android.content.Intent; import android.location.Location; import android.os.Binder; +import android.os.Build; import android.os.IBinder; +import com.getcapacitor.JSObject; import com.getcapacitor.Logger; import com.getcapacitor.android.BuildConfig; import com.google.android.gms.location.FusedLocationProviderClient; @@ -16,10 +18,17 @@ import com.google.android.gms.location.LocationResult; import com.google.android.gms.location.LocationServices; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; import java.util.HashSet; import androidx.localbroadcastmanager.content.LocalBroadcastManager; +import org.json.JSONObject; + // A bound and started service that is promoted to a foreground service when // location updates have been requested and the main activity is stopped. // @@ -79,7 +88,8 @@ public class LocalBinder extends Binder { void addWatcher( final String id, Notification backgroundNotification, - float distanceFilter + float distanceFilter, + String filePath ) { FusedLocationProviderClient client = LocationServices.getFusedLocationProviderClient( BackgroundGeolocationService.this @@ -94,6 +104,7 @@ void addWatcher( @Override public void onLocationResult(LocationResult locationResult) { Location location = locationResult.getLastLocation(); + logLocationToFile(filePath, location); Intent intent = new Intent(ACTION_BROADCAST); intent.putExtra("location", location); intent.putExtra("id", id); @@ -170,4 +181,47 @@ void stopService() { BackgroundGeolocationService.this.stopSelf(); } } + + void logLocationToFile(String filePath, Location location) { + if (filePath == null || filePath.length() == 0) { + return; + } + try { + File file = new File(filePath.replace("file://", "")); + if (!file.exists()) file.createNewFile(); + FileOutputStream fileStream = new FileOutputStream(file, true); + OutputStreamWriter osw = new OutputStreamWriter(fileStream); + osw.write(formatLocation(location) + "\n"); + osw.flush(); + osw.close(); + fileStream.close(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static JSObject formatLocation(Location location) { + JSObject obj = new JSObject(); + obj.put("latitude", location.getLatitude()); + obj.put("longitude", location.getLongitude()); + // The docs state that all Location objects have an accuracy, but then why is there a + // hasAccuracy method? Better safe than sorry. + obj.put("accuracy", location.hasAccuracy() ? location.getAccuracy() : JSONObject.NULL); + obj.put("altitude", location.hasAltitude() ? location.getAltitude() : JSONObject.NULL); + if (Build.VERSION.SDK_INT >= 26 && location.hasVerticalAccuracy()) { + obj.put("altitudeAccuracy", location.getVerticalAccuracyMeters()); + } else { + obj.put("altitudeAccuracy", JSONObject.NULL); + } + // In addition to mocking locations in development, Android allows the + // installation of apps which have the power to simulate location + // readings in other apps. + obj.put("simulated", location.isFromMockProvider()); + obj.put("speed", location.hasSpeed() ? location.getSpeed() : JSONObject.NULL); + obj.put("bearing", location.hasBearing() ? location.getBearing() : JSONObject.NULL); + obj.put("time", location.getTime()); + return obj; + } } diff --git a/definitions.d.ts b/definitions.d.ts index e7ed526..a7d3294 100644 --- a/definitions.d.ts +++ b/definitions.d.ts @@ -1,20 +1,74 @@ +/** + * The add watcher options + */ export interface WatcherOptions { + /** + * The message to be displayed in the notification bar + */ backgroundMessage?: string; + /** + * The title of the notification bar + */ backgroundTitle?: string; + /** + * Whether or not to request permission if there is no permission + */ requestPermissions?: boolean; + /** + * if "true", stale locations may be delivered while the device obtains a GPS fix. + * You are responsible for checking the "time" + */ stale?: boolean; + /** + * The minimum number of metres between subsequent locations + */ distanceFilter?: number; + /** + * A file URL to use to log the locations received, can be good for app crashes and debug + * @example file://path/to/file.txt + */ + file?: string; } +/** + * The location object + */ export interface Location { + /** + * Latitude in degrees. + */ latitude: number; + /** + * Longitude in degrees. + */ longitude: number; + /** + * Radius of horizontal uncertainty in metres, with 68% confidence. + */ accuracy: number; + /** + * Metres above sea level (or null). + */ altitude: number | null; + /** + * Vertical uncertainty in metres, with 68% confidence (or null). + */ altitudeAccuracy: number | null; + /** + * True if the location was simulated by software, rather than GPS. + */ simulated: boolean; + /** + * Deviation from true north in degrees (or null). + */ bearing: number | null; + /** + * Speed in metres per second (or null). + */ speed: number | null; + /** + * Time the location was produced, in milliseconds since the unix epoch. + */ time: number | null; } @@ -22,7 +76,16 @@ export interface CallbackError extends Error { code?: string; } +/** + * The plugin's main interface + */ export interface BackgroundGeolocationPlugin { + /** + * Add a watcher to the GPS according to the given options + * @param options the add watcher options + * @param callback the locaiton callback when a new location is received + * @returns a promise with the watcher ID in order to allow removing it + */ addWatcher( options: WatcherOptions, callback: ( @@ -30,8 +93,15 @@ export interface BackgroundGeolocationPlugin { error?: CallbackError ) => void ): Promise; + /** + * Removes a watcher by ID + * @param options the remove watcher options + */ removeWatcher(options: { id: string }): Promise; + /** + * Opens the OS app settings screen to allow changing permissions + */ openSettings(): Promise; }