react-native-notify-kit 10.2.0 → 10.3.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.
- package/README.md +6 -6
- package/android/src/main/AndroidManifest.xml +0 -1
- package/android/src/main/java/app/notifee/core/ForegroundService.java +3 -1
- package/android/src/main/java/app/notifee/core/NotifeeAlarmManager.java +4 -2
- package/android/src/main/java/app/notifee/core/NotificationManager.java +43 -43
- package/android/src/main/java/app/notifee/core/ReceiverService.java +18 -13
- package/android/src/main/java/app/notifee/core/database/NotifeeCoreDatabase.java +1 -4
- package/android/src/main/java/app/notifee/core/model/ChannelModel.java +8 -13
- package/android/src/main/java/app/notifee/core/model/IntervalTriggerModel.java +2 -6
- package/android/src/main/java/app/notifee/core/model/NotificationAndroidActionModel.java +1 -2
- package/android/src/main/java/app/notifee/core/model/NotificationAndroidModel.java +30 -33
- package/android/src/main/java/app/notifee/core/model/NotificationAndroidPressActionModel.java +3 -9
- package/android/src/main/java/app/notifee/core/model/NotificationAndroidStyleModel.java +6 -4
- package/android/src/main/java/app/notifee/core/model/TimestampTriggerModel.java +7 -4
- package/android/src/main/java/app/notifee/core/utility/BundleValueReader.java +65 -0
- package/android/src/main/java/app/notifee/core/utility/IntentUtils.java +0 -28
- package/android/src/main/java/app/notifee/core/utility/ObjectUtils.java +1 -1
- package/android/src/main/java/app/notifee/core/utility/ParcelableCompatReader.java +64 -0
- package/android/src/main/java/app/notifee/core/utility/PowerManagerUtils.java +79 -41
- package/android/src/main/java/app/notifee/core/utility/ResourceUtils.java +3 -0
- package/android/src/main/kotlin/io/invertase/notifee/NotifeeApiModule.kt +1 -1
- package/android/src/main/kotlin/io/invertase/notifee/NotifeeInitProvider.kt +0 -26
- package/android/src/main/kotlin/io/invertase/notifee/NotifeeReactUtils.kt +1 -2
- package/android/src/test/java/app/notifee/core/NotifeeAlarmManagerCurrentBehaviorTest.java +446 -0
- package/android/src/test/java/app/notifee/core/NotificationManagerCreateTriggerNotificationCurrentBehaviorTest.java +203 -0
- package/android/src/test/java/app/notifee/core/NotificationManagerPressActionOptOutTest.java +180 -0
- package/android/src/test/java/app/notifee/core/NotificationReceiverHandlerInitialNotificationTest.java +157 -0
- package/android/src/test/java/app/notifee/core/ReceiverServiceInitialNotificationTest.java +169 -0
- package/android/src/test/java/app/notifee/core/model/ChannelModelTest.java +116 -0
- package/android/src/test/java/app/notifee/core/model/IntervalTriggerModelTest.java +71 -0
- package/android/src/test/java/app/notifee/core/model/NotificationAndroidModelTest.java +309 -0
- package/android/src/test/java/app/notifee/core/model/NotificationAndroidStyleModelTest.java +154 -0
- package/android/src/test/java/app/notifee/core/model/TimestampTriggerModelTest.java +296 -14
- package/android/src/test/java/app/notifee/core/utility/BundleValueReaderTest.java +131 -0
- package/android/src/test/java/app/notifee/core/utility/ObjectUtilsTest.java +142 -0
- package/android/src/test/java/app/notifee/core/utility/ParcelableCompatReaderTest.java +124 -0
- package/android/src/test/java/io/invertase/notifee/HeadlessTaskReactHostTest.java +136 -0
- package/android/src/test/java/io/invertase/notifee/NotifeeEventSubscriberRoutingTest.java +262 -0
- package/android/src/test/java/io/invertase/notifee/NotifeeReactUtilsDrawerTest.java +24 -0
- package/cli/dist/lib/patchPodfile.d.ts +3 -1
- package/cli/dist/lib/patchPodfile.js +125 -33
- package/cli/dist/lib/patchPodfile.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/types/Module.d.ts +20 -6
- package/dist/types/Notification.d.ts +5 -2
- package/dist/types/Notification.js +5 -2
- package/dist/types/Notification.js.map +1 -1
- package/dist/types/NotificationAndroid.d.ts +4 -0
- package/dist/types/NotificationAndroid.js +4 -0
- package/dist/types/NotificationAndroid.js.map +1 -1
- package/dist/types/PowerManagerInfo.d.ts +9 -4
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/ios/NotifeeCore/NotifeeCore.m +101 -114
- package/package.json +1 -1
- package/src/index.ts +2 -0
- package/src/types/Module.ts +20 -6
- package/src/types/Notification.ts +5 -2
- package/src/types/NotificationAndroid.ts +4 -0
- package/src/types/PowerManagerInfo.ts +9 -4
- package/src/version.ts +1 -1
|
@@ -0,0 +1,446 @@
|
|
|
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
|
+
import static org.junit.Assert.assertEquals;
|
|
20
|
+
import static org.junit.Assert.assertNotNull;
|
|
21
|
+
import static org.junit.Assert.assertNull;
|
|
22
|
+
import static org.junit.Assert.assertThrows;
|
|
23
|
+
import static org.junit.Assert.assertTrue;
|
|
24
|
+
import static org.mockito.ArgumentMatchers.any;
|
|
25
|
+
import static org.mockito.Mockito.mock;
|
|
26
|
+
import static org.mockito.Mockito.mockStatic;
|
|
27
|
+
import static org.mockito.Mockito.never;
|
|
28
|
+
import static org.mockito.Mockito.timeout;
|
|
29
|
+
import static org.mockito.Mockito.verify;
|
|
30
|
+
import static org.mockito.Mockito.when;
|
|
31
|
+
|
|
32
|
+
import android.app.AlarmManager;
|
|
33
|
+
import android.content.BroadcastReceiver;
|
|
34
|
+
import android.content.Context;
|
|
35
|
+
import android.os.Bundle;
|
|
36
|
+
import androidx.core.app.NotificationChannelCompat;
|
|
37
|
+
import androidx.core.app.NotificationManagerCompat;
|
|
38
|
+
import app.notifee.core.database.WorkDataEntity;
|
|
39
|
+
import app.notifee.core.database.WorkDataRepository;
|
|
40
|
+
import app.notifee.core.utility.AlarmUtils;
|
|
41
|
+
import app.notifee.core.utility.ObjectUtils;
|
|
42
|
+
import com.google.common.util.concurrent.Futures;
|
|
43
|
+
import com.google.common.util.concurrent.ListenableFuture;
|
|
44
|
+
import java.util.List;
|
|
45
|
+
import java.util.concurrent.TimeUnit;
|
|
46
|
+
import org.junit.After;
|
|
47
|
+
import org.junit.Before;
|
|
48
|
+
import org.junit.Test;
|
|
49
|
+
import org.junit.runner.RunWith;
|
|
50
|
+
import org.mockito.ArgumentCaptor;
|
|
51
|
+
import org.mockito.MockedStatic;
|
|
52
|
+
import org.robolectric.RobolectricTestRunner;
|
|
53
|
+
import org.robolectric.RuntimeEnvironment;
|
|
54
|
+
import org.robolectric.annotation.Config;
|
|
55
|
+
|
|
56
|
+
@RunWith(RobolectricTestRunner.class)
|
|
57
|
+
@Config(sdk = 28)
|
|
58
|
+
public class NotifeeAlarmManagerCurrentBehaviorTest {
|
|
59
|
+
private static final String CHANNEL_ID = "manager-current-behavior-channel";
|
|
60
|
+
private static final long OLD_ANCHOR_OFFSET_MS = TimeUnit.HOURS.toMillis(2);
|
|
61
|
+
|
|
62
|
+
private Context context;
|
|
63
|
+
private WorkDataRepository repo;
|
|
64
|
+
|
|
65
|
+
@Before
|
|
66
|
+
public void setUp() throws Exception {
|
|
67
|
+
context = RuntimeEnvironment.getApplication();
|
|
68
|
+
ContextHolder.setApplicationContext(context);
|
|
69
|
+
repo = WorkDataRepository.getInstance(context);
|
|
70
|
+
repo.deleteAll().get(5, TimeUnit.SECONDS);
|
|
71
|
+
|
|
72
|
+
NotificationChannelCompat channel =
|
|
73
|
+
new NotificationChannelCompat.Builder(
|
|
74
|
+
CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_DEFAULT)
|
|
75
|
+
.setName("Manager Current Behavior")
|
|
76
|
+
.build();
|
|
77
|
+
NotificationManagerCompat.from(context).createNotificationChannel(channel);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@After
|
|
81
|
+
public void tearDown() throws Exception {
|
|
82
|
+
if (repo != null) {
|
|
83
|
+
repo.deleteAll().get(5, TimeUnit.SECONDS);
|
|
84
|
+
}
|
|
85
|
+
if (context != null) {
|
|
86
|
+
NotificationManagerCompat.from(context).cancelAll();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@Test
|
|
91
|
+
public void displayScheduledNotification_missingRepeatFrequency_deletesRoomRow()
|
|
92
|
+
throws Exception {
|
|
93
|
+
String id = "am-repeat-missing";
|
|
94
|
+
long oldAnchor = oldAnchor();
|
|
95
|
+
seedDisplayRow(id, timestampTriggerWithRepeatValue(null, false, oldAnchor));
|
|
96
|
+
|
|
97
|
+
runDisplayScheduledNotification(id);
|
|
98
|
+
|
|
99
|
+
assertNull(
|
|
100
|
+
"missing repeatFrequency must take the one-shot delete branch",
|
|
101
|
+
repo.getWorkDataById(id).get(5, TimeUnit.SECONDS));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
@Test
|
|
105
|
+
public void displayScheduledNotification_explicitNullRepeatFrequency_updatesRoomRow()
|
|
106
|
+
throws Exception {
|
|
107
|
+
String id = "am-repeat-null";
|
|
108
|
+
long oldAnchor = oldAnchor();
|
|
109
|
+
seedDisplayRow(id, timestampTriggerWithRepeatValue(null, true, oldAnchor));
|
|
110
|
+
|
|
111
|
+
long minExpectedAnchor = System.currentTimeMillis();
|
|
112
|
+
runDisplayScheduledNotification(id);
|
|
113
|
+
|
|
114
|
+
assertRowUpdatedToFuture(id, oldAnchor, minExpectedAnchor);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@Test
|
|
118
|
+
public void displayScheduledNotification_minusOneRepeatFrequency_deletesRoomRow()
|
|
119
|
+
throws Exception {
|
|
120
|
+
String id = "am-repeat-minus-one";
|
|
121
|
+
long oldAnchor = oldAnchor();
|
|
122
|
+
seedDisplayRow(id, timestampTriggerWithRepeatValue(Integer.valueOf(-1), true, oldAnchor));
|
|
123
|
+
|
|
124
|
+
runDisplayScheduledNotification(id);
|
|
125
|
+
|
|
126
|
+
assertNull(
|
|
127
|
+
"repeatFrequency -1 must take the one-shot delete branch",
|
|
128
|
+
repo.getWorkDataById(id).get(5, TimeUnit.SECONDS));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@Test
|
|
132
|
+
public void displayScheduledNotification_integerRepeatFrequencies_updateRoomRow()
|
|
133
|
+
throws Exception {
|
|
134
|
+
for (int repeatFrequency = 0; repeatFrequency <= 3; repeatFrequency++) {
|
|
135
|
+
String id = "am-repeat-int-" + repeatFrequency;
|
|
136
|
+
long oldAnchor = oldAnchor();
|
|
137
|
+
seedDisplayRow(id, timestampTriggerWithRepeatValue(repeatFrequency, true, oldAnchor));
|
|
138
|
+
|
|
139
|
+
long minExpectedAnchor = System.currentTimeMillis();
|
|
140
|
+
runDisplayScheduledNotification(id);
|
|
141
|
+
|
|
142
|
+
assertRowUpdatedToFuture(id, oldAnchor, minExpectedAnchor);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
@Test
|
|
147
|
+
public void displayScheduledNotification_doubleRepeatFrequency_truncatesAndUpdatesRoomRow()
|
|
148
|
+
throws Exception {
|
|
149
|
+
String id = "am-repeat-double";
|
|
150
|
+
long oldAnchor = oldAnchor();
|
|
151
|
+
seedDisplayRow(id, timestampTriggerWithRepeatValue(Double.valueOf(0.9d), true, oldAnchor));
|
|
152
|
+
|
|
153
|
+
long minExpectedAnchor = System.currentTimeMillis();
|
|
154
|
+
runDisplayScheduledNotification(id);
|
|
155
|
+
|
|
156
|
+
assertRowUpdatedToFuture(id, oldAnchor, minExpectedAnchor);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
@Test
|
|
160
|
+
public void displayScheduledNotification_unsupportedRepeatFrequency_keepsRoomRowUnchanged()
|
|
161
|
+
throws Exception {
|
|
162
|
+
assertUnsupportedRepeatFrequencyKeepsRow(Long.valueOf(0L), "long");
|
|
163
|
+
assertUnsupportedRepeatFrequencyKeepsRow(Float.valueOf(0.5f), "float");
|
|
164
|
+
assertUnsupportedRepeatFrequencyKeepsRow("0", "string");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
@Test
|
|
168
|
+
public void rescheduleNotification_missingType_dispatchesTimestampAndUpdatesRoomRow()
|
|
169
|
+
throws Exception {
|
|
170
|
+
WorkDataEntity updated =
|
|
171
|
+
runRescheduleNotificationExpectingUpdate(rescheduleTriggerWithType(null, false));
|
|
172
|
+
|
|
173
|
+
assertEquals("am-type", updated.getId());
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
@Test
|
|
177
|
+
public void rescheduleNotification_explicitNullType_dispatchesTimestampAndUpdatesRoomRow()
|
|
178
|
+
throws Exception {
|
|
179
|
+
WorkDataEntity updated =
|
|
180
|
+
runRescheduleNotificationExpectingUpdate(rescheduleTriggerWithType(null, true));
|
|
181
|
+
|
|
182
|
+
assertEquals("am-type", updated.getId());
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
@Test
|
|
186
|
+
public void rescheduleNotification_integerZero_dispatchesTimestampAndUpdatesRoomRow()
|
|
187
|
+
throws Exception {
|
|
188
|
+
WorkDataEntity updated =
|
|
189
|
+
runRescheduleNotificationExpectingUpdate(
|
|
190
|
+
rescheduleTriggerWithType(Integer.valueOf(0), true));
|
|
191
|
+
|
|
192
|
+
assertEquals("am-type", updated.getId());
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
@Test
|
|
196
|
+
public void rescheduleNotification_integerOne_completesWithoutRoomUpdate() throws Exception {
|
|
197
|
+
runRescheduleNotificationExpectingNoUpdate(rescheduleTriggerWithType(Integer.valueOf(1), true));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
@Test
|
|
201
|
+
public void rescheduleNotification_otherInteger_completesWithoutRoomUpdate() throws Exception {
|
|
202
|
+
runRescheduleNotificationExpectingNoUpdate(rescheduleTriggerWithType(Integer.valueOf(99), true));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
@Test
|
|
206
|
+
public void rescheduleNotification_doubleType_truncatesToTimestampBranch() throws Exception {
|
|
207
|
+
WorkDataEntity updated =
|
|
208
|
+
runRescheduleNotificationExpectingUpdate(
|
|
209
|
+
rescheduleTriggerWithType(Double.valueOf(0.9d), true));
|
|
210
|
+
|
|
211
|
+
assertEquals("am-type", updated.getId());
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
@Test
|
|
215
|
+
public void rescheduleNotification_unsupportedType_preservesClassCastException() {
|
|
216
|
+
assertUnsupportedTypeThrows(Long.valueOf(0L));
|
|
217
|
+
assertUnsupportedTypeThrows(Float.valueOf(0.5f));
|
|
218
|
+
assertUnsupportedTypeThrows("0");
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
@Test
|
|
222
|
+
public void rescheduleNotifications_intervalAndDefaultType_keepRoomRowsUnchanged()
|
|
223
|
+
throws Exception {
|
|
224
|
+
assertRescheduleNotificationsKeepsRowUnchanged(Integer.valueOf(1), "interval");
|
|
225
|
+
assertRescheduleNotificationsKeepsRowUnchanged(Integer.valueOf(99), "default");
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
@Test
|
|
229
|
+
public void rescheduleNotifications_unsupportedType_keepsRoomRowAfterPerEntityCatch()
|
|
230
|
+
throws Exception {
|
|
231
|
+
assertRescheduleNotificationsKeepsRowUnchanged(Long.valueOf(0L), "long");
|
|
232
|
+
assertRescheduleNotificationsKeepsRowUnchanged(Float.valueOf(0.5f), "float");
|
|
233
|
+
assertRescheduleNotificationsKeepsRowUnchanged("0", "string");
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
private void assertUnsupportedRepeatFrequencyKeepsRow(Object repeatFrequency, String suffix)
|
|
237
|
+
throws Exception {
|
|
238
|
+
String id = "am-repeat-" + suffix;
|
|
239
|
+
long oldAnchor = oldAnchor();
|
|
240
|
+
seedDisplayRow(id, timestampTriggerWithRepeatValue(repeatFrequency, true, oldAnchor));
|
|
241
|
+
|
|
242
|
+
runDisplayScheduledNotification(id);
|
|
243
|
+
|
|
244
|
+
WorkDataEntity row = repo.getWorkDataById(id).get(5, TimeUnit.SECONDS);
|
|
245
|
+
assertNotNull(
|
|
246
|
+
"unsupported repeatFrequency must fail before update/delete and leave the row", row);
|
|
247
|
+
assertEquals(
|
|
248
|
+
"unsupported repeatFrequency must not rewrite the persisted timestamp",
|
|
249
|
+
oldAnchor,
|
|
250
|
+
triggerTimestamp(row));
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
private void assertRescheduleNotificationsKeepsRowUnchanged(Object typeValue, String suffix)
|
|
254
|
+
throws Exception {
|
|
255
|
+
repo.deleteAll().get(5, TimeUnit.SECONDS);
|
|
256
|
+
String id = "am-type-reboot-" + suffix;
|
|
257
|
+
Bundle triggerBundle = rescheduleTriggerWithType(typeValue, true);
|
|
258
|
+
seedDisplayRow(id, triggerBundle);
|
|
259
|
+
long originalTimestamp =
|
|
260
|
+
triggerTimestamp(repo.getWorkDataById(id).get(5, TimeUnit.SECONDS));
|
|
261
|
+
|
|
262
|
+
runRescheduleNotificationsFromRoom();
|
|
263
|
+
|
|
264
|
+
WorkDataEntity row = repo.getWorkDataById(id).get(5, TimeUnit.SECONDS);
|
|
265
|
+
assertNotNull("reboot recovery must leave no-op/wrong-type rows in Room", row);
|
|
266
|
+
assertEquals(
|
|
267
|
+
"reboot recovery must not rewrite no-op/wrong-type trigger rows",
|
|
268
|
+
originalTimestamp,
|
|
269
|
+
triggerTimestamp(row));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
private WorkDataEntity runRescheduleNotificationExpectingUpdate(Bundle triggerBundle)
|
|
273
|
+
throws Exception {
|
|
274
|
+
WorkDataRepository mockRepo = mock(WorkDataRepository.class);
|
|
275
|
+
when(mockRepo.update(any())).thenReturn(Futures.immediateFuture(null));
|
|
276
|
+
AlarmManager mockAlarmManager = mock(AlarmManager.class);
|
|
277
|
+
ArgumentCaptor<WorkDataEntity> updateCaptor = ArgumentCaptor.forClass(WorkDataEntity.class);
|
|
278
|
+
|
|
279
|
+
try (MockedStatic<WorkDataRepository> repoMock = mockStatic(WorkDataRepository.class);
|
|
280
|
+
MockedStatic<AlarmUtils> alarmUtilsMock = mockStatic(AlarmUtils.class)) {
|
|
281
|
+
repoMock.when(() -> WorkDataRepository.getInstance(any())).thenReturn(mockRepo);
|
|
282
|
+
alarmUtilsMock.when(AlarmUtils::getAlarmManager).thenReturn(mockAlarmManager);
|
|
283
|
+
|
|
284
|
+
new NotifeeAlarmManager()
|
|
285
|
+
.rescheduleNotification(buildEntity("am-type", triggerBundle))
|
|
286
|
+
.get(5, TimeUnit.SECONDS);
|
|
287
|
+
|
|
288
|
+
verify(mockRepo).update(updateCaptor.capture());
|
|
289
|
+
return updateCaptor.getValue();
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
private void runRescheduleNotificationExpectingNoUpdate(Bundle triggerBundle) throws Exception {
|
|
294
|
+
WorkDataRepository mockRepo = mock(WorkDataRepository.class);
|
|
295
|
+
AlarmManager mockAlarmManager = mock(AlarmManager.class);
|
|
296
|
+
|
|
297
|
+
try (MockedStatic<WorkDataRepository> repoMock = mockStatic(WorkDataRepository.class);
|
|
298
|
+
MockedStatic<AlarmUtils> alarmUtilsMock = mockStatic(AlarmUtils.class)) {
|
|
299
|
+
repoMock.when(() -> WorkDataRepository.getInstance(any())).thenReturn(mockRepo);
|
|
300
|
+
alarmUtilsMock.when(AlarmUtils::getAlarmManager).thenReturn(mockAlarmManager);
|
|
301
|
+
|
|
302
|
+
new NotifeeAlarmManager()
|
|
303
|
+
.rescheduleNotification(buildEntity("am-type", triggerBundle))
|
|
304
|
+
.get(5, TimeUnit.SECONDS);
|
|
305
|
+
|
|
306
|
+
verify(mockRepo, never()).update(any());
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
private void assertUnsupportedTypeThrows(Object typeValue) {
|
|
311
|
+
WorkDataRepository mockRepo = mock(WorkDataRepository.class);
|
|
312
|
+
|
|
313
|
+
try (MockedStatic<WorkDataRepository> repoMock = mockStatic(WorkDataRepository.class)) {
|
|
314
|
+
repoMock.when(() -> WorkDataRepository.getInstance(any())).thenReturn(mockRepo);
|
|
315
|
+
|
|
316
|
+
assertThrows(
|
|
317
|
+
ClassCastException.class,
|
|
318
|
+
() ->
|
|
319
|
+
new NotifeeAlarmManager()
|
|
320
|
+
.rescheduleNotification(
|
|
321
|
+
buildEntity("am-type", rescheduleTriggerWithType(typeValue, true))));
|
|
322
|
+
|
|
323
|
+
verify(mockRepo, never()).update(any());
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
private void runDisplayScheduledNotification(String id) {
|
|
328
|
+
BroadcastReceiver.PendingResult pendingResult = mock(BroadcastReceiver.PendingResult.class);
|
|
329
|
+
Bundle alarmManagerNotification = new Bundle();
|
|
330
|
+
alarmManagerNotification.putString("notificationId", id);
|
|
331
|
+
|
|
332
|
+
NotifeeAlarmManager.displayScheduledNotification(alarmManagerNotification, pendingResult);
|
|
333
|
+
|
|
334
|
+
verify(pendingResult, timeout(10_000)).finish();
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
private void runRescheduleNotificationsFromRoom() {
|
|
338
|
+
BroadcastReceiver.PendingResult pendingResult = mock(BroadcastReceiver.PendingResult.class);
|
|
339
|
+
|
|
340
|
+
new RoomBackedAlarmManager().rescheduleNotifications(pendingResult);
|
|
341
|
+
|
|
342
|
+
verify(pendingResult, timeout(10_000)).finish();
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
private void seedDisplayRow(String id, Bundle triggerBundle) throws Exception {
|
|
346
|
+
repo.insert(buildEntity(id, triggerBundle)).get(5, TimeUnit.SECONDS);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
private WorkDataEntity buildEntity(String id, Bundle triggerBundle) {
|
|
350
|
+
return new WorkDataEntity(
|
|
351
|
+
id,
|
|
352
|
+
ObjectUtils.bundleToBytes(notificationBundle(id)),
|
|
353
|
+
ObjectUtils.bundleToBytes(triggerBundle),
|
|
354
|
+
true);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
private void assertRowUpdatedToFuture(String id, long oldAnchor, long minExpectedAnchor)
|
|
358
|
+
throws Exception {
|
|
359
|
+
WorkDataEntity row = repo.getWorkDataById(id).get(5, TimeUnit.SECONDS);
|
|
360
|
+
assertNotNull("repeating trigger row must survive after update branch", row);
|
|
361
|
+
|
|
362
|
+
long updatedAnchor = triggerTimestamp(row);
|
|
363
|
+
assertTrue(
|
|
364
|
+
"repeating trigger timestamp must be advanced by the update branch",
|
|
365
|
+
updatedAnchor > oldAnchor);
|
|
366
|
+
assertTrue(
|
|
367
|
+
"repeating trigger timestamp must be advanced to the future",
|
|
368
|
+
updatedAnchor >= minExpectedAnchor);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
private static long triggerTimestamp(WorkDataEntity row) {
|
|
372
|
+
return ObjectUtils.getLong(ObjectUtils.bytesToBundle(row.getTrigger()).get("timestamp"));
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
private static long oldAnchor() {
|
|
376
|
+
return System.currentTimeMillis() - OLD_ANCHOR_OFFSET_MS;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
private static Bundle notificationBundle(String id) {
|
|
380
|
+
Bundle notificationBundle = new Bundle();
|
|
381
|
+
notificationBundle.putString("id", id);
|
|
382
|
+
notificationBundle.putString("title", "Manager Current Behavior " + id);
|
|
383
|
+
|
|
384
|
+
Bundle androidBundle = new Bundle();
|
|
385
|
+
androidBundle.putString("channelId", CHANNEL_ID);
|
|
386
|
+
notificationBundle.putBundle("android", androidBundle);
|
|
387
|
+
|
|
388
|
+
return notificationBundle;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
private static Bundle timestampTriggerWithRepeatValue(
|
|
392
|
+
Object repeatFrequency, boolean includeRepeatFrequency, long timestamp) {
|
|
393
|
+
Bundle triggerBundle = new Bundle();
|
|
394
|
+
triggerBundle.putInt("type", 0);
|
|
395
|
+
triggerBundle.putLong("timestamp", timestamp);
|
|
396
|
+
if (includeRepeatFrequency) {
|
|
397
|
+
putBundleValue(triggerBundle, "repeatFrequency", repeatFrequency);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
Bundle alarmManagerBundle = new Bundle();
|
|
401
|
+
alarmManagerBundle.putInt("type", 3);
|
|
402
|
+
triggerBundle.putBundle("alarmManager", alarmManagerBundle);
|
|
403
|
+
|
|
404
|
+
return triggerBundle;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
private static Bundle rescheduleTriggerWithType(Object typeValue, boolean includeType) {
|
|
408
|
+
Bundle triggerBundle = new Bundle();
|
|
409
|
+
if (includeType) {
|
|
410
|
+
putBundleValue(triggerBundle, "type", typeValue);
|
|
411
|
+
}
|
|
412
|
+
triggerBundle.putLong("timestamp", System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(10));
|
|
413
|
+
triggerBundle.putInt("repeatFrequency", 0);
|
|
414
|
+
|
|
415
|
+
Bundle alarmManagerBundle = new Bundle();
|
|
416
|
+
alarmManagerBundle.putInt("type", 3);
|
|
417
|
+
triggerBundle.putBundle("alarmManager", alarmManagerBundle);
|
|
418
|
+
|
|
419
|
+
return triggerBundle;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
private static void putBundleValue(Bundle bundle, String key, Object value) {
|
|
423
|
+
if (value == null) {
|
|
424
|
+
bundle.putString(key, null);
|
|
425
|
+
} else if (value instanceof Integer) {
|
|
426
|
+
bundle.putInt(key, (Integer) value);
|
|
427
|
+
} else if (value instanceof Double) {
|
|
428
|
+
bundle.putDouble(key, (Double) value);
|
|
429
|
+
} else if (value instanceof Long) {
|
|
430
|
+
bundle.putLong(key, (Long) value);
|
|
431
|
+
} else if (value instanceof Float) {
|
|
432
|
+
bundle.putFloat(key, (Float) value);
|
|
433
|
+
} else if (value instanceof String) {
|
|
434
|
+
bundle.putString(key, (String) value);
|
|
435
|
+
} else {
|
|
436
|
+
throw new IllegalArgumentException("Unsupported test value: " + value);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
private class RoomBackedAlarmManager extends NotifeeAlarmManager {
|
|
441
|
+
@Override
|
|
442
|
+
ListenableFuture<List<WorkDataEntity>> getScheduledNotifications() {
|
|
443
|
+
return repo.getAllWithAlarmManager(true);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
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
|
+
import static org.junit.Assert.assertThrows;
|
|
20
|
+
import static org.mockito.ArgumentMatchers.any;
|
|
21
|
+
import static org.mockito.ArgumentMatchers.same;
|
|
22
|
+
import static org.mockito.Mockito.CALLS_REAL_METHODS;
|
|
23
|
+
import static org.mockito.Mockito.mockStatic;
|
|
24
|
+
import static org.mockito.Mockito.never;
|
|
25
|
+
|
|
26
|
+
import android.os.Bundle;
|
|
27
|
+
import app.notifee.core.model.NotificationModel;
|
|
28
|
+
import com.google.common.util.concurrent.Futures;
|
|
29
|
+
import com.google.common.util.concurrent.ListenableFuture;
|
|
30
|
+
import java.util.concurrent.TimeUnit;
|
|
31
|
+
import org.junit.After;
|
|
32
|
+
import org.junit.Before;
|
|
33
|
+
import org.junit.Test;
|
|
34
|
+
import org.junit.runner.RunWith;
|
|
35
|
+
import org.mockito.MockedStatic;
|
|
36
|
+
import org.robolectric.RobolectricTestRunner;
|
|
37
|
+
|
|
38
|
+
@RunWith(RobolectricTestRunner.class)
|
|
39
|
+
public class NotificationManagerCreateTriggerNotificationCurrentBehaviorTest {
|
|
40
|
+
private MockedStatic<NotificationManager> notificationManagerMock;
|
|
41
|
+
private NotificationModel notificationModel;
|
|
42
|
+
|
|
43
|
+
@Before
|
|
44
|
+
public void setUp() {
|
|
45
|
+
notificationModel = buildNotificationModel("create-trigger-current-behavior");
|
|
46
|
+
notificationManagerMock = mockStatic(NotificationManager.class, CALLS_REAL_METHODS);
|
|
47
|
+
notificationManagerMock
|
|
48
|
+
.when(() -> NotificationManager.createTimestampTriggerNotification(any(), any()))
|
|
49
|
+
.thenReturn(Futures.immediateFuture(null));
|
|
50
|
+
notificationManagerMock
|
|
51
|
+
.when(() -> NotificationManager.createIntervalTriggerNotification(any(), any()))
|
|
52
|
+
.thenReturn(Futures.immediateFuture(null));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@After
|
|
56
|
+
public void tearDown() {
|
|
57
|
+
if (notificationManagerMock != null) {
|
|
58
|
+
notificationManagerMock.close();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@Test
|
|
63
|
+
public void createTriggerNotification_missingType_dispatchesTimestampBranch() throws Exception {
|
|
64
|
+
Bundle triggerBundle = new Bundle();
|
|
65
|
+
|
|
66
|
+
assertCompletes(NotificationManager.createTriggerNotification(notificationModel, triggerBundle));
|
|
67
|
+
|
|
68
|
+
verifyTimestampBranch(triggerBundle);
|
|
69
|
+
verifyIntervalBranchNeverCalled();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@Test
|
|
73
|
+
public void createTriggerNotification_explicitNullType_dispatchesTimestampBranch()
|
|
74
|
+
throws Exception {
|
|
75
|
+
Bundle triggerBundle = new Bundle();
|
|
76
|
+
triggerBundle.putString("type", null);
|
|
77
|
+
|
|
78
|
+
assertCompletes(NotificationManager.createTriggerNotification(notificationModel, triggerBundle));
|
|
79
|
+
|
|
80
|
+
verifyTimestampBranch(triggerBundle);
|
|
81
|
+
verifyIntervalBranchNeverCalled();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@Test
|
|
85
|
+
public void createTriggerNotification_integerZero_dispatchesTimestampBranch() throws Exception {
|
|
86
|
+
Bundle triggerBundle = triggerBundleWithType(Integer.valueOf(0));
|
|
87
|
+
|
|
88
|
+
assertCompletes(NotificationManager.createTriggerNotification(notificationModel, triggerBundle));
|
|
89
|
+
|
|
90
|
+
verifyTimestampBranch(triggerBundle);
|
|
91
|
+
verifyIntervalBranchNeverCalled();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@Test
|
|
95
|
+
public void createTriggerNotification_integerOne_dispatchesIntervalBranch() throws Exception {
|
|
96
|
+
Bundle triggerBundle = triggerBundleWithType(Integer.valueOf(1));
|
|
97
|
+
|
|
98
|
+
assertCompletes(NotificationManager.createTriggerNotification(notificationModel, triggerBundle));
|
|
99
|
+
|
|
100
|
+
verifyIntervalBranch(triggerBundle);
|
|
101
|
+
verifyTimestampBranchNeverCalled();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
@Test
|
|
105
|
+
public void createTriggerNotification_otherInteger_completesWithoutScheduling() throws Exception {
|
|
106
|
+
Bundle triggerBundle = triggerBundleWithType(Integer.valueOf(99));
|
|
107
|
+
|
|
108
|
+
assertCompletes(NotificationManager.createTriggerNotification(notificationModel, triggerBundle));
|
|
109
|
+
|
|
110
|
+
verifyTimestampBranchNeverCalled();
|
|
111
|
+
verifyIntervalBranchNeverCalled();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
@Test
|
|
115
|
+
public void createTriggerNotification_doubleType_truncatesBeforeDispatch() throws Exception {
|
|
116
|
+
Bundle triggerBundle = triggerBundleWithType(Double.valueOf(1.9d));
|
|
117
|
+
|
|
118
|
+
assertCompletes(NotificationManager.createTriggerNotification(notificationModel, triggerBundle));
|
|
119
|
+
|
|
120
|
+
verifyIntervalBranch(triggerBundle);
|
|
121
|
+
verifyTimestampBranchNeverCalled();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
@Test
|
|
125
|
+
public void createTriggerNotification_unsupportedType_preservesClassCastException() {
|
|
126
|
+
assertThrows(
|
|
127
|
+
ClassCastException.class,
|
|
128
|
+
() ->
|
|
129
|
+
NotificationManager.createTriggerNotification(
|
|
130
|
+
notificationModel, triggerBundleWithType(Long.valueOf(1L))));
|
|
131
|
+
assertThrows(
|
|
132
|
+
ClassCastException.class,
|
|
133
|
+
() ->
|
|
134
|
+
NotificationManager.createTriggerNotification(
|
|
135
|
+
notificationModel, triggerBundleWithType(Float.valueOf(1.5f))));
|
|
136
|
+
assertThrows(
|
|
137
|
+
ClassCastException.class,
|
|
138
|
+
() ->
|
|
139
|
+
NotificationManager.createTriggerNotification(
|
|
140
|
+
notificationModel, triggerBundleWithType("1")));
|
|
141
|
+
|
|
142
|
+
verifyTimestampBranchNeverCalled();
|
|
143
|
+
verifyIntervalBranchNeverCalled();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private void verifyTimestampBranch(Bundle triggerBundle) {
|
|
147
|
+
notificationManagerMock.verify(
|
|
148
|
+
() ->
|
|
149
|
+
NotificationManager.createTimestampTriggerNotification(
|
|
150
|
+
same(notificationModel), same(triggerBundle)));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private void verifyIntervalBranch(Bundle triggerBundle) {
|
|
154
|
+
notificationManagerMock.verify(
|
|
155
|
+
() ->
|
|
156
|
+
NotificationManager.createIntervalTriggerNotification(
|
|
157
|
+
same(notificationModel), same(triggerBundle)));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private void verifyTimestampBranchNeverCalled() {
|
|
161
|
+
notificationManagerMock.verify(
|
|
162
|
+
() -> NotificationManager.createTimestampTriggerNotification(any(), any()), never());
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private void verifyIntervalBranchNeverCalled() {
|
|
166
|
+
notificationManagerMock.verify(
|
|
167
|
+
() -> NotificationManager.createIntervalTriggerNotification(any(), any()), never());
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private static void assertCompletes(ListenableFuture<Void> future) throws Exception {
|
|
171
|
+
future.get(5, TimeUnit.SECONDS);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private static NotificationModel buildNotificationModel(String id) {
|
|
175
|
+
Bundle notificationBundle = new Bundle();
|
|
176
|
+
notificationBundle.putString("id", id);
|
|
177
|
+
return NotificationModel.fromBundle(notificationBundle);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private static Bundle triggerBundleWithType(Object type) {
|
|
181
|
+
Bundle triggerBundle = new Bundle();
|
|
182
|
+
putBundleValue(triggerBundle, "type", type);
|
|
183
|
+
return triggerBundle;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private static void putBundleValue(Bundle bundle, String key, Object value) {
|
|
187
|
+
if (value == null) {
|
|
188
|
+
bundle.putString(key, null);
|
|
189
|
+
} else if (value instanceof Integer) {
|
|
190
|
+
bundle.putInt(key, (Integer) value);
|
|
191
|
+
} else if (value instanceof Double) {
|
|
192
|
+
bundle.putDouble(key, (Double) value);
|
|
193
|
+
} else if (value instanceof Long) {
|
|
194
|
+
bundle.putLong(key, (Long) value);
|
|
195
|
+
} else if (value instanceof Float) {
|
|
196
|
+
bundle.putFloat(key, (Float) value);
|
|
197
|
+
} else if (value instanceof String) {
|
|
198
|
+
bundle.putString(key, (String) value);
|
|
199
|
+
} else {
|
|
200
|
+
throw new IllegalArgumentException("Unsupported test value: " + value);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|