Open seyedmostafahasani opened 4 months ago
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 intent.setData(Uri.fromParts("package", reactContext.getPackageName(), null));
at line 215 of the RNPermissionsModuleImpl.java
.
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. I will add it to the code.
@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.
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.md
example/App.tsx
)