Skip to content
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(local-notifications): Adding permissions for using SCHEDULE_EXACT_ALARM in Android 14 #1840

Merged
merged 43 commits into from
Dec 12, 2023
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
1825d52
Adding config option for using inexact alarms
theproducer Oct 6, 2023
178bd12
updating setExactIfPossible for inExact option
theproducer Oct 6, 2023
ae0de87
Supporting inExact in TimedNotificationPublisher
theproducer Oct 6, 2023
dd72224
renaming inexact to exact
theproducer Oct 10, 2023
5d348e7
adding requestExactAlarmSpecialPermission
theproducer Oct 11, 2023
499f62f
Adding docs
theproducer Oct 11, 2023
6903b5e
Pulling exact alarm out of normal permissions checks
theproducer Oct 11, 2023
4151c0f
Add stub functions for iOS
theproducer Oct 11, 2023
465f77e
Adding warning
theproducer Oct 11, 2023
039d5f7
fmt
theproducer Oct 11, 2023
2b8ee64
Using Logger instead of Log
theproducer Oct 17, 2023
11e898c
Removing unneeded permission alias and exact option
theproducer Oct 17, 2023
957dd88
renaming requestExact / checkExactAlarmSpecialPermission
theproducer Oct 17, 2023
7efb9cc
build
theproducer Oct 17, 2023
47bc7ae
fmt
theproducer Oct 17, 2023
3c1f282
Adding Android 14 note to readme
theproducer Oct 18, 2023
c558ce3
Merge branch 'main' into local-notifications/alarm-permissions
theproducer Oct 18, 2023
96714b5
Merge branch 'main' into local-notifications/alarm-permissions
IT-MikeS Oct 23, 2023
1450ba7
Merge branch 'main' into local-notifications/alarm-permissions
IT-MikeS Oct 23, 2023
84ff170
Merge branch 'main' into local-notifications/alarm-permissions
IT-MikeS Oct 25, 2023
fddad8b
Merge branch 'main' into local-notifications/alarm-permissions
jcesarmobile Oct 27, 2023
5bf47d4
Returning unimplemented on non android platforms
theproducer Nov 7, 2023
f7b0d0e
Adding fallback for Android < S
theproducer Nov 7, 2023
0e4b164
docs updates
theproducer Nov 7, 2023
2779045
Merge branch 'main' into local-notifications/alarm-permissions
theproducer Nov 7, 2023
5ec7a30
Merge branch 'main' into local-notifications/alarm-permissions
theproducer Nov 7, 2023
0731fa9
Merge branch 'main' into local-notifications/alarm-permissions
jcesarmobile Nov 8, 2023
23c9d8c
Directly open app settings page
theproducer Nov 10, 2023
bdb86ea
Removing unused import
theproducer Nov 13, 2023
ea23c0c
Updating docs
theproducer Nov 13, 2023
9268ba4
Merge branch 'main' into local-notifications/alarm-permissions
theproducer Nov 14, 2023
7e9b537
Adding doc notes about disabling exact alarms
theproducer Nov 14, 2023
158f7f8
fmt
theproducer Nov 14, 2023
de58fa3
Merge branch 'main' into local-notifications/alarm-permissions
theproducer Nov 15, 2023
0514771
Merge branch 'main' into local-notifications/alarm-permissions
theproducer Nov 27, 2023
824a093
Merge branch 'main' into local-notifications/alarm-permissions
jcesarmobile Dec 5, 2023
218c5c0
documentation tweaks
theproducer Dec 11, 2023
eddc231
Merge branch 'main' into local-notifications/alarm-permissions
theproducer Dec 11, 2023
109bac8
Adding new methods to pluginMethods array
theproducer Dec 11, 2023
93bf46e
regenerate README
jcesarmobile Dec 11, 2023
f5322e0
Update local-notifications/README.md
jcesarmobile Dec 12, 2023
ccfe36d
Merge branch 'main' into local-notifications/alarm-permissions
theproducer Dec 12, 2023
f6dfde7
Merge branch 'local-notifications/alarm-permissions' of github.com:io…
theproducer Dec 12, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 49 additions & 1 deletion local-notifications/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ Starting on Android 12, scheduled notifications won't be exact unless this permi
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
```

Note that even if the permission is present, users can still disable exact notifications from the app settings.
Note that even if the permission is present, users can still disable exact notifications from the app settings. Use `checkExactNotificationSetting()` to check the the value of the setting. If a user disables this setting, the app will restart and any notification scheduled with an exact alarm will be deleted. If your application depends on exact alarms, be sure to check this setting in order provide fallbacks or alternative behavior.
theproducer marked this conversation as resolved.
Show resolved Hide resolved

On Android 14, there is a new permission called `USE_EXACT_ALARM`. Use this permission to use exact alarms without needing to request permission from the user. This should only be used if the use of exact alarms is central to your app's functionality. Read more about the implications of using this permission [here](https://developer.android.com/reference/android/Manifest.permission#USE_EXACT_ALARM).

## Configuration

Expand Down Expand Up @@ -94,6 +96,8 @@ If the device has entered [Doze](https://developer.android.com/training/monitori
* [`listChannels()`](#listchannels)
* [`checkPermissions()`](#checkpermissions)
* [`requestPermissions()`](#requestpermissions)
* [`changeExactNotificationSetting()`](#changeexactnotificationsetting)
* [`checkExactNotificationSetting()`](#checkexactnotificationsetting)
* [`addListener('localNotificationReceived', ...)`](#addlistenerlocalnotificationreceived)
* [`addListener('localNotificationActionPerformed', ...)`](#addlistenerlocalnotificationactionperformed)
* [`removeAllListeners()`](#removealllisteners)
Expand Down Expand Up @@ -321,6 +325,43 @@ Request permission to display local notifications.
--------------------


### changeExactNotificationSetting()

```typescript
changeExactNotificationSetting() => Promise<SettingsPermissionStatus>
```

Direct user to the application settings screen to configure exact alarms.

In the event that a user changes the settings from granted to denied, the application
will restart and any notification scheduled with an exact alarm will be deleted.

Only available on Android &gt;= 12.

**Returns:** <code>Promise&lt;<a href="#settingspermissionstatus">SettingsPermissionStatus</a>&gt;</code>

**Since:** 6.0.0

--------------------


### checkExactNotificationSetting()

```typescript
checkExactNotificationSetting() => Promise<SettingsPermissionStatus>
```

Check application setting for using exact alarms.

Only available on Android.

**Returns:** <code>Promise&lt;<a href="#settingspermissionstatus">SettingsPermissionStatus</a>&gt;</code>

**Since:** 6.0.0

--------------------


### addListener('localNotificationReceived', ...)

```typescript
Expand Down Expand Up @@ -652,6 +693,13 @@ An action that can be taken when a notification is displayed.
| **`display`** | <code><a href="#permissionstate">PermissionState</a></code> | Permission state of displaying notifications. | 1.0.0 |


