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.
Files changed (67) hide show
  1. package/.github/FUNDING.yml +1 -0
  2. package/.github/ISSUE_TEMPLATE/report_issue.yml +126 -0
  3. package/CHANGELOG.md +358 -0
  4. package/LICENSE +202 -0
  5. package/README.md +1531 -0
  6. package/images/android-actions-with-input-clicked.png +0 -0
  7. package/images/android-actions-with-input-not-clicked.png +0 -0
  8. package/images/android-actions.png +0 -0
  9. package/images/android-alarms-and-reminders-in-app-settings.png +0 -0
  10. package/images/android-alarms-and-reminders-setting.png +0 -0
  11. package/images/android-app-hibernation-notification.png +0 -0
  12. package/images/android-app-hibernation-settings-android-12.png +0 -0
  13. package/images/android-app-hibernation-settings-android-13-14.png +0 -0
  14. package/images/android-app-hibernation-settings-android-15.png +0 -0
  15. package/images/android-attachments-image-folded.png +0 -0
  16. package/images/android-attachments-image-unfolded.png +0 -0
  17. package/images/android-chat.png +0 -0
  18. package/images/android-icon.svg +19 -0
  19. package/images/android-inbox.png +0 -0
  20. package/images/android-notification-example.png +0 -0
  21. package/images/android-progress.png +0 -0
  22. package/images/android-request-permission.png +0 -0
  23. package/images/android-stack.png +0 -0
  24. package/images/apple-icon.svg +1 -0
  25. package/images/cordova-app-notified-logo.png +0 -0
  26. package/images/ios-actions-with-input.png +0 -0
  27. package/images/ios-actions.png +0 -0
  28. package/images/ios-attachments-image-folded.png +0 -0
  29. package/images/ios-attachments-image-unfolded.png +0 -0
  30. package/images/ios-notification.png +0 -0
  31. package/images/ios-request-permission.png +0 -0
  32. package/images/logo.png +0 -0
  33. package/package.json +53 -0
  34. package/plugin.xml +266 -0
  35. package/src/android/ClickActivity.java +72 -0
  36. package/src/android/LocalNotification.java +779 -0
  37. package/src/android/Manager.java +327 -0
  38. package/src/android/Notification.java +844 -0
  39. package/src/android/Options.java +956 -0
  40. package/src/android/OptionsTrigger.java +104 -0
  41. package/src/android/action/Action.java +214 -0
  42. package/src/android/action/ActionGroup.java +135 -0
  43. package/src/android/build/localnotification.gradle +29 -0
  44. package/src/android/receiver/ClearReceiver.java +61 -0
  45. package/src/android/receiver/RestoreReceiver.java +70 -0
  46. package/src/android/receiver/TriggerReceiver.java +70 -0
  47. package/src/android/trigger/TriggerHandler.java +195 -0
  48. package/src/android/trigger/TriggerHandlerAt.java +63 -0
  49. package/src/android/trigger/TriggerHandlerEvery.java +257 -0
  50. package/src/android/trigger/TriggerHandlerIn.java +69 -0
  51. package/src/android/util/AssetUtil.java +385 -0
  52. package/src/android/util/CallbackContextUtil.java +88 -0
  53. package/src/android/util/PluginFileProvider.java +34 -0
  54. package/src/android/xml/shared_files_provider_paths.xml +38 -0
  55. package/src/ios/APPLocalNotification.h +56 -0
  56. package/src/ios/APPLocalNotification.m +650 -0
  57. package/src/ios/APPNotificationCategory.h +28 -0
  58. package/src/ios/APPNotificationCategory.m +107 -0
  59. package/src/ios/APPNotificationContent.h +32 -0
  60. package/src/ios/APPNotificationContent.m +122 -0
  61. package/src/ios/APPNotificationOptions.h +42 -0
  62. package/src/ios/APPNotificationOptions.m +689 -0
  63. package/src/ios/UNNotificationRequest+APPLocalNotification.h +32 -0
  64. package/src/ios/UNNotificationRequest+APPLocalNotification.m +100 -0
  65. package/src/ios/UNUserNotificationCenter+APPLocalNotification.h +60 -0
  66. package/src/ios/UNUserNotificationCenter+APPLocalNotification.m +344 -0
  67. package/www/local-notification.js +1104 -0
