Skip to content

Commit

Permalink
iOS bug fix. Android restore receiver enhancement
Browse files Browse the repository at this point in the history
- Fix iOS schedule method to remove existing notification first.
- Refactor Android receivers.
- Create a base receiver with the logic for triggering a notification.
- Extend that receiver for both restore and trigger.
- Use same notification logic on boot as you do during a normal trigger.
  • Loading branch information
timkellypa committed May 6, 2020
1 parent 52002b0 commit 802963c
Show file tree
Hide file tree
Showing 8 changed files with 165 additions and 81 deletions.
Binary file added .DS_Store
Binary file not shown.
4 changes: 4 additions & 0 deletions plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,10 @@
src="src/android/notification/receiver/AbstractRestoreReceiver.java"
target-dir="src/de/appplant/cordova/plugin/notification/receiver" />

<source-file
src="src/android/notification/receiver/AbstractNotificationReceiver.java"
target-dir="src/de/appplant/cordova/plugin/notification/receiver" />

<source-file
src="src/android/notification/receiver/AbstractTriggerReceiver.java"
target-dir="src/de/appplant/cordova/plugin/notification/receiver" />
Expand Down
16 changes: 14 additions & 2 deletions src/android/RestoreReceiver.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,15 @@
import de.appplant.cordova.plugin.notification.Request;
import de.appplant.cordova.plugin.notification.receiver.AbstractRestoreReceiver;

import static de.appplant.cordova.plugin.localnotification.LocalNotification.fireEvent;
import static de.appplant.cordova.plugin.localnotification.LocalNotification.isAppRunning;

