cordova-plugin-local-notifications-continued 1.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/FUNDING.yml +1 -0
- package/.github/ISSUE_TEMPLATE/report_issue.yml +126 -0
- package/CHANGELOG.md +358 -0
- package/LICENSE +202 -0
- package/README.md +1531 -0
- package/images/android-actions-with-input-clicked.png +0 -0
- package/images/android-actions-with-input-not-clicked.png +0 -0
- package/images/android-actions.png +0 -0
- package/images/android-alarms-and-reminders-in-app-settings.png +0 -0
- package/images/android-alarms-and-reminders-setting.png +0 -0
- package/images/android-app-hibernation-notification.png +0 -0
- package/images/android-app-hibernation-settings-android-12.png +0 -0
- package/images/android-app-hibernation-settings-android-13-14.png +0 -0
- package/images/android-app-hibernation-settings-android-15.png +0 -0
- package/images/android-attachments-image-folded.png +0 -0
- package/images/android-attachments-image-unfolded.png +0 -0
- package/images/android-chat.png +0 -0
- package/images/android-icon.svg +19 -0
- package/images/android-inbox.png +0 -0
- package/images/android-notification-example.png +0 -0
- package/images/android-progress.png +0 -0
- package/images/android-request-permission.png +0 -0
- package/images/android-stack.png +0 -0
- package/images/apple-icon.svg +1 -0
- package/images/cordova-app-notified-logo.png +0 -0
- package/images/ios-actions-with-input.png +0 -0
- package/images/ios-actions.png +0 -0
- package/images/ios-attachments-image-folded.png +0 -0
- package/images/ios-attachments-image-unfolded.png +0 -0
- package/images/ios-notification.png +0 -0
- package/images/ios-request-permission.png +0 -0
- package/images/logo.png +0 -0
- package/package.json +53 -0
- package/plugin.xml +266 -0
- package/src/android/ClickActivity.java +72 -0
- package/src/android/LocalNotification.java +779 -0
- package/src/android/Manager.java +327 -0
- package/src/android/Notification.java +844 -0
- package/src/android/Options.java +956 -0
- package/src/android/OptionsTrigger.java +104 -0
- package/src/android/action/Action.java +214 -0
- package/src/android/action/ActionGroup.java +135 -0
- package/src/android/build/localnotification.gradle +29 -0
- package/src/android/receiver/ClearReceiver.java +61 -0
- package/src/android/receiver/RestoreReceiver.java +70 -0
- package/src/android/receiver/TriggerReceiver.java +70 -0
- package/src/android/trigger/TriggerHandler.java +195 -0
- package/src/android/trigger/TriggerHandlerAt.java +63 -0
- package/src/android/trigger/TriggerHandlerEvery.java +257 -0
- package/src/android/trigger/TriggerHandlerIn.java +69 -0
- package/src/android/util/AssetUtil.java +385 -0
- package/src/android/util/CallbackContextUtil.java +88 -0
- package/src/android/util/PluginFileProvider.java +34 -0
- package/src/android/xml/shared_files_provider_paths.xml +38 -0
- package/src/ios/APPLocalNotification.h +56 -0
- package/src/ios/APPLocalNotification.m +650 -0
- package/src/ios/APPNotificationCategory.h +28 -0
- package/src/ios/APPNotificationCategory.m +107 -0
- package/src/ios/APPNotificationContent.h +32 -0
- package/src/ios/APPNotificationContent.m +122 -0
- package/src/ios/APPNotificationOptions.h +42 -0
- package/src/ios/APPNotificationOptions.m +689 -0
- package/src/ios/UNNotificationRequest+APPLocalNotification.h +32 -0
- package/src/ios/UNNotificationRequest+APPLocalNotification.m +100 -0
- package/src/ios/UNUserNotificationCenter+APPLocalNotification.h +60 -0
- package/src/ios/UNUserNotificationCenter+APPLocalNotification.m +344 -0
- package/www/local-notification.js +1104 -0
|
@@ -0,0 +1,844 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Apache 2.0 License
|
|
3
|
+
*
|
|
4
|
+
* Copyright (c) Sebastian Katzer 2017
|
|
5
|
+
* Copyright (c) Manuel Beck 2024
|
|
6
|
+
*
|
|
7
|
+
* This file contains Original Code and/or Modifications of Original Code
|
|
8
|
+
* as defined in and that are subject to the Apache License
|
|
9
|
+
* Version 2.0 (the 'License'). You may not use this file except in
|
|
10
|
+
* compliance with the License. Please obtain a copy of the License at
|
|
11
|
+
* http://opensource.org/licenses/Apache-2.0/ and read it before using this
|
|
12
|
+
* file.
|
|
13
|
+
*
|
|
14
|
+
* The Original Code and all software distributed under the License are
|
|
15
|
+
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
|
|
16
|
+
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
|
|
17
|
+
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
|
|
19
|
+
* Please see the License for the specific language governing rights and
|
|
20
|
+
* limitations under the License.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
package de.appplant.cordova.plugin.localnotification;
|
|
24
|
+
|
|
25
|
+
import android.app.AlarmManager;
|
|
26
|
+
import android.app.PendingIntent;
|
|
27
|
+
import android.content.BroadcastReceiver;
|
|
28
|
+
import android.content.Context;
|
|
29
|
+
import android.content.Intent;
|
|
30
|
+
import android.content.SharedPreferences;
|
|
31
|
+
import android.graphics.Bitmap;
|
|
32
|
+
import android.net.Uri;
|
|
33
|
+
import android.os.Build;
|
|
34
|
+
import android.os.Bundle;
|
|
35
|
+
import android.os.PowerManager;
|
|
36
|
+
import android.service.notification.StatusBarNotification;
|
|
37
|
+
import androidx.core.app.NotificationCompat;
|
|
38
|
+
import androidx.core.app.NotificationCompat.MessagingStyle;
|
|
39
|
+
import androidx.core.app.NotificationManagerCompat;
|
|
40
|
+
import androidx.collection.ArraySet;
|
|
41
|
+
import androidx.core.util.Pair;
|
|
42
|
+
import android.util.Log;
|
|
43
|
+
import android.util.SparseArray;
|
|
44
|
+
|
|
45
|
+
import org.json.JSONException;
|
|
46
|
+
import org.json.JSONObject;
|
|
47
|
+
|
|
48
|
+
import java.util.ArrayList;
|
|
49
|
+
import java.util.Date;
|
|
50
|
+
import java.util.Iterator;
|
|
51
|
+
import java.util.List;
|
|
52
|
+
import java.util.Set;
|
|
53
|
+
|
|
54
|
+
import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
|
|
55
|
+
import static android.os.Build.VERSION.SDK_INT;
|
|
56
|
+
|
|
57
|
+
import de.appplant.cordova.plugin.localnotification.action.Action;
|
|
58
|
+
import de.appplant.cordova.plugin.localnotification.action.ActionGroup;
|
|
59
|
+
import de.appplant.cordova.plugin.localnotification.receiver.ClearReceiver;
|
|
60
|
+
import de.appplant.cordova.plugin.localnotification.receiver.TriggerReceiver;
|
|
61
|
+
import de.appplant.cordova.plugin.localnotification.trigger.TriggerHandler;
|
|
62
|
+
import de.appplant.cordova.plugin.localnotification.trigger.TriggerHandlerAt;
|
|
63
|
+
import de.appplant.cordova.plugin.localnotification.trigger.TriggerHandlerIn;
|
|
64
|
+
import de.appplant.cordova.plugin.localnotification.trigger.TriggerHandlerEvery;
|
|
65
|
+
import de.appplant.cordova.plugin.localnotification.util.AssetUtil;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Wrapper class around OS notification class. Handles basic operations
|
|
69
|
+
* like show, delete, cancel for a single local notification instance.
|
|
70
|
+
*/
|
|
71
|
+
public class Notification {
|
|
72
|
+
|
|
73
|
+
private static final String TAG = "Notification";
|
|
74
|
+
|
|
75
|
+
// Used to differ notifications by their life cycle state
|
|
76
|
+
public enum Type {
|
|
77
|
+
ALL, SCHEDULED, TRIGGERED
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Extra key for the id
|
|
81
|
+
public static String EXTRA_ID = "NOTIFICATION_ID";
|
|
82
|
+
|
|
83
|
+
// Application context passed by constructor
|
|
84
|
+
private Context context;
|
|
85
|
+
|
|
86
|
+
// Notification options passed by JS
|
|
87
|
+
private Options options;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Trigger handler for trigger.at/in/every
|
|
91
|
+
*/
|
|
92
|
+
private TriggerHandler triggerhandler;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Constructor
|
|
96
|
+
* @param context Application context.
|
|
97
|
+
* @param options Parsed notification options.
|
|
98
|
+
* @param builder Pre-configured notification builder.
|
|
99
|
+
*/
|
|
100
|
+
public Notification(Context context, JSONObject options) {
|
|
101
|
+
this.context = context;
|
|
102
|
+
this.options = new Options(context, options);
|
|
103
|
+
OptionsTrigger optionsTrigger = this.options.getOptionsTrigger();
|
|
104
|
+
|
|
105
|
+
// Handle trigger.at
|
|
106
|
+
// Example: trigger: { at: new Date(2017, 10, 27, 15) }
|
|
107
|
+
if (optionsTrigger.getJSON().has("at")) {
|
|
108
|
+
this.triggerhandler = new TriggerHandlerAt(this.options);
|
|
109
|
+
|
|
110
|
+
// Handle trigger.in
|
|
111
|
+
// Example: trigger: { in: 1, unit: 'hour' }
|
|
112
|
+
} else if (optionsTrigger.getJSON().has("in")) {
|
|
113
|
+
this.triggerhandler = new TriggerHandlerIn(this.options);
|
|
114
|
+
|
|
115
|
+
// Handle trigger.every
|
|
116
|
+
// Example:
|
|
117
|
+
// trigger: { every: 'day', count: 5 }
|
|
118
|
+
// trigger: { every: { month: 10, day: 27, hour: 9, minute: 0 } }
|
|
119
|
+
} else if (optionsTrigger.getJSON().has("every")) {
|
|
120
|
+
this.triggerhandler = new TriggerHandlerEvery(this.options);
|
|
121
|
+
|
|
122
|
+
} else {
|
|
123
|
+
throw new IllegalArgumentException("Trigger not property set");
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Get application context.
|
|
129
|
+
*/
|
|
130
|
+
public Context getContext() {
|
|
131
|
+
return context;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get notification options.
|
|
136
|
+
*/
|
|
137
|
+
public Options getOptions() {
|
|
138
|
+
return options;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Get notification ID.
|
|
143
|
+
*/
|
|
144
|
+
public int getId() {
|
|
145
|
+
return options.getId();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* The action identifier in the form NOTIFICATION_ID{notification-id}-{occurrence} like "NOTIFICATION_ID1173-1"
|
|
150
|
+
*/
|
|
151
|
+
String getIntentActionIdentifier() {
|
|
152
|
+
return Manager.PREF_KEY_ID + options.getId();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Notification type can be one of triggered or scheduled.
|
|
158
|
+
*/
|
|
159
|
+
public Type getType() {
|
|
160
|
+
for (StatusBarNotification statusBarNotification : new Manager(context).getActiveNotifications()) {
|
|
161
|
+
if (statusBarNotification.getId() == getId()) {
|
|
162
|
+
return Type.TRIGGERED;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return Type.SCHEDULED;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Schedules the first or next occurrence of the notification. This has also to be called
|
|
171
|
+
* when the notification is scheduled for the first time, so the first occurrence
|
|
172
|
+
* can be calculated.
|
|
173
|
+
* @return true if the notification was scheduled, false otherwise, which can happen,
|
|
174
|
+
* if the notification was triggered directly, an error occured or there is no next trigger date.
|
|
175
|
+
*/
|
|
176
|
+
public boolean scheduleNext() {
|
|
177
|
+
Date triggerDate = triggerhandler.getNextTriggerDate();
|
|
178
|
+
|
|
179
|
+
// No next trigger date available, all triggers are done
|
|
180
|
+
// The notification will not be removed from SharedPreferences.
|
|
181
|
+
// This will always do the ClearReceiver and ClickActivity
|
|
182
|
+
if (triggerDate == null) {
|
|
183
|
+
Log.d(TAG, "No next trigger date available" +
|
|
184
|
+
", notificationId=" + options.getId() +
|
|
185
|
+
", occurrence=" + triggerhandler.getOccurrence() +
|
|
186
|
+
", triggerDate=" + triggerhandler.getTriggerDate() +
|
|
187
|
+
", triggerBaseDate=" + triggerhandler.getBaseDate() +
|
|
188
|
+
", options=" + options);
|
|
189
|
+
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return schedule();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Schedules the notification with the current state of the dateTrigger.
|
|
198
|
+
* If the triggerDate is in the past, the notification will be triggered directly.
|
|
199
|
+
* @return true if the notification was scheduled, false otherwise, which can happen,
|
|
200
|
+
* if the notification was triggered directly or an error occured.
|
|
201
|
+
*/
|
|
202
|
+
public boolean schedule() {
|
|
203
|
+
Date triggerDate = triggerhandler.getTriggerDate();
|
|
204
|
+
|
|
205
|
+
if (triggerDate == null) {
|
|
206
|
+
Log.e(TAG, "schedule was wrongly called with triggerDate = null, options=" + options);
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Create channel if not exists
|
|
211
|
+
Manager.createChannel(context, options);
|
|
212
|
+
|
|
213
|
+
// Store notification data for restoration
|
|
214
|
+
// Needed for ClickActivity and ClearReceiver
|
|
215
|
+
storeInSharedPreferences();
|
|
216
|
+
|
|
217
|
+
// Date is in the past, show directly
|
|
218
|
+
if (!triggerDate.after(new Date())) {
|
|
219
|
+
show(false);
|
|
220
|
+
scheduleNext();
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
boolean canScheduleExactAlarms = Manager.canScheduleExactAlarms(context);
|
|
225
|
+
|
|
226
|
+
// Intent when the alarm goes off
|
|
227
|
+
Intent alarmFiresIntent = new Intent(context, TriggerReceiver.class)
|
|
228
|
+
// action identifier for the intent like "NOTIFICATION_ID1173"
|
|
229
|
+
.setAction(getIntentActionIdentifier())
|
|
230
|
+
// Notification-ID
|
|
231
|
+
.putExtra(EXTRA_ID, options.getId());
|
|
232
|
+
|
|
233
|
+
Log.d(TAG, "Schedule notification" +
|
|
234
|
+
", notificationId: " + options.getId() +
|
|
235
|
+
", canScheduleExactAlarms: " + canScheduleExactAlarms +
|
|
236
|
+
", intentAction=" + alarmFiresIntent.getAction() +
|
|
237
|
+
", occurrence: " + triggerhandler.getOccurrence() +
|
|
238
|
+
", triggerDate: " + triggerDate +
|
|
239
|
+
", options=" + options);
|
|
240
|
+
|
|
241
|
+
// AlarmManager.set: If there is already an alarm scheduled for the same IntentSender,
|
|
242
|
+
// that previous alarm will first be canceled.
|
|
243
|
+
PendingIntent alarmFiresPendingIntent = PendingIntent.getBroadcast(context, 0, alarmFiresIntent, PendingIntent.FLAG_IMMUTABLE);
|
|
244
|
+
|
|
245
|
+
// A maximum of 500 alarms can be scheduled, catch exception
|
|
246
|
+
try {
|
|
247
|
+
// Execute alarm even when the system is in low-power idle (a.k.a. doze) modes.
|
|
248
|
+
if (options.isAndroidAllowWhileIdle()) {
|
|
249
|
+
if (canScheduleExactAlarms) {
|
|
250
|
+
getAlarmManager().setExactAndAllowWhileIdle(
|
|
251
|
+
options.getAndroidAlarmType(), triggerDate.getTime(), alarmFiresPendingIntent);
|
|
252
|
+
} else {
|
|
253
|
+
getAlarmManager().setAndAllowWhileIdle(
|
|
254
|
+
options.getAndroidAlarmType(), triggerDate.getTime(), alarmFiresPendingIntent);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Execute alarm by RTC or RTC_WAKEUP
|
|
258
|
+
} else {
|
|
259
|
+
if (canScheduleExactAlarms) {
|
|
260
|
+
getAlarmManager().setExact(
|
|
261
|
+
options.getAndroidAlarmType(), triggerDate.getTime(), alarmFiresPendingIntent);
|
|
262
|
+
} else {
|
|
263
|
+
getAlarmManager().set(
|
|
264
|
+
options.getAndroidAlarmType(), triggerDate.getTime(), alarmFiresPendingIntent);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return true;
|
|
269
|
+
|
|
270
|
+
// Maximum 500 alarms can be scheduled.
|
|
271
|
+
// If more are scheduled, an exception will be thrown.
|
|
272
|
+
} catch (Exception exception) {
|
|
273
|
+
Log.d(TAG, "Exception occurred during scheduling notification", exception);
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Present the notification to the user
|
|
280
|
+
*/
|
|
281
|
+
public void show(boolean isUpdate) {
|
|
282
|
+
Log.d(TAG, "Show notification, options=" + options + ", isUpdate=" + isUpdate);
|
|
283
|
+
|
|
284
|
+
NotificationCompat.Builder builder = getBuilder(isUpdate);
|
|
285
|
+
|
|
286
|
+
// Notification should be silent
|
|
287
|
+
if (builder == null) {
|
|
288
|
+
Log.d(TAG, "Notification is silent, don't show it");
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Turn the screen on
|
|
293
|
+
PowerManager.WakeLock wakeLock = options.isAndroidWakeUpScreen() ? Manager.wakeUpScreen(context) : null;
|
|
294
|
+
|
|
295
|
+
NotificationManagerCompat.from(context).notify(
|
|
296
|
+
LocalNotification.getAppName(context), options.getId(), builder.build());
|
|
297
|
+
|
|
298
|
+
// The wake lock has to be released after the notification was shown
|
|
299
|
+
if (wakeLock != null) wakeLock.release();
|
|
300
|
+
|
|
301
|
+
// Fire trigger event if it's not an update and the app is running
|
|
302
|
+
if (!isUpdate && LocalNotification.isAppRunning()) {
|
|
303
|
+
LocalNotification.fireEvent("trigger", this);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Update the notification properties.
|
|
309
|
+
* @param updates The properties to update.
|
|
310
|
+
*/
|
|
311
|
+
public void update(JSONObject updates) {
|
|
312
|
+
Log.d(TAG, "Update notification, options=" + options + ", updates=" + updates);
|
|
313
|
+
|
|
314
|
+
// Update options of notification
|
|
315
|
+
mergeJSONObjects(updates);
|
|
316
|
+
|
|
317
|
+
// Store notification data
|
|
318
|
+
storeInSharedPreferences();
|
|
319
|
+
|
|
320
|
+
// Update triggered notification in status bar
|
|
321
|
+
if (getType() == Type.TRIGGERED) show(true);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Handle when the notification was clicked.
|
|
326
|
+
*/
|
|
327
|
+
public void handleClick() {
|
|
328
|
+
Log.d(TAG, "Handle click, options=" + options);
|
|
329
|
+
|
|
330
|
+
// Fire notification click event to JS
|
|
331
|
+
LocalNotification.fireEvent("click", this);
|
|
332
|
+
|
|
333
|
+
// Clear notification from statusbar if it should not be ongoing
|
|
334
|
+
// This will also remove the notification from the SharedPreferences
|
|
335
|
+
// if it is the last one
|
|
336
|
+
if (!options.isAndroidOngoing()) clear();
|
|
337
|
+
|
|
338
|
+
// Launch the app if required
|
|
339
|
+
if (options.isLaunch()) LocalNotification.launchApp(context);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
public void handleActionClick(Intent intent, String actionId) {
|
|
343
|
+
options.getActionsGroup().getActionById(actionId).handleClick(intent, this);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Clears the notification from Statusbar. If the notification was the last one,
|
|
348
|
+
* the notification will be removed from SharedPreferences.
|
|
349
|
+
* The WebView will receive a clear event, if the app is running.
|
|
350
|
+
*/
|
|
351
|
+
public void clear() {
|
|
352
|
+
Log.d(TAG, "Clear notification, options=" + options);
|
|
353
|
+
|
|
354
|
+
// Clear the notification from the statusbar if posted
|
|
355
|
+
NotificationManagerCompat.from(context).cancel(LocalNotification.getAppName(context), getId());
|
|
356
|
+
|
|
357
|
+
// If it is the last occurrence remove the notification from SharedPreferences
|
|
358
|
+
if (triggerhandler.isLastOccurrence()) {
|
|
359
|
+
removeFromSharedPreferences();
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Inform WebView about the clearing
|
|
363
|
+
if (LocalNotification.isAppRunning()) LocalNotification.fireEvent("clear", this);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Cancels the notification and removes it from SharedPreferences.
|
|
368
|
+
* The WebView will receive a cancel event, if the app is running.
|
|
369
|
+
*/
|
|
370
|
+
public void cancel() {
|
|
371
|
+
Log.d(TAG, "Cancel notification, options=" + options);
|
|
372
|
+
|
|
373
|
+
cancelScheduledAlarm();
|
|
374
|
+
|
|
375
|
+
// Remove saved notification data from the app
|
|
376
|
+
removeFromSharedPreferences();
|
|
377
|
+
|
|
378
|
+
// Clear the notification from the status bar if posted
|
|
379
|
+
NotificationManagerCompat.from(context).cancel(LocalNotification.getAppName(context), getId());
|
|
380
|
+
|
|
381
|
+
// Inform WebView about the canceling
|
|
382
|
+
if (LocalNotification.isAppRunning()) LocalNotification.fireEvent("cancel", this);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Cancel all alarms for this notification. If the notification is repeating, it can
|
|
387
|
+
* have multiple alarms.
|
|
388
|
+
*/
|
|
389
|
+
private void cancelScheduledAlarm() {
|
|
390
|
+
String intentActionId = getIntentActionIdentifier();
|
|
391
|
+
Log.d(TAG, "Cancel PendingIntent, intentActionId=" + intentActionId);
|
|
392
|
+
|
|
393
|
+
// Create similar PendingIntent to cancel the alarm
|
|
394
|
+
PendingIntent pendingIntent = PendingIntent.getBroadcast(
|
|
395
|
+
context, 0,
|
|
396
|
+
// The intent have to be build with the same context, class and action
|
|
397
|
+
new Intent(context, TriggerReceiver.class).setAction(intentActionId),
|
|
398
|
+
// If the PendingIntent could not be found, null will be returned (configured by FLAG_NO_CREATE)
|
|
399
|
+
PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_NO_CREATE);
|
|
400
|
+
|
|
401
|
+
// PendingIntent could not be found
|
|
402
|
+
if (pendingIntent == null) {
|
|
403
|
+
Log.d(TAG, "Could not cancel PendingIntent, intentActionId=" + intentActionId + ", PendingIntent not found");
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Remove any alarms with a matching Intent. Any alarm, of any type, whose Intent matches this one
|
|
408
|
+
// (as defined by Intent#filterEquals), will be canceled.
|
|
409
|
+
// Intent#filterEquals: That is, if their action, data, type, identity, class, and categories are the same.
|
|
410
|
+
// This does not compare any extra data included in the intents.
|
|
411
|
+
getAlarmManager().cancel(pendingIntent);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Stores the information of this notification in the SharedPreferences.
|
|
416
|
+
* This will allow the application to restore the notification upon device reboot,
|
|
417
|
+
* app restart, retrieve notifications, etc.
|
|
418
|
+
*/
|
|
419
|
+
private void storeInSharedPreferences() {
|
|
420
|
+
Log.d(TAG, "Store notification in SharedPreferences" +
|
|
421
|
+
", notificationId=" + options.getId() +
|
|
422
|
+
", occurrence=" + triggerhandler.getOccurrence() +
|
|
423
|
+
", triggerDate=" + triggerhandler.getTriggerDate() +
|
|
424
|
+
", triggerBaseDate=" + triggerhandler.getBaseDate() +
|
|
425
|
+
", options=" + options);
|
|
426
|
+
|
|
427
|
+
Manager.getSharedPreferences(context).edit()
|
|
428
|
+
// options as JSON string
|
|
429
|
+
.putString(getSharedPreferencesKeyOptions(), options.toString())
|
|
430
|
+
// occurrence for restoration
|
|
431
|
+
.putInt(getSharedPreferencesKeyOccurrence(), triggerhandler.getOccurrence())
|
|
432
|
+
// trigger base date for restoration
|
|
433
|
+
.putLong(getSharedPreferencesKeyTriggerBaseDate(), triggerhandler.getBaseDate().getTime())
|
|
434
|
+
// calculated triggerDate for restoration
|
|
435
|
+
.putLong(getSharedPreferencesKeyTriggerDate(), triggerhandler.getTriggerDate().getTime())
|
|
436
|
+
.apply();
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Removes the notification data from the SharedPreferences.
|
|
441
|
+
*/
|
|
442
|
+
private void removeFromSharedPreferences() {
|
|
443
|
+
Log.d(TAG, "Remove notification from SharedPreferences" +
|
|
444
|
+
", notificationId=" + options.getId() +
|
|
445
|
+
", occurrence=" + triggerhandler.getOccurrence() +
|
|
446
|
+
", triggerDate=" + triggerhandler.getTriggerDate() +
|
|
447
|
+
", triggerBaseDate=" + triggerhandler.getBaseDate() +
|
|
448
|
+
", options=" + options);
|
|
449
|
+
|
|
450
|
+
Manager.getSharedPreferences(context).edit()
|
|
451
|
+
.remove(getSharedPreferencesKeyOptions())
|
|
452
|
+
.remove(getSharedPreferencesKeyOccurrence())
|
|
453
|
+
.remove(getSharedPreferencesKeyTriggerBaseDate())
|
|
454
|
+
.remove(getSharedPreferencesKeyTriggerDate())
|
|
455
|
+
.apply();
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Gets a stored notification from the {@link SharedPreferences} by id.
|
|
460
|
+
* If the notification does not exists, null will be returned.
|
|
461
|
+
*/
|
|
462
|
+
public static Notification getFromSharedPreferences(Context context, int notificationId) {
|
|
463
|
+
String optionsJSONString = Manager.getSharedPreferences(context).getString(
|
|
464
|
+
getSharedPreferencesKeyOptions(notificationId), null);
|
|
465
|
+
|
|
466
|
+
Log.d(TAG, "Restoring notification from SharedPreferences" +
|
|
467
|
+
", notificationId=" + notificationId +
|
|
468
|
+
", options=" + optionsJSONString);
|
|
469
|
+
|
|
470
|
+
// Notification does not exists
|
|
471
|
+
if (optionsJSONString == null) {
|
|
472
|
+
Log.w(TAG, "Could not restore notification from SharedPreferences, options are null" +
|
|
473
|
+
", notificationId=" + notificationId);
|
|
474
|
+
return null;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
try {
|
|
478
|
+
// Parse options string to JSONObject
|
|
479
|
+
Notification notification = new Notification(context, new JSONObject(optionsJSONString));
|
|
480
|
+
|
|
481
|
+
// Restore state of dateTrigger
|
|
482
|
+
// Get occurrence
|
|
483
|
+
int occurrence = Manager.getSharedPreferences(context).getInt(
|
|
484
|
+
notification.getSharedPreferencesKeyOccurrence(), 0);
|
|
485
|
+
|
|
486
|
+
// Get triger base date
|
|
487
|
+
long triggerBaseTime = Manager.getSharedPreferences(context).getLong(
|
|
488
|
+
notification.getSharedPreferencesKeyTriggerBaseDate(), 0);
|
|
489
|
+
|
|
490
|
+
// Get triggerDate
|
|
491
|
+
long triggerTime = Manager.getSharedPreferences(context).getLong(
|
|
492
|
+
notification.getSharedPreferencesKeyTriggerDate(), 0);
|
|
493
|
+
|
|
494
|
+
TriggerHandler triggerHandler = notification.getTriggerHandler();
|
|
495
|
+
|
|
496
|
+
// The saving of occurrence, triggerBaseDate and triggerDate exists since version 1.1.4
|
|
497
|
+
// Before only the options were saved
|
|
498
|
+
// Just caclulate the next trigger from the current time
|
|
499
|
+
// Durring the development of version 1.1.4, first only the occurrence was saved,
|
|
500
|
+
// later also the triggerBaseDate and triggerDate, so check this also
|
|
501
|
+
if (occurrence == 0 || triggerBaseTime == 0 || triggerTime == 0) {
|
|
502
|
+
triggerHandler.getNextTriggerDate();
|
|
503
|
+
|
|
504
|
+
// Restore the state of the trigger date
|
|
505
|
+
} else {
|
|
506
|
+
triggerHandler.restoreState(occurrence, new Date(triggerBaseTime), new Date(triggerTime));
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
Log.d(TAG, "Restored trigger date" +
|
|
510
|
+
", notificationId=" + notificationId +
|
|
511
|
+
", occurrence=" + triggerHandler.getOccurrence() +
|
|
512
|
+
", triggerDate=" + triggerHandler.getTriggerDate() +
|
|
513
|
+
", triggerBaseDate=" + triggerHandler.getBaseDate());
|
|
514
|
+
|
|
515
|
+
return notification;
|
|
516
|
+
} catch (JSONException exception) {
|
|
517
|
+
Log.e(TAG, "Could not parse stored notification options to JSON" +
|
|
518
|
+
", notificationId=" + notificationId +
|
|
519
|
+
", jsonString=" + optionsJSONString,
|
|
520
|
+
exception);
|
|
521
|
+
return null;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Update options
|
|
527
|
+
*/
|
|
528
|
+
private void mergeJSONObjects(JSONObject updates) {
|
|
529
|
+
Iterator keys = updates.keys();
|
|
530
|
+
|
|
531
|
+
while (keys.hasNext()) {
|
|
532
|
+
try {
|
|
533
|
+
String key = (String) keys.next();
|
|
534
|
+
options.getJSON().put(key, updates.opt(key));
|
|
535
|
+
} catch (JSONException jsonException) {
|
|
536
|
+
jsonException.printStackTrace();
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
public String getSharedPreferencesKeyOccurrence() {
|
|
542
|
+
return options.getId() + "_occurrence";
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
public String getSharedPreferencesKeyTriggerBaseDate() {
|
|
546
|
+
return options.getId() + "_triggerBaseDate";
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
public String getSharedPreferencesKeyTriggerDate() {
|
|
550
|
+
return options.getId() + "_triggerDate";
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Get the SharedPreferences key for the notification options.
|
|
555
|
+
* @return
|
|
556
|
+
*/
|
|
557
|
+
public String getSharedPreferencesKeyOptions() {
|
|
558
|
+
return "" + getSharedPreferencesKeyOptions(options.getId());
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Static version of {@link #getSharedPreferencesKeyOptions()}.
|
|
563
|
+
* To use, when there is no notification instance available.
|
|
564
|
+
* @param notificationId
|
|
565
|
+
* @return
|
|
566
|
+
*/
|
|
567
|
+
public static String getSharedPreferencesKeyOptions(int notificationId) {
|
|
568
|
+
return "" + notificationId;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
public TriggerHandler getTriggerHandler() {
|
|
572
|
+
return triggerhandler;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Alarm manager for the application.
|
|
577
|
+
*/
|
|
578
|
+
private AlarmManager getAlarmManager() {
|
|
579
|
+
return (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Creates a {@link NotificationCompat.Builder} from options
|
|
584
|
+
* @return The builder instance or null if the notification is silent.
|
|
585
|
+
*/
|
|
586
|
+
public NotificationCompat.Builder getBuilder(boolean isUpdate) {
|
|
587
|
+
if (options.isSilent()) return null;
|
|
588
|
+
|
|
589
|
+
Bundle extras = new Bundle();
|
|
590
|
+
extras.putInt(Notification.EXTRA_ID, options.getId());
|
|
591
|
+
|
|
592
|
+
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, options.getAndroidChannelId())
|
|
593
|
+
.setExtras(extras)
|
|
594
|
+
.setOnlyAlertOnce(options.isOnlyAlertOnce())
|
|
595
|
+
.setContentTitle(options.getTitle())
|
|
596
|
+
.setContentText(options.getText())
|
|
597
|
+
// Text that summarizes this notification for accessibility services.
|
|
598
|
+
// Since Android 5, this text is no longer shown on screen, but it is
|
|
599
|
+
// still useful to accessibility services (where it serves as an audible announcement
|
|
600
|
+
// of the notification's appearance).
|
|
601
|
+
.setTicker(options.getText())
|
|
602
|
+
// Since Android 8 shows as a badge count for Launchers that support badging
|
|
603
|
+
// prior to 8 it could be shown in the header
|
|
604
|
+
.setNumber(options.getBadgeNumber())
|
|
605
|
+
.setAutoCancel(options.isAndroidAutoCancel())
|
|
606
|
+
.setOngoing(options.isAndroidOngoing())
|
|
607
|
+
.setColor(options.getColor())
|
|
608
|
+
.setVisibility(options.getVisibility())
|
|
609
|
+
// Show the Notification when date
|
|
610
|
+
.setShowWhen(options.isAndroidShowWhen())
|
|
611
|
+
// Show the Notification#when field as a stopwatch. Instead of presenting when as a timestamp,
|
|
612
|
+
// the notification will show an automatically updating display of the minutes and seconds since
|
|
613
|
+
// when
|
|
614
|
+
.setUsesChronometer(options.isAndroidUsesChronometer())
|
|
615
|
+
.setGroup(options.getGroup())
|
|
616
|
+
.setGroupSummary(options.isGroupSummary());
|
|
617
|
+
|
|
618
|
+
// Specify the duration in milliseconds after which this notification should be canceled
|
|
619
|
+
if (options.getAndroidTimeoutAfter() >= 0) builder.setTimeoutAfter(options.getAndroidTimeoutAfter());
|
|
620
|
+
|
|
621
|
+
// Settings for Android older than 8
|
|
622
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
|
623
|
+
builder.setDefaults(options.getDefaults());
|
|
624
|
+
builder.setPriority(options.getPriorityByImportance());
|
|
625
|
+
builder.setLights(options.getLedColor(), options.getLedOn(), options.getLedOff());
|
|
626
|
+
|
|
627
|
+
Uri soundUri = options.getSoundUri();
|
|
628
|
+
|
|
629
|
+
// Set sound only, if the notification should not be updated
|
|
630
|
+
if (soundUri != Uri.EMPTY && !isUpdate) {
|
|
631
|
+
// Grant permission to the system to play the sound, needed in Android 7 and 8
|
|
632
|
+
Manager.grantUriPermission(context, soundUri);
|
|
633
|
+
builder.setSound(soundUri);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
if (options.getProgressBar() != null) {
|
|
638
|
+
builder.setProgress(
|
|
639
|
+
options.getProgressMaxValue(),
|
|
640
|
+
options.getProgressValue(),
|
|
641
|
+
options.isProgressIndeterminate());
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// Get smallIcon from resources only
|
|
645
|
+
//
|
|
646
|
+
// There exists also a setSmallIcon(IconCompat icon) method, but tested on Android 15, when multiple notifications with the
|
|
647
|
+
// same icon are posted, the app icon will be shown in statusbar instead of the small icon. This does not happen,
|
|
648
|
+
// when using the setSmallIcon(int icon) method.
|
|
649
|
+
builder.setSmallIcon(options.getSmallIcon());
|
|
650
|
+
|
|
651
|
+
Bitmap largeIcon = new AssetUtil(context).getBitmap(options.getAndroidLargeIcon());
|
|
652
|
+
|
|
653
|
+
if (largeIcon != null) {
|
|
654
|
+
if (options.getAndroidLargeIconType().equals(Options.LARGE_ICON_TYPE_CIRCLE)) {
|
|
655
|
+
largeIcon = AssetUtil.getCircleBitmap(largeIcon);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
builder.setLargeIcon(largeIcon);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
applyStyle(builder);
|
|
662
|
+
addActions(builder);
|
|
663
|
+
|
|
664
|
+
// Supply a PendingIntent to send when the notification is cleared by the user directly from the notification panel
|
|
665
|
+
setDeleteIntent(builder);
|
|
666
|
+
|
|
667
|
+
// Supply a PendingIntent to send when the notification is clicked
|
|
668
|
+
setContentIntent(builder);
|
|
669
|
+
|
|
670
|
+
return builder;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* Find out and set the notification style.
|
|
675
|
+
* @param builder Notification builder instance.
|
|
676
|
+
*/
|
|
677
|
+
private void applyStyle(NotificationCompat.Builder builder) {
|
|
678
|
+
if (applyMessagingStyle(builder)) return;
|
|
679
|
+
if (applyBigPictureStyle(builder)) return;
|
|
680
|
+
if (applyInboxStyle(builder)) return;
|
|
681
|
+
if (applyBigTextStyle(builder)) return;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* Apply messaging style
|
|
686
|
+
* @param builder Notification builder instance
|
|
687
|
+
* @return true if the messaging style was applied
|
|
688
|
+
*/
|
|
689
|
+
private boolean applyMessagingStyle(NotificationCompat.Builder builder) {
|
|
690
|
+
MessagingStyle.Message[] messages = options.getAndroidMessages();
|
|
691
|
+
if (messages == null) return false;
|
|
692
|
+
|
|
693
|
+
// Find if there is a notification already displayed with this ID.
|
|
694
|
+
StatusBarNotification activeNotification = new Manager(context).getActiveNotification(options.getId());
|
|
695
|
+
|
|
696
|
+
MessagingStyle style = activeNotification != null ?
|
|
697
|
+
// If the notification was already displayed, extract the MessagingStyle to add the message
|
|
698
|
+
MessagingStyle.extractMessagingStyleFromNotification(activeNotification.getNotification()) :
|
|
699
|
+
// No active notification, create a new style
|
|
700
|
+
new NotificationCompat.MessagingStyle("");
|
|
701
|
+
|
|
702
|
+
// Add the new messages to the style
|
|
703
|
+
for (MessagingStyle.Message message : messages) {
|
|
704
|
+
style.addMessage(message);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
String title = options.getTitle();
|
|
708
|
+
|
|
709
|
+
// Add the count of messages to the title if there is more than 1.
|
|
710
|
+
Integer messagesCount = style.getMessages().size();
|
|
711
|
+
|
|
712
|
+
if (messagesCount > 1 && options.getTitleCount() != null && !options.getTitleCount().trim().isEmpty()) {
|
|
713
|
+
title += " " + options.getTitleCount().replace("%n%", "" + messagesCount);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
style.setConversationTitle(title);
|
|
717
|
+
|
|
718
|
+
// Use the style.
|
|
719
|
+
builder.setStyle(style);
|
|
720
|
+
|
|
721
|
+
return true; // style applied
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* Apply big picture style. Only uses the first attachment.
|
|
726
|
+
* @param builder Notification builder instance
|
|
727
|
+
* @return true if the big picture style was applied
|
|
728
|
+
*/
|
|
729
|
+
private boolean applyBigPictureStyle(NotificationCompat.Builder builder) {
|
|
730
|
+
List<Bitmap> attachmentsPictures = options.getAttachments();
|
|
731
|
+
if (attachmentsPictures == null || attachmentsPictures.size() == 0) return false;
|
|
732
|
+
|
|
733
|
+
builder.setStyle(new NotificationCompat.BigPictureStyle(builder)
|
|
734
|
+
.setSummaryText(options.getSummary() != null ? options.getSummary() : options.getText())
|
|
735
|
+
.bigPicture(attachmentsPictures.get(0))
|
|
736
|
+
);
|
|
737
|
+
|
|
738
|
+
return true; // style applied
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* Apply inbox style
|
|
743
|
+
* @param builder Notification builder instance
|
|
744
|
+
* @return true if the inbox style was applied
|
|
745
|
+
*/
|
|
746
|
+
private boolean applyInboxStyle(NotificationCompat.Builder builder) {
|
|
747
|
+
if (!options.getText().contains("\n")) return false;
|
|
748
|
+
|
|
749
|
+
NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle(builder)
|
|
750
|
+
.setSummaryText(options.getSummary());
|
|
751
|
+
|
|
752
|
+
for (String line : options.getText().split("\n")) {
|
|
753
|
+
style.addLine(line);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
builder.setStyle(style);
|
|
757
|
+
return true; // style applied
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
/**
|
|
761
|
+
* Apply big text style
|
|
762
|
+
* @param builder Notification builder instance
|
|
763
|
+
* @return true if the big text style was applied
|
|
764
|
+
*/
|
|
765
|
+
private boolean applyBigTextStyle(NotificationCompat.Builder builder) {
|
|
766
|
+
if (options.getSummary() == null && options.getText().length() < 45) return false;
|
|
767
|
+
builder.setStyle(new NotificationCompat.BigTextStyle(builder)
|
|
768
|
+
.setSummaryText(options.getSummary())
|
|
769
|
+
.bigText(options.getText()));
|
|
770
|
+
return true; // style applied
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* Supply a PendingIntent to send when the notification is cleared by the user directly
|
|
775
|
+
* from the notification panel. For example, this intent is sent when the user clicks the
|
|
776
|
+
* "Clear all" button, or the individual "X" buttons on notifications.
|
|
777
|
+
* This intent is not sent when the application calls NotificationManager.cancel(int).
|
|
778
|
+
*/
|
|
779
|
+
private void setDeleteIntent(NotificationCompat.Builder builder) {
|
|
780
|
+
Intent intent = new Intent(context, ClearReceiver.class)
|
|
781
|
+
.setAction(options.getId().toString())
|
|
782
|
+
.putExtra(Notification.EXTRA_ID, options.getId());
|
|
783
|
+
|
|
784
|
+
builder.setDeleteIntent(PendingIntent.getBroadcast(
|
|
785
|
+
context, 0, intent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT));
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
/**
|
|
789
|
+
* Supply a PendingIntent to send when the notification is clicked.
|
|
790
|
+
* Will bring the app to foreground.
|
|
791
|
+
*/
|
|
792
|
+
private void setContentIntent(NotificationCompat.Builder builder) {
|
|
793
|
+
// Route content tap to a transparent activity to avoid trampoline and still run plugin logic
|
|
794
|
+
Intent clickIntent = new Intent(context, ClickActivity.class)
|
|
795
|
+
.putExtra(Notification.EXTRA_ID, options.getId())
|
|
796
|
+
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
|
797
|
+
|
|
798
|
+
PendingIntent contentPendingIntent = PendingIntent.getActivity(
|
|
799
|
+
context,
|
|
800
|
+
Manager.getRandomRequestCode(),
|
|
801
|
+
clickIntent,
|
|
802
|
+
PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
|
|
803
|
+
|
|
804
|
+
builder.setContentIntent(contentPendingIntent);
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
/**
|
|
808
|
+
* Add actions to the builder if there are any.
|
|
809
|
+
*/
|
|
810
|
+
private void addActions(NotificationCompat.Builder builder) {
|
|
811
|
+
ActionGroup actionGroup = options.getActionsGroup();
|
|
812
|
+
if (actionGroup == null) return;
|
|
813
|
+
|
|
814
|
+
for (Action action : actionGroup.getActions()) {
|
|
815
|
+
addAction(builder, action);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
/**
|
|
820
|
+
* Add an action to the builder.
|
|
821
|
+
*/
|
|
822
|
+
private void addAction(NotificationCompat.Builder builder, Action action) {
|
|
823
|
+
// Route content tap to a transparent activity to avoid trampoline and still run plugin logic
|
|
824
|
+
Intent actionClickIntent = new Intent(context, ClickActivity.class)
|
|
825
|
+
.putExtra(Notification.EXTRA_ID, options.getId())
|
|
826
|
+
.putExtra(Action.EXTRA_ID, action.getId())
|
|
827
|
+
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
|
828
|
+
|
|
829
|
+
PendingIntent actionClickPendingIntent = PendingIntent.getActivity(
|
|
830
|
+
context,
|
|
831
|
+
Manager.getRandomRequestCode(),
|
|
832
|
+
actionClickIntent,
|
|
833
|
+
// Input actions need FLAG_MUTABLE, otherwise it would give the error
|
|
834
|
+
// "PendingIntents attached to actions with remote inputs must be mutable"
|
|
835
|
+
(action.isInput() ? PendingIntent.FLAG_MUTABLE : PendingIntent.FLAG_IMMUTABLE) | PendingIntent.FLAG_UPDATE_CURRENT);
|
|
836
|
+
|
|
837
|
+
NotificationCompat.Action.Builder actionBuilder = new NotificationCompat.Action.Builder(
|
|
838
|
+
action.getIcon(), action.getTitle(), actionClickPendingIntent);
|
|
839
|
+
|
|
840
|
+
if (action.isInput()) actionBuilder.addRemoteInput(action.buildRemoteInput());
|
|
841
|
+
|
|
842
|
+
builder.addAction(actionBuilder.build());
|
|
843
|
+
}
|
|
844
|
+
}
|