@ammarahmed/notifee-react-native 7.3.0 → 7.4.1

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 (79) hide show
  1. package/android/build.gradle +35 -17
  2. package/android/schemas/app.notifee.core.database.NotifeeCoreDatabase/1.json +46 -0
  3. package/android/schemas/app.notifee.core.database.NotifeeCoreDatabase/2.json +53 -0
  4. package/android/src/main/java/app/notifee/core/AlarmPermissionBroadcastReceiver.java +19 -0
  5. package/android/src/main/java/app/notifee/core/BlockStateBroadcastReceiver.java +164 -0
  6. package/android/src/main/java/app/notifee/core/ChannelManager.java +351 -0
  7. package/android/src/main/java/app/notifee/core/ContextHolder.java +33 -0
  8. package/android/src/main/java/app/notifee/core/EventBus.java +63 -0
  9. package/android/src/main/java/app/notifee/core/EventSubscriber.java +82 -0
  10. package/android/src/main/java/app/notifee/core/ForegroundService.java +125 -0
  11. package/android/src/main/java/app/notifee/core/InitProvider.java +92 -0
  12. package/android/src/main/java/app/notifee/core/KeepForSdk.java +26 -0
  13. package/android/src/main/java/app/notifee/core/Logger.java +68 -0
  14. package/android/src/main/java/app/notifee/core/Notifee.java +533 -0
  15. package/android/src/main/java/app/notifee/core/NotifeeAlarmManager.java +256 -0
  16. package/android/src/main/java/app/notifee/core/NotificationAlarmReceiver.java +32 -0
  17. package/android/src/main/java/app/notifee/core/NotificationManager.java +903 -0
  18. package/android/src/main/java/app/notifee/core/NotificationPendingIntent.java +189 -0
  19. package/android/src/main/java/app/notifee/core/NotificationReceiverActivity.java +38 -0
  20. package/android/src/main/java/app/notifee/core/NotificationReceiverHandler.java +140 -0
  21. package/android/src/main/java/app/notifee/core/Preferences.java +79 -0
  22. package/android/src/main/java/app/notifee/core/RebootBroadcastReceiver.java +38 -0
  23. package/android/src/main/java/app/notifee/core/ReceiverService.java +268 -0
  24. package/android/src/main/java/app/notifee/core/Worker.java +87 -0
  25. package/android/src/main/java/app/notifee/core/database/NotifeeCoreDatabase.java +76 -0
  26. package/android/src/main/java/app/notifee/core/database/WorkDataDao.java +52 -0
  27. package/android/src/main/java/app/notifee/core/database/WorkDataEntity.java +68 -0
  28. package/android/src/main/java/app/notifee/core/database/WorkDataRepository.java +107 -0
  29. package/android/src/main/java/app/notifee/core/event/BlockStateEvent.java +102 -0
  30. package/android/src/main/java/app/notifee/core/event/ForegroundServiceEvent.java +48 -0
  31. package/android/src/main/java/app/notifee/core/event/InitialNotificationEvent.java +50 -0
  32. package/android/src/main/java/app/notifee/core/event/LogEvent.java +74 -0
  33. package/android/src/main/java/app/notifee/core/event/MainComponentEvent.java +33 -0
  34. package/android/src/main/java/app/notifee/core/event/NotificationEvent.java +97 -0
  35. package/android/src/main/java/app/notifee/core/interfaces/EventListener.java +39 -0
  36. package/android/src/main/java/app/notifee/core/interfaces/MethodCallResult.java +27 -0
  37. package/android/src/main/java/app/notifee/core/model/ChannelGroupModel.java +48 -0
  38. package/android/src/main/java/app/notifee/core/model/ChannelModel.java +123 -0
  39. package/android/src/main/java/app/notifee/core/model/IntervalTriggerModel.java +63 -0
  40. package/android/src/main/java/app/notifee/core/model/NotificationAndroidActionModel.java +125 -0
  41. package/android/src/main/java/app/notifee/core/model/NotificationAndroidModel.java +654 -0
  42. package/android/src/main/java/app/notifee/core/model/NotificationAndroidPressActionModel.java +146 -0
  43. package/android/src/main/java/app/notifee/core/model/NotificationAndroidStyleModel.java +341 -0
  44. package/android/src/main/java/app/notifee/core/model/NotificationModel.java +72 -0
  45. package/android/src/main/java/app/notifee/core/model/TimestampTriggerModel.java +163 -0
  46. package/android/src/main/java/app/notifee/core/model/package-info.java +4 -0
  47. package/android/src/main/java/app/notifee/core/utility/AlarmUtils.java +52 -0
  48. package/android/src/main/java/app/notifee/core/utility/Callbackable.java +12 -0
  49. package/android/src/main/java/app/notifee/core/utility/ColorUtils.java +62 -0
  50. package/android/src/main/java/app/notifee/core/utility/ExtendedListenableFuture.java +84 -0
  51. package/android/src/main/java/app/notifee/core/utility/IntentUtils.java +136 -0
  52. package/android/src/main/java/app/notifee/core/utility/ObjectUtils.java +187 -0
  53. package/android/src/main/java/app/notifee/core/utility/PowerManagerUtils.java +334 -0
  54. package/android/src/main/java/app/notifee/core/utility/ResourceUtils.java +309 -0
  55. package/android/src/main/java/app/notifee/core/utility/TextUtils.java +28 -0
  56. package/android/src/main/java/app/notifee/core/utility/package-info.java +4 -0
  57. package/dist/types/Notification.d.ts +8 -1
  58. package/dist/types/Notification.js +6 -0
  59. package/dist/types/Notification.js.map +1 -1
  60. package/dist/version.d.ts +1 -1
  61. package/jest-mock.js +7 -4
  62. package/package.json +1 -3
  63. package/src/types/Notification.ts +8 -0
  64. package/src/version.ts +1 -1
  65. package/android/libs/app/notifee/core/202108261756/core-202108261756.aar +0 -0
  66. package/android/libs/app/notifee/core/202108261756/core-202108261756.aar.md5 +0 -1
  67. package/android/libs/app/notifee/core/202108261756/core-202108261756.aar.sha1 +0 -1
  68. package/android/libs/app/notifee/core/202108261756/core-202108261756.aar.sha256 +0 -1
  69. package/android/libs/app/notifee/core/202108261756/core-202108261756.aar.sha512 +0 -1
  70. package/android/libs/app/notifee/core/202108261756/core-202108261756.pom +0 -9
  71. package/android/libs/app/notifee/core/202108261756/core-202108261756.pom.md5 +0 -1
  72. package/android/libs/app/notifee/core/202108261756/core-202108261756.pom.sha1 +0 -1
  73. package/android/libs/app/notifee/core/202108261756/core-202108261756.pom.sha256 +0 -1
  74. package/android/libs/app/notifee/core/202108261756/core-202108261756.pom.sha512 +0 -1
  75. package/android/libs/app/notifee/core/maven-metadata.xml +0 -13
  76. package/android/libs/app/notifee/core/maven-metadata.xml.md5 +0 -1
  77. package/android/libs/app/notifee/core/maven-metadata.xml.sha1 +0 -1
  78. package/android/libs/app/notifee/core/maven-metadata.xml.sha256 +0 -1
  79. package/android/libs/app/notifee/core/maven-metadata.xml.sha512 +0 -1
