react-native-notify-kit 9.1.21 → 9.2.0

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