Skip to content

Commit

Permalink
Merge pull request #3 from dpalou/MOBILE-4313
Browse files Browse the repository at this point in the history
Mobile 4313
  • Loading branch information
crazyserver authored Feb 19, 2024
2 parents bc78fef + 6661b07 commit 6d445bb
Show file tree
Hide file tree
Showing 9 changed files with 241 additions and 24 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ We created this fork because we needed to include the following modifications in
| - | Fix crash with target SDK 31 |
| - | Declare SCHEDULE_EXACT_ALARM permission |
| - | Fix click notifications in Android 12 |
| - | Inexact alarms will be scheduled in Android if 'Alarms & reminders' setting is disabled |
| - | Add new methods to check permissions and open native settings |

It also includes some commits that are in master and haven't been released.

Expand Down
4 changes: 4 additions & 0 deletions plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,10 @@
src="src/android/ClickHandlerActivity.java"
target-dir="src/de/appplant/cordova/plugin/localnotification" />

<source-file
src="src/android/AlarmPermissionReceiver.java"
target-dir="src/de/appplant/cordova/plugin/localnotification" />

<source-file
src="src/android/ClearReceiver.java"
target-dir="src/de/appplant/cordova/plugin/localnotification" />
Expand Down
61 changes: 61 additions & 0 deletions src/android/AlarmPermissionReceiver.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Apache 2.0 License
*
* Copyright (c) Sebastian Katzer 2017
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apache License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. Please obtain a copy of the License at
* http://opensource.org/licenses/Apache-2.0/ and read it before using this
* file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*/

package de.appplant.cordova.plugin.localnotification;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import java.util.List;
import android.app.AlarmManager;

import de.appplant.cordova.plugin.notification.Manager;
import de.appplant.cordova.plugin.notification.Notification;
import de.appplant.cordova.plugin.notification.Request;

import static de.appplant.cordova.plugin.notification.Notification.Type.SCHEDULED;

/**
* Receiver to detect changes in the Alarms and reminders permission.
*/
public class AlarmPermissionReceiver extends BroadcastReceiver {

/**
* Called when the alarms and reminder permission changes.
*
* @param context Application context
* @param intent Received intent with content data
*/
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equalsIgnoreCase(AlarmManager.ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED)) {
// Permission has been granted, reschedule all notifications to use exact time.
List<Notification> notifications = Manager.getInstance(context).getByType(SCHEDULED);

for (Notification notification : notifications) {
notification.schedule(new Request(notification.getOptions()), TriggerReceiver.class);
}
}
}


}
71 changes: 70 additions & 1 deletion src/android/LocalNotification.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@
import android.Manifest;
import android.content.pm.PackageManager;
import android.util.Log;
import android.content.Intent;
import android.provider.Settings;
import android.net.Uri;
import android.os.Build;
import android.app.AlarmManager;
import android.content.IntentFilter;

import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaInterface;
Expand All @@ -52,6 +58,7 @@
import de.appplant.cordova.plugin.notification.Request;
import de.appplant.cordova.plugin.notification.action.ActionGroup;
import de.appplant.cordova.plugin.notification.util.CallbackContextUtil;
import de.appplant.cordova.plugin.localnotification.AlarmPermissionReceiver;

