-
-
Notifications
You must be signed in to change notification settings - Fork 847
feat(android): add schedule exact alarm permission #878
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(android): add schedule exact alarm permission #878
Conversation
android/src/main/java/com/zoontek/rnpermissions/RNPermissionsModuleImpl.java
Outdated
Show resolved
Hide resolved
android/src/main/java/com/zoontek/rnpermissions/RNPermissionsModuleImpl.java
Outdated
Show resolved
Hide resolved
android/src/main/java/com/zoontek/rnpermissions/RNPermissionsModuleImpl.java
Outdated
Show resolved
Hide resolved
Must be request changes (instead of approval)
zoontek
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
checkMultiple / requestMultiple support must be added
I handled schedule exact alarm with both functions. |
|
Hello @zoontek, anything we could do to help this PR be merged? We also need the feature and I was wondering if we could provide additional support to speed things up. |
|
Not really, I'm in a middle of packing / selling all my stuff because I'm moving soon. I will probably have a bit more bandwidth next week. |
|
I think the method to open a specific Alarm settings could be added as well. |
|
@zoontek If you have any suggestions, I am available to apply them. |
|
I just tried the code. When requesting permission, it opens up a list, displaying all apps. To create a better UX, I would add patch: index 97bc712..cda5f87 100644
--- a/node_modules/react-native-permissions/android/src/main/java/com/zoontek/rnpermissions/RNPermissionsModuleImpl.java
+++ b/node_modules/react-native-permissions/android/src/main/java/com/zoontek/rnpermissions/RNPermissionsModuleImpl.java
@@ -2,6 +2,7 @@ package com.zoontek.rnpermissions;
import android.Manifest;
import android.app.Activity;
+import android.app.AlarmManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -12,11 +13,13 @@ import android.provider.Settings;
import android.util.SparseArray;
import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationManagerCompat;
import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Callback;
+import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
@@ -27,6 +30,7 @@ import com.facebook.react.modules.core.PermissionListener;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
public class RNPermissionsModuleImpl {
@@ -47,6 +51,9 @@ public class RNPermissionsModuleImpl {
}
private static boolean isPermissionUnavailable(@NonNull final String permission) {
+ if (Manifest.permission.SCHEDULE_EXACT_ALARM.equals(permission)) {
+ return false;
+ }
String fieldName = permission
.replace("android.permission.", "")
.replace("com.android.voicemail.permission.", "");
@@ -113,6 +120,17 @@ public class RNPermissionsModuleImpl {
return;
}
+ if (Manifest.permission.SCHEDULE_EXACT_ALARM.equals(permission)) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ if (context.getSystemService(AlarmManager.class).canScheduleExactAlarms()) {
+ promise.resolve(GRANTED);
+ } else {
+ promise.resolve(DENIED);
+ }
+ return;
+ }
+ }
+
if (context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED) {
promise.resolve(GRANTED);
} else {
@@ -147,6 +165,14 @@ public class RNPermissionsModuleImpl {
== PackageManager.PERMISSION_GRANTED
? GRANTED
: BLOCKED);
+ } else if (Manifest.permission.SCHEDULE_EXACT_ALARM.equals(permission)) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ if (context.getSystemService(AlarmManager.class).canScheduleExactAlarms()) {
+ output.putString(permission, GRANTED);
+ } else {
+ output.putString(permission, DENIED);
+ }
+ }
} else if (context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED) {
output.putString(permission, GRANTED);
} else {
@@ -179,6 +205,42 @@ public class RNPermissionsModuleImpl {
return;
}
+ if (Manifest.permission.SCHEDULE_EXACT_ALARM.equals(permission)) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ if (context.getSystemService(AlarmManager.class).canScheduleExactAlarms()) {
+ promise.resolve(GRANTED);
+ } else {
+ try {
+ Intent intent = new Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM);
+ intent.setData(Uri.fromParts("package", reactContext.getPackageName(), null));
+ reactContext.startActivity(intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ // Register a lifecycle listener to check permission status when app resumes
+ reactContext.addLifecycleEventListener(new LifecycleEventListener() {
+ @Override
+ public void onHostResume() {
+ // Check the permission status when the app resumes
+ if (context.getSystemService(AlarmManager.class).canScheduleExactAlarms()) {
+ promise.resolve(GRANTED);
+ } else {
+ promise.resolve(DENIED);
+ }
+ reactContext.removeLifecycleEventListener(this);
+ }
+
+ @Override
+ public void onHostPause() {}
+
+ @Override
+ public void onHostDestroy() {}
+ });
+ } catch (Exception e) {
+ promise.reject(ERROR_INVALID_ACTIVITY, e);
+ }
+ }
+ return;
+ }
+ }
+
if (context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED) {
promise.resolve(GRANTED);
return;
@@ -222,6 +284,7 @@ public class RNPermissionsModuleImpl {
promise.resolve(getLegacyNotificationsResponse(reactContext, BLOCKED));
}
+ @RequiresApi(api = Build.VERSION_CODES.S)
public static void requestMultiple(
final ReactApplicationContext reactContext,
final PermissionListener listener,
@@ -230,8 +293,8 @@ public class RNPermissionsModuleImpl {
final Promise promise
) {
final WritableMap output = new WritableNativeMap();
- final ArrayList<String> permissionsToCheck = new ArrayList<String>();
- int checkedPermissionsCount = 0;
+ final ArrayList<String> permissionsToCheck = new ArrayList<>();
+ final HashSet<String> pendingPermissions = new HashSet<>();
Context context = reactContext.getBaseContext();
for (int i = 0; i < permissions.size(); i++) {
@@ -239,7 +302,6 @@ public class RNPermissionsModuleImpl {
if (isPermissionUnavailable(permission)) {
output.putString(permission, UNAVAILABLE);
- checkedPermissionsCount++;
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
output.putString(
permission,
@@ -247,17 +309,24 @@ public class RNPermissionsModuleImpl {
== PackageManager.PERMISSION_GRANTED
? GRANTED
: BLOCKED);
-
- checkedPermissionsCount++;
+ } else if (Manifest.permission.SCHEDULE_EXACT_ALARM.equals(permission)) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ if (context.getSystemService(AlarmManager.class).canScheduleExactAlarms()) {
+ output.putString(permission, GRANTED);
+ } else {
+ permissionsToCheck.add(permission);
+ pendingPermissions.add(permission);
+ }
+ }
} else if (context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED) {
output.putString(permission, GRANTED);
- checkedPermissionsCount++;
} else {
permissionsToCheck.add(permission);
+ pendingPermissions.add(permission);
}
}
- if (permissions.size() == checkedPermissionsCount) {
+ if (pendingPermissions.isEmpty()) {
promise.resolve(output);
return;
}
@@ -276,18 +345,49 @@ public class RNPermissionsModuleImpl {
for (int j = 0; j < permissionsToCheck.size(); j++) {
String permission = permissionsToCheck.get(j);
- if (results.length > 0 && results[j] == PackageManager.PERMISSION_GRANTED) {
- output.putString(permission, GRANTED);
+ if (Manifest.permission.SCHEDULE_EXACT_ALARM.equals(permission)) {
+ reactContext.addLifecycleEventListener(new LifecycleEventListener() {
+ @Override
+ public void onHostResume() {
+ if (context.getSystemService(AlarmManager.class).canScheduleExactAlarms()) {
+ output.putString(permission, GRANTED);
+ } else {
+ output.putString(permission, DENIED);
+ }
+ reactContext.removeLifecycleEventListener(this);
+ pendingPermissions.remove(permission);
+
+ if (pendingPermissions.isEmpty()) {
+ promise.resolve(output);
+ }
+ }
+
+ @Override
+ public void onHostPause() {}
+
+ @Override
+ public void onHostDestroy() {}
+ });
+
+ Intent intent = new Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM);
+ reactContext.startActivity(intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
} else {
- if (activity.shouldShowRequestPermissionRationale(permission)) {
- output.putString(permission, DENIED);
+ if (results.length > 0 && results[j] == PackageManager.PERMISSION_GRANTED) {
+ output.putString(permission, GRANTED);
} else {
- output.putString(permission, BLOCKED);
+ if (activity.shouldShowRequestPermissionRationale(permission)) {
+ output.putString(permission, DENIED);
+ } else {
+ output.putString(permission, BLOCKED);
+ }
}
+ pendingPermissions.remove(permission);
}
}
- promise.resolve(output);
+ if (pendingPermissions.isEmpty()) {
+ promise.resolve(output);
+ }
}
});
diff --git a/node_modules/react-native-permissions/src/permissions.android.ts b/node_modules/react-native-permissions/src/permissions.android.ts
index ee15437..b029453 100644
--- a/node_modules/react-native-permissions/src/permissions.android.ts
+++ b/node_modules/react-native-permissions/src/permissions.android.ts
@@ -43,6 +43,7 @@ const ANDROID = Object.freeze({
WRITE_CALL_LOG: 'android.permission.WRITE_CALL_LOG',
WRITE_CONTACTS: 'android.permission.WRITE_CONTACTS',
WRITE_EXTERNAL_STORAGE: 'android.permission.WRITE_EXTERNAL_STORAGE',
+ SCHEDULE_EXACT_ALARM: 'android.permission.SCHEDULE_EXACT_ALARM',
} as const);
export type AndroidPermissionMap = typeof ANDROID;(and ofc also in the requestMultiple, but out of scope in my implementation) |
|
@ThomasReyskens it is a good idea. |
|
@zoontek if you have enough time, please check out my PR. I think a lot of people would like to use this feature. I am available to apply any suggestions you may have. |
android/src/main/java/com/zoontek/rnpermissions/RNPermissionsModuleImpl.java
Outdated
Show resolved
Hide resolved
android/src/main/java/com/zoontek/rnpermissions/RNPermissionsModuleImpl.java
Outdated
Show resolved
Hide resolved
|
Excuse me, how was this merged ? I do not see any of this functionality in d253fb0 (v5.0.0) |
# Conflicts: # android/src/main/java/com/zoontek/rnpermissions/RNPermissionsModuleImpl.java
|
@zoontek |
|
@zoontek |
|
@seyedmostafahasani It's on my TODO, but please stop pressuring maintainers (for context: you already reached me by email too). I'm currently working a lot on react-native-edge-to-edge and helping debugging new architecture issues, this comes just after. EDIT: Just ran the example app for a really quick check and without specifically trying to achieve weird stuff (just a Screen.Recording.2024-10-28.at.10.46.58.mp4 |
Summary
I managed the new permission for scheduling exact alarms in Android 13 and higher.
Test Plan
I added this permission to the sample app.
What's required for testing (prerequisites)?
What are the steps to test it (after prerequisites)?
Compatibility
Checklist
README.mdexample/App.tsx)