@@ -0,0 +1,903 @@
1
+ package app.notifee.core;
2
+
3
+ /*
4
+ * Copyright (c) 2016-present Invertase Limited & Contributors
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this library except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ *
18
+ */
19
+
20
+ import static app.notifee.core.ContextHolder.getApplicationContext;
21
+ import static app.notifee.core.ReceiverService.ACTION_PRESS_INTENT;
22
+ import static app.notifee.core.event.NotificationEvent.TYPE_ACTION_PRESS;
23
+ import static app.notifee.core.event.NotificationEvent.TYPE_PRESS;
24
+ import static java.lang.Integer.parseInt;
25
+
26
+ import android.app.Notification;
27
+ import android.app.PendingIntent;
28
+ import android.content.Context;
29
+ import android.content.Intent;
30
+ import android.graphics.Bitmap;
31
+ import android.net.Uri;
32
+ import android.os.Build;
33
+ import android.os.Bundle;
34
+ import android.service.notification.StatusBarNotification;
35
+ import androidx.annotation.NonNull;
36
+ import androidx.concurrent.futures.CallbackToFutureAdapter;
37
+ import androidx.core.app.NotificationCompat;
38
+ import androidx.core.app.NotificationManagerCompat;
39
+ import androidx.core.app.RemoteInput;
40
+ import androidx.core.graphics.drawable.IconCompat;
41
+ import androidx.work.Data;
42
+ import androidx.work.ExistingPeriodicWorkPolicy;
43
+ import androidx.work.ExistingWorkPolicy;
44
+ import androidx.work.ListenableWorker;
45
+ import androidx.work.ListenableWorker.Result;
46
+ import androidx.work.OneTimeWorkRequest;
47
+ import androidx.work.PeriodicWorkRequest;
48
+ import androidx.work.WorkManager;
49
+ import app.notifee.core.database.WorkDataEntity;
50
+ import app.notifee.core.database.WorkDataRepository;
51
+ import app.notifee.core.event.MainComponentEvent;
52
+ import app.notifee.core.event.NotificationEvent;
53
+ import app.notifee.core.interfaces.MethodCallResult;
54
+ import app.notifee.core.model.IntervalTriggerModel;
55
+ import app.notifee.core.model.NotificationAndroidActionModel;
56
+ import app.notifee.core.model.NotificationAndroidModel;
57
+ import app.notifee.core.model.NotificationAndroidPressActionModel;
58
+ import app.notifee.core.model.NotificationAndroidStyleModel;
59
+ import app.notifee.core.model.NotificationModel;
60
+ import app.notifee.core.model.TimestampTriggerModel;
61
+ import app.notifee.core.utility.Callbackable;
62
+ import app.notifee.core.utility.ExtendedListenableFuture;
63
+ import app.notifee.core.utility.IntentUtils;
64
+ import app.notifee.core.utility.ObjectUtils;
65
+ import app.notifee.core.utility.PowerManagerUtils;
66
+ import app.notifee.core.utility.ResourceUtils;
67
+ import app.notifee.core.utility.TextUtils;
68
+ import com.google.common.util.concurrent.AsyncFunction;
69
+ import com.google.common.util.concurrent.FutureCallback;
70
+ import com.google.common.util.concurrent.Futures;
71
+ import com.google.common.util.concurrent.ListenableFuture;
72
+ import com.google.common.util.concurrent.ListeningExecutorService;
73
+ import com.google.common.util.concurrent.MoreExecutors;
74
+ import java.util.ArrayList;
75
+ import java.util.List;
76
+ import java.util.Objects;
77
+ import java.util.concurrent.Callable;
78
+ import java.util.concurrent.ExecutorService;
79
+ import java.util.concurrent.Executors;
80
+ import java.util.concurrent.TimeUnit;
81
+ import java.util.concurrent.TimeoutException;
82
+
83
+ class NotificationManager {
84
+ private static final String TAG = "NotificationManager";
85
+ private static final String EXTRA_NOTIFEE_NOTIFICATION = "notifee.notification";
86
+ private static final String EXTRA_NOTIFEE_TRIGGER = "notifee.trigger";
87
+ private static final ExecutorService CACHED_THREAD_POOL = Executors.newCachedThreadPool();
88
+ private static final ListeningExecutorService LISTENING_CACHED_THREAD_POOL = MoreExecutors.listeningDecorator(
89
+ CACHED_THREAD_POOL);
90
+ private static final int NOTIFICATION_TYPE_ALL = 0;
91
+ private static final int NOTIFICATION_TYPE_DISPLAYED = 1;
92
+ private static final int NOTIFICATION_TYPE_TRIGGER = 2;
93
+
94
+ private static ListenableFuture<NotificationCompat.Builder> notificationBundleToBuilder(
95
+ NotificationModel notificationModel) {
96
+ final NotificationAndroidModel androidModel = notificationModel.getAndroid();
97
+
98
+ /*
99
+ * Construct the initial NotificationCompat.Builder instance
100
+ */
101
+ Callable<NotificationCompat.Builder> builderCallable =
102
+ () -> {
103
+ Boolean hasCustomSound = false;
104
+ NotificationCompat.Builder builder =
105
+ new NotificationCompat.Builder(getApplicationContext(), androidModel.getChannelId());
106
+
107
+ // must always keep at top
108
+ builder.setExtras(notificationModel.getData());
109
+
110
+ builder.setDeleteIntent(
111
+ ReceiverService.createIntent(
112
+ ReceiverService.DELETE_INTENT,
113
+ new String[] {"notification"},
114
+ notificationModel.toBundle()));
115
+ int targetSdkVersion =
116
+ ContextHolder.getApplicationContext().getApplicationInfo().targetSdkVersion;
117
+ if (targetSdkVersion >= Build.VERSION_CODES.S
118
+ && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
119
+ builder.setContentIntent(
120
+ NotificationPendingIntent.createIntent(
121
+ notificationModel.getHashCode(),
122
+ androidModel.getPressAction(),
123
+ TYPE_PRESS,
124
+ new String[] {"notification", "pressAction"},
125
+ notificationModel.toBundle(),
126
+ androidModel.getPressAction()));
127
+ } else {
128
+ builder.setContentIntent(
129
+ ReceiverService.createIntent(
130
+ ReceiverService.PRESS_INTENT,
131
+ new String[] {"notification", "pressAction"},
132
+ notificationModel.toBundle(),
133
+ androidModel.getPressAction()));
134
+ }
135
+
136
+ if (notificationModel.getTitle() != null) {
137
+ builder.setContentTitle(TextUtils.fromHtml(notificationModel.getTitle()));
138
+ }
139
+
140
+ if (notificationModel.getSubTitle() != null) {
141
+ builder.setSubText(TextUtils.fromHtml(notificationModel.getSubTitle()));
142
+ }
143
+
144
+ if (notificationModel.getBody() != null) {
145
+ builder.setContentText(TextUtils.fromHtml(notificationModel.getBody()));
146
+ }
147
+
148
+ if (androidModel.getBadgeIconType() != null) {
149
+ builder.setBadgeIconType(androidModel.getBadgeIconType());
150
+ }
151
+
152
+ if (androidModel.getCategory() != null) {
153
+ builder.setCategory(androidModel.getCategory());
154
+ }
155
+
156
+ if (androidModel.getColor() != null) {
157
+ builder.setColor(androidModel.getColor());
158
+ }
159
+
160
+ builder.setColorized(androidModel.getColorized());
161
+
162
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
163
+ builder.setChronometerCountDown(androidModel.getChronometerCountDown());
164
+ }
165
+
166
+ if (androidModel.getGroup() != null) {
167
+ builder.setGroup(androidModel.getGroup());
168
+ }
169
+
170
+ builder.setGroupAlertBehavior(androidModel.getGroupAlertBehaviour());
171
+ builder.setGroupSummary(androidModel.getGroupSummary());
172
+
173
+ if (androidModel.getInputHistory() != null) {
174
+ builder.setRemoteInputHistory(androidModel.getInputHistory());
175
+ }
176
+
177
+ if (androidModel.getLights() != null) {
178
+ ArrayList<Integer> lights = androidModel.getLights();
179
+ builder.setLights(lights.get(0), lights.get(1), lights.get(2));
180
+ }
181
+
182
+ builder.setLocalOnly(androidModel.getLocalOnly());
183
+
184
+ if (androidModel.getNumber() != null) {
185
+ builder.setNumber(androidModel.getNumber());
186
+ }
187
+
188
+ if (androidModel.getSound() != null) {
189
+ Uri soundUri = ResourceUtils.getSoundUri(androidModel.getSound());
190
+ if (soundUri != null) {
191
+ hasCustomSound = true;
192
+ builder.setSound(soundUri);
193
+ } else {
194
+ Logger.w(
195
+ TAG,
196
+ "Unable to retrieve sound for notification, sound was specified as: "
197
+ + androidModel.getSound());
198
+ }
199
+ }
200
+
201
+ builder.setDefaults(androidModel.getDefaults(hasCustomSound));
202
+ builder.setOngoing(androidModel.getOngoing());
203
+ builder.setOnlyAlertOnce(androidModel.getOnlyAlertOnce());
204
+ builder.setPriority(androidModel.getPriority());
205
+
206
+ NotificationAndroidModel.AndroidProgress progress = androidModel.getProgress();
207
+ if (progress != null) {
208
+ builder.setProgress(
209
+ progress.getMax(), progress.getCurrent(), progress.getIndeterminate());
210
+ }
211
+
212
+ if (androidModel.getShortcutId() != null) {
213
+ builder.setShortcutId(androidModel.getShortcutId());
214
+ }
215
+
216
+ builder.setShowWhen(androidModel.getShowTimestamp());
217
+
218
+ Integer smallIconId = androidModel.getSmallIcon();
219
+ if (smallIconId != null) {
220
+ Integer smallIconLevel = androidModel.getSmallIconLevel();
221
+ if (smallIconLevel != null) {
222
+ builder.setSmallIcon(smallIconId, smallIconLevel);
223
+ } else {
224
+ builder.setSmallIcon(smallIconId);
225
+ }
226
+ }
227
+
228
+ if (androidModel.getSortKey() != null) {
229
+ builder.setSortKey(androidModel.getSortKey());
230
+ }
231
+
232
+ if (androidModel.getTicker() != null) {
233
+ builder.setTicker(androidModel.getTicker());
234
+ }
235
+
236
+ if (androidModel.getTimeoutAfter() != null) {
237
+ builder.setTimeoutAfter(androidModel.getTimeoutAfter());
238
+ }
239
+
240
+ builder.setUsesChronometer(androidModel.getShowChronometer());
241
+
242
+ long[] vibrationPattern = androidModel.getVibrationPattern();
243
+ if (vibrationPattern.length > 0) builder.setVibrate(vibrationPattern);
244
+
245
+ builder.setVisibility(androidModel.getVisibility());
246
+
247
+ long timestamp = androidModel.getTimestamp();
248
+ if (timestamp > -1) builder.setWhen(timestamp);
249
+
250
+ builder.setAutoCancel(androidModel.getAutoCancel());
251
+
252
+ return builder;
253
+ };
254
+
255
+ /*
256
+ * A task continuation that fetches the largeIcon through Fresco, if specified.
257
+ */
258
+ AsyncFunction<NotificationCompat.Builder, NotificationCompat.Builder> largeIconContinuation =
259
+ taskResult -> LISTENING_CACHED_THREAD_POOL.submit(() -> {
260
+ NotificationCompat.Builder builder = taskResult;
261
+
262
+ if (androidModel.hasLargeIcon()) {
263
+ String largeIcon = androidModel.getLargeIcon();
264
+ Bitmap largeIconBitmap = null;
265
+
266
+ try {
267
+ largeIconBitmap = ResourceUtils.getImageBitmapFromUrl(largeIcon)
268
+ .get(10, TimeUnit.SECONDS);
269
+ } catch (TimeoutException e) {
270
+ Logger.e(
271
+ TAG,
272
+ "Timeout occurred whilst trying to retrieve a largeIcon image: " + largeIcon,
273
+ e);
274
+ } catch (Exception e) {
275
+ Logger.e(
276
+ TAG,
277
+ "An error occurred whilst trying to retrieve a largeIcon image: " + largeIcon,
278
+ e);
279
+ }
280
+
281
+ if (largeIconBitmap != null) {
282
+ if (androidModel.getCircularLargeIcon()) {
283
+ largeIconBitmap = ResourceUtils.getCircularBitmap(largeIconBitmap);
284
+ }
285
+
286
+ builder.setLargeIcon(largeIconBitmap);
287
+ }
288
+ }
289
+
290
+ return builder;
291
+ });
292
+
293
+ /*
294
+ * A task continuation for full-screen action, if specified.
295
+ */
296
+ AsyncFunction<NotificationCompat.Builder, NotificationCompat.Builder>
297
+ fullScreenActionContinuation =
298
+ taskResult -> LISTENING_CACHED_THREAD_POOL.submit(() -> {
299
+ NotificationCompat.Builder builder = taskResult;
300
+ if (androidModel.hasFullScreenAction()) {
301
+ NotificationAndroidPressActionModel fullScreenActionBundle =
302
+ androidModel.getFullScreenAction();
303
+
304
+ String launchActivity = fullScreenActionBundle.getLaunchActivity();
305
+ Class launchActivityClass = IntentUtils.getLaunchActivity(launchActivity);
306
+ if (launchActivityClass == null) {
307
+ Logger.e(
308
+ TAG,
309
+ String.format(
310
+ "Launch Activity for full-screen action does not exist ('%s').",
311
+ launchActivity));
312
+ return builder;
313
+ }
314
+
315
+ Intent launchIntent = new Intent(getApplicationContext(), launchActivityClass);
316
+ if (fullScreenActionBundle.getLaunchActivityFlags() != -1) {
317
+ launchIntent.addFlags(fullScreenActionBundle.getLaunchActivityFlags());
318
+ }
319
+
320
+ if (fullScreenActionBundle.getMainComponent() != null) {
321
+ launchIntent.putExtra("mainComponent", fullScreenActionBundle.getMainComponent());
322
+ launchIntent.putExtra("notification", notificationModel.toBundle());
323
+ EventBus.postSticky(
324
+ new MainComponentEvent(fullScreenActionBundle.getMainComponent()));
325
+ }
326
+
327
+ PendingIntent fullScreenPendingIntent =
328
+ PendingIntent.getActivity(
329
+ getApplicationContext(),
330
+ notificationModel.getHashCode(),
331
+ launchIntent,
332
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
333
+ builder.setFullScreenIntent(fullScreenPendingIntent, true);
334
+ }
335
+
336
+ return builder;
337
+ });
338
+
339
+ /*
340
+ * A task continuation that builds all actions, if any. Additionally fetches
341
+ * icon bitmaps through Fresco.
342
+ */
343
+ AsyncFunction<NotificationCompat.Builder, NotificationCompat.Builder> actionsContinuation =
344
+ taskResult -> LISTENING_CACHED_THREAD_POOL.submit(() -> {
345
+ NotificationCompat.Builder builder = taskResult;
346
+ ArrayList<NotificationAndroidActionModel> actionBundles = androidModel.getActions();
347
+
348
+ if (actionBundles == null) {
349
+ return builder;
350
+ }
351
+
352
+ for (NotificationAndroidActionModel actionBundle : actionBundles) {
353
+ PendingIntent pendingIntent = null;
354
+ int targetSdkVersion =
355
+ ContextHolder.getApplicationContext().getApplicationInfo().targetSdkVersion;
356
+ if (targetSdkVersion >= Build.VERSION_CODES.S
357
+ && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
358
+ pendingIntent =
359
+ NotificationPendingIntent.createIntent(
360
+ notificationModel.getHashCode(),
361
+ actionBundle.getPressAction().toBundle(),
362
+ TYPE_ACTION_PRESS,
363
+ new String[] {"notification", "pressAction"},
364
+ notificationModel.toBundle(),
365
+ actionBundle.getPressAction().toBundle());
366
+ } else {
367
+ pendingIntent =
368
+ ReceiverService.createIntent(
369
+ ACTION_PRESS_INTENT,
370
+ new String[] {"notification", "pressAction"},
371
+ notificationModel.toBundle(),
372
+ actionBundle.getPressAction().toBundle());
373
+ }
374
+
375
+ String icon = actionBundle.getIcon();
376
+ Bitmap iconBitmap = null;
377
+
378
+ if (icon != null) {
379
+ try {
380
+ iconBitmap =
381
+ ResourceUtils.getImageBitmapFromUrl(actionBundle.getIcon())
382
+ .get(10, TimeUnit.SECONDS);
383
+ } catch (TimeoutException e) {
384
+ Logger.e(
385
+ TAG, "Timeout occurred whilst trying to retrieve an action icon: " + icon, e);
386
+ } catch (Exception e) {
387
+ Logger.e(
388
+ TAG, "An error occurred whilst trying to retrieve an action icon: " + icon, e);
389
+ }
390
+ }
391
+
392
+ IconCompat iconCompat = null;
393
+ if (iconBitmap != null) {
394
+ iconCompat = IconCompat.createWithAdaptiveBitmap(iconBitmap);
395
+ }
396
+
397
+ NotificationCompat.Action.Builder actionBuilder =
398
+ new NotificationCompat.Action.Builder(
399
+ iconCompat, TextUtils.fromHtml(actionBundle.getTitle()), pendingIntent);
400
+
401
+ RemoteInput remoteInput = actionBundle.getRemoteInput(actionBuilder);
402
+ if (remoteInput != null) {
403
+ actionBuilder.addRemoteInput(remoteInput);
404
+ }
405
+
406
+ builder.addAction(actionBuilder.build());
407
+ }
408
+
409
+ return builder;
410
+ });
411
+
412
+ /*
413
+ * A task continuation that builds the notification style, if any. Additionally
414
+ * fetches any image bitmaps (e.g. Person image, or BigPicture image) through
415
+ * Fresco.
416
+ */
417
+ AsyncFunction<NotificationCompat.Builder, NotificationCompat.Builder> styleContinuation =
418
+ builder -> LISTENING_CACHED_THREAD_POOL.submit(() -> {
419
+ NotificationAndroidStyleModel androidStyleBundle = androidModel.getStyle();
420
+ if (androidStyleBundle == null) {
421
+ return builder;
422
+ }
423
+
424
+ ListenableFuture<NotificationCompat.Style> styleTask =
425
+ androidStyleBundle.getStyleTask(LISTENING_CACHED_THREAD_POOL);
426
+ if (styleTask == null) {
427
+ return builder;
428
+ }
429
+
430
+ NotificationCompat.Style style = styleTask.get();
431
+ if (style != null) {
432
+ builder.setStyle(style);
433
+ }
434
+
435
+ return builder;
436
+ });
437
+
438
+ return new ExtendedListenableFuture<>(
439
+ LISTENING_CACHED_THREAD_POOL.submit(builderCallable))
440
+ // get a large image bitmap if largeIcon is set
441
+ .continueWith(largeIconContinuation, LISTENING_CACHED_THREAD_POOL)
442
+ // build notification actions, tasks based to allow image fetching
443
+ .continueWith(actionsContinuation, LISTENING_CACHED_THREAD_POOL)
444
+ // build notification style, tasks based to allow image fetching
445
+ .continueWith(styleContinuation, LISTENING_CACHED_THREAD_POOL)
446
+ // set full screen action, if fullScreenAction is set
447
+ .continueWith(fullScreenActionContinuation, LISTENING_CACHED_THREAD_POOL);
448
+ }
449
+
450
+ static ListenableFuture<Void> cancelAllNotifications(@NonNull int notificationType) {
451
+ return new ExtendedListenableFuture<>(LISTENING_CACHED_THREAD_POOL.submit(
452
+ () -> {
453
+ NotificationManagerCompat notificationManagerCompat =
454
+ NotificationManagerCompat.from(getApplicationContext());
455
+
456
+ if (notificationType == NOTIFICATION_TYPE_DISPLAYED
457
+ || notificationType == NOTIFICATION_TYPE_ALL) {
458
+ notificationManagerCompat.cancelAll();
459
+ }
460
+
461
+ if (notificationType == NOTIFICATION_TYPE_TRIGGER
462
+ || notificationType == NOTIFICATION_TYPE_ALL) {
463
+ WorkManager workManager = WorkManager.getInstance(getApplicationContext());
464
+ workManager.cancelAllWorkByTag(Worker.WORK_TYPE_NOTIFICATION_TRIGGER);
465
+
466
+ // Remove all cancelled and finished work from its internal database
467
+ // states include SUCCEEDED, FAILED and CANCELLED
468
+ workManager.pruneWork();
469
+ }
470
+ return null;
471
+ }))
472
+ .continueWith(task -> {
473
+ if (notificationType == NOTIFICATION_TYPE_TRIGGER
474
+ || notificationType == NOTIFICATION_TYPE_ALL) {
475
+ return new ExtendedListenableFuture<Void>(NotifeeAlarmManager.cancelAllNotifications())
476
+ .addOnCompleteListener((e, result) -> {
477
+ if (e == null) {
478
+ WorkDataRepository.getInstance(getApplicationContext())
479
+ .deleteAll();
480
+ }
481
+ }, LISTENING_CACHED_THREAD_POOL);
482
+ }
483
+ return Futures.immediateFuture(null);
484
+ }, LISTENING_CACHED_THREAD_POOL);
485
+ }
486
+
487
+ static ListenableFuture<Void> cancelAllNotificationsWithIds(
488
+ @NonNull int notificationType, @NonNull List<String> ids, String tag) {
489
+ return new ExtendedListenableFuture<>(LISTENING_CACHED_THREAD_POOL.submit(
490
+ () -> {
491
+ WorkManager workManager = WorkManager.getInstance(getApplicationContext());
492
+ NotificationManagerCompat notificationManagerCompat =
493
+ NotificationManagerCompat.from(getApplicationContext());
494
+
495
+ for (String id : ids) {
496
+ Logger.i(TAG, "Removing notification with id " + id);
497
+
498
+ if (notificationType != NOTIFICATION_TYPE_TRIGGER) {
499
+ // Cancel notifications displayed by FCM which will always have
500
+ // an id of 0 and a tag, see https://github.com/invertase/notifee/pull/175
501
+ if (tag != null && id.equals("0")) {
502
+ // Attempt to parse id as integer
503
+ Integer integerId = null;
504
+
505
+ try {
506
+ integerId = parseInt(id);
507
+ } catch (Exception e) {
508
+ Logger.e(
509
+ TAG,
510
+ "cancelAllNotificationsWithIds -> Failed to parse id as integer " + id);
511
+ }
512
+
513
+ if (integerId != null) {
514
+ notificationManagerCompat.cancel(tag, integerId);
515
+ }
516
+ }
517
+
518
+ // Cancel a notification created with notifee
519
+ notificationManagerCompat.cancel(tag, id.hashCode());
520
+ }
521
+
522
+ if (notificationType != NOTIFICATION_TYPE_DISPLAYED) {
523
+ Logger.i(TAG, "Removing notification with id " + id);
524
+
525
+ workManager.cancelUniqueWork("trigger:" + id);
526
+ // Remove all cancelled and finished work from its internal database
527
+ // states include SUCCEEDED, FAILED and CANCELLED
528
+ workManager.pruneWork();
529
+
530
+ // And with alarm manager
531
+ NotifeeAlarmManager.cancelNotification(id);
532
+ }
533
+ }
534
+
535
+ return null;
536
+ }))
537
+ .continueWith(
538
+ task -> {
539
+ // delete all from database
540
+ if (notificationType != NOTIFICATION_TYPE_DISPLAYED) {
541
+ WorkDataRepository.getInstance(getApplicationContext()).deleteByIds(ids);
542
+ }
543
+ return Futures.immediateFuture(null);
544
+ }, LISTENING_CACHED_THREAD_POOL);
545
+ }
546
+
547
+ static ListenableFuture<Void> displayNotification(NotificationModel notificationModel, Bundle triggerBundle) {
548
+ return new ExtendedListenableFuture<>(notificationBundleToBuilder(notificationModel))
549
+ .continueWith(
550
+ (taskResult) -> {
551
+ NotificationCompat.Builder builder = taskResult;
552
+
553
+ // Add the following extras for `getDisplayedNotifications()`
554
+ Bundle extrasBundle = new Bundle();
555
+ extrasBundle.putBundle(EXTRA_NOTIFEE_NOTIFICATION, notificationModel.toBundle());
556
+ if (triggerBundle != null) {
557
+ extrasBundle.putBundle(EXTRA_NOTIFEE_TRIGGER, triggerBundle);
558
+ }
559
+ builder.addExtras(extrasBundle);
560
+
561
+ // build notification
562
+ Notification notification = Objects.requireNonNull(builder).build();
563
+
564
+ int hashCode = notificationModel.getHashCode();
565
+
566
+ NotificationAndroidModel androidBundle = notificationModel.getAndroid();
567
+ if (androidBundle.getLoopSound()) {
568
+ notification.flags |= Notification.FLAG_INSISTENT;
569
+ }
570
+
571
+ if (androidBundle.getFlags() != null && androidBundle.getFlags().length > 0) {
572
+ for (int flag : androidBundle.getFlags()) {
573
+ notification.flags |= flag;
574
+ }
575
+ }
576
+
577
+ if (androidBundle.getLightUpScreen()) {
578
+ PowerManagerUtils.lightUpScreenIfNeeded(ContextHolder.getApplicationContext());
579
+ }
580
+
581
+ if (androidBundle.getAsForegroundService()) {
582
+ ForegroundService.start(hashCode, notification, notificationModel.toBundle());
583
+ } else {
584
+ NotificationManagerCompat.from(getApplicationContext())
585
+ .notify(androidBundle.getTag(), hashCode, notification);
586
+ }
587
+
588
+ EventBus.post(
589
+ new NotificationEvent(NotificationEvent.TYPE_DELIVERED, notificationModel));
590
+
591
+ return Futures.immediateFuture(null);
592
+ }, CACHED_THREAD_POOL);
593
+ }
594
+
595
+ static ListenableFuture<Void> createTriggerNotification(
596
+ NotificationModel notificationModel, Bundle triggerBundle) {
597
+ return
598
+ LISTENING_CACHED_THREAD_POOL.submit(
599
+ () -> {
600
+ int triggerType = ObjectUtils.getInt(triggerBundle.get("type"));
601
+ switch (triggerType) {
602
+ case 0:
603
+ createTimestampTriggerNotification(notificationModel, triggerBundle);
604
+ break;
605
+ case 1:
606
+ createIntervalTriggerNotification(notificationModel, triggerBundle);
607
+ break;
608
+ }
609
+
610
+ EventBus.post(
611
+ new NotificationEvent(
612
+ NotificationEvent.TYPE_TRIGGER_NOTIFICATION_CREATED, notificationModel));
613
+
614
+ return null;
615
+ });
616
+ }
617
+
618
+ static void createIntervalTriggerNotification(
619
+ NotificationModel notificationModel, Bundle triggerBundle) {
620
+ IntervalTriggerModel trigger = IntervalTriggerModel.fromBundle(triggerBundle);
621
+ String uniqueWorkName = "trigger:" + notificationModel.getId();
622
+ WorkManager workManager = WorkManager.getInstance(getApplicationContext());
623
+
624
+ Data.Builder workDataBuilder =
625
+ new Data.Builder()
626
+ .putString(Worker.KEY_WORK_TYPE, Worker.WORK_TYPE_NOTIFICATION_TRIGGER)
627
+ .putString(Worker.KEY_WORK_REQUEST, Worker.WORK_REQUEST_PERIODIC)
628
+ .putString("id", notificationModel.getId());
629
+
630
+ WorkDataRepository.getInstance(getApplicationContext())
631
+ .insertTriggerNotification(notificationModel, triggerBundle, false);
632
+
633
+ long interval = trigger.getInterval();
634
+
635
+ PeriodicWorkRequest.Builder workRequestBuilder;
636
+ workRequestBuilder =
637
+ new PeriodicWorkRequest.Builder(Worker.class, interval, trigger.getTimeUnit());
638
+
639
+ workRequestBuilder.addTag(Worker.WORK_TYPE_NOTIFICATION_TRIGGER);
640
+ workRequestBuilder.addTag(uniqueWorkName);
641
+ workRequestBuilder.setInputData(workDataBuilder.build());
642
+ workManager.enqueueUniquePeriodicWork(
643
+ uniqueWorkName, ExistingPeriodicWorkPolicy.REPLACE, workRequestBuilder.build());
644
+ }
645
+
646
+ static void createTimestampTriggerNotification(
647
+ NotificationModel notificationModel, Bundle triggerBundle) {
648
+ TimestampTriggerModel trigger = TimestampTriggerModel.fromBundle(triggerBundle);
649
+
650
+ String uniqueWorkName = "trigger:" + notificationModel.getId();
651
+
652
+ long delay = trigger.getDelay();
653
+ int interval = trigger.getInterval();
654
+
655
+ // Save in DB
656
+ Data.Builder workDataBuilder =
657
+ new Data.Builder()
658
+ .putString(Worker.KEY_WORK_TYPE, Worker.WORK_TYPE_NOTIFICATION_TRIGGER)
659
+ .putString("id", notificationModel.getId());
660
+
661
+ Boolean withAlarmManager = trigger.getWithAlarmManager();
662
+
663
+ WorkDataRepository.getInstance(getApplicationContext())
664
+ .insertTriggerNotification(notificationModel, triggerBundle, withAlarmManager);
665
+
666
+ // Schedule notification with alarm manager
667
+ if (withAlarmManager) {
668
+ NotifeeAlarmManager.scheduleTimestampTriggerNotification(notificationModel, trigger);
669
+ return;
670
+ }
671
+
672
+ // Continue to schedule trigger notification with WorkManager
673
+ WorkManager workManager = WorkManager.getInstance(getApplicationContext());
674
+
675
+ // WorkManager - One time trigger
676
+ if (interval == -1) {
677
+ OneTimeWorkRequest.Builder workRequestBuilder = new OneTimeWorkRequest.Builder(Worker.class);
678
+ workRequestBuilder.addTag(Worker.WORK_TYPE_NOTIFICATION_TRIGGER);
679
+ workRequestBuilder.addTag(uniqueWorkName);
680
+ workDataBuilder.putString(Worker.KEY_WORK_REQUEST, Worker.WORK_REQUEST_ONE_TIME);
681
+ workRequestBuilder.setInputData(workDataBuilder.build());
682
+ workRequestBuilder.setInitialDelay(delay, TimeUnit.SECONDS);
683
+ workManager.enqueueUniqueWork(
684
+ uniqueWorkName, ExistingWorkPolicy.REPLACE, workRequestBuilder.build());
685
+ } else {
686
+ // WorkManager - repeat trigger
687
+ PeriodicWorkRequest.Builder workRequestBuilder;
688
+
689
+ workRequestBuilder =
690
+ new PeriodicWorkRequest.Builder(
691
+ Worker.class, trigger.getInterval(), trigger.getTimeUnit());
692
+
693
+ workRequestBuilder.addTag(Worker.WORK_TYPE_NOTIFICATION_TRIGGER);
694
+ workRequestBuilder.addTag(uniqueWorkName);
695
+ workRequestBuilder.setInitialDelay(delay, TimeUnit.SECONDS);
696
+ workDataBuilder.putString(Worker.KEY_WORK_REQUEST, Worker.WORK_REQUEST_PERIODIC);
697
+ workRequestBuilder.setInputData(workDataBuilder.build());
698
+ workManager.enqueueUniquePeriodicWork(
699
+ uniqueWorkName, ExistingPeriodicWorkPolicy.REPLACE, workRequestBuilder.build());
700
+ }
701
+ }
702
+
703
+ static ListenableFuture<List<Bundle>> getDisplayedNotifications() {
704
+ return LISTENING_CACHED_THREAD_POOL.submit(
705
+ () -> {
706
+ List<Bundle> notifications = new ArrayList<Bundle>();
707
+
708
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
709
+ return notifications;
710
+ }
711
+
712
+ android.app.NotificationManager notificationManager =
713
+ (android.app.NotificationManager)
714
+ getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
715
+
716
+ StatusBarNotification delivered[] = notificationManager.getActiveNotifications();
717
+
718
+ for (StatusBarNotification sbNotification : delivered) {
719
+ Notification original = sbNotification.getNotification();
720
+
721
+ Bundle extras = original.extras;
722
+ Bundle displayNotificationBundle = new Bundle();
723
+
724
+ Bundle notificationBundle = extras.getBundle(EXTRA_NOTIFEE_NOTIFICATION);
725
+ Bundle triggerBundle = extras.getBundle(EXTRA_NOTIFEE_TRIGGER);
726
+
727
+ if (notificationBundle == null) {
728
+ notificationBundle = new Bundle();
729
+ notificationBundle.putString("id", "" + sbNotification.getId());
730
+
731
+ Object title = extras.get(Notification.EXTRA_TITLE);
732
+
733
+ if (title != null) {
734
+ notificationBundle.putString("title", title.toString());
735
+ }
736
+
737
+ Object text = extras.get(Notification.EXTRA_TEXT);
738
+
739
+ if (text != null) {
740
+ notificationBundle.putString("body", text.toString());
741
+ }
742
+
743
+ Object subtitle = extras.get(Notification.EXTRA_SUB_TEXT);
744
+
745
+ if (subtitle != null) {
746
+ notificationBundle.putString("subtitle", subtitle.toString());
747
+ }
748
+
749
+ Bundle androidBundle = new Bundle();
750
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
751
+ androidBundle.putString("channelId", original.getChannelId());
752
+ }
753
+ androidBundle.putString("tag", sbNotification.getTag());
754
+ androidBundle.putString("group", original.getGroup());
755
+
756
+ notificationBundle.putBundle("android", androidBundle);
757
+
758
+ displayNotificationBundle.putString("id", "" + sbNotification.getId());
759
+ } else {
760
+ displayNotificationBundle.putString("id", "" + notificationBundle.get("id"));
761
+ }
762
+
763
+ if (triggerBundle != null) {
764
+ displayNotificationBundle.putBundle("trigger", triggerBundle);
765
+ }
766
+
767
+ displayNotificationBundle.putBundle("notification", notificationBundle);
768
+ displayNotificationBundle.putString("date", "" + sbNotification.getPostTime());
769
+
770
+ notifications.add(displayNotificationBundle);
771
+ }
772
+
773
+ return notifications;
774
+ });
775
+ }
776
+
777
+ static void getTriggerNotifications(MethodCallResult<List<Bundle>> result) {
778
+ WorkDataRepository workDataRepository = new WorkDataRepository(getApplicationContext());
779
+
780
+ List<Bundle> triggerNotifications = new ArrayList<Bundle>();
781
+
782
+ Futures.addCallback(workDataRepository.getAll(),
783
+ new FutureCallback<List<WorkDataEntity>>() {
784
+ @Override
785
+ public void onSuccess(List<WorkDataEntity> workDataEntities) {
786
+ for (WorkDataEntity workDataEntity : workDataEntities) {
787
+ Bundle triggerNotificationBundle = new Bundle();
788
+
789
+ triggerNotificationBundle.putBundle(
790
+ "notification", ObjectUtils.bytesToBundle(workDataEntity.getNotification()));
791
+
792
+ triggerNotificationBundle.putBundle(
793
+ "trigger", ObjectUtils.bytesToBundle(workDataEntity.getTrigger()));
794
+ triggerNotifications.add(triggerNotificationBundle);
795
+ }
796
+
797
+ result.onComplete(null, triggerNotifications);
798
+ }
799
+
800
+ @Override
801
+ public void onFailure(Throwable t) {
802
+ result.onComplete(new Exception(t), triggerNotifications);
803
+ }
804
+ }, LISTENING_CACHED_THREAD_POOL);
805
+ }
806
+
807
+ static void getTriggerNotificationIds(MethodCallResult<List<String>> result) {
808
+ WorkDataRepository workDataRepository = new WorkDataRepository(getApplicationContext());
809
+
810
+ Futures.addCallback(workDataRepository
811
+ .getAll(),
812
+ new FutureCallback<List<WorkDataEntity>>() {
813
+ @Override
814
+ public void onSuccess(List<WorkDataEntity> workDataEntities) {
815
+ List<String> triggerNotificationIds = new ArrayList<String>();
816
+ for (WorkDataEntity workDataEntity : workDataEntities) {
817
+ triggerNotificationIds.add(workDataEntity.getId());
818
+ }
819
+
820
+ result.onComplete(null, triggerNotificationIds);
821
+ }
822
+
823
+ @Override
824
+ public void onFailure(Throwable t) {
825
+ result.onComplete(new Exception(t), null);
826
+ }
827
+ }, LISTENING_CACHED_THREAD_POOL);
828
+
829
+ }
830
+
831
+ /* Execute work from trigger notifications via WorkManager*/
832
+ static void doScheduledWork(
833
+ Data data, CallbackToFutureAdapter.Completer<ListenableWorker.Result> completer) {
834
+
835
+ String id = data.getString("id");
836
+
837
+ WorkDataRepository workDataRepository = new WorkDataRepository(getApplicationContext());
838
+
839
+ AsyncFunction<WorkDataEntity, ListenableFuture<Void>> workContinuation =
840
+ workDataEntity -> LISTENING_CACHED_THREAD_POOL.submit(() -> {
841
+ byte[] notificationBytes;
842
+
843
+ if (workDataEntity == null || workDataEntity.getNotification() == null) {
844
+ // check if notification bundle is stored with Work Manager
845
+ notificationBytes = data.getByteArray("notification");
846
+ if (notificationBytes != null) {
847
+ Logger.w(
848
+ TAG,
849
+ "The trigger notification was created using an older version, please consider"
850
+ + " recreating the notification.");
851
+ } else {
852
+ Logger.w(
853
+ TAG, "Attempted to handle doScheduledWork but no notification data was found.");
854
+ completer.set(ListenableWorker.Result.success());
855
+ return Futures.immediateFuture(null);
856
+ }
857
+ } else {
858
+ notificationBytes = workDataEntity.getNotification();
859
+ }
860
+
861
+ NotificationModel notificationModel =
862
+ NotificationModel.fromBundle(ObjectUtils.bytesToBundle(notificationBytes));
863
+
864
+ byte[] triggerBytes = workDataEntity.getTrigger();
865
+ Bundle triggerBundle = null;
866
+
867
+ if (workDataEntity.getTrigger() != null) {
868
+ triggerBundle = ObjectUtils.bytesToBundle(triggerBytes);
869
+ }
870
+
871
+ return NotificationManager.displayNotification(notificationModel, triggerBundle);
872
+ });
873
+
874
+ new ExtendedListenableFuture<>(workDataRepository
875
+ .getWorkDataById(id))
876
+ .continueWith(workContinuation, LISTENING_CACHED_THREAD_POOL)
877
+ .addOnCompleteListener(
878
+ (e, result) -> {
879
+ if (e == null) {
880
+ new ExtendedListenableFuture<>(result).addOnCompleteListener(
881
+ (e2, _unused) -> {
882
+ completer.set(Result.success());
883
+ if (e2 != null) {
884
+ Logger.e(TAG, "Failed to display notification", e2);
885
+ } else {
886
+ String workerRequestType = data.getString(
887
+ Worker.KEY_WORK_REQUEST);
888
+ if (workerRequestType != null
889
+ && workerRequestType.equals(
890
+ Worker.WORK_REQUEST_ONE_TIME)) {
891
+ // delete database entry if work is a one-time request
892
+ WorkDataRepository.getInstance(getApplicationContext())
893
+ .deleteById(id);
894
+ }
895
+ }
896
+ }, LISTENING_CACHED_THREAD_POOL);
897
+ } else {
898
+ completer.set(Result.success());
899
+ Logger.e(TAG, "Failed to display notification", e);
900
+ }
901
+ }, LISTENING_CACHED_THREAD_POOL);
902
+ }
903
+ }