import static de.appplant.cordova.plugin.notification.Notification.Type.SCHEDULED;
import static de.appplant.cordova.plugin.notification.Notification.Type.TRIGGERED;
Expand Down Expand Up @@ -79,6 +86,8 @@ public class LocalNotification extends CordovaPlugin {
// Launch details
private static Pair<Integer, String> launchDetails;

private AlarmPermissionReceiver alarmPermissionReceiver = new AlarmPermissionReceiver();

/**
* Called after plugin construction and fields have been initialized.
* Prefer to use pluginInitialize instead since there is no value in
Expand All @@ -87,6 +96,11 @@ public class LocalNotification extends CordovaPlugin {
@Override
public void initialize (CordovaInterface cordova, CordovaWebView webView) {
LocalNotification.webView = new WeakReference<CordovaWebView>(webView);

this.cordova.getActivity().getApplicationContext().registerReceiver(
alarmPermissionReceiver,
new IntentFilter(AlarmManager.ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED)
);
}

/**
Expand All @@ -106,6 +120,7 @@ public void onResume (boolean multitasking) {
@Override
public void onDestroy() {
deviceready = false;
this.cordova.getActivity().getApplicationContext().unregisterReceiver(alarmPermissionReceiver);
}

/**
Expand Down Expand Up @@ -177,6 +192,15 @@ public void run() {
} else
if (action.equals("notifications")) {
notifications(args, command);
} else
if (action.equals("canScheduleExactAlarms")) {
canScheduleExactAlarms(command);
} else
if (action.equals("openNotificationSettings")) {
openNotificationSettings(command);
} else
if (action.equals("openAlarmSettings")) {
openAlarmSettings(command);
}
}
});
Expand Down Expand Up @@ -220,6 +244,15 @@ private void check (CallbackContext command) {
success(command, allowed);
}

/**
* Ask if if the setting to schedule exact alarms is enabled.
*
* @param command The callback context used when calling back into JavaScript.
*/
private void canScheduleExactAlarms (CallbackContext command) {
success(command, getNotMgr().canScheduleExactAlarms());
}

/**
* Request permission for local notifications.
*
Expand All @@ -233,7 +266,7 @@ private void request (CallbackContext command) {
return;
}

if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.TIRAMISU) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
// Notifications are disabled and POST_NOTIFICATIONS runtime permission is not supported.
success(command, false);

Expand Down Expand Up @@ -504,6 +537,42 @@ private void notifications (JSONArray args, CallbackContext command) {

command.success(new JSONArray(options));
}
/**
* Open the Android Notification settings for current app.
*
* @param command The callback context used when calling back into JavaScript.
*/
private void openNotificationSettings (CallbackContext command) {
String packageName = cordova.getActivity().getPackageName();
Intent intent = new Intent();

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
intent.setAction(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
} else {
// In old Android versions it's not possible to view notification settings, open app settings.
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse("package:" + packageName));
}

cordova.getActivity().startActivity(intent);

command.success();
}

/**
* Open the Alarms & Reminders setting for current app.
*
* @param command The callback context used when calling back into JavaScript.
*/
private void openAlarmSettings (CallbackContext command) {
String packageName = cordova.getActivity().getPackageName();
Intent intent = new Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM, Uri.parse("package:" + packageName));

cordova.getActivity().startActivity(intent);

command.success();
}

/**
* Call all pending callbacks after the deviceready event has been fired.
Expand Down
17 changes: 16 additions & 1 deletion src/android/notification/Manager.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import android.content.SharedPreferences;
import android.service.notification.StatusBarNotification;
import androidx.core.app.NotificationManagerCompat;
import android.app.AlarmManager;

import org.json.JSONException;
import org.json.JSONObject;
Expand All @@ -43,6 +44,7 @@
import static android.os.Build.VERSION.SDK_INT;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.S;
import static androidx.core.app.NotificationManagerCompat.IMPORTANCE_DEFAULT;
import static de.appplant.cordova.plugin.notification.Notification.PREF_KEY_ID;
import static de.appplant.cordova.plugin.notification.Notification.Type.TRIGGERED;
Expand Down Expand Up @@ -89,6 +91,19 @@ public boolean areNotificationsEnabled () {
return getNotCompMgr().areNotificationsEnabled();
}

/**
* Check if the setting to schedule exact alarms is enabled.
*/
public boolean canScheduleExactAlarms () {
if (SDK_INT < S){
return true;
}

AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);

return alarmManager.canScheduleExactAlarms() == true;
}

