From 0d3ebe9b93238223ada24b7dd048472777b7c086 Mon Sep 17 00:00:00 2001 From: Hoseong-Ryu Date: Tue, 9 Sep 2025 16:09:42 +0900 Subject: [PATCH 1/4] fix: prevent SecurityException when location permissions revoked in background --- .../bgactions/RNBackgroundActionsTask.java | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/android/src/main/java/com/asterinet/react/bgactions/RNBackgroundActionsTask.java b/android/src/main/java/com/asterinet/react/bgactions/RNBackgroundActionsTask.java index 9900fc0..c216d3e 100644 --- a/android/src/main/java/com/asterinet/react/bgactions/RNBackgroundActionsTask.java +++ b/android/src/main/java/com/asterinet/react/bgactions/RNBackgroundActionsTask.java @@ -5,15 +5,21 @@ import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ServiceInfo; import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.util.Log; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.app.NotificationCompat; +import androidx.core.content.ContextCompat; import com.facebook.react.HeadlessJsTaskService; import com.facebook.react.bridge.Arguments; @@ -90,6 +96,16 @@ public int onStartCommand(Intent intent, int flags, int startId) { // Create the notification final Notification notification = buildNotification(this, bgOptions); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + // Android 10+ (API 29+): Check required permissions based on foregroundServiceType + // This prevents SecurityException when permissions are revoked while service is running + if (!hasRequiredPermissions()) { + Log.e("RNBackgroundActionsTask", "Required permissions not granted for foreground service! Stopping."); + stopSelf(); + return START_NOT_STICKY; + } + } + startForeground(SERVICE_NOTIFICATION_ID, notification); return super.onStartCommand(intent, flags, startId); } @@ -103,4 +119,63 @@ private void createNotificationChannel(@NonNull final String taskTitle, @NonNull notificationManager.createNotificationChannel(channel); } } + + /** + * Check if required permissions are granted for location foreground service + * @return true if all required permissions are granted, false otherwise + */ + private boolean hasRequiredPermissions() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + return true; // No permission check needed for Android < 10 + } + + // Only check permissions if this is a location foreground service + if (!isLocationForegroundService()) { + return true; // Not a location service, no location permissions required + } + + // Check location permissions + boolean hasFineLocation = ContextCompat.checkSelfPermission(this, + android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED; + boolean hasCoarseLocation = ContextCompat.checkSelfPermission(this, + android.Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED; + boolean hasBackgroundLocation = ContextCompat.checkSelfPermission(this, + android.Manifest.permission.ACCESS_BACKGROUND_LOCATION) == PackageManager.PERMISSION_GRANTED; + + boolean allPermissionsGranted = hasFineLocation && hasCoarseLocation && hasBackgroundLocation; + + return allPermissionsGranted; + } + + /** + * Check if this service is configured as a location foreground service + * @return true if foregroundServiceType includes location, false otherwise + */ + private boolean isLocationForegroundService() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + return false; + } + + try { + PackageManager pm = getPackageManager(); + ComponentName componentName = new ComponentName(this, this.getClass()); + ServiceInfo serviceInfo = pm.getServiceInfo(componentName, PackageManager.GET_META_DATA); + + // Use reflection to access foregroundServiceType field (available from API 29+) + try { + int serviceType = (Integer) ServiceInfo.class.getField("foregroundServiceType").get(serviceInfo); + int FOREGROUND_SERVICE_TYPE_LOCATION = (Integer) ServiceInfo.class.getField("FOREGROUND_SERVICE_TYPE_LOCATION").get(null); + + boolean isLocation = (serviceType & FOREGROUND_SERVICE_TYPE_LOCATION) != 0; + Log.d("RNBackgroundActionsTask", "Service type check - isLocation: " + isLocation); + return isLocation; + } catch (Exception e) { + Log.w("RNBackgroundActionsTask", "Could not read foregroundServiceType field", e); + return false; + } + } catch (PackageManager.NameNotFoundException e) { + Log.w("RNBackgroundActionsTask", "Could not find service info", e); + return false; + } + } } From a34cb265b960897c77661897566ccb12240f23c9 Mon Sep 17 00:00:00 2001 From: Hoseong-Ryu Date: Tue, 9 Sep 2025 17:54:10 +0900 Subject: [PATCH 2/4] chore: add checkLocationPermissions option to prevent SecurityException --- .../bgactions/BackgroundTaskOptions.java | 14 +++++ .../bgactions/RNBackgroundActionsTask.java | 59 ++----------------- lib/types/index.d.ts | 2 + src/index.js | 1 + 4 files changed, 23 insertions(+), 53 deletions(-) diff --git a/android/src/main/java/com/asterinet/react/bgactions/BackgroundTaskOptions.java b/android/src/main/java/com/asterinet/react/bgactions/BackgroundTaskOptions.java index 9951e02..1dcbc00 100644 --- a/android/src/main/java/com/asterinet/react/bgactions/BackgroundTaskOptions.java +++ b/android/src/main/java/com/asterinet/react/bgactions/BackgroundTaskOptions.java @@ -68,6 +68,16 @@ public BackgroundTaskOptions(@NonNull final ReactContext reactContext, @NonNull } catch (Exception e) { extras.putInt("color", Color.parseColor("#ffffff")); } + + // Handle checkLocationPermissions - Arguments.toBundle may not include boolean values properly + try { + if (options.hasKey("checkLocationPermissions")) { + boolean checkLocationPermissions = options.getBoolean("checkLocationPermissions"); + extras.putBoolean("checkLocationPermissions", checkLocationPermissions); + } + } catch (Exception e) { + throw new IllegalArgumentException("checkLocationPermissions not found"); + } } public Bundle getExtras() { @@ -101,4 +111,8 @@ public String getLinkingURI() { public Bundle getProgressBar() { return extras.getBundle("progressBar"); } + + public boolean shouldCheckLocationPermissions() { + return extras.getBoolean("checkLocationPermissions", false); + } } diff --git a/android/src/main/java/com/asterinet/react/bgactions/RNBackgroundActionsTask.java b/android/src/main/java/com/asterinet/react/bgactions/RNBackgroundActionsTask.java index c216d3e..4db174d 100644 --- a/android/src/main/java/com/asterinet/react/bgactions/RNBackgroundActionsTask.java +++ b/android/src/main/java/com/asterinet/react/bgactions/RNBackgroundActionsTask.java @@ -5,11 +5,9 @@ import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; -import android.content.pm.ServiceInfo; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -96,11 +94,10 @@ public int onStartCommand(Intent intent, int flags, int startId) { // Create the notification final Notification notification = buildNotification(this, bgOptions); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - // Android 10+ (API 29+): Check required permissions based on foregroundServiceType + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && bgOptions.shouldCheckLocationPermissions()) { + // Android 10+ (API 29+): Check required permissions for location foreground service // This prevents SecurityException when permissions are revoked while service is running - if (!hasRequiredPermissions()) { - Log.e("RNBackgroundActionsTask", "Required permissions not granted for foreground service! Stopping."); + if (!hasLocationPermissions()) { stopSelf(); return START_NOT_STICKY; } @@ -121,19 +118,9 @@ private void createNotificationChannel(@NonNull final String taskTitle, @NonNull } /** - * Check if required permissions are granted for location foreground service - * @return true if all required permissions are granted, false otherwise + * Check if location permissions are granted */ - private boolean hasRequiredPermissions() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - return true; // No permission check needed for Android < 10 - } - - // Only check permissions if this is a location foreground service - if (!isLocationForegroundService()) { - return true; // Not a location service, no location permissions required - } - + private boolean hasLocationPermissions() { // Check location permissions boolean hasFineLocation = ContextCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED; @@ -142,40 +129,6 @@ private boolean hasRequiredPermissions() { boolean hasBackgroundLocation = ContextCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_BACKGROUND_LOCATION) == PackageManager.PERMISSION_GRANTED; - boolean allPermissionsGranted = hasFineLocation && hasCoarseLocation && hasBackgroundLocation; - - return allPermissionsGranted; - } - - /** - * Check if this service is configured as a location foreground service - * @return true if foregroundServiceType includes location, false otherwise - */ - private boolean isLocationForegroundService() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - return false; - } - - try { - PackageManager pm = getPackageManager(); - ComponentName componentName = new ComponentName(this, this.getClass()); - ServiceInfo serviceInfo = pm.getServiceInfo(componentName, PackageManager.GET_META_DATA); - - // Use reflection to access foregroundServiceType field (available from API 29+) - try { - int serviceType = (Integer) ServiceInfo.class.getField("foregroundServiceType").get(serviceInfo); - int FOREGROUND_SERVICE_TYPE_LOCATION = (Integer) ServiceInfo.class.getField("FOREGROUND_SERVICE_TYPE_LOCATION").get(null); - - boolean isLocation = (serviceType & FOREGROUND_SERVICE_TYPE_LOCATION) != 0; - Log.d("RNBackgroundActionsTask", "Service type check - isLocation: " + isLocation); - return isLocation; - } catch (Exception e) { - Log.w("RNBackgroundActionsTask", "Could not read foregroundServiceType field", e); - return false; - } - } catch (PackageManager.NameNotFoundException e) { - Log.w("RNBackgroundActionsTask", "Could not find service info", e); - return false; - } + return hasFineLocation && hasCoarseLocation && hasBackgroundLocation; } } diff --git a/lib/types/index.d.ts b/lib/types/index.d.ts index 9a9c02a..dd8b46e 100644 --- a/lib/types/index.d.ts +++ b/lib/types/index.d.ts @@ -15,6 +15,7 @@ export type BackgroundTaskOptions = { value: number; indeterminate?: boolean | undefined; } | undefined; + checkLocationPermissions?: boolean | undefined; }; declare const backgroundServer: BackgroundServer; /** @@ -102,6 +103,7 @@ declare class BackgroundServer extends EventEmitter<"expiration", any> { value: number; indeterminate?: boolean | undefined; } | undefined; + checkLocationPermissions?: boolean | undefined; } & { parameters?: T | undefined; }): Promise; diff --git a/src/index.js b/src/index.js index 3719399..7fcfd0a 100644 --- a/src/index.js +++ b/src/index.js @@ -118,6 +118,7 @@ class BackgroundServer extends EventEmitter { color: options.color || '#ffffff', linkingURI: options.linkingURI, progressBar: options.progressBar, + checkLocationPermissions: options.checkLocationPermissions, }; } From 85f53dc4b20e2ee49ff12cd563a03cb97b88d98a Mon Sep 17 00:00:00 2001 From: Hoseong-Ryu Date: Tue, 9 Sep 2025 17:54:10 +0900 Subject: [PATCH 3/4] chore: add checkLocationPermissions option to prevent SecurityException --- lib/types/index.d.ts | 1 + src/index.js | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/types/index.d.ts b/lib/types/index.d.ts index dd8b46e..4057441 100644 --- a/lib/types/index.d.ts +++ b/lib/types/index.d.ts @@ -26,6 +26,7 @@ declare const backgroundServer: BackgroundServer; * color?: string * linkingURI?: string, * progressBar?: {max: number, value: number, indeterminate?: boolean} + * checkLocationPermissions?: boolean * }} BackgroundTaskOptions * @extends EventEmitter<'expiration',any> */ diff --git a/src/index.js b/src/index.js index 7fcfd0a..1c0e2bb 100644 --- a/src/index.js +++ b/src/index.js @@ -10,6 +10,7 @@ import EventEmitter from 'eventemitter3'; * color?: string * linkingURI?: string, * progressBar?: {max: number, value: number, indeterminate?: boolean} + * checkLocationPermissions?: boolean * }} BackgroundTaskOptions * @extends EventEmitter<'expiration',any> */ From de3ba4803bd70ab1546daa6619ca7ef55491638f Mon Sep 17 00:00:00 2001 From: Hoseong-Ryu Date: Fri, 12 Sep 2025 17:11:17 +0900 Subject: [PATCH 4/4] chore: remove manual changes from auto-generated type definitions --- lib/types/index.d.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/types/index.d.ts b/lib/types/index.d.ts index 4057441..9a9c02a 100644 --- a/lib/types/index.d.ts +++ b/lib/types/index.d.ts @@ -15,7 +15,6 @@ export type BackgroundTaskOptions = { value: number; indeterminate?: boolean | undefined; } | undefined; - checkLocationPermissions?: boolean | undefined; }; declare const backgroundServer: BackgroundServer; /** @@ -26,7 +25,6 @@ declare const backgroundServer: BackgroundServer; * color?: string * linkingURI?: string, * progressBar?: {max: number, value: number, indeterminate?: boolean} - * checkLocationPermissions?: boolean * }} BackgroundTaskOptions * @extends EventEmitter<'expiration',any> */ @@ -104,7 +102,6 @@ declare class BackgroundServer extends EventEmitter<"expiration", any> { value: number; indeterminate?: boolean | undefined; } | undefined; - checkLocationPermissions?: boolean | undefined; } & { parameters?: T | undefined; }): Promise;