#### SettingsPermissionStatus

| Prop | Type | Description | Since |
| ----------------- | ----------------------------------------------------------- | --------------------------------------- | ----- |
| **`exact_alarm`** | <code><a href="#permissionstate">PermissionState</a></code> | Permission state of using exact alarms. | 6.0.0 |


#### PluginListenerHandle

| Prop | Type |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,10 @@ private void setExactIfPossible(
PendingIntent pendingIntent
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !alarmManager.canScheduleExactAlarms()) {
Logger.warn(
"Capacitor/LocalNotification",
"Exact alarms not allowed in user settings. Notification scheduled with non-exact alarm."
);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && schedule.allowWhileIdle()) {
alarmManager.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, trigger, pendingIntent);
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
package com.capacitorjs.plugins.localnotifications;

import static android.provider.Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM;

import android.Manifest;
import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.service.notification.StatusBarNotification;
import androidx.activity.result.ActivityResult;
import com.getcapacitor.Bridge;
import com.getcapacitor.JSArray;
import com.getcapacitor.JSObject;
Expand All @@ -15,6 +20,7 @@
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginHandle;
import com.getcapacitor.PluginMethod;
import com.getcapacitor.annotation.ActivityCallback;
import com.getcapacitor.annotation.CapacitorPlugin;
import com.getcapacitor.annotation.Permission;
import com.getcapacitor.annotation.PermissionCallback;
Expand Down Expand Up @@ -221,13 +227,40 @@ public void requestPermissions(PluginCall call) {
}
}

@PluginMethod
public void changeExactNotificationSetting(PluginCall call) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
startActivityForResult(
call,
new Intent(ACTION_REQUEST_SCHEDULE_EXACT_ALARM, Uri.parse("package:" + getActivity().getPackageName())),
"alarmPermissionsCallback"
);
} else {
checkExactNotificationSetting(call);
}
theproducer marked this conversation as resolved.
Show resolved Hide resolved
}

@PluginMethod
public void checkExactNotificationSetting(PluginCall call) {
JSObject permissionsResultJSON = new JSObject();
permissionsResultJSON.put("exact_alarm", getExactAlarmPermissionText());

call.resolve(permissionsResultJSON);
}

@PermissionCallback
private void permissionsCallback(PluginCall call) {
JSObject permissionsResultJSON = new JSObject();
permissionsResultJSON.put("display", getNotificationPermissionText());

call.resolve(permissionsResultJSON);
}

@ActivityCallback
private void alarmPermissionsCallback(PluginCall call, ActivityResult result) {
checkExactNotificationSetting(call);
}