/**
* Schedule local notification specified by request.
*
Expand Down Expand Up @@ -276,7 +291,7 @@ public List<Notification> getAll() {
*
* @param type The notification life cycle type
*/
private List<Notification> getByType(Notification.Type type) {
public List<Notification> getByType(Notification.Type type) {

if (type == Notification.Type.ALL)
return getAll();
Expand Down
44 changes: 22 additions & 22 deletions src/android/notification/Notification.java
Original file line number Diff line number Diff line change
Expand Up @@ -167,26 +167,13 @@ public Type getType() {
return Type.SCHEDULED;
}

/**
* For the app with target is android 12, we need to check if the app has “Alarm and Reminders” permission before using them else app throws SecurityException
*/
public boolean checkAlarmPermission() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S){
return true;
} else {
AlarmManager alarmManager = getAlarmMgr();
boolean hasPermission = (alarmManager.canScheduleExactAlarms() == true);
return hasPermission;
}
}

/**
* Schedule the local notification.
*
* @param request Set of notification options.
* @param receiver Receiver to handle the trigger event.
*/
void schedule(Request request, Class<?> receiver) {
public void schedule(Request request, Class<?> receiver) {
List<Pair<Date, Intent>> intents = new ArrayList<Pair<Date, Intent>>();
Set<String> ids = new ArraySet<String>();
AlarmManager mgr = getAlarmMgr();
Expand Down Expand Up @@ -216,7 +203,7 @@ void schedule(Request request, Class<?> receiver) {
return;
}

boolean hasAlarmPermission = checkAlarmPermission();
boolean hasAlarmPermission = Manager.getInstance(context).canScheduleExactAlarms();
persist(ids);

if (!options.isInfiniteTrigger()) {
Expand All @@ -232,9 +219,6 @@ void schedule(Request request, Class<?> receiver) {
if (!date.after(new Date()) && trigger(intent, receiver))
continue;

if (!hasAlarmPermission)
continue; // Cannot schedule the alarm.

PendingIntent pi = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
pi = PendingIntent.getBroadcast(
Expand All @@ -247,17 +231,33 @@ void schedule(Request request, Class<?> receiver) {
try {
switch (options.getPrio()) {
case PRIORITY_MIN:
mgr.setExact(RTC, time, pi);
if (hasAlarmPermission) {
mgr.setExact(RTC, time, pi);
} else {
mgr.set(RTC, time, pi);
}
break;
case PRIORITY_MAX:
if (SDK_INT >= M) {
mgr.setExactAndAllowWhileIdle(RTC_WAKEUP, time, pi);
if (hasAlarmPermission) {
mgr.setExactAndAllowWhileIdle(RTC_WAKEUP, time, pi);
} else {
mgr.setAndAllowWhileIdle(RTC_WAKEUP, time, pi);
}
} else {
mgr.setExact(RTC, time, pi);
if (hasAlarmPermission) {
mgr.setExact(RTC, time, pi);
} else {
mgr.set(RTC, time, pi);
}
}
break;
default:
mgr.setExact(RTC_WAKEUP, time, pi);
if (hasAlarmPermission) {
mgr.setExact(RTC_WAKEUP, time, pi);
} else {
mgr.set(RTC_WAKEUP, time, pi);
}
break;
}
} catch (Exception ignore) {
Expand Down
2 changes: 2 additions & 0 deletions src/ios/APPLocalNotification.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,6 @@
- (void) notification:(CDVInvokedUrlCommand*)command;
- (void) notifications:(CDVInvokedUrlCommand*)command;

- (void) openNotificationSettings:(CDVInvokedUrlCommand*)command;

@end
28 changes: 28 additions & 0 deletions src/ios/APPLocalNotification.m
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,34 @@ - (void) actions:(CDVInvokedUrlCommand *)command
}];
}

/**
* Open native settings to enable notifications.
* In iOS it's not possible to open the notification settings, only the app settings.
*
* @return [ Void ]
*/
- (void) openNotificationSettings:(CDVInvokedUrlCommand*)command
{
@try {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString: UIApplicationOpenSettingsURLString] options:@{} completionHandler:^(BOOL success) {
if (success) {
[self.commandDelegate
sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK]
callbackId:command.callbackId];
} else {
[self.commandDelegate
sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR]
callbackId:command.callbackId];
}
}];
}
@catch (NSException *exception) {
[self.commandDelegate
sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:exception.reason]
callbackId:command.callbackId];
}
}

#pragma mark -
#pragma mark Private

Expand Down
Loading

0 comments on commit 6d445bb

Please sign in to comment.