Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions packages/android_alarm_manager/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
<!--Within the application tag body-->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

<!--Within the manifest tag body-->
<receiver
android:name="io.flutter.plugins.androidalarmmanager.RebootBroadcastReceiver"
android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"></action>
</intent-filter>
</receiver>

```

## 0.4.0
* **Breaking change**. Migrated the underlying AlarmService to utilize a
BroadcastReceiver with a JobIntentService instead of a Service to handle
Expand Down
16 changes: 15 additions & 1 deletion packages/android_alarm_manager/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<manifest></manifest>` tags:

```xml
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
```

Next, within the `<application></application>` tags, add:

```xml
<service
Expand All @@ -18,6 +24,14 @@ After importing this plugin to your project as usual, add the following to your
<receiver
android:name="io.flutter.plugins.androidalarmmanager.AlarmBroadcastReceiver"
android:exported="false"/>
<receiver
android:name="io.flutter.plugins.androidalarmmanager.RebootBroadcastReceiver"
android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"></action>
</intent-filter>
</receiver>

```

Then in Dart code add:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Intent> sAlarmQueue = Collections.synchronizedList(new LinkedList<Intent>());
private static FlutterNativeView sBackgroundFlutterView;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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(
Expand All @@ -216,6 +246,7 @@ public static void setPeriodic(
boolean wakeup,
long startMillis,
long intervalMillis,
boolean rescheduleOnReboot,
long callbackHandle) {
final boolean repeating = true;
scheduleAlarm(
Expand All @@ -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);
Expand All @@ -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<String, Object> 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<String> 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<String> 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<String> persistentAlarms = p.getStringSet(PERSISTENT_ALARMS_SET_KEY, null);
// No alarms to reschedule.
if (persistentAlarms == null) {
return;
}

Iterator<String> 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);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="io.flutter.plugins.androidalarmmanagerexample">

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.INTERNET"/>

<application
Expand Down Expand Up @@ -29,5 +30,12 @@
<receiver
android:name="io.flutter.plugins.androidalarmmanager.AlarmBroadcastReceiver"
android:exported="false"/>
<receiver
android:name="io.flutter.plugins.androidalarmmanager.RebootBroadcastReceiver"
android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"></action>
</intent-filter>
</receiver>
</application>
</manifest>
Loading