Skip to content

Commit d07f9e7

Browse files
authored
Add schedule exact alarms (#905)
1 parent 73aeb4d commit d07f9e7

File tree

18 files changed

+138
-38
lines changed

18 files changed

+138
-38
lines changed

README.md

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ Add all wanted permissions to your app `android/app/src/main/AndroidManifest.xml
188188
<uses-permission android:name="android.permission.RECEIVE_SMS" />
189189
<uses-permission android:name="android.permission.RECEIVE_WAP_PUSH" />
190190
<uses-permission android:name="android.permission.RECORD_AUDIO" />
191+
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
191192
<uses-permission android:name="android.permission.SEND_SMS" />
192193
<uses-permission android:name="android.permission.USE_SIP" />
193194
<uses-permission android:name="android.permission.UWB_RANGING" />
@@ -729,7 +730,7 @@ request(PERMISSIONS.IOS.CAMERA).then((status) => {
729730
Check notifications permission status and get notifications settings values.
730731

731732
> [!IMPORTANT]
732-
> On Android >= 13, the `checkNotifications` function will never return a `blocked` status. You need to call `requestNotifications` to obtain that information.
733+
> On Android 13+, the `checkNotifications` function will never return a `blocked` status. You need to call `requestNotifications` to obtain that information.
733734
734735
```ts
735736
function checkNotifications(): Promise<NotificationsResponse>;
@@ -810,16 +811,38 @@ requestMultiple([PERMISSIONS.IOS.CAMERA, PERMISSIONS.IOS.FACE_ID]).then((statuse
810811

811812
#### openSettings
812813

813-
Open application settings.
814+
Open application / alarms / notifications settings (default to `application`).
815+
816+
> [!NOTE]
817+
>
818+
> - `notifications` settings are only available on Android 8+ and iOS 15.4+
819+
> - `alarms` settings are only available on Android 12+
820+
> - When a choice is not available, it fallbacks to `application` settings
814821
815822
```ts
816-
function openSettings(): Promise<void>;
823+
function openSettings(type?: 'application' | 'alarms' | 'notifications'): Promise<void>;
817824
```
818825

819826
```ts
820827
import {openSettings} from 'react-native-permissions';
821828

822-
openSettings().catch(() => console.warn('Cannot open app settings'));
829+
openSettings('application').catch(() => console.warn('Cannot open app settings'));
830+
```
831+
832+
#### canScheduleExactAlarms (Android)
833+
834+
Check if your app can schedule exact alarms.
835+
836+
```ts
837+
function canScheduleExactAlarms(): Promise<boolean>;
838+
```
839+
840+
```ts
841+
import {canScheduleExactAlarms} from 'react-native-permissions';
842+
843+
canScheduleExactAlarms()
844+
.then((value) => console.log(`Can schedule exact alarms: ${value}`))
845+
.catch(() => console.warn('Cannot check exact alarms scheduling setting'));
823846
```
824847

825848
#### openPhotoPicker (iOS 14+)

android/src/main/java/com/zoontek/rnpermissions/RNPermissionsModuleImpl.kt

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package com.zoontek.rnpermissions
22

33
import android.Manifest
4+
import android.app.AlarmManager
5+
import android.content.Context
46
import android.content.Intent
57
import android.content.pm.PackageManager
68
import android.net.Uri
9+
import android.os.Build
710
import android.provider.Settings
811
import android.util.SparseArray
912

@@ -61,12 +64,25 @@ object RNPermissionsModuleImpl {
6164
return false
6265
}
6366

64-
fun openSettings(reactContext: ReactApplicationContext, promise: Promise) {
67+
fun openSettings(reactContext: ReactApplicationContext, type: String?, promise: Promise) {
6568
try {
66-
val intent = Intent().apply {
67-
setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
69+
val packageName = reactContext.packageName
70+
71+
val intent = when {
72+
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && type == "alarms" -> Intent().apply {
73+
setAction(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM)
74+
setData(Uri.parse("package:${packageName}"))
75+
}
76+
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && type == "notifications" -> Intent().apply {
77+
setAction(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
78+
putExtra(Settings.EXTRA_APP_PACKAGE, packageName)
79+
}
80+
else -> Intent().apply {
81+
setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
82+
setData(Uri.parse("package:${packageName}"))
83+
}
84+
}.apply {
6885
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
69-
setData(Uri.fromParts("package", reactContext.packageName, null))
7086
}
7187

7288
reactContext.startActivity(intent)
@@ -76,6 +92,17 @@ object RNPermissionsModuleImpl {
7692
}
7793
}
7894

95+
fun canScheduleExactAlarms(reactContext: ReactApplicationContext, promise: Promise) {
96+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
97+
return promise.resolve(true)
98+
}
99+
100+
val alarmManager = reactContext.getSystemService(Context.ALARM_SERVICE) as? AlarmManager
101+
?: return promise.resolve(false);
102+
103+
promise.resolve(alarmManager.canScheduleExactAlarms())
104+
}
105+
79106
fun check(reactContext: ReactApplicationContext, permission: String, promise: Promise) {
80107
if (!isPermissionAvailable(reactContext, permission)) {
81108
return promise.resolve(UNAVAILABLE)

android/src/newarch/com/zoontek/rnpermissions/RNPermissionsModule.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,12 @@ class RNPermissionsModule(reactContext: ReactApplicationContext?) :
1919
return RNPermissionsModuleImpl.NAME
2020
}
2121

22-
override fun openSettings(promise: Promise) {
23-
RNPermissionsModuleImpl.openSettings(reactApplicationContext, promise)
22+
override fun openSettings(type: String?, promise: Promise) {
23+
RNPermissionsModuleImpl.openSettings(reactApplicationContext, type, promise)
24+
}
25+
26+
override fun canScheduleExactAlarms(promise: Promise) {
27+
RNPermissionsModuleImpl.canScheduleExactAlarms(reactApplicationContext, promise)
2428
}
2529

2630
override fun check(permission: String, promise: Promise) {

android/src/oldarch/com/zoontek/rnpermissions/RNPermissionsModule.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,13 @@ class RNPermissionsModule(reactContext: ReactApplicationContext?) :
2222
}
2323

2424
@ReactMethod
25-
fun openSettings(promise: Promise) {
26-
RNPermissionsModuleImpl.openSettings(reactApplicationContext, promise)
25+
fun openSettings(type: String?, promise: Promise) {
26+
RNPermissionsModuleImpl.openSettings(reactApplicationContext, type, promise)
27+
}
28+
29+
@ReactMethod
30+
fun canScheduleExactAlarms(promise: Promise) {
31+
RNPermissionsModuleImpl.canScheduleExactAlarms(reactApplicationContext, promise)
2732
}
2833

2934
@ReactMethod

example/android/app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
3030
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
3131
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
32+
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
3233
<uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
3334
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
3435
<uses-permission android:name="android.permission.READ_SMS" />

example/ios/Podfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1575,7 +1575,7 @@ PODS:
15751575
- React-logger (= 0.76.0)
15761576
- React-perflogger (= 0.76.0)
15771577
- React-utils (= 0.76.0)
1578-
- RNPermissions (5.0.2):
1578+
- RNPermissions (5.1.0):
15791579
- DoubleConversion
15801580
- glog
15811581
- hermes-engine
@@ -1893,7 +1893,7 @@ SPEC CHECKSUMS:
18931893
React-utils: d9624101245ebaab39c9f1bd786132da0b4f27ff
18941894
ReactCodegen: dbfef1fef26f42c900bb1884fa149d49d501d64d
18951895
ReactCommon: 429ca28cd813c31359c73ffac6dc24f93347d522
1896-
RNPermissions: 16e35cb70f2752307bedf3b1f0a92b80d0506435
1896+
RNPermissions: f15581411f464b3fdf5c372da79c2ad985281b70
18971897
RNVectorIcons: 07792a9538e8577c1263fcad187712e90d65d8fb
18981898
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
18991899
Yoga: f8ec45ce98bba1bc93dd28f2ee37215180e6d2b6

example/ios/RNPermissionsExample.xcodeproj/project.pbxproj

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,10 @@
384384
"-DFOLLY_CFG_NO_COROUTINES=1",
385385
"-DFOLLY_HAVE_CLOCK_GETTIME=1",
386386
);
387-
OTHER_LDFLAGS = "$(inherited) ";
387+
OTHER_LDFLAGS = (
388+
"$(inherited)",
389+
" ",
390+
);
388391
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
389392
SDKROOT = iphoneos;
390393
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG";
@@ -453,7 +456,10 @@
453456
"-DFOLLY_CFG_NO_COROUTINES=1",
454457
"-DFOLLY_HAVE_CLOCK_GETTIME=1",
455458
);
456-
OTHER_LDFLAGS = "$(inherited) ";
459+
OTHER_LDFLAGS = (
460+
"$(inherited)",
461+
" ",
462+
);
457463
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
458464
SDKROOT = iphoneos;
459465
USE_HERMES = true;

ios/RNPermissions.mm

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -317,16 +317,23 @@ + (bool)isFlaggedAsRequested:(NSString * _Nonnull)handlerId {
317317
return requested != nil && [requested containsObject:handlerId];
318318
}
319319

320-
RCT_EXPORT_METHOD(openSettings:(RCTPromiseResolveBlock)resolve
320+
RCT_EXPORT_METHOD(openSettings:(NSString *)type
321+
resolve:(RCTPromiseResolveBlock)resolve
321322
reject:(RCTPromiseRejectBlock)reject) {
322323
UIApplication *sharedApplication = [UIApplication sharedApplication];
323-
NSURL *url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
324+
NSString *urlString = UIApplicationOpenSettingsURLString;
325+
326+
if (@available(iOS 15.4, *)) {
327+
if ([type isEqualToString:@"notifications"]) {
328+
urlString = UIApplicationOpenNotificationSettingsURLString;
329+
}
330+
}
324331

325-
[sharedApplication openURL:url options:@{} completionHandler:^(BOOL success) {
332+
[sharedApplication openURL:[NSURL URLWithString:urlString] options:@{} completionHandler:^(BOOL success) {
326333
if (success) {
327334
resolve(@(true));
328335
} else {
329-
reject(@"cannot_open_settings", @"Cannot open application settings", nil);
336+
reject(@"cannot_open_settings", [NSString stringWithFormat:@"Cannot open %@ settings", type], nil);
330337
}
331338
}];
332339
}
@@ -436,8 +443,10 @@ + (bool)isFlaggedAsRequested:(NSString * _Nonnull)handlerId {
436443
return std::make_shared<facebook::react::NativeRNPermissionsSpecJSI>(params);
437444
}
438445

439-
- (NSDictionary *)checkMultiple:(NSArray *)permissions {
440-
@throw [NSException exceptionWithName:@"RNPermissions:checkMultiple" reason:@"checkMultiple is not supported on iOS" userInfo:nil];
446+
- (void)checkMultiple:(NSArray *)permissions
447+
resolve:(RCTPromiseResolveBlock)resolve
448+
reject:(RCTPromiseRejectBlock)reject {
449+
reject(@"RNPermissions:checkMultiple", @"checkMultiple is not supported on iOS", nil);
441450
}
442451

443452
- (void)requestMultiple:(NSArray *)permissions
@@ -446,6 +455,11 @@ - (void)requestMultiple:(NSArray *)permissions
446455
reject(@"RNPermissions:requestMultiple", @"requestMultiple is not supported on iOS", nil);
447456
}
448457

458+
- (void)canScheduleExactAlarms:(RCTPromiseResolveBlock)resolve
459+
reject:(RCTPromiseRejectBlock)reject {
460+
reject(@"RNPermissions:canScheduleExactAlarms", @"canScheduleExactAlarms is not supported on iOS", nil);
461+
}
462+
449463
- (void)shouldShowRequestRationale:(NSString *)permission
450464
resolve:(RCTPromiseResolveBlock)resolve
451465
reject:(RCTPromiseRejectBlock)reject {

mock.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@ const {RESULTS} = require('./dist/commonjs/results');
1212
const PERMISSIONS = {ANDROID, IOS, WINDOWS};
1313
export {PERMISSIONS, RESULTS};
1414

15+
export const canScheduleExactAlarms = jest.fn(async () => true);
16+
export const check = jest.fn(async () => RESULTS.GRANTED);
17+
export const checkLocationAccuracy = jest.fn(async () => 'full');
1518
export const openPhotoPicker = jest.fn(async () => {});
1619
export const openSettings = jest.fn(async () => {});
17-
export const check = jest.fn(async (permission) => RESULTS.GRANTED);
18-
export const request = jest.fn(async (permission) => RESULTS.GRANTED);
19-
export const checkLocationAccuracy = jest.fn(async () => 'full');
20-
export const requestLocationAccuracy = jest.fn(async (options) => 'full');
20+
export const request = jest.fn(async () => RESULTS.GRANTED);
21+
export const requestLocationAccuracy = jest.fn(async () => 'full');
2122

2223
const notificationOptions = [
2324
'alert',
@@ -68,6 +69,7 @@ export default {
6869
PERMISSIONS,
6970
RESULTS,
7071

72+
canScheduleExactAlarms,
7173
check,
7274
checkLocationAccuracy,
7375
checkMultiple,

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-native-permissions",
3-
"version": "5.0.2",
3+
"version": "5.1.0",
44
"license": "MIT",
55
"description": "An unified permissions API for React Native on iOS, Android and Windows",
66
"author": "Mathieu Acthernoene <[email protected]>",

0 commit comments

Comments
 (0)