From 0808e48317936b4e659ce453c972e5ff4104c5a2 Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Thu, 28 Jun 2018 16:31:41 -0700 Subject: [PATCH 1/6] Updated `android_alarm_manager` to work after engine refactor. Fixes issue #17566: alarm_manager plugin stopped working --- .../androidalarmmanager/AlarmService.java | 154 +++++++++--------- .../AndroidAlarmManagerPlugin.java | 45 ++++- .../example/lib/main.dart | 13 +- .../lib/android_alarm_manager.dart | 140 ++++++++++++---- 4 files changed, 233 insertions(+), 119 deletions(-) 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 d984a927badf..0e060b305c02 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 @@ -15,40 +15,70 @@ import android.util.Log; import io.flutter.app.FlutterActivity; import io.flutter.app.FlutterApplication; +import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback; +import io.flutter.view.FlutterIsolateStartedEvent; import io.flutter.view.FlutterMain; import io.flutter.view.FlutterNativeView; +import java.util.concurrent.atomic.AtomicBoolean; public class AlarmService extends Service { public static final String TAG = "AlarmService"; + private static AtomicBoolean sStarted; private static FlutterNativeView sSharedFlutterView; + private static MethodChannel sBackgroundChannel; + private static OnStartedCallback sOnStartedCallback; private static PluginRegistrantCallback sPluginRegistrantCallback; - private FlutterNativeView mFlutterView; - private String appBundlePath; + private String mAppBundlePath; - public static void setOneShot( - Context context, - int requestCode, - boolean exact, - boolean wakeup, - long startMillis, - String entrypoint) { + private static class OnStartedCallback implements FlutterIsolateStartedEvent { + public void onStarted(boolean success) { + if (!success) { + Log.e(TAG, "AlarmService start failed. Bailing out."); + return; + } + sStarted.set(true); + } + } + + public static void startAlarmService(Context context, String entrypoint, + String libraryPath) { + FlutterMain.ensureInitializationComplete(context, null); + String mAppBundlePath = FlutterMain.findAppBundlePath(context); + sSharedFlutterView = new FlutterNativeView(context, true); + sStarted = new AtomicBoolean(false); + if (mAppBundlePath != null && !sStarted.get()) { + Log.i(TAG, "Starting AlarmService..."); + sOnStartedCallback = new OnStartedCallback(); + sSharedFlutterView.runFromBundle(mAppBundlePath, null, entrypoint, + libraryPath, false, sOnStartedCallback); + sPluginRegistrantCallback.registerWith( + sSharedFlutterView.getPluginRegistry()); + } + } + + public static void setBackgroundChannel(MethodChannel channel) { + sBackgroundChannel = channel; + } + + public static void setOneShot(Context context, int requestCode, boolean exact, + boolean wakeup, long startMillis, + String entrypoint, String className, + String libraryPath) { final boolean repeating = false; - scheduleAlarm(context, requestCode, repeating, exact, wakeup, startMillis, 0, entrypoint); + scheduleAlarm(context, requestCode, repeating, exact, wakeup, startMillis, + 0, entrypoint, className, libraryPath); } - public static void setPeriodic( - Context context, - int requestCode, - boolean exact, - boolean wakeup, - long startMillis, - long intervalMillis, - String entrypoint) { + public static void setPeriodic(Context context, int requestCode, + boolean exact, boolean wakeup, + long startMillis, long intervalMillis, + String entrypoint, String className, + String libraryPath) { final boolean repeating = true; - scheduleAlarm( - context, requestCode, repeating, exact, wakeup, startMillis, intervalMillis, entrypoint); + scheduleAlarm(context, requestCode, repeating, exact, wakeup, startMillis, + intervalMillis, entrypoint, className, libraryPath); } public static void cancel(Context context, int requestCode) { @@ -72,7 +102,6 @@ public static boolean setSharedFlutterView(FlutterNativeView view) { Log.i(TAG, "setSharedFlutterView tried to overwrite an existing FlutterNativeView"); return false; } - Log.i(TAG, "setSharedFlutterView set"); sSharedFlutterView = view; return true; } @@ -81,28 +110,6 @@ public static void setPluginRegistrant(PluginRegistrantCallback callback) { sPluginRegistrantCallback = callback; } - private void ensureFlutterView() { - if (mFlutterView != null) { - return; - } - - if (sSharedFlutterView != null) { - mFlutterView = sSharedFlutterView; - return; - } - - // mFlutterView and sSharedFlutterView are both null. That likely means that - // no FlutterView has ever been created in this process before. So, we'll - // make one, and assign it to both mFlutterView and sSharedFlutterView. - mFlutterView = new FlutterNativeView(getApplicationContext()); - sSharedFlutterView = mFlutterView; - - // If there was no FlutterNativeView before now, then we also must - // initialize the PluginRegistry. - sPluginRegistrantCallback.registerWith(mFlutterView.getPluginRegistry()); - return; - } - // This returns the FlutterView for the main FlutterActivity if there is one. private static FlutterNativeView viewFromAppContext(Context context) { Application app = (Application) context; @@ -128,45 +135,32 @@ private static FlutterNativeView viewFromAppContext(Context context) { public void onCreate() { super.onCreate(); Context context = getApplicationContext(); - mFlutterView = viewFromAppContext(context); FlutterMain.ensureInitializationComplete(context, null); - if (appBundlePath == null) { - appBundlePath = FlutterMain.findAppBundlePath(context); - } - } - - @Override - public void onDestroy() { - // Try to find the native view of the main activity if there is one. - Context context = getApplicationContext(); - FlutterNativeView nativeView = viewFromAppContext(context); - - // Don't destroy mFlutterView if it is the same as the native view for the - // main activity, or the same as the shared native view. - if (mFlutterView != nativeView && mFlutterView != sSharedFlutterView) { - mFlutterView.destroy(); - } - mFlutterView = null; - - // Don't destroy the shared native view if it is the same native view as - // for the main activity. - if (sSharedFlutterView != nativeView) { - sSharedFlutterView.destroy(); - } - sSharedFlutterView = null; + mAppBundlePath = FlutterMain.findAppBundlePath(context); } @Override public int onStartCommand(Intent intent, int flags, int startId) { - ensureFlutterView(); + if (!sStarted.get()) { + Log.i(TAG, "AlarmService has not yet started."); + // TODO(bkonyi): queue up alarm events. + return START_NOT_STICKY; + } String entrypoint = intent.getStringExtra("entrypoint"); + String className = intent.getStringExtra("className"); + String libraryPath = intent.getStringExtra("libraryPath"); if (entrypoint == null) { - Log.i(TAG, "onStartCommand got a null entrypoint. Bailing out"); + Log.e(TAG, "onStartCommand got a null entrypoint. Bailing out."); return START_NOT_STICKY; } - if (appBundlePath != null) { - mFlutterView.runFromBundle(appBundlePath, null, entrypoint, true); + if (sBackgroundChannel == null) { + Log.e(TAG, + "setBackgroundChannel was not called before alarms were scheduled." + + " Bailing out."); + return START_NOT_STICKY; } + sBackgroundChannel.invokeMethod( + "", new Object[] {entrypoint, libraryPath, className}); return START_NOT_STICKY; } @@ -175,18 +169,16 @@ public IBinder onBind(Intent intent) { return null; } - private static void scheduleAlarm( - Context context, - int requestCode, - boolean repeating, - boolean exact, - boolean wakeup, - long startMillis, - long intervalMillis, - String entrypoint) { + private static void scheduleAlarm(Context context, int requestCode, + boolean repeating, boolean exact, + boolean wakeup, long startMillis, + long intervalMillis, String entrypoint, + String className, String libraryPath) { // Create an Intent for the alarm and set the desired Dart entrypoint. Intent alarm = new Intent(context, AlarmService.class); alarm.putExtra("entrypoint", entrypoint); + alarm.putExtra("className", className); + alarm.putExtra("libraryPath", libraryPath); PendingIntent pendingIntent = PendingIntent.getService(context, requestCode, alarm, PendingIntent.FLAG_UPDATE_CURRENT); 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 d27cb5922422..6dbb2d22be77 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 @@ -25,9 +25,15 @@ public static void registerWith(Registrar registrar) { registrar.messenger(), "plugins.flutter.io/android_alarm_manager", JSONMethodCodec.INSTANCE); + final MethodChannel backgroundChannel = + new MethodChannel(registrar.messenger(), + "plugins.flutter.io/android_alarm_manager_background", + JSONMethodCodec.INSTANCE); AndroidAlarmManagerPlugin plugin = new AndroidAlarmManagerPlugin(registrar.context()); channel.setMethodCallHandler(plugin); + backgroundChannel.setMethodCallHandler(plugin); registrar.addViewDestroyListener(plugin); + AlarmService.setBackgroundChannel(backgroundChannel); } private Context mContext; @@ -41,7 +47,10 @@ public void onMethodCall(MethodCall call, Result result) { String method = call.method; Object arguments = call.arguments; try { - if (method.equals("Alarm.periodic")) { + if (method.equals("AlarmService.start")) { + startService((JSONArray)arguments); + result.success(true); + } else if (method.equals("Alarm.periodic")) { periodic((JSONArray) arguments); result.success(true); } else if (method.equals("Alarm.oneShot")) { @@ -58,13 +67,30 @@ public void onMethodCall(MethodCall call, Result result) { } } + private void startService(JSONArray arguments) throws JSONException { + String entrypoint = arguments.getString(0); + String libraryPath = arguments.getString(1); + AlarmService.startAlarmService(mContext, entrypoint, libraryPath); + } + private void oneShot(JSONArray arguments) throws JSONException { int requestCode = arguments.getInt(0); boolean exact = arguments.getBoolean(1); boolean wakeup = arguments.getBoolean(2); long startMillis = arguments.getLong(3); String entrypoint = arguments.getString(4); - AlarmService.setOneShot(mContext, requestCode, exact, wakeup, startMillis, entrypoint); + + // JSONArray.getString will coerce null to "null". + String className = null; + if (!arguments.get(5).equals(null)) { + className = arguments.getString(5); + } + String libraryPath = null; + if (!arguments.get(6).equals(null)) { + libraryPath = arguments.getString(6); + } + AlarmService.setOneShot(mContext, requestCode, exact, wakeup, startMillis, + entrypoint, className, libraryPath); } private void periodic(JSONArray arguments) throws JSONException { @@ -74,8 +100,19 @@ private void periodic(JSONArray arguments) throws JSONException { long startMillis = arguments.getLong(3); long intervalMillis = arguments.getLong(4); String entrypoint = arguments.getString(5); - AlarmService.setPeriodic( - mContext, requestCode, exact, wakeup, startMillis, intervalMillis, entrypoint); + + // JSONArray.getString will coerce null to "null". + String className = null; + if (!arguments.get(6).equals(null)) { + className = arguments.getString(6); + } + String libraryPath = null; + if (!arguments.get(7).equals(null)) { + libraryPath = arguments.getString(7); + } + AlarmService.setPeriodic(mContext, requestCode, exact, wakeup, startMillis, + intervalMillis, entrypoint, className, + libraryPath); } private void cancel(JSONArray arguments) throws JSONException { diff --git a/packages/android_alarm_manager/example/lib/main.dart b/packages/android_alarm_manager/example/lib/main.dart index 43f2f8a082e6..4e6ac36efd50 100644 --- a/packages/android_alarm_manager/example/lib/main.dart +++ b/packages/android_alarm_manager/example/lib/main.dart @@ -71,15 +71,20 @@ Future main() async { final int helloAlarmID = 0; final int goodbyeAlarmID = 1; final int oneShotID = 2; + + // Start the AlarmManager service. + await AndroidAlarmManager.initialize(); + printHelloMessage("Hello, main()!"); runApp(const Center( child: Text('Hello, world!', textDirection: TextDirection.ltr))); await AndroidAlarmManager.periodic( - const Duration(minutes: 1), helloAlarmID, printHello); - await AndroidAlarmManager.periodic( - const Duration(minutes: 1), goodbyeAlarmID, printGoodbye); + const Duration(seconds: 5), helloAlarmID, printHello, + wakeup: true); + await AndroidAlarmManager.oneShot( + const Duration(seconds: 5), goodbyeAlarmID, printGoodbye); if (!oneShotFired) { await AndroidAlarmManager.oneShot( - const Duration(minutes: 1), oneShotID, printOneShot); + const Duration(seconds: 5), oneShotID, printOneShot); } } diff --git a/packages/android_alarm_manager/lib/android_alarm_manager.dart b/packages/android_alarm_manager/lib/android_alarm_manager.dart index a1271ede0fb7..8bf97a4e05f1 100644 --- a/packages/android_alarm_manager/lib/android_alarm_manager.dart +++ b/packages/android_alarm_manager/lib/android_alarm_manager.dart @@ -3,9 +3,54 @@ // 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'; + +void _alarmManagerCallbackDispatcher() { + const MethodChannel _channel = + const MethodChannel(_backgroundName, const JSONMethodCodec()); + final Map _callbackCache = new Map(); + WidgetsFlutterBinding.ensureInitialized(); + _channel.setMethodCallHandler((MethodCall call) async { + final args = call.arguments; + // args[0].runtimeType == List + // pair[0] = closure name + // pair[1] = closure library path + // pair[2] = closure containing class + final pair = args.cast(); + final cacheKey = pair.join('@'); + Function closure; + // To avoid making repeated lookups of our callback, store the resulting + // closure in a cache based on the closure name and its library path. + if (_callbackCache.containsKey(cacheKey)) { + closure = _callbackCache[cacheKey]; + } else { + // PluginUtilities.getClosureByName performs a lookup based on the name + // of a closure as well as its library Uri. + closure = PluginUtilities.getClosureByName( + name: pair[0], + libraryPath: pair[1], + className: (pair[2] == 'null') ? null : pair[2]); + + if (closure == null) { + print('Could not find closure: ${pair[0]} in ${pair[1]}.'); + print('Either ${pair[0]} does not exist or it is an instance method.'); + exit(-1); + } + _callbackCache[cacheKey] = closure; + } + assert( + closure != null, 'Could not find closure: ${pair[0]} in ${pair[1]}.'); + closure(); + }); +} + /// A Flutter plugin for registering Dart callbacks with the Android /// AlarmManager service. /// @@ -15,14 +60,32 @@ class AndroidAlarmManager { static const MethodChannel _channel = MethodChannel(_channelName, JSONMethodCodec()); + /// 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 initialize() async { + final String functionName = + PluginUtilities.getNameOfFunction(_alarmManagerCallbackDispatcher); + final String libraryPath = PluginUtilities + .getPathForFunctionLibrary(_alarmManagerCallbackDispatcher); + if (functionName == null) { + return false; + } + final dynamic r = await _channel.invokeMethod( + 'AlarmService.start', [functionName, libraryPath]); + 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 same Isolate as the main application - /// if one is available, otherwise a new Isolate will be created. + /// in the foreground. It will run in the Isolate owned by the + /// AndroidAlarmManager service. /// - /// `callback` must be a top-level function in the application's root library - /// (that is, in the same library as the application's `main()` function). + /// `callback` must be either a top-level function or a static method from a + /// class. /// /// The timer is uniquely identified by `id`. Calling this function again /// again with the same `id` will cancel and replace the existing timer. @@ -46,23 +109,39 @@ class AndroidAlarmManager { }) async { final int now = new DateTime.now().millisecondsSinceEpoch; final int first = now + delay.inMilliseconds; - final String functionName = _nameOfFunction(callback); + final String functionName = PluginUtilities.getNameOfFunction(callback); + final String className = PluginUtilities.getNameOfFunctionClass(callback); + final String libraryPath = + PluginUtilities.getPathForFunctionLibrary(callback); + if (functionName == null) { return false; } - final dynamic r = await _channel.invokeMethod( - 'Alarm.oneShot', [id, exact, wakeup, first, functionName]); + + if (libraryPath == null) { + return false; + } + + final dynamic r = await _channel.invokeMethod('Alarm.oneShot', [ + id, + exact, + wakeup, + first, + functionName, + className, + libraryPath + ]); 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 same Isolate as the main application - /// if one is available, otherwise a new Isolate will be created. + /// in the foreground. It will run in the Isolate owned by the + /// AndroidAlarmManager service. /// - /// `callback` must be a top-level function in the application's root library - /// (that is, in the same library as the application's `main()` function). + /// `callback` must be either a top-level function or a static method from a + /// class. /// /// The repeating timer is uniquely identified by `id`. Calling this function /// again with the same `id` will cancel and replace the existing timer. @@ -87,12 +166,29 @@ class AndroidAlarmManager { final int now = new DateTime.now().millisecondsSinceEpoch; final int period = duration.inMilliseconds; final int first = now + period; - final String functionName = _nameOfFunction(callback); + final String functionName = PluginUtilities.getNameOfFunction(callback); + final String className = PluginUtilities.getNameOfFunctionClass(callback); + final String libraryPath = + PluginUtilities.getPathForFunctionLibrary(callback); + if (functionName == null) { return false; } - final dynamic r = await _channel.invokeMethod('Alarm.periodic', - [id, exact, wakeup, first, period, functionName]); + + if (libraryPath == null) { + return false; + } + + final dynamic r = await _channel.invokeMethod('Alarm.periodic', [ + id, + exact, + wakeup, + first, + period, + functionName, + className, + libraryPath + ]); return (r == null) ? false : r; } @@ -108,20 +204,4 @@ class AndroidAlarmManager { await _channel.invokeMethod('Alarm.cancel', [id]); return (r == null) ? false : r; } - - // Extracts the name of a top-level function from the .toString() of its - // closure-ization. The Java side of this plugin accepts the entrypoint into - // Dart code as a string. However, the Dart side of this API can't use a - // string to specify the entrypoint, otherwise it won't be visited by Dart's - // AOT compiler. - static String _nameOfFunction(dynamic Function() callback) { - final String longName = callback.toString(); - final int functionIndex = longName.indexOf('Function'); - if (functionIndex == -1) return null; - final int openQuote = longName.indexOf("'", functionIndex + 1); - if (openQuote == -1) return null; - final int closeQuote = longName.indexOf("'", openQuote + 1); - if (closeQuote == -1) return null; - return longName.substring(openQuote + 1, closeQuote); - } } From e1992ec782cf42b8438b8aa2467dfa24052ddd32 Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Tue, 17 Jul 2018 16:07:37 -0700 Subject: [PATCH 2/6] Updated to use FlutterRunArguments --- .../androidalarmmanager/AlarmService.java | 52 ++++++------ .../AndroidAlarmManagerPlugin.java | 36 ++------- .../lib/android_alarm_manager.dart | 79 +++++-------------- 3 files changed, 53 insertions(+), 114 deletions(-) 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 0e060b305c02..f0eb2fb5f0a6 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 @@ -17,9 +17,11 @@ import io.flutter.app.FlutterApplication; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback; +import io.flutter.view.FlutterCallbackInformation; import io.flutter.view.FlutterIsolateStartedEvent; import io.flutter.view.FlutterMain; import io.flutter.view.FlutterNativeView; +import io.flutter.view.FlutterRunArguments; import java.util.concurrent.atomic.AtomicBoolean; public class AlarmService extends Service { @@ -38,21 +40,32 @@ public void onStarted(boolean success) { Log.e(TAG, "AlarmService start failed. Bailing out."); return; } - sStarted.set(true); } } - public static void startAlarmService(Context context, String entrypoint, - String libraryPath) { + public static void onInitialized() { + sStarted.set(true); + } + + public static void startAlarmService(Context context, long callbackHandle) { FlutterMain.ensureInitializationComplete(context, null); String mAppBundlePath = FlutterMain.findAppBundlePath(context); + FlutterCallbackInformation cb = FlutterCallbackInformation.lookupCallbackInformation(callbackHandle); + if (cb == null) { + Log.e(TAG, "Fatal: failed to find callback"); + return; + } sSharedFlutterView = new FlutterNativeView(context, true); sStarted = new AtomicBoolean(false); if (mAppBundlePath != null && !sStarted.get()) { Log.i(TAG, "Starting AlarmService..."); sOnStartedCallback = new OnStartedCallback(); - sSharedFlutterView.runFromBundle(mAppBundlePath, null, entrypoint, - libraryPath, false, sOnStartedCallback); + FlutterRunArguments args = new FlutterRunArguments(); + args.bundlePath = mAppBundlePath; + args.entrypoint = cb.callbackName; + args.libraryPath = cb.callbackLibraryPath; + args.onStartedEvent = sOnStartedCallback; + sSharedFlutterView.runFromBundle(args); sPluginRegistrantCallback.registerWith( sSharedFlutterView.getPluginRegistry()); } @@ -64,21 +77,19 @@ public static void setBackgroundChannel(MethodChannel channel) { public static void setOneShot(Context context, int requestCode, boolean exact, boolean wakeup, long startMillis, - String entrypoint, String className, - String libraryPath) { + long callbackHandle) { final boolean repeating = false; scheduleAlarm(context, requestCode, repeating, exact, wakeup, startMillis, - 0, entrypoint, className, libraryPath); + 0, callbackHandle); } public static void setPeriodic(Context context, int requestCode, boolean exact, boolean wakeup, long startMillis, long intervalMillis, - String entrypoint, String className, - String libraryPath) { + long callbackHandle) { final boolean repeating = true; scheduleAlarm(context, requestCode, repeating, exact, wakeup, startMillis, - intervalMillis, entrypoint, className, libraryPath); + intervalMillis, callbackHandle); } public static void cancel(Context context, int requestCode) { @@ -146,13 +157,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { // TODO(bkonyi): queue up alarm events. return START_NOT_STICKY; } - String entrypoint = intent.getStringExtra("entrypoint"); - String className = intent.getStringExtra("className"); - String libraryPath = intent.getStringExtra("libraryPath"); - if (entrypoint == null) { - Log.e(TAG, "onStartCommand got a null entrypoint. Bailing out."); - return START_NOT_STICKY; - } + long callbackHandle = intent.getLongExtra("callbackHandle", 0); if (sBackgroundChannel == null) { Log.e(TAG, "setBackgroundChannel was not called before alarms were scheduled." @@ -160,7 +165,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { return START_NOT_STICKY; } sBackgroundChannel.invokeMethod( - "", new Object[] {entrypoint, libraryPath, className}); + "", new Object[] {callbackHandle}); return START_NOT_STICKY; } @@ -172,13 +177,10 @@ public IBinder onBind(Intent intent) { private static void scheduleAlarm(Context context, int requestCode, boolean repeating, boolean exact, boolean wakeup, long startMillis, - long intervalMillis, String entrypoint, - String className, String libraryPath) { - // Create an Intent for the alarm and set the desired Dart entrypoint. + long intervalMillis, long callbackHandle) { + // Create an Intent for the alarm and set the desired Dart callback handle. Intent alarm = new Intent(context, AlarmService.class); - alarm.putExtra("entrypoint", entrypoint); - alarm.putExtra("className", className); - alarm.putExtra("libraryPath", libraryPath); + alarm.putExtra("callbackHandle", callbackHandle); PendingIntent pendingIntent = PendingIntent.getService(context, requestCode, alarm, PendingIntent.FLAG_UPDATE_CURRENT); 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 6dbb2d22be77..1899b9405762 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 @@ -50,6 +50,8 @@ public void onMethodCall(MethodCall call, Result result) { if (method.equals("AlarmService.start")) { startService((JSONArray)arguments); result.success(true); + } else if (method.equals("AlarmService.initialized")) { + AlarmService.onInitialized(); } else if (method.equals("Alarm.periodic")) { periodic((JSONArray) arguments); result.success(true); @@ -68,9 +70,8 @@ public void onMethodCall(MethodCall call, Result result) { } private void startService(JSONArray arguments) throws JSONException { - String entrypoint = arguments.getString(0); - String libraryPath = arguments.getString(1); - AlarmService.startAlarmService(mContext, entrypoint, libraryPath); + long callbackHandle = arguments.getLong(0); + AlarmService.startAlarmService(mContext, callbackHandle); } private void oneShot(JSONArray arguments) throws JSONException { @@ -78,19 +79,9 @@ private void oneShot(JSONArray arguments) throws JSONException { boolean exact = arguments.getBoolean(1); boolean wakeup = arguments.getBoolean(2); long startMillis = arguments.getLong(3); - String entrypoint = arguments.getString(4); - - // JSONArray.getString will coerce null to "null". - String className = null; - if (!arguments.get(5).equals(null)) { - className = arguments.getString(5); - } - String libraryPath = null; - if (!arguments.get(6).equals(null)) { - libraryPath = arguments.getString(6); - } + long callbackHandle = arguments.getLong(4); AlarmService.setOneShot(mContext, requestCode, exact, wakeup, startMillis, - entrypoint, className, libraryPath); + callbackHandle); } private void periodic(JSONArray arguments) throws JSONException { @@ -99,20 +90,9 @@ private void periodic(JSONArray arguments) throws JSONException { boolean wakeup = arguments.getBoolean(2); long startMillis = arguments.getLong(3); long intervalMillis = arguments.getLong(4); - String entrypoint = arguments.getString(5); - - // JSONArray.getString will coerce null to "null". - String className = null; - if (!arguments.get(6).equals(null)) { - className = arguments.getString(6); - } - String libraryPath = null; - if (!arguments.get(7).equals(null)) { - libraryPath = arguments.getString(7); - } + long callbackHandle = arguments.getLong(5); AlarmService.setPeriodic(mContext, requestCode, exact, wakeup, startMillis, - intervalMillis, entrypoint, className, - libraryPath); + intervalMillis, callbackHandle); } private void cancel(JSONArray arguments) throws JSONException { diff --git a/packages/android_alarm_manager/lib/android_alarm_manager.dart b/packages/android_alarm_manager/lib/android_alarm_manager.dart index 8bf97a4e05f1..d4037e25ac47 100644 --- a/packages/android_alarm_manager/lib/android_alarm_manager.dart +++ b/packages/android_alarm_manager/lib/android_alarm_manager.dart @@ -19,36 +19,17 @@ void _alarmManagerCallbackDispatcher() { WidgetsFlutterBinding.ensureInitialized(); _channel.setMethodCallHandler((MethodCall call) async { final args = call.arguments; - // args[0].runtimeType == List - // pair[0] = closure name - // pair[1] = closure library path - // pair[2] = closure containing class - final pair = args.cast(); - final cacheKey = pair.join('@'); - Function closure; - // To avoid making repeated lookups of our callback, store the resulting - // closure in a cache based on the closure name and its library path. - if (_callbackCache.containsKey(cacheKey)) { - closure = _callbackCache[cacheKey]; - } else { - // PluginUtilities.getClosureByName performs a lookup based on the name - // of a closure as well as its library Uri. - closure = PluginUtilities.getClosureByName( - name: pair[0], - libraryPath: pair[1], - className: (pair[2] == 'null') ? null : pair[2]); - - if (closure == null) { - print('Could not find closure: ${pair[0]} in ${pair[1]}.'); - print('Either ${pair[0]} does not exist or it is an instance method.'); - exit(-1); - } - _callbackCache[cacheKey] = closure; + final CallbackHandle handle = new CallbackHandle.fromRawHandle(args[0]); + // PluginUtilities.getCallbackFromHandle performs a lookup based on the + // callback handle and returns a tear-off of the original callback. + Function closure = PluginUtilities.getCallbackFromHandle(handle); + if (closure == null) { + print('Fatal: could not find callback'); + exit(-1); } - assert( - closure != null, 'Could not find closure: ${pair[0]} in ${pair[1]}.'); closure(); }); + _channel.invokeMethod('AlarmService.initialized'); } /// A Flutter plugin for registering Dart callbacks with the Android @@ -66,15 +47,13 @@ class AndroidAlarmManager { /// Returns a [Future] that resolves to `true` on success and `false` on /// failure. static Future initialize() async { - final String functionName = - PluginUtilities.getNameOfFunction(_alarmManagerCallbackDispatcher); - final String libraryPath = PluginUtilities - .getPathForFunctionLibrary(_alarmManagerCallbackDispatcher); - if (functionName == null) { + final CallbackHandle handle = + PluginUtilities.getCallbackHandle(_alarmManagerCallbackDispatcher); + if (handle == null) { return false; } final dynamic r = await _channel.invokeMethod( - 'AlarmService.start', [functionName, libraryPath]); + 'AlarmService.start', [handle.toRawHandle()]); return r ?? false; } @@ -109,27 +88,16 @@ class AndroidAlarmManager { }) async { final int now = new DateTime.now().millisecondsSinceEpoch; final int first = now + delay.inMilliseconds; - final String functionName = PluginUtilities.getNameOfFunction(callback); - final String className = PluginUtilities.getNameOfFunctionClass(callback); - final String libraryPath = - PluginUtilities.getPathForFunctionLibrary(callback); - - if (functionName == null) { - return false; - } - - if (libraryPath == null) { + final CallbackHandle handle = PluginUtilities.getCallbackHandle(callback); + if (handle == null) { return false; } - final dynamic r = await _channel.invokeMethod('Alarm.oneShot', [ id, exact, wakeup, first, - functionName, - className, - libraryPath + handle.toRawHandle(), ]); return (r == null) ? false : r; } @@ -166,28 +134,17 @@ class AndroidAlarmManager { final int now = new DateTime.now().millisecondsSinceEpoch; final int period = duration.inMilliseconds; final int first = now + period; - final String functionName = PluginUtilities.getNameOfFunction(callback); - final String className = PluginUtilities.getNameOfFunctionClass(callback); - final String libraryPath = - PluginUtilities.getPathForFunctionLibrary(callback); - - if (functionName == null) { + final CallbackHandle handle = PluginUtilities.getCallbackHandle(callback); + if (handle == null) { return false; } - - if (libraryPath == null) { - return false; - } - final dynamic r = await _channel.invokeMethod('Alarm.periodic', [ id, exact, wakeup, first, period, - functionName, - className, - libraryPath + handle.toRawHandle() ]); return (r == null) ? false : r; } From e6b6160c757f2ac73691fe53c8b5bdc380e1881c Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Wed, 18 Jul 2018 12:43:51 -0700 Subject: [PATCH 3/6] Formatting and fixed analyzer issues --- .../androidalarmmanager/AlarmService.java | 64 ++++++++++++------- .../AndroidAlarmManagerPlugin.java | 16 ++--- .../lib/android_alarm_manager.dart | 21 ++---- 3 files changed, 56 insertions(+), 45 deletions(-) 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 f0eb2fb5f0a6..04ff62e3a863 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 @@ -50,7 +50,8 @@ public static void onInitialized() { public static void startAlarmService(Context context, long callbackHandle) { FlutterMain.ensureInitializationComplete(context, null); String mAppBundlePath = FlutterMain.findAppBundlePath(context); - FlutterCallbackInformation cb = FlutterCallbackInformation.lookupCallbackInformation(callbackHandle); + FlutterCallbackInformation cb = + FlutterCallbackInformation.lookupCallbackInformation(callbackHandle); if (cb == null) { Log.e(TAG, "Fatal: failed to find callback"); return; @@ -66,8 +67,7 @@ public static void startAlarmService(Context context, long callbackHandle) { args.libraryPath = cb.callbackLibraryPath; args.onStartedEvent = sOnStartedCallback; sSharedFlutterView.runFromBundle(args); - sPluginRegistrantCallback.registerWith( - sSharedFlutterView.getPluginRegistry()); + sPluginRegistrantCallback.registerWith(sSharedFlutterView.getPluginRegistry()); } } @@ -75,21 +75,35 @@ public static void setBackgroundChannel(MethodChannel channel) { sBackgroundChannel = channel; } - public static void setOneShot(Context context, int requestCode, boolean exact, - boolean wakeup, long startMillis, - long callbackHandle) { + public static void setOneShot( + Context context, + int requestCode, + boolean exact, + boolean wakeup, + long startMillis, + long callbackHandle) { final boolean repeating = false; - scheduleAlarm(context, requestCode, repeating, exact, wakeup, startMillis, - 0, callbackHandle); + scheduleAlarm(context, requestCode, repeating, exact, wakeup, startMillis, 0, callbackHandle); } - public static void setPeriodic(Context context, int requestCode, - boolean exact, boolean wakeup, - long startMillis, long intervalMillis, - long callbackHandle) { + public static void setPeriodic( + Context context, + int requestCode, + boolean exact, + boolean wakeup, + long startMillis, + long intervalMillis, + long callbackHandle) { final boolean repeating = true; - scheduleAlarm(context, requestCode, repeating, exact, wakeup, startMillis, - intervalMillis, callbackHandle); + scheduleAlarm( + context, + requestCode, + repeating, + exact, + wakeup, + startMillis, + intervalMillis, + callbackHandle); } public static void cancel(Context context, int requestCode) { @@ -159,13 +173,12 @@ public int onStartCommand(Intent intent, int flags, int startId) { } long callbackHandle = intent.getLongExtra("callbackHandle", 0); if (sBackgroundChannel == null) { - Log.e(TAG, - "setBackgroundChannel was not called before alarms were scheduled." - + " Bailing out."); + Log.e( + TAG, + "setBackgroundChannel was not called before alarms were scheduled." + " Bailing out."); return START_NOT_STICKY; } - sBackgroundChannel.invokeMethod( - "", new Object[] {callbackHandle}); + sBackgroundChannel.invokeMethod("", new Object[] {callbackHandle}); return START_NOT_STICKY; } @@ -174,10 +187,15 @@ public IBinder onBind(Intent intent) { return null; } - private static void scheduleAlarm(Context context, int requestCode, - boolean repeating, boolean exact, - boolean wakeup, long startMillis, - long intervalMillis, long callbackHandle) { + private static void scheduleAlarm( + Context context, + int requestCode, + boolean repeating, + boolean exact, + boolean wakeup, + long startMillis, + long intervalMillis, + long callbackHandle) { // Create an Intent for the alarm and set the desired Dart callback handle. Intent alarm = new Intent(context, AlarmService.class); alarm.putExtra("callbackHandle", callbackHandle); 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 1899b9405762..9d036978ae3b 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 @@ -26,9 +26,10 @@ public static void registerWith(Registrar registrar) { "plugins.flutter.io/android_alarm_manager", JSONMethodCodec.INSTANCE); final MethodChannel backgroundChannel = - new MethodChannel(registrar.messenger(), - "plugins.flutter.io/android_alarm_manager_background", - JSONMethodCodec.INSTANCE); + new MethodChannel( + registrar.messenger(), + "plugins.flutter.io/android_alarm_manager_background", + JSONMethodCodec.INSTANCE); AndroidAlarmManagerPlugin plugin = new AndroidAlarmManagerPlugin(registrar.context()); channel.setMethodCallHandler(plugin); backgroundChannel.setMethodCallHandler(plugin); @@ -48,7 +49,7 @@ public void onMethodCall(MethodCall call, Result result) { Object arguments = call.arguments; try { if (method.equals("AlarmService.start")) { - startService((JSONArray)arguments); + startService((JSONArray) arguments); result.success(true); } else if (method.equals("AlarmService.initialized")) { AlarmService.onInitialized(); @@ -80,8 +81,7 @@ private void oneShot(JSONArray arguments) throws JSONException { boolean wakeup = arguments.getBoolean(2); long startMillis = arguments.getLong(3); long callbackHandle = arguments.getLong(4); - AlarmService.setOneShot(mContext, requestCode, exact, wakeup, startMillis, - callbackHandle); + AlarmService.setOneShot(mContext, requestCode, exact, wakeup, startMillis, callbackHandle); } private void periodic(JSONArray arguments) throws JSONException { @@ -91,8 +91,8 @@ private void periodic(JSONArray arguments) throws JSONException { long startMillis = arguments.getLong(3); long intervalMillis = arguments.getLong(4); long callbackHandle = arguments.getLong(5); - AlarmService.setPeriodic(mContext, requestCode, exact, wakeup, startMillis, - intervalMillis, callbackHandle); + AlarmService.setPeriodic( + mContext, requestCode, exact, wakeup, startMillis, intervalMillis, callbackHandle); } private void cancel(JSONArray arguments) throws JSONException { diff --git a/packages/android_alarm_manager/lib/android_alarm_manager.dart b/packages/android_alarm_manager/lib/android_alarm_manager.dart index d4037e25ac47..5d83049a42f5 100644 --- a/packages/android_alarm_manager/lib/android_alarm_manager.dart +++ b/packages/android_alarm_manager/lib/android_alarm_manager.dart @@ -15,14 +15,13 @@ const String _backgroundName = void _alarmManagerCallbackDispatcher() { const MethodChannel _channel = const MethodChannel(_backgroundName, const JSONMethodCodec()); - final Map _callbackCache = new Map(); WidgetsFlutterBinding.ensureInitialized(); _channel.setMethodCallHandler((MethodCall call) async { - final args = call.arguments; + final dynamic args = call.arguments; final CallbackHandle handle = new CallbackHandle.fromRawHandle(args[0]); // PluginUtilities.getCallbackFromHandle performs a lookup based on the // callback handle and returns a tear-off of the original callback. - Function closure = PluginUtilities.getCallbackFromHandle(handle); + final Function closure = PluginUtilities.getCallbackFromHandle(handle); if (closure == null) { print('Fatal: could not find callback'); exit(-1); @@ -48,12 +47,12 @@ class AndroidAlarmManager { /// failure. static Future initialize() async { final CallbackHandle handle = - PluginUtilities.getCallbackHandle(_alarmManagerCallbackDispatcher); + PluginUtilities.getCallbackHandle(_alarmManagerCallbackDispatcher); if (handle == null) { return false; } - final dynamic r = await _channel.invokeMethod( - 'AlarmService.start', [handle.toRawHandle()]); + final dynamic r = await _channel + .invokeMethod('AlarmService.start', [handle.toRawHandle()]); return r ?? false; } @@ -138,14 +137,8 @@ class AndroidAlarmManager { if (handle == null) { return false; } - 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, handle.toRawHandle()]); return (r == null) ? false : r; } From cda6d7b044bfc768c3db53fc5ebe907c52755b59 Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Tue, 7 Aug 2018 15:07:06 -0700 Subject: [PATCH 4/6] Additional fixes --- .../androidalarmmanager/AlarmService.java | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) 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 04ff62e3a863..b28d693e0348 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,7 +18,6 @@ import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback; import io.flutter.view.FlutterCallbackInformation; -import io.flutter.view.FlutterIsolateStartedEvent; import io.flutter.view.FlutterMain; import io.flutter.view.FlutterNativeView; import io.flutter.view.FlutterRunArguments; @@ -26,23 +25,13 @@ public class AlarmService extends Service { public static final String TAG = "AlarmService"; - private static AtomicBoolean sStarted; + private static AtomicBoolean sStarted = new AtomicBoolean(false); private static FlutterNativeView sSharedFlutterView; private static MethodChannel sBackgroundChannel; - private static OnStartedCallback sOnStartedCallback; private static PluginRegistrantCallback sPluginRegistrantCallback; private String mAppBundlePath; - private static class OnStartedCallback implements FlutterIsolateStartedEvent { - public void onStarted(boolean success) { - if (!success) { - Log.e(TAG, "AlarmService start failed. Bailing out."); - return; - } - } - } - public static void onInitialized() { sStarted.set(true); } @@ -57,15 +46,12 @@ public static void startAlarmService(Context context, long callbackHandle) { return; } sSharedFlutterView = new FlutterNativeView(context, true); - sStarted = new AtomicBoolean(false); if (mAppBundlePath != null && !sStarted.get()) { Log.i(TAG, "Starting AlarmService..."); - sOnStartedCallback = new OnStartedCallback(); FlutterRunArguments args = new FlutterRunArguments(); args.bundlePath = mAppBundlePath; args.entrypoint = cb.callbackName; args.libraryPath = cb.callbackLibraryPath; - args.onStartedEvent = sOnStartedCallback; sSharedFlutterView.runFromBundle(args); sPluginRegistrantCallback.registerWith(sSharedFlutterView.getPluginRegistry()); } From 9c93bd37c55fc8bc6da81cde6eba5827d5954469 Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Tue, 7 Aug 2018 15:25:13 -0700 Subject: [PATCH 5/6] Removed extra const --- packages/android_alarm_manager/lib/android_alarm_manager.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/android_alarm_manager/lib/android_alarm_manager.dart b/packages/android_alarm_manager/lib/android_alarm_manager.dart index 5d83049a42f5..2daf4dc8bc47 100644 --- a/packages/android_alarm_manager/lib/android_alarm_manager.dart +++ b/packages/android_alarm_manager/lib/android_alarm_manager.dart @@ -14,7 +14,7 @@ const String _backgroundName = void _alarmManagerCallbackDispatcher() { const MethodChannel _channel = - const MethodChannel(_backgroundName, const JSONMethodCodec()); + MethodChannel(_backgroundName, JSONMethodCodec()); WidgetsFlutterBinding.ensureInitialized(); _channel.setMethodCallHandler((MethodCall call) async { final dynamic args = call.arguments; From 3d736dfaa2da782da4c1e6c8ec72b9e1b47a751f Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Tue, 7 Aug 2018 16:11:46 -0700 Subject: [PATCH 6/6] Added comments, removed dead code --- .../androidalarmmanager/AlarmService.java | 64 +++++++++---------- .../AndroidAlarmManagerPlugin.java | 2 +- .../lib/android_alarm_manager.dart | 15 +++++ 3 files changed, 46 insertions(+), 35 deletions(-) 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 b28d693e0348..5e2fda1f1d7a 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 @@ -4,17 +4,13 @@ package io.flutter.plugins.androidalarmmanager; -import android.app.Activity; import android.app.AlarmManager; -import android.app.Application; import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.IBinder; import android.util.Log; -import io.flutter.app.FlutterActivity; -import io.flutter.app.FlutterApplication; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback; import io.flutter.view.FlutterCallbackInformation; @@ -26,7 +22,7 @@ public class AlarmService extends Service { public static final String TAG = "AlarmService"; private static AtomicBoolean sStarted = new AtomicBoolean(false); - private static FlutterNativeView sSharedFlutterView; + private static FlutterNativeView sBackgroundFlutterView; private static MethodChannel sBackgroundChannel; private static PluginRegistrantCallback sPluginRegistrantCallback; @@ -36,6 +32,15 @@ public static void onInitialized() { sStarted.set(true); } + // Here we start the AlarmService. This method does a few things: + // - Retrieves the callback information for the handle associated with the + // callback dispatcher in the Dart portion of the plugin. + // - Builds the arguments object for running in a new FlutterNativeView. + // - Enters the isolate owned by the FlutterNativeView at the callback + // represented by `callbackHandle` and initializes the callback + // dispatcher. + // - Registers the FlutterNativeView's PluginRegistry to receive + // MethodChannel messages. public static void startAlarmService(Context context, long callbackHandle) { FlutterMain.ensureInitializationComplete(context, null); String mAppBundlePath = FlutterMain.findAppBundlePath(context); @@ -45,15 +50,19 @@ public static void startAlarmService(Context context, long callbackHandle) { Log.e(TAG, "Fatal: failed to find callback"); return; } - sSharedFlutterView = new FlutterNativeView(context, true); + + // Note that we're passing `true` as the second argument to our + // FlutterNativeView constructor. This specifies the FlutterNativeView + // as a background view and does not create a drawing surface. + sBackgroundFlutterView = new FlutterNativeView(context, true); if (mAppBundlePath != null && !sStarted.get()) { Log.i(TAG, "Starting AlarmService..."); FlutterRunArguments args = new FlutterRunArguments(); args.bundlePath = mAppBundlePath; args.entrypoint = cb.callbackName; args.libraryPath = cb.callbackLibraryPath; - sSharedFlutterView.runFromBundle(args); - sPluginRegistrantCallback.registerWith(sSharedFlutterView.getPluginRegistry()); + sBackgroundFlutterView.runFromBundle(args); + sPluginRegistrantCallback.registerWith(sBackgroundFlutterView.getPluginRegistry()); } } @@ -105,15 +114,15 @@ public static void cancel(Context context, int requestCode) { } public static FlutterNativeView getSharedFlutterView() { - return sSharedFlutterView; + return sBackgroundFlutterView; } - public static boolean setSharedFlutterView(FlutterNativeView view) { - if (sSharedFlutterView != null && sSharedFlutterView != view) { - Log.i(TAG, "setSharedFlutterView tried to overwrite an existing FlutterNativeView"); + public static boolean setBackgroundFlutterView(FlutterNativeView view) { + if (sBackgroundFlutterView != null && sBackgroundFlutterView != view) { + Log.i(TAG, "setBackgroundFlutterView tried to overwrite an existing FlutterNativeView"); return false; } - sSharedFlutterView = view; + sBackgroundFlutterView = view; return true; } @@ -121,27 +130,6 @@ public static void setPluginRegistrant(PluginRegistrantCallback callback) { sPluginRegistrantCallback = callback; } - // This returns the FlutterView for the main FlutterActivity if there is one. - private static FlutterNativeView viewFromAppContext(Context context) { - Application app = (Application) context; - if (!(app instanceof FlutterApplication)) { - Log.i(TAG, "viewFromAppContext app not a FlutterApplication"); - return null; - } - FlutterApplication flutterApp = (FlutterApplication) app; - Activity activity = flutterApp.getCurrentActivity(); - if (activity == null) { - Log.i(TAG, "viewFromAppContext activity is null"); - return null; - } - if (!(activity instanceof FlutterActivity)) { - Log.i(TAG, "viewFromAppContext activity is not a FlutterActivity"); - return null; - } - FlutterActivity flutterActivity = (FlutterActivity) activity; - return flutterActivity.getFlutterView().getFlutterNativeView(); - } - @Override public void onCreate() { super.onCreate(); @@ -150,6 +138,8 @@ public void onCreate() { mAppBundlePath = FlutterMain.findAppBundlePath(context); } + // This is where we handle alarm events before sending them to our callback + // dispatcher in Dart. @Override public int onStartCommand(Intent intent, int flags, int startId) { if (!sStarted.get()) { @@ -157,6 +147,9 @@ public int onStartCommand(Intent intent, int flags, int startId) { // TODO(bkonyi): queue up alarm events. return START_NOT_STICKY; } + // Grab the handle for the callback associated with this alarm. Pay close + // attention to the type of the callback handle as storing this value in a + // variable of the wrong size will cause the callback lookup to fail. long callbackHandle = intent.getLongExtra("callbackHandle", 0); if (sBackgroundChannel == null) { Log.e( @@ -164,6 +157,9 @@ public int onStartCommand(Intent intent, int flags, int startId) { "setBackgroundChannel was not called before alarms were scheduled." + " Bailing out."); return START_NOT_STICKY; } + // Handle the alarm event in Dart. Note that for this plugin, we don't + // care about the method name as we simply lookup and invoke the callback + // provided. sBackgroundChannel.invokeMethod("", new Object[] {callbackHandle}); return START_NOT_STICKY; } 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 9d036978ae3b..6b5c394053a6 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 @@ -102,6 +102,6 @@ private void cancel(JSONArray arguments) throws JSONException { @Override public boolean onViewDestroy(FlutterNativeView nativeView) { - return AlarmService.setSharedFlutterView(nativeView); + return AlarmService.setBackgroundFlutterView(nativeView); } } diff --git a/packages/android_alarm_manager/lib/android_alarm_manager.dart b/packages/android_alarm_manager/lib/android_alarm_manager.dart index 2daf4dc8bc47..23b9876bcd86 100644 --- a/packages/android_alarm_manager/lib/android_alarm_manager.dart +++ b/packages/android_alarm_manager/lib/android_alarm_manager.dart @@ -12,22 +12,37 @@ 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 = new 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); } closure(); }); + + // Once we've finished initializing, let the native portion of the plugin + // know that it can start scheduling alarms. _channel.invokeMethod('AlarmService.initialized'); }