diff --git a/packages/android_alarm_manager/CHANGELOG.md b/packages/android_alarm_manager/CHANGELOG.md
index bf58bb8d0a4b..0ec184f085f7 100644
--- a/packages/android_alarm_manager/CHANGELOG.md
+++ b/packages/android_alarm_manager/CHANGELOG.md
@@ -1,3 +1,27 @@
+## 0.4.1
+* Added support for setting alarms which persist across reboots.
+ * Both `AndroidAlarmManager.oneShot` and `AndroidAlarmManager.periodic` have
+ an optional `rescheduleOnReboot` parameter which specifies whether the new
+ alarm should be rescheduled to run after a reboot (default: false). If set
+ to false, the alarm will not survive a device reboot.
+ * Requires AndroidManifest.xml to be updated to include the following
+ entries:
+
+ ```xml
+
+
+
+
+
+
+
+
+
+
+ ```
+
## 0.4.0
* **Breaking change**. Migrated the underlying AlarmService to utilize a
BroadcastReceiver with a JobIntentService instead of a Service to handle
diff --git a/packages/android_alarm_manager/README.md b/packages/android_alarm_manager/README.md
index bcbb007aac8d..33aeacd5adae 100644
--- a/packages/android_alarm_manager/README.md
+++ b/packages/android_alarm_manager/README.md
@@ -8,7 +8,13 @@ Dart code in the background when alarms fire.
## Getting Started
After importing this plugin to your project as usual, add the following to your
-`AndroidManifest.xml`:
+`AndroidManifest.xml` within the `` tags:
+
+```xml
+
+```
+
+Next, within the `` tags, add:
```xml
+
+
+
+
+
+
```
Then in Dart code add:
diff --git a/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/AlarmService.java b/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/AlarmService.java
index 85637cf2e4dd..6d1d3a7b6086 100644
--- a/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/AlarmService.java
+++ b/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/AlarmService.java
@@ -18,16 +18,23 @@
import io.flutter.view.FlutterNativeView;
import io.flutter.view.FlutterRunArguments;
import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
+import org.json.JSONException;
+import org.json.JSONObject;
public class AlarmService extends JobIntentService {
public static final String TAG = "AlarmService";
- private static final String SHARED_PREFERENCES_KEY = "io.flutter.android_alarm_manager_plugin";
private static final String CALLBACK_HANDLE_KEY = "callback_handle";
+ private static final String PERSISTENT_ALARMS_SET_KEY = "persistent_alarm_ids";
+ private static final String SHARED_PREFERENCES_KEY = "io.flutter.android_alarm_manager_plugin";
private static final int JOB_ID = 1984; // Random job ID.
+ private static final Object sPersistentAlarmsLock = new Object();
private static AtomicBoolean sStarted = new AtomicBoolean(false);
private static List sAlarmQueue = Collections.synchronizedList(new LinkedList());
private static FlutterNativeView sBackgroundFlutterView;
@@ -168,7 +175,20 @@ private static void scheduleAlarm(
boolean wakeup,
long startMillis,
long intervalMillis,
+ boolean rescheduleOnReboot,
long callbackHandle) {
+ if (rescheduleOnReboot) {
+ addPersistentAlarm(
+ context,
+ requestCode,
+ repeating,
+ exact,
+ wakeup,
+ startMillis,
+ intervalMillis,
+ callbackHandle);
+ }
+
// Create an Intent for the alarm and set the desired Dart callback handle.
Intent alarm = new Intent(context, AlarmBroadcastReceiver.class);
alarm.putExtra("callbackHandle", callbackHandle);
@@ -204,9 +224,19 @@ public static void setOneShot(
boolean exact,
boolean wakeup,
long startMillis,
+ boolean rescheduleOnReboot,
long callbackHandle) {
final boolean repeating = false;
- scheduleAlarm(context, requestCode, repeating, exact, wakeup, startMillis, 0, callbackHandle);
+ scheduleAlarm(
+ context,
+ requestCode,
+ repeating,
+ exact,
+ wakeup,
+ startMillis,
+ 0,
+ rescheduleOnReboot,
+ callbackHandle);
}
public static void setPeriodic(
@@ -216,6 +246,7 @@ public static void setPeriodic(
boolean wakeup,
long startMillis,
long intervalMillis,
+ boolean rescheduleOnReboot,
long callbackHandle) {
final boolean repeating = true;
scheduleAlarm(
@@ -226,10 +257,15 @@ public static void setPeriodic(
wakeup,
startMillis,
intervalMillis,
+ rescheduleOnReboot,
callbackHandle);
}
public static void cancel(Context context, int requestCode) {
+ // Clear the alarm if it was set to be rescheduled after reboots.
+ clearPersistentAlarm(context, requestCode);
+
+ // Cancel the alarm with the system alarm service.
Intent alarm = new Intent(context, AlarmBroadcastReceiver.class);
PendingIntent existingIntent =
PendingIntent.getBroadcast(context, requestCode, alarm, PendingIntent.FLAG_NO_CREATE);
@@ -240,4 +276,110 @@ public static void cancel(Context context, int requestCode) {
AlarmManager manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
manager.cancel(existingIntent);
}
+
+ private static String getPersistentAlarmKey(int requestCode) {
+ return "android_alarm_manager/persistent_alarm_" + Integer.toString(requestCode);
+ }
+
+ private static void addPersistentAlarm(
+ Context context,
+ int requestCode,
+ boolean repeating,
+ boolean exact,
+ boolean wakeup,
+ long startMillis,
+ long intervalMillis,
+ long callbackHandle) {
+ HashMap alarmSettings = new HashMap<>();
+ alarmSettings.put("repeating", repeating);
+ alarmSettings.put("exact", exact);
+ alarmSettings.put("wakeup", wakeup);
+ alarmSettings.put("startMillis", startMillis);
+ alarmSettings.put("intervalMillis", intervalMillis);
+ alarmSettings.put("callbackHandle", callbackHandle);
+ JSONObject obj = new JSONObject(alarmSettings);
+ String key = getPersistentAlarmKey(requestCode);
+ SharedPreferences p = context.getSharedPreferences(SHARED_PREFERENCES_KEY, 0);
+
+ synchronized (sPersistentAlarmsLock) {
+ Set persistentAlarms = p.getStringSet(PERSISTENT_ALARMS_SET_KEY, null);
+ if (persistentAlarms == null) {
+ persistentAlarms = new HashSet<>();
+ }
+ if (persistentAlarms.isEmpty()) {
+ RebootBroadcastReceiver.enableRescheduleOnReboot(context);
+ }
+ persistentAlarms.add(Integer.toString(requestCode));
+ p.edit()
+ .putString(key, obj.toString())
+ .putStringSet(PERSISTENT_ALARMS_SET_KEY, persistentAlarms)
+ .commit();
+ }
+ }
+
+ private static void clearPersistentAlarm(Context context, int requestCode) {
+ SharedPreferences p = context.getSharedPreferences(SHARED_PREFERENCES_KEY, 0);
+ synchronized (sPersistentAlarmsLock) {
+ Set persistentAlarms = p.getStringSet(PERSISTENT_ALARMS_SET_KEY, null);
+ if ((persistentAlarms == null) || !persistentAlarms.contains(requestCode)) {
+ return;
+ }
+ persistentAlarms.remove(requestCode);
+ String key = getPersistentAlarmKey(requestCode);
+ p.edit().remove(key).putStringSet(PERSISTENT_ALARMS_SET_KEY, persistentAlarms).commit();
+
+ if (persistentAlarms.isEmpty()) {
+ RebootBroadcastReceiver.disableRescheduleOnReboot(context);
+ }
+ }
+ }
+
+ public static void reschedulePersistentAlarms(Context context) {
+ synchronized (sPersistentAlarmsLock) {
+ SharedPreferences p = context.getSharedPreferences(SHARED_PREFERENCES_KEY, 0);
+ Set persistentAlarms = p.getStringSet(PERSISTENT_ALARMS_SET_KEY, null);
+ // No alarms to reschedule.
+ if (persistentAlarms == null) {
+ return;
+ }
+
+ Iterator it = persistentAlarms.iterator();
+ while (it.hasNext()) {
+ int requestCode = Integer.parseInt(it.next());
+ String key = getPersistentAlarmKey(requestCode);
+ String json = p.getString(key, null);
+ if (json == null) {
+ Log.e(
+ TAG, "Data for alarm request code " + Integer.toString(requestCode) + " is invalid.");
+ continue;
+ }
+ try {
+ JSONObject alarm = new JSONObject(json);
+ boolean repeating = alarm.getBoolean("repeating");
+ boolean exact = alarm.getBoolean("exact");
+ boolean wakeup = alarm.getBoolean("wakeup");
+ long startMillis = alarm.getLong("startMillis");
+ long intervalMillis = alarm.getLong("intervalMillis");
+ long callbackHandle = alarm.getLong("callbackHandle");
+ scheduleAlarm(
+ context,
+ requestCode,
+ repeating,
+ exact,
+ wakeup,
+ startMillis,
+ intervalMillis,
+ false,
+ callbackHandle);
+ } catch (JSONException e) {
+ Log.e(
+ TAG,
+ "Data for alarm request code "
+ + Integer.toString(requestCode)
+ + " is invalid: "
+ + json);
+ }
+ }
+ }
+ }
}
diff --git a/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/AndroidAlarmManagerPlugin.java b/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/AndroidAlarmManagerPlugin.java
index b0060c146950..594ac35908c7 100644
--- a/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/AndroidAlarmManagerPlugin.java
+++ b/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/AndroidAlarmManagerPlugin.java
@@ -81,8 +81,10 @@ private void oneShot(JSONArray arguments) throws JSONException {
boolean exact = arguments.getBoolean(1);
boolean wakeup = arguments.getBoolean(2);
long startMillis = arguments.getLong(3);
- long callbackHandle = arguments.getLong(4);
- AlarmService.setOneShot(mContext, requestCode, exact, wakeup, startMillis, callbackHandle);
+ boolean rescheduleOnReboot = arguments.getBoolean(4);
+ long callbackHandle = arguments.getLong(5);
+ AlarmService.setOneShot(
+ mContext, requestCode, exact, wakeup, startMillis, rescheduleOnReboot, callbackHandle);
}
private void periodic(JSONArray arguments) throws JSONException {
@@ -91,9 +93,17 @@ private void periodic(JSONArray arguments) throws JSONException {
boolean wakeup = arguments.getBoolean(2);
long startMillis = arguments.getLong(3);
long intervalMillis = arguments.getLong(4);
- long callbackHandle = arguments.getLong(5);
+ boolean rescheduleOnReboot = arguments.getBoolean(5);
+ long callbackHandle = arguments.getLong(6);
AlarmService.setPeriodic(
- mContext, requestCode, exact, wakeup, startMillis, intervalMillis, callbackHandle);
+ mContext,
+ requestCode,
+ exact,
+ wakeup,
+ startMillis,
+ intervalMillis,
+ rescheduleOnReboot,
+ callbackHandle);
}
private void cancel(JSONArray arguments) throws JSONException {
diff --git a/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/RebootBroadcastReceiver.java b/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/RebootBroadcastReceiver.java
new file mode 100644
index 000000000000..91836ccbdaad
--- /dev/null
+++ b/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/RebootBroadcastReceiver.java
@@ -0,0 +1,36 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package io.flutter.plugins.androidalarmmanager;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.util.Log;
+
+public class RebootBroadcastReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) {
+ Log.i("AlarmService", "Rescheduling after boot!");
+ AlarmService.reschedulePersistentAlarms(context);
+ }
+ }
+
+ public static void enableRescheduleOnReboot(Context context) {
+ scheduleOnReboot(context, PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+ }
+
+ public static void disableRescheduleOnReboot(Context context) {
+ scheduleOnReboot(context, PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
+ }
+
+ private static void scheduleOnReboot(Context context, int state) {
+ ComponentName receiver = new ComponentName(context, RebootBroadcastReceiver.class);
+ PackageManager pm = context.getPackageManager();
+ pm.setComponentEnabledSetting(receiver, state, PackageManager.DONT_KILL_APP);
+ }
+}
diff --git a/packages/android_alarm_manager/example/android/app/src/main/AndroidManifest.xml b/packages/android_alarm_manager/example/android/app/src/main/AndroidManifest.xml
index e1724dac19ad..862e20924a0d 100644
--- a/packages/android_alarm_manager/example/android/app/src/main/AndroidManifest.xml
+++ b/packages/android_alarm_manager/example/android/app/src/main/AndroidManifest.xml
@@ -1,6 +1,7 @@
+
+
+
+
+
+
diff --git a/packages/android_alarm_manager/lib/android_alarm_manager.dart b/packages/android_alarm_manager/lib/android_alarm_manager.dart
index 36aba4e250b7..9354a1b34840 100644
--- a/packages/android_alarm_manager/lib/android_alarm_manager.dart
+++ b/packages/android_alarm_manager/lib/android_alarm_manager.dart
@@ -97,6 +97,10 @@ class AndroidAlarmManager {
/// alarm fires. If `wakeup` is false (the default), the device will not be
/// woken up to service the alarm.
///
+ /// If `rescheduleOnReboot` is passed as `true`, the alarm will be persisted
+ /// across reboots. If `rescheduleOnReboot` is false (the default), the alarm
+ /// will not be rescheduled after a reboot and will not be executed.
+ ///
/// Returns a [Future] that resolves to `true` on success and `false` on
/// failure.
static Future oneShot(
@@ -105,6 +109,7 @@ class AndroidAlarmManager {
dynamic Function() callback, {
bool exact = false,
bool wakeup = false,
+ bool rescheduleOnReboot = false,
}) async {
final int now = DateTime.now().millisecondsSinceEpoch;
final int first = now + delay.inMilliseconds;
@@ -120,6 +125,7 @@ class AndroidAlarmManager {
exact,
wakeup,
first,
+ rescheduleOnReboot,
handle.toRawHandle(),
]);
return (r == null) ? false : r;
@@ -145,6 +151,10 @@ class AndroidAlarmManager {
/// alarm fires. If `wakeup` is false (the default), the device will not be
/// woken up to service the alarm.
///
+ /// If `rescheduleOnReboot` is passed as `true`, the alarm will be persisted
+ /// across reboots. If `rescheduleOnReboot` is false (the default), the alarm
+ /// will not be rescheduled after a reboot and will not be executed.
+ ///
/// Returns a [Future] that resolves to `true` on success and `false` on
/// failure.
static Future periodic(
@@ -153,6 +163,7 @@ class AndroidAlarmManager {
dynamic Function() callback, {
bool exact = false,
bool wakeup = false,
+ bool rescheduleOnReboot = false,
}) async {
final int now = DateTime.now().millisecondsSinceEpoch;
final int period = duration.inMilliseconds;
@@ -164,8 +175,15 @@ class AndroidAlarmManager {
// TODO(amirh): remove this on when the invokeMethod update makes it to stable Flutter.
// https://github.com/flutter/flutter/issues/26431
// ignore: strong_mode_implicit_dynamic_method
- final dynamic r = await _channel.invokeMethod('Alarm.periodic',
- [id, exact, wakeup, first, period, handle.toRawHandle()]);
+ final dynamic r = await _channel.invokeMethod('Alarm.periodic', [
+ id,
+ exact,
+ wakeup,
+ first,
+ period,
+ rescheduleOnReboot,
+ handle.toRawHandle()
+ ]);
return (r == null) ? false : r;
}
diff --git a/packages/android_alarm_manager/pubspec.yaml b/packages/android_alarm_manager/pubspec.yaml
index b12ab8c69563..5d3006a85aef 100644
--- a/packages/android_alarm_manager/pubspec.yaml
+++ b/packages/android_alarm_manager/pubspec.yaml
@@ -1,7 +1,7 @@
name: android_alarm_manager
description: Flutter plugin for accessing the Android AlarmManager service, and
running Dart code in the background when alarms fire.
-version: 0.4.0
+version: 0.4.1
author: Flutter Team
homepage: https://github.com/flutter/plugins/tree/master/packages/android_alarm_manager