Skip to content

Commit

Permalink
fix(android): restore local notifications after device reboot (#3027)
Browse files Browse the repository at this point in the history
  • Loading branch information
tntwist authored Jul 10, 2020
1 parent 4e8b59e commit 2a39a7d
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 24 deletions.
10 changes: 10 additions & 0 deletions android/capacitor/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,15 @@
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<receiver android:name="com.getcapacitor.plugin.notification.LocalNotificationRestoreReceiver" android:directBootAware="true" android:exported="false">
<intent-filter>
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED"/>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
</intent-filter>
</receiver>
</application>

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public void schedule(PluginCall call) {
}
JSONArray ids = manager.schedule(call, localNotifications);
if (ids != null) {
notificationStorage.appendNotificationIds(localNotifications);
notificationStorage.appendNotifications(localNotifications);
JSObject result = new JSObject();
JSArray jsArray = new JSArray();
for (int i=0; i < ids.length(); i++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,31 +186,37 @@ public static List<LocalNotification> buildNotificationList(PluginCall call) {
call.error("Invalid JSON object sent to NotificationPlugin", e);
return null;
}
LocalNotification activeLocalNotification = new LocalNotification();
activeLocalNotification.setSource(notification.toString());
activeLocalNotification.setId(notification.getInteger("id"));
activeLocalNotification.setBody(notification.getString("body"));
activeLocalNotification.setActionTypeId(notification.getString("actionTypeId"));
activeLocalNotification.setGroup(notification.getString("group"));
activeLocalNotification.setSound(notification.getString("sound"));
activeLocalNotification.setTitle(notification.getString("title"));
activeLocalNotification.setSmallIcon(notification.getString("smallIcon"));
activeLocalNotification.setIconColor(notification.getString("iconColor"));
activeLocalNotification.setAttachments(LocalNotificationAttachment.getAttachments(notification));
activeLocalNotification.setGroupSummary(notification.getBoolean("groupSummary", false));
activeLocalNotification.setChannelId(notification.getString("channelId"));

try {
activeLocalNotification.setSchedule(new LocalNotificationSchedule(notification));
LocalNotification activeLocalNotification = buildNotificationFromJSObject(notification);
resultLocalNotifications.add(activeLocalNotification);
} catch (ParseException e) {
call.error("Invalid date format sent to Notification plugin", e);
return null;
}
activeLocalNotification.setExtra(notification.getJSObject("extra"));
resultLocalNotifications.add(activeLocalNotification);
}
return resultLocalNotifications;
}

public static LocalNotification buildNotificationFromJSObject(JSObject jsonObject) throws ParseException {
LocalNotification localNotification = new LocalNotification();
localNotification.setSource(jsonObject.toString());
localNotification.setId(jsonObject.getInteger("id"));
localNotification.setBody(jsonObject.getString("body"));
localNotification.setActionTypeId(jsonObject.getString("actionTypeId"));
localNotification.setGroup(jsonObject.getString("group"));
localNotification.setSound(jsonObject.getString("sound"));
localNotification.setTitle(jsonObject.getString("title"));
localNotification.setSmallIcon(jsonObject.getString("smallIcon"));
localNotification.setIconColor(jsonObject.getString("iconColor"));
localNotification.setAttachments(LocalNotificationAttachment.getAttachments(jsonObject));
localNotification.setGroupSummary(jsonObject.getBoolean("groupSummary", false));
localNotification.setChannelId(jsonObject.getString("channelId"));
localNotification.setSchedule(new LocalNotificationSchedule(jsonObject));
localNotification.setExtra(jsonObject.getJSObject("extra"));

return localNotification;
}

public static List<Integer> getLocalNotificationPendingList(PluginCall call) {
List<JSONObject> notifications = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,13 +137,17 @@ public JSONArray schedule(PluginCall call, List<LocalNotification> localNotifica

boolean notificationsEnabled = notificationManager.areNotificationsEnabled();
if (!notificationsEnabled) {
call.error("Notifications not enabled on this device");
if(call != null){
call.error("Notifications not enabled on this device");
}
return null;
}
for (LocalNotification localNotification : localNotifications) {
Integer id = localNotification.getId();
if (localNotification.getId() == null) {
call.error("LocalNotification missing identifier");
if(call != null) {
call.error("LocalNotification missing identifier");
}
return null;
}
dismissVisibleNotification(id);
Expand Down Expand Up @@ -214,7 +218,9 @@ private void buildNotification(NotificationManagerCompat notificationManager, Lo
try {
mBuilder.setColor(Color.parseColor(iconColor));
} catch (IllegalArgumentException ex) {
call.error("Invalid color provided. Must be a hex string (ex: #ff0000");
if(call != null) {
call.error("Invalid color provided. Must be a hex string (ex: #ff0000");
}
return;
}
}
Expand Down Expand Up @@ -294,7 +300,6 @@ private Intent buildIntent(LocalNotification localNotification, String action) {
* on a certain date "shape" (such as every first of the month)
*/
// TODO support different AlarmManager.RTC modes depending on priority
// TODO restore alarm on device shutdown (requires persistence)
private void triggerScheduledNotification(Notification notification, LocalNotification request) {
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
LocalNotificationSchedule schedule = request.getSchedule();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.getcapacitor.plugin.notification;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.UserManager;

import com.getcapacitor.CapConfig;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import static android.os.Build.VERSION.SDK_INT;

public class LocalNotificationRestoreReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
if (SDK_INT >= 24) {
UserManager um = context.getSystemService(UserManager.class);
if (um == null || !um.isUserUnlocked()) return;
}

NotificationStorage storage = new NotificationStorage(context);
List<String> ids = storage.getSavedNotificationIds();

ArrayList<LocalNotification> notifications = new ArrayList<>(ids.size());
ArrayList<LocalNotification> updatedNotifications = new ArrayList<>();
for (String id : ids) {
LocalNotification notification = storage.getSavedNotification(id);
if(notification == null) {
continue;
}

LocalNotificationSchedule schedule = notification.getSchedule();
if(schedule != null) {
Date at = schedule.getAt();
if(at != null && at.before(new Date())) {
// modify the scheduled date in order to show notifications that would have been delivered while device was off.
long newDateTime = new Date().getTime() + 15 * 1000;
schedule.setAt(new Date(newDateTime));
notification.setSchedule(schedule);
updatedNotifications.add(notification);
}
}

notifications.add(notification);
}

if(updatedNotifications.size() > 0){
storage.appendNotifications(updatedNotifications);
}

CapConfig config = new CapConfig(context.getAssets(), null);
LocalNotificationManager localNotificationManager = new LocalNotificationManager(storage, null, context, config);

localNotificationManager.schedule(null, notifications);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@
import android.content.Context;
import android.content.SharedPreferences;

import com.getcapacitor.JSObject;

import org.json.JSONException;
import org.json.JSONObject;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -31,13 +38,12 @@ public NotificationStorage(Context context) {
/**
* Persist the id of currently scheduled notification
*/
public void appendNotificationIds(List<LocalNotification> localNotifications) {
public void appendNotifications(List<LocalNotification> localNotifications) {
SharedPreferences storage = getStorage(NOTIFICATION_STORE_ID);
SharedPreferences.Editor editor = storage.edit();
long creationTime = new Date().getTime();
for (LocalNotification request : localNotifications) {
String key = request.getId().toString();
editor.putLong(key, creationTime);
editor.putString(key, request.getSource());
}
editor.apply();
}
Expand All @@ -51,6 +57,40 @@ public List<String> getSavedNotificationIds() {
return new ArrayList<>();
}

public JSObject getSavedNotificationAsJSObject(String key) {
SharedPreferences storage = getStorage(NOTIFICATION_STORE_ID);
String notificationString = storage.getString(key, null);

if(notificationString == null){
return null;
}

JSObject jsNotification;
try {
jsNotification = new JSObject(notificationString);
} catch (JSONException ex) {
return null;
}

return jsNotification;
}

public LocalNotification getSavedNotification(String key) {
JSObject jsNotification = getSavedNotificationAsJSObject(key);
if(jsNotification == null) {
return null;
}

LocalNotification notification;
try {
notification = LocalNotification.buildNotificationFromJSObject(jsNotification);
} catch (ParseException ex) {
return null;
}

return notification;
}

/**
* Remove the stored notifications
*/
Expand Down

0 comments on commit 2a39a7d

Please sign in to comment.