30
30
import android .text .Spanned ;
31
31
import android .text .TextUtils ;
32
32
import android .text .style .ForegroundColorSpan ;
33
+ import android .util .Log ;
33
34
34
35
import androidx .annotation .Keep ;
35
36
import androidx .annotation .NonNull ;
54
55
import com .dexterous .flutterlocalnotifications .models .NotificationChannelDetails ;
55
56
import com .dexterous .flutterlocalnotifications .models .NotificationChannelGroupDetails ;
56
57
import com .dexterous .flutterlocalnotifications .models .NotificationDetails ;
58
+ import com .dexterous .flutterlocalnotifications .models .NotificationStyle ;
57
59
import com .dexterous .flutterlocalnotifications .models .PersonDetails ;
60
+ import com .dexterous .flutterlocalnotifications .models .ScheduleMode ;
58
61
import com .dexterous .flutterlocalnotifications .models .ScheduledNotificationRepeatFrequency ;
59
62
import com .dexterous .flutterlocalnotifications .models .SoundSource ;
60
63
import com .dexterous .flutterlocalnotifications .models .styles .BigPictureStyleInformation ;
@@ -126,6 +129,8 @@ public class FlutterLocalNotificationsPlugin
126
129
private static final String INITIALIZE_METHOD = "initialize" ;
127
130
private static final String GET_CALLBACK_HANDLE_METHOD = "getCallbackHandle" ;
128
131
private static final String ARE_NOTIFICATIONS_ENABLED_METHOD = "areNotificationsEnabled" ;
132
+ private static final String CAN_SCHEDULE_EXACT_NOTIFICATIONS_METHOD =
133
+ "canScheduleExactNotifications" ;
129
134
private static final String CREATE_NOTIFICATION_CHANNEL_GROUP_METHOD =
130
135
"createNotificationChannelGroup" ;
131
136
private static final String DELETE_NOTIFICATION_CHANNEL_GROUP_METHOD =
@@ -176,6 +181,7 @@ public class FlutterLocalNotificationsPlugin
176
181
"permissionRequestInProgress" ;
177
182
private static final String PERMISSION_REQUEST_IN_PROGRESS_ERROR_MESSAGE =
178
183
"Another permission request is already in progress" ;
184
+ private static final String EXACT_ALARMS_PERMISSION_ERROR_CODE = "exact_alarms_not_permitted" ;
179
185
private static final String CANCEL_ID = "id" ;
180
186
private static final String CANCEL_TAG = "tag" ;
181
187
private static final String ACTION_ID = "actionId" ;
@@ -193,16 +199,36 @@ public class FlutterLocalNotificationsPlugin
193
199
194
200
static void rescheduleNotifications (Context context ) {
195
201
ArrayList <NotificationDetails > scheduledNotifications = loadScheduledNotifications (context );
196
- for (NotificationDetails scheduledNotification : scheduledNotifications ) {
197
- if (scheduledNotification .repeatInterval == null ) {
198
- if (scheduledNotification .timeZoneName == null ) {
199
- scheduleNotification (context , scheduledNotification , false );
202
+ for (NotificationDetails notificationDetails : scheduledNotifications ) {
203
+ try {
204
+ if (notificationDetails .repeatInterval != null ) {
205
+ repeatNotification (context , notificationDetails , false );
206
+ } else if (notificationDetails .timeZoneName != null ) {
207
+ zonedScheduleNotification (context , notificationDetails , false );
200
208
} else {
201
- zonedScheduleNotification (context , scheduledNotification , false );
209
+ scheduleNotification (context , notificationDetails , false );
202
210
}
211
+ } catch (ExactAlarmPermissionException e ) {
212
+ Log .e ("notification" , e .getMessage ());
213
+ removeNotificationFromCache (context , notificationDetails .id );
214
+ }
215
+ }
216
+ }
217
+
218
+ static void scheduleNextNotification (Context context , NotificationDetails notificationDetails ) {
219
+ try {
220
+ if (notificationDetails .scheduledNotificationRepeatFrequency != null ) {
221
+ zonedScheduleNextNotification (context , notificationDetails );
222
+ } else if (notificationDetails .matchDateTimeComponents != null ) {
223
+ zonedScheduleNextNotificationMatchingDateComponents (context , notificationDetails );
224
+ } else if (notificationDetails .repeatInterval != null ) {
225
+ scheduleNextRepeatingNotification (context , notificationDetails );
203
226
} else {
204
- repeatNotification (context , scheduledNotification , false );
227
+ removeNotificationFromCache (context , notificationDetails . id );
205
228
}
229
+ } catch (ExactAlarmPermissionException e ) {
230
+ Log .e ("notification" , e .getMessage ());
231
+ removeNotificationFromCache (context , notificationDetails .id );
206
232
}
207
233
}
208
234
@@ -440,7 +466,10 @@ static Gson buildGson() {
440
466
.registerSubtype (BigPictureStyleInformation .class )
441
467
.registerSubtype (InboxStyleInformation .class )
442
468
.registerSubtype (MessagingStyleInformation .class );
443
- GsonBuilder builder = new GsonBuilder ().registerTypeAdapterFactory (styleInformationAdapter );
469
+ GsonBuilder builder =
470
+ new GsonBuilder ()
471
+ .registerTypeAdapter (ScheduleMode .class , new ScheduleMode .Deserializer ())
472
+ .registerTypeAdapterFactory (styleInformationAdapter );
444
473
gson = builder .create ();
445
474
}
446
475
return gson ;
@@ -505,19 +534,11 @@ private static void scheduleNotification(
505
534
getBroadcastPendingIntent (context , notificationDetails .id , notificationIntent );
506
535
507
536
AlarmManager alarmManager = getAlarmManager (context );
508
- if (BooleanUtils .getValue (notificationDetails .allowWhileIdle )) {
509
- AlarmManagerCompat .setExactAndAllowWhileIdle (
510
- alarmManager ,
511
- AlarmManager .RTC_WAKEUP ,
512
- notificationDetails .millisecondsSinceEpoch ,
513
- pendingIntent );
514
- } else {
515
- AlarmManagerCompat .setExact (
516
- alarmManager ,
517
- AlarmManager .RTC_WAKEUP ,
518
- notificationDetails .millisecondsSinceEpoch ,
519
- pendingIntent );
520
- }
537
+ setupAlarm (
538
+ notificationDetails ,
539
+ alarmManager ,
540
+ notificationDetails .millisecondsSinceEpoch ,
541
+ pendingIntent );
521
542
522
543
if (updateScheduledNotificationsCache ) {
523
544
saveScheduledNotification (context , notificationDetails );
@@ -542,19 +563,14 @@ private static void zonedScheduleNotification(
542
563
.toInstant ()
543
564
.toEpochMilli ();
544
565
545
- if (BooleanUtils .getValue (notificationDetails .allowWhileIdle )) {
546
- AlarmManagerCompat .setExactAndAllowWhileIdle (
547
- alarmManager , AlarmManager .RTC_WAKEUP , epochMilli , pendingIntent );
548
- } else {
549
- AlarmManagerCompat .setExact (alarmManager , AlarmManager .RTC_WAKEUP , epochMilli , pendingIntent );
550
- }
566
+ setupAlarm (notificationDetails , alarmManager , epochMilli , pendingIntent );
551
567
552
568
if (updateScheduledNotificationsCache ) {
553
569
saveScheduledNotification (context , notificationDetails );
554
570
}
555
571
}
556
572
557
- static void scheduleNextRepeatingNotification (
573
+ private static void scheduleNextRepeatingNotification (
558
574
Context context , NotificationDetails notificationDetails ) {
559
575
long repeatInterval = calculateRepeatIntervalMilliseconds (notificationDetails );
560
576
long notificationTriggerTime =
@@ -566,8 +582,10 @@ static void scheduleNextRepeatingNotification(
566
582
PendingIntent pendingIntent =
567
583
getBroadcastPendingIntent (context , notificationDetails .id , notificationIntent );
568
584
AlarmManager alarmManager = getAlarmManager (context );
569
- AlarmManagerCompat .setExactAndAllowWhileIdle (
570
- alarmManager , AlarmManager .RTC_WAKEUP , notificationTriggerTime , pendingIntent );
585
+
586
+ setupAllowWhileIdleAlarm (
587
+ notificationDetails , alarmManager , notificationTriggerTime , pendingIntent );
588
+
571
589
saveScheduledNotification (context , notificationDetails );
572
590
}
573
591
@@ -635,18 +653,58 @@ private static void repeatNotification(
635
653
getBroadcastPendingIntent (context , notificationDetails .id , notificationIntent );
636
654
AlarmManager alarmManager = getAlarmManager (context );
637
655
638
- if (BooleanUtils . getValue ( notificationDetails .allowWhileIdle )) {
639
- AlarmManagerCompat . setExactAndAllowWhileIdle (
640
- alarmManager , AlarmManager . RTC_WAKEUP , notificationTriggerTime , pendingIntent );
656
+ if (notificationDetails .scheduleMode . useAllowWhileIdle ( )) {
657
+ setupAllowWhileIdleAlarm (
658
+ notificationDetails , alarmManager , notificationTriggerTime , pendingIntent );
641
659
} else {
642
660
alarmManager .setInexactRepeating (
643
661
AlarmManager .RTC_WAKEUP , notificationTriggerTime , repeatInterval , pendingIntent );
644
662
}
663
+
645
664
if (updateScheduledNotificationsCache ) {
646
665
saveScheduledNotification (context , notificationDetails );
647
666
}
648
667
}
649
668
669
+ private static void setupAlarm (
670
+ NotificationDetails notificationDetails ,
671
+ AlarmManager alarmManager ,
672
+ long epochMilli ,
673
+ PendingIntent pendingIntent ) {
674
+ if (notificationDetails .scheduleMode .useAllowWhileIdle ()) {
675
+ setupAllowWhileIdleAlarm (notificationDetails , alarmManager , epochMilli , pendingIntent );
676
+ } else {
677
+ if (notificationDetails .scheduleMode .useExactAlarm ()) {
678
+ checkCanScheduleExactAlarms (alarmManager );
679
+ AlarmManagerCompat .setExact (
680
+ alarmManager , AlarmManager .RTC_WAKEUP , epochMilli , pendingIntent );
681
+ } else {
682
+ alarmManager .set (AlarmManager .RTC_WAKEUP , epochMilli , pendingIntent );
683
+ }
684
+ }
685
+ }
686
+
687
+ private static void setupAllowWhileIdleAlarm (
688
+ NotificationDetails notificationDetails ,
689
+ AlarmManager alarmManager ,
690
+ long epochMilli ,
691
+ PendingIntent pendingIntent ) {
692
+ if (notificationDetails .scheduleMode .useExactAlarm ()) {
693
+ checkCanScheduleExactAlarms (alarmManager );
694
+ AlarmManagerCompat .setExactAndAllowWhileIdle (
695
+ alarmManager , AlarmManager .RTC_WAKEUP , epochMilli , pendingIntent );
696
+ } else {
697
+ AlarmManagerCompat .setAndAllowWhileIdle (
698
+ alarmManager , AlarmManager .RTC_WAKEUP , epochMilli , pendingIntent );
699
+ }
700
+ }
701
+
702
+ private static void checkCanScheduleExactAlarms (AlarmManager alarmManager ) {
703
+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .S && !alarmManager .canScheduleExactAlarms ()) {
704
+ throw new ExactAlarmPermissionException ();
705
+ }
706
+ }
707
+
650
708
private static long calculateNextNotificationTrigger (
651
709
long notificationTriggerTime , long repeatInterval ) {
652
710
// ensures that time is in the future
@@ -1154,7 +1212,7 @@ static void showNotification(Context context, NotificationDetails notificationDe
1154
1212
}
1155
1213
}
1156
1214
1157
- static void zonedScheduleNextNotification (
1215
+ private static void zonedScheduleNextNotification (
1158
1216
Context context , NotificationDetails notificationDetails ) {
1159
1217
String nextFireDate = getNextFireDate (notificationDetails );
1160
1218
if (nextFireDate == null ) {
@@ -1164,7 +1222,7 @@ static void zonedScheduleNextNotification(
1164
1222
zonedScheduleNotification (context , notificationDetails , true );
1165
1223
}
1166
1224
1167
- static void zonedScheduleNextNotificationMatchingDateComponents (
1225
+ private static void zonedScheduleNextNotificationMatchingDateComponents (
1168
1226
Context context , NotificationDetails notificationDetails ) {
1169
1227
String nextFireDate = getNextFireDateMatchingDateTimeComponents (notificationDetails );
1170
1228
if (nextFireDate == null ) {
@@ -1174,7 +1232,7 @@ static void zonedScheduleNextNotificationMatchingDateComponents(
1174
1232
zonedScheduleNotification (context , notificationDetails , true );
1175
1233
}
1176
1234
1177
- static String getNextFireDate (NotificationDetails notificationDetails ) {
1235
+ private static String getNextFireDate (NotificationDetails notificationDetails ) {
1178
1236
if (notificationDetails .scheduledNotificationRepeatFrequency
1179
1237
== ScheduledNotificationRepeatFrequency .Daily ) {
1180
1238
LocalDateTime localDateTime =
@@ -1189,7 +1247,8 @@ static String getNextFireDate(NotificationDetails notificationDetails) {
1189
1247
return null ;
1190
1248
}
1191
1249
1192
- static String getNextFireDateMatchingDateTimeComponents (NotificationDetails notificationDetails ) {
1250
+ private static String getNextFireDateMatchingDateTimeComponents (
1251
+ NotificationDetails notificationDetails ) {
1193
1252
ZoneId zoneId = ZoneId .of (notificationDetails .timeZoneName );
1194
1253
ZonedDateTime scheduledDateTime =
1195
1254
ZonedDateTime .of (LocalDateTime .parse (notificationDetails .scheduledDateTime ), zoneId );
@@ -1335,6 +1394,9 @@ public void fail(String message) {
1335
1394
case ARE_NOTIFICATIONS_ENABLED_METHOD :
1336
1395
areNotificationsEnabled (result );
1337
1396
break ;
1397
+ case CAN_SCHEDULE_EXACT_NOTIFICATIONS_METHOD :
1398
+ setCanScheduleExactNotifications (result );
1399
+ break ;
1338
1400
case CREATE_NOTIFICATION_CHANNEL_GROUP_METHOD :
1339
1401
createNotificationChannelGroup (call , result );
1340
1402
break ;
@@ -1425,33 +1487,42 @@ private void cancel(MethodCall call, Result result) {
1425
1487
}
1426
1488
1427
1489
private void repeat (MethodCall call , Result result ) {
1428
- Map <String , Object > arguments = call .arguments ();
1429
- NotificationDetails notificationDetails = extractNotificationDetails (result , arguments );
1490
+ NotificationDetails notificationDetails = extractNotificationDetails (result , call .arguments ());
1430
1491
if (notificationDetails != null ) {
1431
- repeatNotification (applicationContext , notificationDetails , true );
1432
- result .success (null );
1492
+ try {
1493
+ repeatNotification (applicationContext , notificationDetails , true );
1494
+ result .success (null );
1495
+ } catch (PluginException e ) {
1496
+ result .error (e .code , e .getMessage (), null );
1497
+ }
1433
1498
}
1434
1499
}
1435
1500
1436
1501
private void schedule (MethodCall call , Result result ) {
1437
- Map <String , Object > arguments = call .arguments ();
1438
- NotificationDetails notificationDetails = extractNotificationDetails (result , arguments );
1502
+ NotificationDetails notificationDetails = extractNotificationDetails (result , call .arguments ());
1439
1503
if (notificationDetails != null ) {
1440
- scheduleNotification (applicationContext , notificationDetails , true );
1441
- result .success (null );
1504
+ try {
1505
+ scheduleNotification (applicationContext , notificationDetails , true );
1506
+ result .success (null );
1507
+ } catch (PluginException e ) {
1508
+ result .error (e .code , e .getMessage (), null );
1509
+ }
1442
1510
}
1443
1511
}
1444
1512
1445
1513
private void zonedSchedule (MethodCall call , Result result ) {
1446
- Map <String , Object > arguments = call .arguments ();
1447
- NotificationDetails notificationDetails = extractNotificationDetails (result , arguments );
1514
+ NotificationDetails notificationDetails = extractNotificationDetails (result , call .arguments ());
1448
1515
if (notificationDetails != null ) {
1449
1516
if (notificationDetails .matchDateTimeComponents != null ) {
1450
1517
notificationDetails .scheduledDateTime =
1451
1518
getNextFireDateMatchingDateTimeComponents (notificationDetails );
1452
1519
}
1453
- zonedScheduleNotification (applicationContext , notificationDetails , true );
1454
- result .success (null );
1520
+ try {
1521
+ zonedScheduleNotification (applicationContext , notificationDetails , true );
1522
+ result .success (null );
1523
+ } catch (PluginException e ) {
1524
+ result .error (e .code , e .getMessage (), null );
1525
+ }
1455
1526
}
1456
1527
}
1457
1528
@@ -1970,4 +2041,28 @@ private void areNotificationsEnabled(Result result) {
1970
2041
NotificationManagerCompat notificationManager = getNotificationManager (applicationContext );
1971
2042
result .success (notificationManager .areNotificationsEnabled ());
1972
2043
}
2044
+
2045
+ private void setCanScheduleExactNotifications (Result result ) {
2046
+ if (Build .VERSION .SDK_INT < Build .VERSION_CODES .S ) {
2047
+ result .success (true );
2048
+ } else {
2049
+ AlarmManager alarmManager = getAlarmManager (applicationContext );
2050
+ result .success (alarmManager .canScheduleExactAlarms ());
2051
+ }
2052
+ }
2053
+
2054
+ private static class PluginException extends RuntimeException {
2055
+ public final String code ;
2056
+
2057
+ PluginException (String code , String message ) {
2058
+ super (message );
2059
+ this .code = code ;
2060
+ }
2061
+ }
2062
+
2063
+ private static class ExactAlarmPermissionException extends PluginException {
2064
+ public ExactAlarmPermissionException () {
2065
+ super (EXACT_ALARMS_PERMISSION_ERROR_CODE , "Exact alarms are not permitted" );
2066
+ }
2067
+ }
1973
2068
}
0 commit comments