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

fix(android): restore local notifications after device reboot #3027

Merged
merged 6 commits into from
Jul 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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