private String getNotificationPermissionText() {
if (manager.areNotificationsEnabled()) {
return "granted";
Expand All @@ -236,6 +269,19 @@ private String getNotificationPermissionText() {
}
}

private String getExactAlarmPermissionText() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
AlarmManager alarmManager = (AlarmManager) getActivity().getSystemService(Context.ALARM_SERVICE);
if (alarmManager.canScheduleExactAlarms()) {
return "granted";
} else {
return "denied";
}
}

return "granted";
}

public static void fireReceived(JSObject notification) {
LocalNotificationsPlugin localNotificationsPlugin = LocalNotificationsPlugin.getLocalNotificationsInstance();
if (localNotificationsPlugin != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,11 @@ private Notification getParcelableExtraLegacy(Intent intent, String string) {

private boolean rescheduleNotificationIfNeeded(Context context, Intent intent, int id) {
String dateString = intent.getStringExtra(CRON_KEY);

if (dateString != null) {
DateMatch date = DateMatch.fromMatchString(dateString);
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);

long trigger = date.nextTrigger(new Date());
Intent clone = (Intent) intent.clone();
int flags = PendingIntent.FLAG_CANCEL_CURRENT;
Expand All @@ -70,6 +72,10 @@ private boolean rescheduleNotificationIfNeeded(Context context, Intent intent, i
}
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, id, clone, flags);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !alarmManager.canScheduleExactAlarms()) {
Logger.warn(
"Capacitor/LocalNotification",
"Exact alarms not allowed in user settings. Notification scheduled with non-exact alarm."
);
alarmManager.set(AlarmManager.RTC, trigger, pendingIntent);
} else {
alarmManager.setExact(AlarmManager.RTC, trigger, pendingIntent);
Expand Down
2 changes: 2 additions & 0 deletions local-notifications/ios/Plugin/LocalNotificationsPlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
CAP_PLUGIN_METHOD(schedule, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(requestPermissions, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(checkPermissions, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(checkExactNotificationSetting, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(changeExactNotificationSetting, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(cancel, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(getPending, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(registerActionTypes, CAPPluginReturnPromise);
Expand Down
8 changes: 8 additions & 0 deletions local-notifications/ios/Plugin/LocalNotificationsPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,14 @@ public class LocalNotificationsPlugin: CAPPlugin {
}
}

@objc public func checkExactNotificationSetting(_ call: CAPPluginCall) {
call.unimplemented()
}

@objc public func changeExactNotificationSetting(_ call: CAPPluginCall) {
call.unimplemented()
}

/**
* Cancel notifications by id
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,4 @@ import XCTest
@testable import Plugin

class LocalNotificationsTests: XCTestCase {
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
}

override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}
}
30 changes: 30 additions & 0 deletions local-notifications/src/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,27 @@ export interface LocalNotificationsPlugin {
*/
requestPermissions(): Promise<PermissionStatus>;

/**
* Direct user to the application settings screen to configure exact alarms.
*
* In the event that a user changes the settings from granted to denied, the application
* will restart and any notification scheduled with an exact alarm will be deleted.
*
* Only available on Android >= 12.
theproducer marked this conversation as resolved.
Show resolved Hide resolved
*
* @since 6.0.0
*/
changeExactNotificationSetting(): Promise<SettingsPermissionStatus>;

/**
* Check application setting for using exact alarms.
*
* Only available on Android.
*
* @since 6.0.0
*/
checkExactNotificationSetting(): Promise<SettingsPermissionStatus>;

/**
* Listen for when notifications are displayed.
*
Expand Down Expand Up @@ -844,6 +865,15 @@ export interface PermissionStatus {
display: PermissionState;
}

export interface SettingsPermissionStatus {
/**
* Permission state of using exact alarms.
*
* @since 6.0.0
*/
exact_alarm: PermissionState;
}

export interface ActionPerformed {
/**
* The identifier of the performed action.
Expand Down
9 changes: 9 additions & 0 deletions local-notifications/src/web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {
PermissionStatus,
ScheduleOptions,
ScheduleResult,
SettingsPermissionStatus,
} from './definitions';

export class LocalNotificationsWeb
Expand Down Expand Up @@ -106,6 +107,14 @@ export class LocalNotificationsWeb
};
}

async changeExactNotificationSetting(): Promise<SettingsPermissionStatus> {
throw this.unimplemented('Not implemented on web.');
}

async checkExactNotificationSetting(): Promise<SettingsPermissionStatus> {
throw this.unimplemented('Not implemented on web.');
}

async requestPermissions(): Promise<PermissionStatus> {
if (!this.hasNotificationSupport()) {
throw this.unavailable('Notifications not supported in this browser.');
Expand Down
Loading