Skip to content

Commit 2a39a7d

Browse files
authored
fix(android): restore local notifications after device reboot (#3027)
1 parent 4e8b59e commit 2a39a7d

File tree

6 files changed

+145
-24
lines changed

6 files changed

+145
-24
lines changed

android/capacitor/src/main/AndroidManifest.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,15 @@
1414
<action android:name="com.google.firebase.MESSAGING_EVENT" />
1515
</intent-filter>
1616
</service>
17+
<receiver android:name="com.getcapacitor.plugin.notification.LocalNotificationRestoreReceiver" android:directBootAware="true" android:exported="false">
18+
<intent-filter>
19+
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED"/>
20+
<action android:name="android.intent.action.BOOT_COMPLETED"/>
21+
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
22+
</intent-filter>
23+
</receiver>
1724
</application>
25+
26+
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
27+
<uses-permission android:name="android.permission.WAKE_LOCK"/>
1828
</manifest>

android/capacitor/src/main/java/com/getcapacitor/plugin/LocalNotifications.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public void schedule(PluginCall call) {
7474
}
7575
JSONArray ids = manager.schedule(call, localNotifications);
7676
if (ids != null) {
77-
notificationStorage.appendNotificationIds(localNotifications);
77+
notificationStorage.appendNotifications(localNotifications);
7878
JSObject result = new JSObject();
7979
JSArray jsArray = new JSArray();
8080
for (int i=0; i < ids.length(); i++) {

android/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotification.java

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -186,31 +186,37 @@ public static List<LocalNotification> buildNotificationList(PluginCall call) {
186186
call.error("Invalid JSON object sent to NotificationPlugin", e);
187187
return null;
188188
}
189-
LocalNotification activeLocalNotification = new LocalNotification();
190-
activeLocalNotification.setSource(notification.toString());
191-
activeLocalNotification.setId(notification.getInteger("id"));
192-
activeLocalNotification.setBody(notification.getString("body"));
193-
activeLocalNotification.setActionTypeId(notification.getString("actionTypeId"));
194-
activeLocalNotification.setGroup(notification.getString("group"));
195-
activeLocalNotification.setSound(notification.getString("sound"));
196-
activeLocalNotification.setTitle(notification.getString("title"));
197-
activeLocalNotification.setSmallIcon(notification.getString("smallIcon"));
198-
activeLocalNotification.setIconColor(notification.getString("iconColor"));
199-
activeLocalNotification.setAttachments(LocalNotificationAttachment.getAttachments(notification));
200-
activeLocalNotification.setGroupSummary(notification.getBoolean("groupSummary", false));
201-
activeLocalNotification.setChannelId(notification.getString("channelId"));
189+
202190
try {
203-
activeLocalNotification.setSchedule(new LocalNotificationSchedule(notification));
191+
LocalNotification activeLocalNotification = buildNotificationFromJSObject(notification);
192+
resultLocalNotifications.add(activeLocalNotification);
204193
} catch (ParseException e) {
205194
call.error("Invalid date format sent to Notification plugin", e);
206195
return null;
207196
}
208-
activeLocalNotification.setExtra(notification.getJSObject("extra"));
209-
resultLocalNotifications.add(activeLocalNotification);
210197
}
211198
return resultLocalNotifications;
212199
}
213200

201+
public static LocalNotification buildNotificationFromJSObject(JSObject jsonObject) throws ParseException {
202+
LocalNotification localNotification = new LocalNotification();
203+
localNotification.setSource(jsonObject.toString());
204+
localNotification.setId(jsonObject.getInteger("id"));
205+
localNotification.setBody(jsonObject.getString("body"));
206+
localNotification.setActionTypeId(jsonObject.getString("actionTypeId"));
207+
localNotification.setGroup(jsonObject.getString("group"));
208+
localNotification.setSound(jsonObject.getString("sound"));
209+
localNotification.setTitle(jsonObject.getString("title"));
210+
localNotification.setSmallIcon(jsonObject.getString("smallIcon"));
211+
localNotification.setIconColor(jsonObject.getString("iconColor"));
212+
localNotification.setAttachments(LocalNotificationAttachment.getAttachments(jsonObject));
213+
localNotification.setGroupSummary(jsonObject.getBoolean("groupSummary", false));
214+
localNotification.setChannelId(jsonObject.getString("channelId"));
215+
localNotification.setSchedule(new LocalNotificationSchedule(jsonObject));
216+
localNotification.setExtra(jsonObject.getJSObject("extra"));
217+
218+
return localNotification;
219+
}
214220

215221
public static List<Integer> getLocalNotificationPendingList(PluginCall call) {
216222
List<JSONObject> notifications = null;

android/capacitor/src/main/java/com/getcapacitor/plugin/notification/LocalNotificationManager.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,13 +137,17 @@ public JSONArray schedule(PluginCall call, List<LocalNotification> localNotifica
137137

138138
boolean notificationsEnabled = notificationManager.areNotificationsEnabled();
139139
if (!notificationsEnabled) {
140-
call.error("Notifications not enabled on this device");
140+
if(call != null){
141+
call.error("Notifications not enabled on this device");
142+
}
141143
return null;
142144
}
143145
for (LocalNotification localNotification : localNotifications) {
144146
Integer id = localNotification.getId();
145147
if (localNotification.getId() == null) {
146-
call.error("LocalNotification missing identifier");
148+
if(call != null) {
149+
call.error("LocalNotification missing identifier");
150+
}
147151
return null;
148152
}
149153
dismissVisibleNotification(id);
@@ -214,7 +218,9 @@ private void buildNotification(NotificationManagerCompat notificationManager, Lo
214218
try {
215219
mBuilder.setColor(Color.parseColor(iconColor));
216220
} catch (IllegalArgumentException ex) {
217-
call.error("Invalid color provided. Must be a hex string (ex: #ff0000");
221+
if(call != null) {
222+
call.error("Invalid color provided. Must be a hex string (ex: #ff0000");
223+
}
218224
return;
219225
}
220226
}
@@ -294,7 +300,6 @@ private Intent buildIntent(LocalNotification localNotification, String action) {
294300
* on a certain date "shape" (such as every first of the month)
295301
*/
296302
// TODO support different AlarmManager.RTC modes depending on priority
297-
// TODO restore alarm on device shutdown (requires persistence)
298303
private void triggerScheduledNotification(Notification notification, LocalNotification request) {
299304
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
300305
LocalNotificationSchedule schedule = request.getSchedule();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package com.getcapacitor.plugin.notification;
2+
3+
import android.content.BroadcastReceiver;
4+
import android.content.Context;
5+
import android.content.Intent;
6+
import android.os.UserManager;
7+
8+
import com.getcapacitor.CapConfig;
9+
10+
import java.util.ArrayList;
11+
import java.util.Date;
12+
import java.util.List;
13+
14+
import static android.os.Build.VERSION.SDK_INT;
15+
16+
public class LocalNotificationRestoreReceiver extends BroadcastReceiver {
17+
18+
@Override
19+
public void onReceive(Context context, Intent intent) {
20+
if (SDK_INT >= 24) {
21+
UserManager um = context.getSystemService(UserManager.class);
22+
if (um == null || !um.isUserUnlocked()) return;
23+
}
24+
25+
NotificationStorage storage = new NotificationStorage(context);
26+
List<String> ids = storage.getSavedNotificationIds();
27+
28+
ArrayList<LocalNotification> notifications = new ArrayList<>(ids.size());
29+
ArrayList<LocalNotification> updatedNotifications = new ArrayList<>();
30+
for (String id : ids) {
31+
LocalNotification notification = storage.getSavedNotification(id);
32+
if(notification == null) {
33+
continue;
34+
}
35+
36+
LocalNotificationSchedule schedule = notification.getSchedule();
37+
if(schedule != null) {
38+
Date at = schedule.getAt();
39+
if(at != null && at.before(new Date())) {
40+
// modify the scheduled date in order to show notifications that would have been delivered while device was off.
41+
long newDateTime = new Date().getTime() + 15 * 1000;
42+
schedule.setAt(new Date(newDateTime));
43+
notification.setSchedule(schedule);
44+
updatedNotifications.add(notification);
45+
}
46+
}
47+
48+
notifications.add(notification);
49+
}
50+
51+
if(updatedNotifications.size() > 0){
52+
storage.appendNotifications(updatedNotifications);
53+
}
54+
55+
CapConfig config = new CapConfig(context.getAssets(), null);
56+
LocalNotificationManager localNotificationManager = new LocalNotificationManager(storage, null, context, config);
57+
58+
localNotificationManager.schedule(null, notifications);
59+
}
60+
}

android/capacitor/src/main/java/com/getcapacitor/plugin/notification/NotificationStorage.java

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,14 @@
33
import android.content.Context;
44
import android.content.SharedPreferences;
55

6+
import com.getcapacitor.JSObject;
7+
8+
import org.json.JSONException;
9+
import org.json.JSONObject;
10+
11+
import java.text.ParseException;
612
import java.util.ArrayList;
13+
import java.util.Collection;
714
import java.util.Date;
815
import java.util.List;
916
import java.util.Map;
@@ -31,13 +38,12 @@ public NotificationStorage(Context context) {
3138
/**
3239
* Persist the id of currently scheduled notification
3340
*/
34-
public void appendNotificationIds(List<LocalNotification> localNotifications) {
41+
public void appendNotifications(List<LocalNotification> localNotifications) {
3542
SharedPreferences storage = getStorage(NOTIFICATION_STORE_ID);
3643
SharedPreferences.Editor editor = storage.edit();
37-
long creationTime = new Date().getTime();
3844
for (LocalNotification request : localNotifications) {
3945
String key = request.getId().toString();
40-
editor.putLong(key, creationTime);
46+
editor.putString(key, request.getSource());
4147
}
4248
editor.apply();
4349
}
@@ -51,6 +57,40 @@ public List<String> getSavedNotificationIds() {
5157
return new ArrayList<>();
5258
}
5359

60+
public JSObject getSavedNotificationAsJSObject(String key) {
61+
SharedPreferences storage = getStorage(NOTIFICATION_STORE_ID);
62+
String notificationString = storage.getString(key, null);
63+
64+
if(notificationString == null){
65+
return null;
66+
}
67+
68+
JSObject jsNotification;
69+
try {
70+
jsNotification = new JSObject(notificationString);
71+
} catch (JSONException ex) {
72+
return null;
73+
}
74+
75+
return jsNotification;
76+
}
77+
78+
public LocalNotification getSavedNotification(String key) {
79+
JSObject jsNotification = getSavedNotificationAsJSObject(key);
80+
if(jsNotification == null) {
81+
return null;
82+
}
83+
84+
LocalNotification notification;
85+
try {
86+
notification = LocalNotification.buildNotificationFromJSObject(jsNotification);
87+
} catch (ParseException ex) {
88+
return null;
89+
}
90+
91+
return notification;
92+
}
93+
5494
/**
5595
* Remove the stored notifications
5696
*/

0 commit comments

Comments
 (0)