1818import io .flutter .view .FlutterNativeView ;
1919import io .flutter .view .FlutterRunArguments ;
2020import java .util .Collections ;
21+ import java .util .HashMap ;
22+ import java .util .HashSet ;
2123import java .util .Iterator ;
2224import java .util .LinkedList ;
2325import java .util .List ;
26+ import java .util .Set ;
2427import java .util .concurrent .atomic .AtomicBoolean ;
28+ import org .json .JSONException ;
29+ import org .json .JSONObject ;
2530
2631public class AlarmService extends JobIntentService {
2732 public static final String TAG = "AlarmService" ;
28- private static final String SHARED_PREFERENCES_KEY = "io.flutter.android_alarm_manager_plugin" ;
2933 private static final String CALLBACK_HANDLE_KEY = "callback_handle" ;
34+ private static final String PERSISTENT_ALARMS_SET_KEY = "persistent_alarm_ids" ;
35+ private static final String SHARED_PREFERENCES_KEY = "io.flutter.android_alarm_manager_plugin" ;
3036 private static final int JOB_ID = 1984 ; // Random job ID.
37+ private static final Object sPersistentAlarmsLock = new Object ();
3138 private static AtomicBoolean sStarted = new AtomicBoolean (false );
3239 private static List <Intent > sAlarmQueue = Collections .synchronizedList (new LinkedList <Intent >());
3340 private static FlutterNativeView sBackgroundFlutterView ;
@@ -168,7 +175,20 @@ private static void scheduleAlarm(
168175 boolean wakeup ,
169176 long startMillis ,
170177 long intervalMillis ,
178+ boolean rescheduleOnReboot ,
171179 long callbackHandle ) {
180+ if (rescheduleOnReboot ) {
181+ addPersistentAlarm (
182+ context ,
183+ requestCode ,
184+ repeating ,
185+ exact ,
186+ wakeup ,
187+ startMillis ,
188+ intervalMillis ,
189+ callbackHandle );
190+ }
191+
172192 // Create an Intent for the alarm and set the desired Dart callback handle.
173193 Intent alarm = new Intent (context , AlarmBroadcastReceiver .class );
174194 alarm .putExtra ("callbackHandle" , callbackHandle );
@@ -204,9 +224,19 @@ public static void setOneShot(
204224 boolean exact ,
205225 boolean wakeup ,
206226 long startMillis ,
227+ boolean rescheduleOnReboot ,
207228 long callbackHandle ) {
208229 final boolean repeating = false ;
209- scheduleAlarm (context , requestCode , repeating , exact , wakeup , startMillis , 0 , callbackHandle );
230+ scheduleAlarm (
231+ context ,
232+ requestCode ,
233+ repeating ,
234+ exact ,
235+ wakeup ,
236+ startMillis ,
237+ 0 ,
238+ rescheduleOnReboot ,
239+ callbackHandle );
210240 }
211241
212242 public static void setPeriodic (
@@ -216,6 +246,7 @@ public static void setPeriodic(
216246 boolean wakeup ,
217247 long startMillis ,
218248 long intervalMillis ,
249+ boolean rescheduleOnReboot ,
219250 long callbackHandle ) {
220251 final boolean repeating = true ;
221252 scheduleAlarm (
@@ -226,10 +257,15 @@ public static void setPeriodic(
226257 wakeup ,
227258 startMillis ,
228259 intervalMillis ,
260+ rescheduleOnReboot ,
229261 callbackHandle );
230262 }
231263
232264 public static void cancel (Context context , int requestCode ) {
265+ // Clear the alarm if it was set to be rescheduled after reboots.
266+ clearPersistentAlarm (context , requestCode );
267+
268+ // Cancel the alarm with the system alarm service.
233269 Intent alarm = new Intent (context , AlarmBroadcastReceiver .class );
234270 PendingIntent existingIntent =
235271 PendingIntent .getBroadcast (context , requestCode , alarm , PendingIntent .FLAG_NO_CREATE );
@@ -240,4 +276,110 @@ public static void cancel(Context context, int requestCode) {
240276 AlarmManager manager = (AlarmManager ) context .getSystemService (Context .ALARM_SERVICE );
241277 manager .cancel (existingIntent );
242278 }
279+
280+ private static String getPersistentAlarmKey (int requestCode ) {
281+ return "android_alarm_manager/persistent_alarm_" + Integer .toString (requestCode );
282+ }
283+
284+ private static void addPersistentAlarm (
285+ Context context ,
286+ int requestCode ,
287+ boolean repeating ,
288+ boolean exact ,
289+ boolean wakeup ,
290+ long startMillis ,
291+ long intervalMillis ,
292+ long callbackHandle ) {
293+ HashMap <String , Object > alarmSettings = new HashMap <>();
294+ alarmSettings .put ("repeating" , repeating );
295+ alarmSettings .put ("exact" , exact );
296+ alarmSettings .put ("wakeup" , wakeup );
297+ alarmSettings .put ("startMillis" , startMillis );
298+ alarmSettings .put ("intervalMillis" , intervalMillis );
299+ alarmSettings .put ("callbackHandle" , callbackHandle );
300+ JSONObject obj = new JSONObject (alarmSettings );
301+ String key = getPersistentAlarmKey (requestCode );
302+ SharedPreferences p = context .getSharedPreferences (SHARED_PREFERENCES_KEY , 0 );
303+
304+ synchronized (sPersistentAlarmsLock ) {
305+ Set <String > persistentAlarms = p .getStringSet (PERSISTENT_ALARMS_SET_KEY , null );
306+ if (persistentAlarms == null ) {
307+ persistentAlarms = new HashSet <>();
308+ }
309+ if (persistentAlarms .isEmpty ()) {
310+ RebootBroadcastReceiver .enableRescheduleOnReboot (context );
311+ }
312+ persistentAlarms .add (Integer .toString (requestCode ));
313+ p .edit ()
314+ .putString (key , obj .toString ())
315+ .putStringSet (PERSISTENT_ALARMS_SET_KEY , persistentAlarms )
316+ .commit ();
317+ }
318+ }
319+
320+ private static void clearPersistentAlarm (Context context , int requestCode ) {
321+ SharedPreferences p = context .getSharedPreferences (SHARED_PREFERENCES_KEY , 0 );
322+ synchronized (sPersistentAlarmsLock ) {
323+ Set <String > persistentAlarms = p .getStringSet (PERSISTENT_ALARMS_SET_KEY , null );
324+ if ((persistentAlarms == null ) || !persistentAlarms .contains (requestCode )) {
325+ return ;
326+ }
327+ persistentAlarms .remove (requestCode );
328+ String key = getPersistentAlarmKey (requestCode );
329+ p .edit ().remove (key ).putStringSet (PERSISTENT_ALARMS_SET_KEY , persistentAlarms ).commit ();
330+
331+ if (persistentAlarms .isEmpty ()) {
332+ RebootBroadcastReceiver .disableRescheduleOnReboot (context );
333+ }
334+ }
335+ }
336+
337+ public static void reschedulePersistentAlarms (Context context ) {
338+ synchronized (sPersistentAlarmsLock ) {
339+ SharedPreferences p = context .getSharedPreferences (SHARED_PREFERENCES_KEY , 0 );
340+ Set <String > persistentAlarms = p .getStringSet (PERSISTENT_ALARMS_SET_KEY , null );
341+ // No alarms to reschedule.
342+ if (persistentAlarms == null ) {
343+ return ;
344+ }
345+
346+ Iterator <String > it = persistentAlarms .iterator ();
347+ while (it .hasNext ()) {
348+ int requestCode = Integer .parseInt (it .next ());
349+ String key = getPersistentAlarmKey (requestCode );
350+ String json = p .getString (key , null );
351+ if (json == null ) {
352+ Log .e (
353+ TAG , "Data for alarm request code " + Integer .toString (requestCode ) + " is invalid." );
354+ continue ;
355+ }
356+ try {
357+ JSONObject alarm = new JSONObject (json );
358+ boolean repeating = alarm .getBoolean ("repeating" );
359+ boolean exact = alarm .getBoolean ("exact" );
360+ boolean wakeup = alarm .getBoolean ("wakeup" );
361+ long startMillis = alarm .getLong ("startMillis" );
362+ long intervalMillis = alarm .getLong ("intervalMillis" );
363+ long callbackHandle = alarm .getLong ("callbackHandle" );
364+ scheduleAlarm (
365+ context ,
366+ requestCode ,
367+ repeating ,
368+ exact ,
369+ wakeup ,
370+ startMillis ,
371+ intervalMillis ,
372+ false ,
373+ callbackHandle );
374+ } catch (JSONException e ) {
375+ Log .e (
376+ TAG ,
377+ "Data for alarm request code "
378+ + Integer .toString (requestCode )
379+ + " is invalid: "
380+ + json );
381+ }
382+ }
383+ }
384+ }
243385}
0 commit comments