@@ -0,0 +1,956 @@
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
+ package de.appplant.cordova.plugin.localnotification;
23
+
24
+ import android.app.AlarmManager;
25
+ import android.content.Context;
26
+ import android.graphics.Bitmap;
27
+ import android.graphics.Color;
28
+ import android.net.Uri;
29
+ import android.media.AudioAttributes;
30
+ import android.media.RingtoneManager;
31
+ import android.util.Log;
32
+
33
+ import androidx.core.app.NotificationCompat;
34
+ import androidx.core.app.NotificationCompat.MessagingStyle;
35
+ import androidx.core.app.NotificationManagerCompat;
36
+ import androidx.core.app.Person;
37
+ import androidx.core.graphics.drawable.IconCompat;
38
+
39
+ import org.json.JSONArray;
40
+ import org.json.JSONException;
41
+ import org.json.JSONObject;
42
+
43
+ import java.util.ArrayList;
44
+ import java.util.Date;
45
+ import java.util.List;
46
+
47
+ import de.appplant.cordova.plugin.localnotification.OptionsTrigger;
48
+ import de.appplant.cordova.plugin.localnotification.action.Action;
49
+ import de.appplant.cordova.plugin.localnotification.action.ActionGroup;
50
+ import de.appplant.cordova.plugin.localnotification.util.AssetUtil;
51
+
52
+ /**
53
+ * Wrapper around the JSON object passed through JS which contains all
54
+ * possible option values. Class provides simple readers and more advanced
55
+ * methods to convert independent values into platform specific values.
56
+ */
57
+ public final class Options {
58
+
59
+ private static final String TAG = "Options";
60
+
61
+ public static final String LARGE_ICON_TYPE_SQUARE = "square";
62
+ public static final String LARGE_ICON_TYPE_CIRCLE = "circle";
63
+
64
+ // The original JSON object
65
+ private final JSONObject options;
66
+
67
+ // The application context
68
+ private final Context context;
69
+
70
+ // Asset util instance
71
+ private final AssetUtil assetUtil;
72
+
73
+ /**
74
+ * Constructor
75
+ *
76
+ * @param context The application context.
77
+ * @param options The options dict map.
78
+ */
79
+ public Options(Context context, JSONObject options) {
80
+ this.context = context;
81
+
82
+ // Workaround: Correct properties in Java instead in JavaScript
83
+ // when the app was updated. {@link RestoreReceiver} will be called
84
+ // and there is no WebView at this point because no Activity will be started.
85
+ try {
86
+ // Check meta.version
87
+ JSONObject meta = options.getJSONObject("meta");
88
+ String metaVersion = meta.getString("version");
89
+
90
+ // Update properties for older versions
91
+ if (isVersionOlder(metaVersion, "1.1.0")) convertPropertiesForVersion110(options);
92
+ if (isVersionOlder(metaVersion, "1.1.1")) convertPropertiesForVersion111(options);
93
+ if (isVersionOlder(metaVersion, "1.1.8")) convertPropertiesForVersion118(options);
94
+
95
+ // Update meta.version to current plugin version
96
+ if (!metaVersion.equals("1.2.4")) {
97
+ meta.put("version", "1.2.4");
98
+ options.put("meta", meta);
99
+ }
100
+ } catch (JSONException exception) {
101
+ Log.e(TAG, "Could not convert properties for current plugin version", exception);
102
+ }
103
+
104
+ this.options = options;
105
+ this.assetUtil = new AssetUtil(context);
106
+ }
107
+
108
+ /**
109
+ * Converts properties for version 1.1.0. There have been some properties renamed.
110
+ * This removes old properties and sets it under the new name.
111
+ */
112
+ public void convertPropertiesForVersion110(JSONObject options) throws JSONException {
113
+ Log.d(TAG, "Converting properties for version 1.1.0");
114
+
115
+ // autoClear to androidAutoCancel
116
+ if (options.has("autoClear")) {
117
+ options.put("androidAutoCancel", options.opt("autoClear"));
118
+ options.remove("autoClear");
119
+ }
120
+
121
+ // badge to badgeNumber
122
+ if (options.has("badge")) {
123
+ options.put("badgeNumber", options.opt("badge"));
124
+ options.remove("badge");
125
+ }
126
+
127
+ // description to androidChannelDescription
128
+ if (options.has("description")) {
129
+ options.put("androidChannelDescription", options.opt("description"));
130
+ options.remove("description");
131
+ }
132
+
133
+ // channelDescription to androidChannelDescription
134
+ if (options.has("channelDescription")) {
135
+ options.put("androidChannelDescription", options.opt("channelDescription"));
136
+ options.remove("channelDescription");
137
+ }
138
+
139
+ // channelId to androidChannelId
140
+ if (options.has("channelId")) {
141
+ options.put("androidChannelId", options.opt("channelId"));
142
+ options.remove("channelId");
143
+ }
144
+
145
+ // importance to androidChannelImportance
146
+ if (options.has("importance")) {
147
+ options.put("androidChannelImportance", options.opt("importance"));
148
+ options.remove("importance");
149
+ }
150
+
151
+ // channelImportance to androidChannelImportance
152
+ if (options.has("channelImportance")) {
153
+ options.put("androidChannelImportance", options.opt("channelImportance"));
154
+ options.remove("channelImportance");
155
+ }
156
+
157
+ // channelName to androidChannelName
158
+ if (options.has("channelName")) {
159
+ options.put("androidChannelName", options.opt("channelName"));
160
+ options.remove("channelName");
161
+ }
162
+
163
+ // clock to androidShowWhen or androidUsesChronometer
164
+ if (options.has("clock")) {
165
+ // Can be boolean or string
166
+ Object clock = options.opt("clock");
167
+
168
+ // clock: boolean to androidShowWhen: boolean
169
+ if (clock instanceof Boolean) {
170
+ options.put("androidShowWhen", clock);
171
+
172
+ // clock: 'chronometer' to androidUsesChronometer: true
173
+ } else if (clock instanceof String) {
174
+ options.put("androidUsesChronometer", (String) clock == "chronometer");
175
+ }
176
+
177
+ options.remove("clock");
178
+ }
179
+
180
+ // color to androidColor
181
+ if (options.has("color")) {
182
+ options.put("androidColor", options.opt("color"));
183
+ options.remove("color");
184
+ }
185
+
186
+ // defaults to androidDefaults
187
+ if (options.has("defaults")) {
188
+ options.put("androidDefaults", options.opt("defaults"));
189
+ options.remove("defaults");
190
+ }
191
+
192
+ // group to androidGroup
193
+ if (options.has("group")) {
194
+ options.put("androidGroup", options.opt("group"));
195
+ options.remove("group");
196
+ }
197
+
198
+ // groupSummary to androidGroupSummary
199
+ if (options.has("groupSummary")) {
200
+ options.put("androidGroupSummary", options.opt("groupSummary"));
201
+ options.remove("groupSummary");
202
+ }
203
+
204
+ // icon to androidLargeIcon
205
+ if (options.has("icon")) {
206
+ options.put("androidLargeIcon", options.opt("icon"));
207
+ options.remove("icon");
208
+ }
209
+
210
+ // iconType to androidLargeIconType
211
+ if (options.has("iconType")) {
212
+ options.put("androidLargeIconType", options.opt("iconType"));
213
+ options.remove("iconType");
214
+ }
215
+
216
+ // lockscreen to androidLockscreen
217
+ if (options.has("lockscreen")) {
218
+ options.put("androidLockscreen", options.opt("lockscreen"));
219
+ options.remove("lockscreen");
220
+ }
221
+
222
+ // onlyAlertOnce to androidOnlyAlertOnce
223
+ if (options.has("onlyAlertOnce")) {
224
+ options.put("androidOnlyAlertOnce", options.opt("onlyAlertOnce"));
225
+ options.remove("onlyAlertOnce");
226
+ }
227
+
228
+ // progressBar to androidProgressBar
229
+ if (options.has("progressBar")) {
230
+ JSONObject progressBar = options.optJSONObject("progressBar");
231
+
232
+ if (progressBar != null) {
233
+ // In plugin version prior to 1.1.0 a progressBar object was always set with enabled: false
234
+ // Only set if property enabled does not exists or is true
235
+ if (!progressBar.has("enabled") || progressBar.optBoolean("enabled")) {
236
+ // Remove enabled property
237
+ progressBar.remove("enabled");
238
+ options.put("androidProgressBar", progressBar);
239
+ }
240
+ }
241
+
242
+ options.remove("progressBar");
243
+ }
244
+
245
+ // smallIcon to androidSmallIcon
246
+ if (options.has("smallIcon")) {
247
+ options.put("androidSmallIcon", options.opt("smallIcon"));
248
+ options.remove("smallIcon");
249
+ }
250
+
251
+ // sticky to androidOngoing
252
+ if (options.has("sticky")) {
253
+ options.put("androidOngoing", options.opt("sticky"));
254
+ options.remove("sticky");
255
+ }
256
+
257
+ // ongoing to androidOngoing
258
+ if (options.has("ongoing")) {
259
+ options.put("androidOngoing", options.opt("ongoing"));
260
+ options.remove("ongoing");
261
+ }
262
+
263
+ // sound boolean to sound string
264
+ // sound: true to sound: 'default'
265
+ // sound: false to sound: null
266
+ if (options.opt("sound") instanceof Boolean) {
267
+ options.put("sound", (Boolean) options.opt("sound") ? "default" : null);
268
+ }
269
+
270
+ // soundUsage to androidChannelSoundUsage
271
+ if (options.has("soundUsage")) {
272
+ options.put("androidChannelSoundUsage", options.opt("soundUsage"));
273
+ options.remove("soundUsage");
274
+ }
275
+
276
+ // summary to androidSummary
277
+ if (options.has("summary")) {
278
+ options.put("androidSummary", options.opt("summary"));
279
+ options.remove("summary");
280
+ }
281
+
282
+ // text Array to androidMessages
283
+ if (options.optJSONArray("text") != null) {
284
+ options.put("androidMessages", options.opt("text"));
285
+ options.remove("text");
286
+ }
287
+
288
+ // timeoutAfter to androidMessages
289
+ if (options.has("timeoutAfter")) {
290
+ options.put("androidTimeoutAfter", options.opt("timeoutAfter"));
291
+ options.remove("timeoutAfter");
292
+ }
293
+
294
+ // titleCount to androidTitleCount
295
+ if (options.has("titleCount")) {
296
+ options.put("androidTitleCount", options.opt("titleCount"));
297
+ options.remove("titleCount");
298
+ }
299
+
300
+ // wakeup to androidWakeUpScreen
301
+ if (options.has("wakeup")) {
302
+ options.put("androidWakeUpScreen", options.opt("wakeup"));
303
+ options.remove("wakeup");
304
+ }
305
+ }
306
+
307
+ /**
308
+ * Converts properties for version 1.1.0
309
+ */
310
+ public void convertPropertiesForVersion111(JSONObject options) throws JSONException {
311
+ Log.d(TAG, "Converting properties for version 1.1.1");
312
+
313
+ // vibrate to androidChannelEnableVibration
314
+ if (options.has("vibrate")) {
315
+ options.put("androidChannelEnableVibration", options.opt("vibrate"));
316
+ options.remove("vibrate");
317
+ }
318
+ }
319
+
320
+ /**
321
+ * Converts properties for version 1.1.8
322
+ */
323
+ public void convertPropertiesForVersion118(JSONObject options) throws JSONException {
324
+ Log.d(TAG, "Converting properties for version 1.1.8");
325
+
326
+ // Ensure, there is minimum a trigger.at otherwise it would schedule notifications endless
327
+ JSONObject trigger = options.getJSONObject("trigger");
328
+
329
+ if (!trigger.has("at") && !trigger.has("in") && !trigger.has("every")) {
330
+ trigger.put("at", new Date().getTime());
331
+ }
332
+ }
333
+
334
+ /**
335
+ * Application context.
336
+ */
337
+ public Context getContext () {
338
+ return context;
339
+ }
340
+
341
+ /**
342
+ * Wrapped JSON object.
343
+ */
344
+ public JSONObject getJSON() {
345
+ return options;
346
+ }
347
+
348
+ /**
349
+ * JSON object as string.
350
+ */
351
+ public String toString() {
352
+ return options.toString();
353
+ }
354
+
355
+ /**
356
+ * Gets the ID for the local notification.
357
+ *
358
+ * @return 0 if the user did not specify.
359
+ */
360
+ public Integer getId() {
361
+ return options.optInt("id");
362
+ }
363
+
364
+ /**
365
+ * Badge number for the notification. 0 hides the badge number, -1 leaves the badge number unchanged.
366
+ * Defaults to 1 if not set.
367
+ */
368
+ public int getBadgeNumber() {
369
+ return options.optInt("badgeNumber", 1);
370
+ }
371
+
372
+ /**
373
+ * Set whether this is an "ongoing" notification. Ongoing notifications cannot be dismissed by the user
374
+ * on locked devices, or by notification listeners, and some notifications (call, device management, media)
375
+ * cannot be dismissed on unlocked devices, so your application or service must take care of canceling them.
376
+ * They are typically used to indicate a background task that the user is actively engaged with
377
+ * (e.g., playing music) or is pending in some way and therefore occupying the device
378
+ * (e.g., a file download, sync operation, active network connection).
379
+ *
380
+ * Defaults to false if not set.
381
+ */
382
+ public Boolean isAndroidOngoing() {
383
+ return options.optBoolean("androidOngoing", false);
384
+ }
385
+
386
+ /**
387
+ * Make this notification automatically dismissed when the user touches it.
388
+ * Defaults to true if not set.
389
+ */
390
+ Boolean isAndroidAutoCancel() {
391
+ return options.optBoolean("androidAutoCancel", true);
392
+ }
393
+
394
+ /**
395
+ * Gets the trigger property as an Object.
396
+ */
397
+ public OptionsTrigger getOptionsTrigger() {
398
+ return new OptionsTrigger(getTriggerJSON());
399
+ }
400
+
401
+ public JSONObject getTriggerJSON() {
402
+ return options.optJSONObject("trigger");
403
+ }
404
+
405
+ /**
406
+ * Gets the value of the silent flag.
407
+ */
408
+ boolean isSilent() {
409
+ return options.optBoolean("silent", false);
410
+ }
411
+
412
+ /**
413
+ * Set this notification to be part of a group of notifications sharing the same key.
414
+ * Grouped notifications may display in a cluster or stack on devices which support such rendering.
415
+ * To make this notification the summary for its group, also call setGroupSummary(boolean).
416
+ * A sort order can be specified for group members by using setSortKey(String).
417
+ *
418
+ * Defaults to null if not set.
419
+ */
420
+ String getGroup() {
421
+ return options.optString("androidGroup", null);
422
+ }
423
+
424
+ /**
425
+ * Set this notification to be the group summary for a group of notifications.
426
+ * Grouped notifications may display in a cluster or stack on devices which
427
+ * support such rendering. Requires a group key also be set using setGroup(String).
428
+ * The group summary may be suppressed if too few notifications are included in the group.
429
+ *
430
+ * Defaults to false if not set.
431
+ */
432
+ boolean isGroupSummary() {
433
+ return options.optBoolean("androidGroupSummary", false);
434
+ }
435
+
436
+ /**
437
+ * Should a click on the notification launch the app.
438
+ */
439
+ boolean isLaunch() {
440
+ return options.optBoolean("launch", true);
441
+ }
442
+
443
+ /**
444
+ * Specifies a duration in milliseconds after which this notification should be canceled,
445
+ * if it is not already canceled.
446
+ *
447
+ * Defaults to 0 if not set.
448
+ */
449
+ long getAndroidTimeoutAfter() {
450
+ return options.optLong("androidTimeoutAfter", 0);
451
+ }
452
+
453
+ /**
454
+ * Since Android 8. The channel id of that notification.
455
+ * Defaults to 'default_channel' if not set.
456
+ */
457
+ public String getAndroidChannelId() {
458
+ return options.optString("androidChannelId", "default_channel");
459
+ }
460
+
461
+ /**
462
+ * Since Android 8.
463
+ * Defaults to 'Default channel' if not set.
464
+ * @return
465
+ */
466
+ String getAndroidChannelName() {
467
+ return options.optString("androidChannelName", "Default channel");
468
+ }
469
+
470
+ /**
471
+ * Since Android 8. The importance of a notification.
472
+ * Defaults to {@link NotificationManagerCompat.IMPORTANCE_DEFAULT} if not set.
473
+ * @return
474
+ */
475
+ int getAndroidChannelImportance() {
476
+ return options.optInt("androidChannelImportance", NotificationManagerCompat.IMPORTANCE_DEFAULT);
477
+ }
478
+
479
+ /**
480
+ * Channel description. Only for Android 8 and higher.
481
+ * Defaults to null if not set.
482
+ */
483
+ String getAndroidChannelDescription() {
484
+ return options.optString("androidChannelDescription", null);
485
+ }
486
+
487
+ /**
488
+ * Text for the notification.
489
+ */
490
+ public String getText() {
491
+ return options.optString("text", "");
492
+ }
493
+
494
+ /**
495
+ * Title for the local notification. If empty, the app name will be used.
496
+ */
497
+ public String getTitle() {
498
+ String title = options.optString("title", "");
499
+
500
+ if (title.isEmpty()) {
501
+ title = context.getApplicationInfo().loadLabel(context.getPackageManager()).toString();
502
+ }
503
+
504
+ return title;
505
+ }
506
+
507
+ /**
508
+ * The notification background color for the small icon.
509
+ * Defaults to {@link NotificationCompat.COLOR_DEFAULT} if not set or invalid.
510
+ */
511
+ public int getColor() {
512
+ String androidColor = options.optString("androidColor", null);
513
+
514
+ if (androidColor == null) return NotificationCompat.COLOR_DEFAULT;
515
+
516
+ try {
517
+ // Remove leading hash from hex, convert to int, add alpha channel
518
+ return Integer.parseInt(hexWithoutHash(androidColor), 16) + 0xFF000000;
519
+ } catch (NumberFormatException exception) {
520
+ Log.e(TAG, "Could not parse androidColor hex to int: " + androidColor, exception);
521
+ return NotificationCompat.COLOR_DEFAULT;
522
+ }
523
+ }
524
+
525
+ /**
526
+ * Since Android 8, this sets the sound of a notification channel.
527
+ * Prior to Android 8, this sets the sound directly of a notification.
528
+ *
529
+ * Defaults to 'default' if not set, which represents the default notification sound.
530
+ */
531
+ String getSound() {
532
+ return options.optString("sound", "default");
533
+ }
534
+
535
+ /**
536
+ * Sound file path for the local notification.
537
+ * @return Uri of the sound file or {@link Uri.EMPTY} if not set.
538
+ */
539
+ public Uri getSoundUri() {
540
+ String soundName = getSound();
541
+
542
+ if (soundName == null || soundName.isEmpty()) return Uri.EMPTY;
543
+
544
+ if (soundName.equals("default")) {
545
+ return RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
546
+ } else {
547
+ return assetUtil.getUri(soundName, AssetUtil.RESOURCE_TYPE_RAW);
548
+ }
549
+ }
550
+
551
+ /**
552
+ * The sound usage for the notification.
553
+ * Defaults to {@link AudioAttributes.USAGE_NOTIFICATION} if not set.
554
+ */
555
+ int getSoundUsage() {
556
+ return options.optInt("androidChannelSoundUsage", AudioAttributes.USAGE_NOTIFICATION);
557
+ }
558
+
559
+ /**
560
+ * Defaults to null if not set.
561
+ */
562
+ public String getAndroidLargeIcon() {
563
+ return options.optString("androidLargeIcon", null);
564
+ }
565
+
566
+ /**
567
+ * Type of the large icon.
568
+ * Defaults to {@link #LARGE_ICON_TYPE_SQUARE} if not set.
569
+ */
570
+ public String getAndroidLargeIconType() {
571
+ return options.optString("androidLargeIconType", LARGE_ICON_TYPE_SQUARE);
572
+ }
573
+
574
+ /**
575
+ * Gets the small icon resource, which will be used to represent the notification in the status bar.
576
+ * The platform template for the expanded view will draw this icon in the left, unless a large icon
577
+ * has also been specified, in which case the small icon will be moved to the right-hand side.
578
+ *
579
+ * Defaults to 'ic_popup_reminder' if not set, or wrongly set.
580
+ */
581
+ int getSmallIcon() {
582
+ String smallIconPath = options.optString("androidSmallIcon");
583
+
584
+ // Try to get the resource from the app resources or system resources
585
+ int resId = assetUtil.getResourceId(smallIconPath, AssetUtil.RESOURCE_TYPE_DRAWABLE);
586
+
587
+ // Log error, if no icon is found and fallback to a system icon, which should exists
588
+ if (resId == 0) {
589
+ Log.e(TAG, "androidSmallIcon not found, using system icon 'ic_popup_reminder', androidSmallIcon=" + smallIconPath);
590
+ // Fallback to a system icon, which should exists
591
+ resId = assetUtil.getResourceId("ic_popup_reminder", AssetUtil.RESOURCE_TYPE_DRAWABLE);
592
+ }
593
+
594
+ return resId;
595
+ }
596
+
597
+ /**
598
+ * Defaults to false if not set.
599
+ */
600
+ boolean isAndroidChannelEnableVibration() {
601
+ return options.optBoolean("androidChannelEnableVibration", false);
602
+ }
603
+
604
+ /**
605
+ * Only prior to Android 8. Can be a Hex-String like `#FF00FF` or
606
+ * `{color: '#FF00FF', on: 500, off: 500}' and sets the led of a notification.
607
+ */
608
+ Object getLed() {
609
+ return options.opt("led");
610
+ }
611
+
612
+ /**
613
+ * Only prior to Android 8. The notification color for LED.
614
+ */
615
+ int getLedColor() {
616
+ String hex = null;
617
+
618
+ if (getLed() instanceof String) hex = (String) getLed();
619
+ if (getLed() instanceof JSONArray) hex = ((JSONArray) getLed()).optString(0);
620
+ if (getLed() instanceof JSONObject) hex = ((JSONObject) getLed()).optString("color");
621
+
622
+ if (hex == null) return 0;
623
+
624
+ try {
625
+ return Integer.parseInt(hexWithoutHash(hex), 16) + 0xFF000000;
626
+ } catch (NumberFormatException e) {
627
+ e.printStackTrace();
628
+ }
629
+
630
+ return 0;
631
+ }
632
+
633
+ /**
634
+ * Only prior to Android 8.
635
+ */
636
+ int getLedOn() {
637
+ int defaultValue = 1000;
638
+
639
+ if (getLed() instanceof JSONArray) return ((JSONArray) getLed()).optInt(1, defaultValue);
640
+ if (getLed() instanceof JSONObject) return ((JSONObject) getLed()).optInt("on", defaultValue);
641
+
642
+ return defaultValue;
643
+ }
644
+
645
+ /**
646
+ * Only prior to Android 8.
647
+ */
648
+ int getLedOff() {
649
+ int defaultValue = 1000;
650
+
651
+ if (getLed() instanceof JSONArray) return ((JSONArray) getLed()).optInt(2, defaultValue);
652
+ if (getLed() instanceof JSONObject) return ((JSONObject) getLed()).optInt("off", defaultValue);
653
+
654
+ return defaultValue;
655
+ }
656
+
657
+ /**
658
+ * Only since Android 8. Sets whether notifications posted to a channel
659
+ * should display notification lights, on devices that support that feature.
660
+ */
661
+ boolean getAndroidChannelEnableLights() {
662
+ return options.optBoolean("androidChannelEnableLights", false);
663
+ }
664
+
665
+ /**
666
+ * Only for Android 7. Defaults for notification, which are bitwise or linked:
667
+ * {@link NotificationCompat.DEFAULT_SOUND},
668
+ * {@link NotificationCompat.DEFAULT_VIBRATE},
669
+ * {@link NotificationCompat.DEFAULT_LIGHTS}.
670
+ */
671
+ int getDefaults() {
672
+ int defaults = options.optInt("androidDefaults");
673
+
674
+ if (isAndroidChannelEnableVibration()) {
675
+ defaults |= NotificationCompat.DEFAULT_VIBRATE;
676
+ } else {
677
+ defaults &= NotificationCompat.DEFAULT_VIBRATE;
678
+ }
679
+
680
+ if (getSound() == null) {
681
+ defaults |= NotificationCompat.DEFAULT_SOUND;
682
+
683
+ // No default sound
684
+ } else {
685
+ defaults &= NotificationCompat.DEFAULT_SOUND;
686
+ }
687
+
688
+ if (getLed() == null) {
689
+ defaults |= NotificationCompat.DEFAULT_LIGHTS;
690
+
691
+ // No default led
692
+ } else {
693
+ defaults &= NotificationCompat.DEFAULT_LIGHTS;
694
+ }
695
+
696
+ return defaults;
697
+ }
698
+
699
+ /**
700
+ * Gets the visibility for the notification.
701
+ * Defaults to {@link NotificationCompat.VISIBILITY_PUBLIC} if not set.
702
+ * @return {@link NotificationCompat.VISIBILITY_PUBLIC} or {@link NotificationCompat.VISIBILITY_SECRET}
703
+ */
704
+ int getVisibility() {
705
+ return options.optBoolean("androidLockscreen", true) ? NotificationCompat.VISIBILITY_PUBLIC : NotificationCompat.VISIBILITY_SECRET;
706
+ }
707
+
708
+ /**
709
+ * For Android 7 backward compatibility. Gets the notifications priority
710
+ * based on {@link #getAndroidChannelImportance()}
711
+ */
712
+ int getPriorityByImportance() {
713
+ switch(getAndroidChannelImportance()){
714
+ case NotificationManagerCompat.IMPORTANCE_NONE: // 0
715
+ case NotificationManagerCompat.IMPORTANCE_MIN: // 1
716
+ return NotificationCompat.PRIORITY_MIN;
717
+
718
+ case NotificationManagerCompat.IMPORTANCE_LOW: // 2
719
+ return NotificationCompat.PRIORITY_LOW;
720
+
721
+ case NotificationManagerCompat.IMPORTANCE_DEFAULT: // 3
722
+ return NotificationCompat.PRIORITY_DEFAULT;
723
+
724
+ case NotificationManagerCompat.IMPORTANCE_HIGH: // 4
725
+ case NotificationManagerCompat.IMPORTANCE_MAX: // 5
726
+ return NotificationCompat.PRIORITY_MAX;
727
+ }
728
+
729
+ return NotificationCompat.PRIORITY_DEFAULT;
730
+ }
731
+
732
+ /**
733
+ * If the Notification should show the when date.
734
+ * Defaults to true.
735
+ */
736
+ boolean isAndroidShowWhen() {
737
+ return options.optBoolean("androidShowWhen", true);
738
+ }
739
+
740
+ /**
741
+ * Show the Notification#when field as a stopwatch. Instead of presenting when as a timestamp,
742
+ * the notification will show an automatically updating display of the minutes and seconds since
743
+ * when. Useful when showing an elapsed time (like an ongoing phone call). The counter can also
744
+ * be set to count down to when when using setChronometerCountDown(boolean).
745
+ *
746
+ * Defaults to false if not set.
747
+ */
748
+ boolean isAndroidUsesChronometer() {
749
+ return options.optBoolean("androidUsesChronometer", false);
750
+ }
751
+
752
+ /**
753
+ * If the notification shall display a progress bar.
754
+ * Defaults to null if not set.
755
+ */
756
+ JSONObject getProgressBar() {
757
+ return options.optJSONObject("androidProgressBar");
758
+ }
759
+
760
+ /**
761
+ * Gets the progress value.
762
+ *
763
+ * @return 0 by default.
764
+ */
765
+ int getProgressValue() {
766
+ return getProgressBar().optInt("value", 0);
767
+ }
768
+
769
+ /**
770
+ * Gets the progress value.
771
+ *
772
+ * @return 100 by default.
773
+ */
774
+ int getProgressMaxValue() {
775
+ return getProgressBar().optInt("maxValue", 100);
776
+ }
777
+
778
+ /**
779
+ * Gets the progress indeterminate value.
780
+ * @return false by default.
781
+ */
782
+ boolean isProgressIndeterminate() {
783
+ return getProgressBar().optBoolean("indeterminate", false);
784
+ }
785
+
786
+ /**
787
+ * The summary for
788
+ * - NotificationCompat.InboxStyle
789
+ * - NotificationCompat.BigPictureStyle
790
+ * - NotificationCompat.BigTextStyle
791
+ *
792
+ * Defaults to null if not set.
793
+ */
794
+ String getSummary() {
795
+ return options.optString("androidSummary", null);
796
+ }
797
+
798
+ /**
799
+ * Image attachments for image style notifications. Only returns the first item,
800
+ * as Android does not support multiple attachments like iOS.
801
+ * @return null if there are no attachments.
802
+ */
803
+ List<Bitmap> getAttachments() {
804
+ JSONArray attachments = options.optJSONArray("attachments");
805
+ if (attachments == null) return null;
806
+
807
+ List<Bitmap> bitmaps = new ArrayList<Bitmap>();
808
+
809
+ for (int index = 0; index < attachments.length(); index++) {
810
+ Bitmap assetBitmap = assetUtil.getBitmap(attachments.optString(index));
811
+
812
+ if (assetBitmap == null) continue;
813
+
814
+ // Only use the first attachment
815
+ bitmaps.add(assetBitmap);
816
+ break;
817
+ }
818
+
819
+ return bitmaps;
820
+ }
821
+
822
+ /**
823
+ * Gets the actions group id or null if not set.
824
+ */
825
+ public ActionGroup getActionsGroup() {
826
+ return ActionGroup.get(context, options.optString("actions", null));
827
+ }
828
+
829
+ /**
830
+ * Gets the list of messages to display.
831
+ * @return null if there are no messages
832
+ */
833
+ MessagingStyle.Message[] getAndroidMessages() {
834
+ JSONArray messagesJSONArray = options.optJSONArray("androidMessages");
835
+
836
+ // Nothing is set
837
+ if (messagesJSONArray == null) return null;
838
+
839
+ MessagingStyle.Message[] messages = new MessagingStyle.Message[messagesJSONArray.length()];
840
+
841
+ for (int i = 0; i < messages.length; i++) {
842
+ JSONObject messageJSON = messagesJSONArray.optJSONObject(i);
843
+
844
+ // Use person icon if available
845
+ Bitmap personBitmap = assetUtil.getBitmap(messageJSON.optString("personIcon", null));
846
+ IconCompat personIcon = personBitmap != null ? IconCompat.createWithBitmap(AssetUtil.getCircleBitmap(personBitmap)) : null;
847
+
848
+ messages[i] = new MessagingStyle.Message(
849
+ messageJSON.optString("message"),
850
+ messageJSON.optLong("date", System.currentTimeMillis()),
851
+ new Person.Builder()
852
+ .setName(messageJSON.optString("person", null))
853
+ .setIcon(personIcon).build()
854
+ );
855
+ }
856
+
857
+ return messages;
858
+ }
859
+
860
+ /**
861
+ * Additional text added to the title for displaying the number of messages
862
+ * if there is more than one. Only used if using {@link NotificationCompat.MessagingStyle}.
863
+ *
864
+ * Defaults to null if not set.
865
+ */
866
+ String getTitleCount() {
867
+ return options.optString("androidTitleCount", null);
868
+ }
869
+
870
+ /**
871
+ * If the notification shall only alert once.
872
+ * Defaults to false if not set.
873
+ */
874
+ public boolean isOnlyAlertOnce(){
875
+ return options.optBoolean("androidOnlyAlertOnce", false);
876
+ }
877
+
878
+ /**
879
+ * Defaults to {@link AlarmManager.RTC_WAKEUP} if not set.
880
+ */
881
+ public int getAndroidAlarmType() {
882
+ return options.optInt("androidAlarmType", AlarmManager.RTC_WAKEUP);
883
+ }
884
+
885
+ public boolean isAndroidAllowWhileIdle() {
886
+ return options.optBoolean("androidAllowWhileIdle", false);
887
+ }
888
+
889
+ /**
890
+ * If the display should be turned on when the notification is triggered.
891
+ * Defaults to true if not set.
892
+ */
893
+ public boolean isAndroidWakeUpScreen() {
894
+ return options.optBoolean("androidWakeUpScreen", true);
895
+ }
896
+
897
+ /**
898
+ * @return Returns the hex code without a leading #
899
+ */
900
+ private String hexWithoutHash(String hex) {
901
+ return (hex.charAt(0) == '#') ? hex.substring(1) : hex;
902
+ }
903
+
904
+ /**
905
+ * Converts a version string to a comparable int. The version
906
+ * must have two or three digits separated by dots.
907
+ * Pre-release identifiers like "-dev" or "-beta" will be removed before.
908
+ *
909
+ * The calculation of the int will be as follows:
910
+ * MAJOR * 10000 + MINOR * 100 + PATCH
911
+ *
912
+ * Examples:
913
+ * - 1.1.1-dev to 10101
914
+ * - 1.2.1-dev to 10201
915
+ * - 0.9-beta.3 to 903
916
+ *
917
+ * Version calculation taken from:
918
+ * https://cordova.apache.org/docs/en/12.x-2025.01/guide/platforms/android/index.html#setting-the-version-code
919
+ *
920
+ * @param version
921
+ * @return
922
+ */
923
+ public static int versionStringToInt(String version) {
924
+ // Remove pre-release identifiers -dev and -beta, split by dot
925
+ String[] parts = version.replaceAll("-dev|-beta", "").split("\\.");
926
+
927
+ int major = Integer.parseInt(parts[0]);
928
+ int minor = Integer.parseInt(parts[1]);
929
+ // Optional patch version
930
+ int patch = parts.length > 2 ? Integer.parseInt(parts[2]) : 0;
931
+
932
+ return major * 10000 + minor * 100 + patch;
933
+ }
934
+
935
+ /**
936
+ * Compares two version strings. The strings will be converted to int and
937
+ * compared by {@link Integer#compare(int, int)}.
938
+ * @param version1
939
+ * @param version2
940
+ * @return -1 if version1 is older than version2, 0 if they are equal and 1 if
941
+ * version1 is newer than version2.
942
+ */
943
+ public static int compareVersion(String version1, String version2) {
944
+ return Integer.compare(versionStringToInt(version1), versionStringToInt(version2));
945
+ }
946
+
947
+ /**
948
+ * Compares two version strings.
949
+ * @param version1
950
+ * @param version2
951
+ * @return true if version1 is older than version2.
952
+ */
953
+ public static boolean isVersionOlder(String version1, String version2) {
954
+ return compareVersion(version1, version2) < 0;
955
+ }
956
+ }