blob: f31b4bc87525a5acf4776d03b765c907da6d2520 [file] [log] [blame]
// Copyright 2017 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.
import 'dart:async';
import 'dart:io';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
const String _backgroundName =
'plugins.flutter.io/android_alarm_manager_background';
// This is the entrypoint for the background isolate. Since we can only enter
// an isolate once, we setup a MethodChannel to listen for method invokations
// from the native portion of the plugin. This allows for the plugin to perform
// any necessary processing in Dart (e.g., populating a custom object) before
// invoking the provided callback.
void _alarmManagerCallbackDispatcher() {
const MethodChannel _channel =
MethodChannel(_backgroundName, JSONMethodCodec());
// Setup Flutter state needed for MethodChannels.
WidgetsFlutterBinding.ensureInitialized();
// This is where the magic happens and we handle background events from the
// native portion of the plugin.
_channel.setMethodCallHandler((MethodCall call) async {
final dynamic args = call.arguments;
final CallbackHandle handle = CallbackHandle.fromRawHandle(args[0]);
// PluginUtilities.getCallbackFromHandle performs a lookup based on the
// callback handle and returns a tear-off of the original callback.
final Function closure = PluginUtilities.getCallbackFromHandle(handle);
if (closure == null) {
print('Fatal: could not find callback');
exit(-1);
}
// ignore: inference_failure_on_function_return_type
if (closure is Function()) {
closure();
// ignore: inference_failure_on_function_return_type
} else if (closure is Function(int)) {
final int id = args[1];
closure(id);
}
});
// Once we've finished initializing, let the native portion of the plugin
// know that it can start scheduling alarms.
_channel.invokeMethod<void>('AlarmService.initialized');
}
// A lambda that returns the current instant in the form of a [DateTime].
typedef DateTime _Now();
// A lambda that gets the handle for the given [callback].
typedef CallbackHandle _GetCallbackHandle(Function callback);
/// A Flutter plugin for registering Dart callbacks with the Android
/// AlarmManager service.
///
/// See the example/ directory in this package for sample usage.
class AndroidAlarmManager {
static const String _channelName = 'plugins.flutter.io/android_alarm_manager';
static MethodChannel _channel =
const MethodChannel(_channelName, JSONMethodCodec());
// Function used to get the current time. It's [DateTime.now] by default.
static _Now _now = () => DateTime.now();
// Callback used to get the handle for a callback. It's
// [PluginUtilities.getCallbackHandle] by default.
static _GetCallbackHandle _getCallbackHandle =
(Function callback) => PluginUtilities.getCallbackHandle(callback);
/// This is exposed for the unit tests. It should not be accessed by users of
/// the plugin.
@visibleForTesting
static void setTestOverides(
{_Now now, _GetCallbackHandle getCallbackHandle}) {
_now = (now ?? _now);
_getCallbackHandle = (getCallbackHandle ?? _getCallbackHandle);
}
/// Starts the [AndroidAlarmManager] service. This must be called before
/// setting any alarms.
///
/// Returns a [Future] that resolves to `true` on success and `false` on
/// failure.
static Future<bool> initialize() async {
final CallbackHandle handle =
_getCallbackHandle(_alarmManagerCallbackDispatcher);
if (handle == null) {
return false;
}
final bool r = await _channel.invokeMethod<bool>(
'AlarmService.start', <dynamic>[handle.toRawHandle()]);
return r ?? false;
}
/// Schedules a one-shot timer to run `callback` after time `delay`.
///
/// The `callback` will run whether or not the main application is running or
/// in the foreground. It will run in the Isolate owned by the
/// AndroidAlarmManager service.
///
/// `callback` must be either a top-level function or a static method from a
/// class.
///
/// `callback` can be `Function()` or `Function(int)`
///
/// The timer is uniquely identified by `id`. Calling this function again
/// with the same `id` will cancel and replace the existing timer.
///
/// `id` will passed to `callback` if it is of type `Function(int)`
///
/// If `alarmClock` is passed as `true`, the timer will be created with
/// Android's `AlarmManagerCompat.setAlarmClock`.
///
/// If `allowWhileIdle` is passed as `true`, the timer will be created with
/// Android's `AlarmManagerCompat.setExactAndAllowWhileIdle` or
/// `AlarmManagerCompat.setAndAllowWhileIdle`.
///
/// If `exact` is passed as `true`, the timer will be created with Android's
/// `AlarmManagerCompat.setExact`. When `exact` is `false` (the default), the
/// timer will be created with `AlarmManager.set`.
///
/// If `wakeup` is passed as `true`, the device will be woken up when the
/// 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<bool> oneShot(
Duration delay,
int id,
Function callback, {
bool alarmClock = false,
bool allowWhileIdle = false,
bool exact = false,
bool wakeup = false,
bool rescheduleOnReboot = false,
}) =>
oneShotAt(
_now().add(delay),
id,
callback,
alarmClock: alarmClock,
allowWhileIdle: allowWhileIdle,
exact: exact,
wakeup: wakeup,
rescheduleOnReboot: rescheduleOnReboot,
);
/// Schedules a one-shot timer to run `callback` at `time`.
///
/// The `callback` will run whether or not the main application is running or
/// in the foreground. It will run in the Isolate owned by the
/// AndroidAlarmManager service.
///
/// `callback` must be either a top-level function or a static method from a
/// class.
///
/// `callback` can be `Function()` or `Function(int)`
///
/// The timer is uniquely identified by `id`. Calling this function again
/// with the same `id` will cancel and replace the existing timer.
///
/// `id` will passed to `callback` if it is of type `Function(int)`
///
/// If `alarmClock` is passed as `true`, the timer will be created with
/// Android's `AlarmManagerCompat.setAlarmClock`.
///
/// If `allowWhileIdle` is passed as `true`, the timer will be created with
/// Android's `AlarmManagerCompat.setExactAndAllowWhileIdle` or
/// `AlarmManagerCompat.setAndAllowWhileIdle`.
///
/// If `exact` is passed as `true`, the timer will be created with Android's
/// `AlarmManagerCompat.setExact`. When `exact` is `false` (the default), the
/// timer will be created with `AlarmManager.set`.
///
/// If `wakeup` is passed as `true`, the device will be woken up when the
/// 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<bool> oneShotAt(
DateTime time,
int id,
Function callback, {
bool alarmClock = false,
bool allowWhileIdle = false,
bool exact = false,
bool wakeup = false,
bool rescheduleOnReboot = false,
}) async {
// ignore: inference_failure_on_function_return_type
assert(callback is Function() || callback is Function(int));
assert(id.bitLength < 32);
final int startMillis = time.millisecondsSinceEpoch;
final CallbackHandle handle = _getCallbackHandle(callback);
if (handle == null) {
return false;
}
final bool r =
await _channel.invokeMethod<bool>('Alarm.oneShotAt', <dynamic>[
id,
alarmClock,
allowWhileIdle,
exact,
wakeup,
startMillis,
rescheduleOnReboot,
handle.toRawHandle(),
]);
return (r == null) ? false : r;
}
/// Schedules a repeating timer to run `callback` with period `duration`.
///
/// The `callback` will run whether or not the main application is running or
/// in the foreground. It will run in the Isolate owned by the
/// AndroidAlarmManager service.
///
/// `callback` must be either a top-level function or a static method from a
/// class.
///
/// `callback` can be `Function()` or `Function(int)`
///
/// The repeating timer is uniquely identified by `id`. Calling this function
/// again with the same `id` will cancel and replace the existing timer.
///
/// `id` will passed to `callback` if it is of type `Function(int)`
///
/// If `startAt` is passed, the timer will first go off at that time and
/// subsequently run with period `duration`.
///
/// If `exact` is passed as `true`, the timer will be created with Android's
/// `AlarmManager.setRepeating`. When `exact` is `false` (the default), the
/// timer will be created with `AlarmManager.setInexactRepeating`.
///
/// If `wakeup` is passed as `true`, the device will be woken up when the
/// 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<bool> periodic(
Duration duration,
int id,
Function callback, {
DateTime startAt,
bool exact = false,
bool wakeup = false,
bool rescheduleOnReboot = false,
}) async {
// ignore: inference_failure_on_function_return_type
assert(callback is Function() || callback is Function(int));
assert(id.bitLength < 32);
final int now = _now().millisecondsSinceEpoch;
final int period = duration.inMilliseconds;
final int first =
startAt != null ? startAt.millisecondsSinceEpoch : now + period;
final CallbackHandle handle = _getCallbackHandle(callback);
if (handle == null) {
return false;
}
final bool r = await _channel.invokeMethod<bool>(
'Alarm.periodic', <dynamic>[
id,
exact,
wakeup,
first,
period,
rescheduleOnReboot,
handle.toRawHandle()
]);
return (r == null) ? false : r;
}
/// Cancels a timer.
///
/// If a timer has been scheduled with `id`, then this function will cancel
/// it.
///
/// Returns a [Future] that resolves to `true` on success and `false` on
/// failure.
static Future<bool> cancel(int id) async {
final bool r =
await _channel.invokeMethod<bool>('Alarm.cancel', <dynamic>[id]);
return (r == null) ? false : r;
}
}