From a0c6e5d73711162aa52586922a7eef19b7c7cb9b Mon Sep 17 00:00:00 2001 From: HarelM Date: Wed, 23 Jul 2025 13:13:15 +0300 Subject: [PATCH 1/2] Rewrite callback hell... --- README.md | 2 +- .../BackgroundGeolocation.java | 177 ++++++++++-------- 2 files changed, 104 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index f167de8..b3efef6 100644 --- a/README.md +++ b/README.md @@ -164,7 +164,7 @@ Add the following keys to `Info.plist.`: Set the the `android.useLegacyBridge` option to `true` in your Capacitor configuration. This prevents location updates halting after 5 minutes in the background. See https://capacitorjs.com/docs/config and https://github.com/capacitor-community/background-geolocation/issues/89. -On Android 13+, the app needs the `POST_NOTIFICATIONS` runtime permission to show the persistent notification informing the user that their location is being used in the background. You may need to [request this permission](https://developer.android.com/develop/ui/views/notifications/notification-permission) from the user, this can be accomplished [using the `@capacitor/local-notifications` plugin](https://capacitorjs.com/docs/apis/local-notifications#checkpermissions). +On Android 13+, the app needs the `POST_NOTIFICATIONS` runtime permission to show the persistent notification informing the user that their location is being used in the background. this plugin will [request this permission](https://developer.android.com/develop/ui/views/notifications/notification-permission) from the user if `backgroundMessage` is defined in the `addWatcher` options. If your app forwards location updates to a server in real time, be aware that after 5 minutes in the background Android will throttle HTTP requests initiated from the WebView. The solution is to use a native HTTP plugin such as [CapacitorHttp](https://capacitorjs.com/docs/apis/http). See https://github.com/capacitor-community/background-geolocation/issues/14. 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 09e81b6..5a2cbb4 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 @@ -12,7 +12,6 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; -import android.content.pm.PackageManager; import android.location.Location; import android.location.LocationManager; import android.net.Uri; @@ -45,7 +44,14 @@ Manifest.permission.ACCESS_FINE_LOCATION }, alias = "location" + ), + @Permission( + strings = { + Manifest.permission.POST_NOTIFICATIONS, + }, + alias = "notifications" ) + } ) public class BackgroundGeolocation extends Plugin { @@ -76,90 +82,106 @@ public void addWatcher(final PluginCall call) { call.reject("Service not running."); return; } - call.setKeepAlive(true); + if (getPermissionState("location") != PermissionState.GRANTED && !call.getBoolean("requestPermissions", true)) { + call.reject("Permission denied.", "NOT_AUTHORIZED"); + return; + } - if (getPermissionState("location") != PermissionState.GRANTED) { - if (call.getBoolean("requestPermissions", true)) { - requestPermissionForAlias("location", call, "locationPermissionsCallback"); - } else { - call.reject("Permission denied.", "NOT_AUTHORIZED"); - } - } else if (!isLocationEnabled(getContext())) { - call.reject("Location services disabled.", "NOT_AUTHORIZED"); + if (getPermissionState("location") != PermissionState.GRANTED && call.getBoolean("requestPermissions", true)) { + call.setKeepAlive(true); + requestPermissionForAlias("location", call, "locationPermissionsCallback"); + return; } - if (call.getBoolean("stale", false)) { - fetchLastLocation(call); + + if (!isLocationEnabled(getContext())) { + call.reject("Location services disabled.", "NOT_AUTHORIZED"); + return; } - Notification backgroundNotification = null; - String backgroundMessage = call.getString("backgroundMessage"); - if (backgroundMessage != null) { - Notification.Builder builder = new Notification.Builder(getContext()) - .setContentTitle( - call.getString( + call.setKeepAlive(true); + addWatcherAfterLocationPermissionGranted(call); + } + + private Notification createNotification(PluginCall call) { + String backgroundMessage = call.getString("backgroundMessage"); + if (backgroundMessage == null) { + return null; + } + Notification.Builder builder = new Notification.Builder(getContext()) + .setContentTitle( + call.getString( "backgroundTitle", "Using your location" - ) - ) - .setContentText(backgroundMessage) - .setOngoing(true) - .setPriority(Notification.PRIORITY_HIGH) - .setWhen(System.currentTimeMillis()); - - try { - String name = getAppString( - "capacitor_background_geolocation_notification_icon", - "mipmap/ic_launcher" - ); - String[] parts = name.split("/"); - // It is actually necessary to set a valid icon for the notification to behave - // correctly when tapped. If there is no icon specified, tapping it will open the - // app's settings, rather than bringing the application to the foreground. - builder.setSmallIcon( - getAppResourceIdentifier(parts[1], parts[0]) - ); - } catch (Exception e) { - Logger.error("Could not set notification icon", e); - } + ) + ) + .setContentText(backgroundMessage) + .setOngoing(true) + .setPriority(Notification.PRIORITY_HIGH) + .setWhen(System.currentTimeMillis()); - try { - String color = getAppString( - "capacitor_background_geolocation_notification_color", - null - ); - if (color != null) { - builder.setColor(Color.parseColor(color)); - } - } catch (Exception e) { - Logger.error("Could not set notification color", e); - } + try { + String name = getAppString( + "capacitor_background_geolocation_notification_icon", + "mipmap/ic_launcher" + ); + String[] parts = name.split("/"); + // It is actually necessary to set a valid icon for the notification to behave + // correctly when tapped. If there is no icon specified, tapping it will open the + // app's settings, rather than bringing the application to the foreground. + builder.setSmallIcon( + getAppResourceIdentifier(parts[1], parts[0]) + ); + } catch (Exception e) { + Logger.error("Could not set notification icon", e); + } - Intent launchIntent = getContext().getPackageManager().getLaunchIntentForPackage( - getContext().getPackageName() + try { + String color = getAppString( + "capacitor_background_geolocation_notification_color", + null ); - if (launchIntent != null) { - launchIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); - builder.setContentIntent( - PendingIntent.getActivity( - getContext(), - 0, - launchIntent, - PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE - ) - ); + if (color != null) { + builder.setColor(Color.parseColor(color)); } + } catch (Exception e) { + Logger.error("Could not set notification color", e); + } - // Set the Channel ID for Android O. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - builder.setChannelId(BackgroundGeolocationService.class.getPackage().getName()); - } + Intent launchIntent = getContext().getPackageManager().getLaunchIntentForPackage( + getContext().getPackageName() + ); + if (launchIntent != null) { + launchIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); + builder.setContentIntent( + PendingIntent.getActivity( + getContext(), + 0, + launchIntent, + PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE + ) + ); + } - backgroundNotification = builder.build(); + // Set the Channel ID for Android O. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + builder.setChannelId(BackgroundGeolocationService.class.getPackage().getName()); + } + + return builder.build(); + } + + private void addWatcherAfterLocationPermissionGranted(PluginCall call) { + if (call.getBoolean("stale", false)) { + fetchLastLocation(call); + } + if (call.getString("backgroundMessage") != null && getPermissionState("notification") != PermissionState.GRANTED) { + requestPermissionForAlias("notifications", call, "notificationsPermissionsCallback"); + return; } service.addWatcher( - call.getCallbackId(), - backgroundNotification, - call.getFloat("distanceFilter", 0f) + call.getCallbackId(), + createNotification(call), + call.getFloat("distanceFilter", 0f) ); } @@ -170,15 +192,22 @@ private void locationPermissionsCallback(PluginCall call) { call.reject("User denied location permission", "NOT_AUTHORIZED"); return; } - if (call.getBoolean("stale", false)) { - fetchLastLocation(call); - } if (service != null) { service.onPermissionsGranted(); // The handleOnResume method will now be called, and we don't need it to call // service.onPermissionsGranted again so we reset this flag. stoppedWithoutPermissions = false; } + addWatcherAfterLocationPermissionGranted(call); + } + + @PermissionCallback + private void notificationsPermissionsCallback(PluginCall call) { + service.addWatcher( + call.getCallbackId(), + createNotification(call), + call.getFloat("distanceFilter", 0f) + ); } @PluginMethod() From cd769d20d6726b1d0c4b24654a052321919735a8 Mon Sep 17 00:00:00 2001 From: HarelM Date: Wed, 23 Jul 2025 13:17:27 +0300 Subject: [PATCH 2/2] Fix capital in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b3efef6..97efeef 100644 --- a/README.md +++ b/README.md @@ -164,7 +164,7 @@ Add the following keys to `Info.plist.`: Set the the `android.useLegacyBridge` option to `true` in your Capacitor configuration. This prevents location updates halting after 5 minutes in the background. See https://capacitorjs.com/docs/config and https://github.com/capacitor-community/background-geolocation/issues/89. -On Android 13+, the app needs the `POST_NOTIFICATIONS` runtime permission to show the persistent notification informing the user that their location is being used in the background. this plugin will [request this permission](https://developer.android.com/develop/ui/views/notifications/notification-permission) from the user if `backgroundMessage` is defined in the `addWatcher` options. +On Android 13+, the app needs the `POST_NOTIFICATIONS` runtime permission to show the persistent notification informing the user that their location is being used in the background. This plugin will [request this permission](https://developer.android.com/develop/ui/views/notifications/notification-permission) from the user if `backgroundMessage` is defined in the `addWatcher` options. If your app forwards location updates to a server in real time, be aware that after 5 minutes in the background Android will throttle HTTP requests initiated from the WebView. The solution is to use a native HTTP plugin such as [CapacitorHttp](https://capacitorjs.com/docs/apis/http). See https://github.com/capacitor-community/background-geolocation/issues/14.