/**
* This class is triggered upon reboot of the device. It needs to re-register
* the alarms with the AlarmManager since these alarms are lost in case of
* reboot.
*/
public class RestoreReceiver extends AbstractRestoreReceiver {

/**
* Called when a local notification need to be restored.
*
Expand All @@ -53,7 +55,7 @@ public void onRestore (Request request, Notification toast) {
boolean after = date != null && date.after(new Date());

if (!after && toast.isHighPrio()) {
toast.show();
performNotification(toast);
} else {
toast.clear();
}
Expand All @@ -66,6 +68,16 @@ public void onRestore (Request request, Notification toast) {
}
}

@Override
public void dispatchAppEvent(String key, Notification notification) {
fireEvent(key, notification);
}

@Override
public boolean checkAppRunning() {
return isAppRunning();
}

/**
* Build notification specified by options.
*
Expand Down
85 changes: 8 additions & 77 deletions src/android/TriggerReceiver.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,86 +62,17 @@ public class TriggerReceiver extends AbstractTriggerReceiver {
*/
@Override
public void onTrigger(Notification notification, Bundle bundle) {
boolean isUpdate = bundle.getBoolean(Notification.EXTRA_UPDATE, false);
boolean didShowNotification = false;
Context context = notification.getContext();
Options options = notification.getOptions();
Manager manager = Manager.getInstance(context);
PowerManager pm = (PowerManager) context.getSystemService(POWER_SERVICE);
boolean autoLaunch = options.isAutoLaunchingApp() && SDK_INT <= P && !options.useFullScreenIntent();

// Check device sleep status here (not after wake)
// If device is asleep in this moment, waking it up with our wakelock
// is not enough to allow the app to have CPU to trigger an event
// in Android 8+
boolean isInteractive = SDK_INT < O || pm.isInteractive();
int badge = options.getBadgeNumber();

if (badge > 0) {
manager.setBadge(badge);
}

if (options.shallWakeUp()) {
wakeUp(notification);
}

if (autoLaunch) {
LaunchUtils.launchApp(context);
}

// Show notification if we should (triggerInApp is false)
// or if we can't trigger in the app due to:
// 1. No autoLaunch configured/supported and app is not running.
// 2. Any SDK >= Oreo is asleep (must be triggered here)
if (!options.triggerInApp() ||
(!autoLaunch && !isAppRunning())
|| !isInteractive
) {
didShowNotification = true;
notification.show();
}

// run trigger function if triggerInApp() is true
// and we did not send a notification.
if (options.triggerInApp() && !didShowNotification) {
// wake up even if we didn't set it to
if (!options.shallWakeUp()) {
wakeUp(notification);
}

fireEvent("trigger", notification);
}

if (!options.isInfiniteTrigger())
return;

Calendar cal = Calendar.getInstance();
cal.add(MINUTE, 1);
Request req = new Request(options, cal.getTime());

manager.schedule(req, this.getClass());
performNotification(notification);
}

/**
* Wakeup the device.
*
* @param context The application context.
*/
private void wakeUp(Notification notification) {
Context context = notification.getContext();
Options options = notification.getOptions();
String wakeLockTag = context.getApplicationInfo().name + ":LocalNotification";
PowerManager pm = (PowerManager) context.getSystemService(POWER_SERVICE);

if (pm == null)
return;

int level = PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE;

PowerManager.WakeLock wakeLock = pm.newWakeLock(level, wakeLockTag);
@Override
public void dispatchAppEvent(String key, Notification notification) {
fireEvent(key, notification);
}

wakeLock.setReferenceCounted(false);
wakeLock.acquire(options.getWakeLockTimeout());
@Override
public boolean checkAppRunning() {
return isAppRunning();
}

/**
Expand Down
126 changes: 126 additions & 0 deletions src/android/notification/receiver/AbstractNotificationReceiver.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package de.appplant.cordova.plugin.notification.receiver;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.os.PowerManager;

import java.util.Calendar;

import de.appplant.cordova.plugin.notification.Manager;
import de.appplant.cordova.plugin.notification.Notification;
import de.appplant.cordova.plugin.notification.Options;
import de.appplant.cordova.plugin.notification.Request;
import de.appplant.cordova.plugin.notification.util.LaunchUtils;

import static android.content.Context.POWER_SERVICE;
import static android.os.Build.VERSION.SDK_INT;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.P;
import static java.util.Calendar.MINUTE;

/**
* The base class for any receiver that is trying to display a notification.
*/
abstract public class AbstractNotificationReceiver extends BroadcastReceiver {
/**
* Perform a notification. All notification logic is here.
* Determines whether to dispatch events, autoLaunch the app, use fullScreenIntents, etc.
* @param notification reference to the notification to be fired
*/
public void performNotification(Notification notification) {
Context context = notification.getContext();
Options options = notification.getOptions();
Manager manager = Manager.getInstance(context);
PowerManager pm = (PowerManager) context.getSystemService(POWER_SERVICE);
boolean autoLaunch = options.isAutoLaunchingApp() && SDK_INT <= P && !options.useFullScreenIntent();

// Check device sleep status here (not after wake)
// If device is asleep in this moment, waking it up with our wakelock
// is not enough to allow the app to have CPU to trigger an event
// in Android 8+
boolean isInteractive = SDK_INT < O || (pm != null && pm.isInteractive());
int badge = options.getBadgeNumber();

if (badge > 0) {
manager.setBadge(badge);
}

if (options.shallWakeUp()) {
wakeUp(notification);
}

if (autoLaunch) {
LaunchUtils.launchApp(context);
}

// Show notification if we should (triggerInApp is false)
// or if we can't trigger in the app due to:
// 1. No autoLaunch configured/supported and app is not running.
// 2. Any SDK >= Oreo is asleep (must be triggered here)
boolean didShowNotification = false;
if (!options.triggerInApp() ||
(!autoLaunch && !checkAppRunning())
|| !isInteractive
) {
didShowNotification = true;
notification.show();
}

// run trigger function if triggerInApp() is true
// and we did not send a notification.
if (options.triggerInApp() && !didShowNotification) {
// wake up even if we didn't set it to
if (!options.shallWakeUp()) {
wakeUp(notification);
}

dispatchAppEvent("trigger", notification);
}

if (!options.isInfiniteTrigger())
return;

Calendar cal = Calendar.getInstance();
cal.add(MINUTE, 1);
Request req = new Request(options, cal.getTime());

manager.schedule(req, this.getClass());
}

/**
* Send the application an event using our notification
* @param key key for our event in the app
* @param notification reference to the notification
*/
abstract public void dispatchAppEvent(String key, Notification notification);

/**
* Check if the application is running.
* Should be developed in local class, which has access to things needed for this.
* @return whether or not app is running
*/
abstract public boolean checkAppRunning();

/**
* Wakeup the device.
*
* @param notification The notification used to wakeup the device.
* contains context and timeout.
*/
private void wakeUp(Notification notification) {
Context context = notification.getContext();
Options options = notification.getOptions();
String wakeLockTag = context.getApplicationInfo().name + ":LocalNotification";
PowerManager pm = (PowerManager) context.getSystemService(POWER_SERVICE);

if (pm == null)
return;

int level = PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE;

PowerManager.WakeLock wakeLock = pm.newWakeLock(level, wakeLockTag);

wakeLock.setReferenceCounted(false);
wakeLock.acquire(options.getWakeLockTimeout());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
* the alarms with the AlarmManager since these alarms are lost in case of
* reboot.
*/
abstract public class AbstractRestoreReceiver extends BroadcastReceiver {
abstract public class AbstractRestoreReceiver extends AbstractNotificationReceiver {

/**
* Called on device reboot.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
* Abstract broadcast receiver for local notifications. Creates the
* notification options and calls the event functions for further proceeding.
*/
abstract public class AbstractTriggerReceiver extends BroadcastReceiver {
abstract public class AbstractTriggerReceiver extends AbstractNotificationReceiver {

/**
* Called when an alarm was triggered.
Expand Down
11 changes: 11 additions & 0 deletions src/ios/APPLocalNotification.m
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,17 @@ - (void) schedule:(CDVInvokedUrlCommand*)command
for (NSDictionary* options in notifications) {
APPNotificationContent* notification;

// Delete an existing alarm with this ID first
NSNumber* id = [options objectForKey:@"id"];
UNNotificationRequest* oldNotification;

oldNotification = [_center getNotificationWithId:id];

if (oldNotification) {
[_center cancelNotification:oldNotification];
}

// Schedule the new notification
notification = [[APPNotificationContent alloc]
initWithOptions:options];

Expand Down

0 comments on commit 802963c

Please sign in to comment.