From dc19c71cb89c3ed74901fdafab200a9ed62c301b Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Tue, 15 Oct 2019 09:25:18 -0700 Subject: [PATCH 01/23] [android_alarm_manager] migrate to the V2 Android embedding --- .../AndroidAlarmManagerPlugin.java | 62 +++++++++++++------ .../example/android/app/build.gradle | 25 ++++++++ .../android/app/src/main/AndroidManifest.xml | 12 +++- .../EmbeddingV1Activity.java | 19 ++++++ .../MainActivity.java | 17 ++--- .../example/android/gradle.properties | 1 + .../lib/android_alarm_manager.dart | 3 + 7 files changed, 110 insertions(+), 29 deletions(-) create mode 100644 packages/android_alarm_manager/example/android/app/src/main/java/io/flutter/plugins/androidalarmmanagerexample/EmbeddingV1Activity.java 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 5cc77413928e..8c5dc9cc6603 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 @@ -5,7 +5,9 @@ package io.flutter.plugins.androidalarmmanager; import android.content.Context; +import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.plugin.common.JSONMethodCodec; +import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel.MethodCallHandler; @@ -38,7 +40,11 @@ * Dart is ready to execute tasks. * */ -public class AndroidAlarmManagerPlugin implements MethodCallHandler, ViewDestroyListener { +public class AndroidAlarmManagerPlugin implements FlutterPlugin, MethodCallHandler, ViewDestroyListener { + private Context mContext; + private MethodChannel mAlarmManagerPluginChannel; + private MethodChannel mBackgroundCallbackChannel; + /** * Registers this plugin with an associated Flutter execution context, represented by the given * {@link Registrar}. @@ -47,38 +53,51 @@ public class AndroidAlarmManagerPlugin implements MethodCallHandler, ViewDestroy * connected to, and running against, the associated Flutter execution context. */ public static void registerWith(Registrar registrar) { - // alarmManagerPluginChannel is the channel responsible for receiving the following messages + final AndroidAlarmManagerPlugin plugin = new AndroidAlarmManagerPlugin(); + // Listen for FlutterView destruction so that this plugin can move itself + // to background mode. + registrar.addViewDestroyListener(plugin); + plugin.onAttachedToEngine(registrar.context(), registrar.messenger()); + } + + @Override + public void onAttachedToEngine(FlutterPluginBinding binding) { + onAttachedToEngine(binding.getApplicationContext(), + binding.getFlutterEngine().getDartExecutor()); + } + + public void onAttachedToEngine(Context applicationContext, BinaryMessenger messenger) { + this.mContext = applicationContext; + + // mAlarmManagerPluginChannel is the channel responsible for receiving the following messages // from the main Flutter app: // - "AlarmService.start" // - "Alarm.oneShotAt" // - "Alarm.periodic" // - "Alarm.cancel" - final MethodChannel alarmManagerPluginChannel = + mAlarmManagerPluginChannel = new MethodChannel( - registrar.messenger(), + messenger, "plugins.flutter.io/android_alarm_manager", JSONMethodCodec.INSTANCE); - // backgroundCallbackChannel is the channel responsible for receiving the following messages + // mBackgroundCallbackChannel is the channel responsible for receiving the following messages // from the background isolate that was setup by this plugin: // - "AlarmService.initialized" // // This channel is also responsible for sending requests from Android to Dart to execute Dart // callbacks in the background isolate. Those messages are sent with an empty method name because // they are the only messages that this channel sends to Dart. - final MethodChannel backgroundCallbackChannel = + mBackgroundCallbackChannel = new MethodChannel( - registrar.messenger(), + messenger, "plugins.flutter.io/android_alarm_manager_background", JSONMethodCodec.INSTANCE); - // Instantiate a new AndroidAlarmManagerPlugin, connect the primary and background - // method channels for Android/Flutter communication, and listen for FlutterView - // destruction so that this plugin can move itself to background mode. - AndroidAlarmManagerPlugin plugin = new AndroidAlarmManagerPlugin(registrar.context()); - alarmManagerPluginChannel.setMethodCallHandler(plugin); - backgroundCallbackChannel.setMethodCallHandler(plugin); - registrar.addViewDestroyListener(plugin); + // Instantiate a new AndroidAlarmManagerPlugin and connect the primary and background + // method channels for Android/Flutter communication. + mAlarmManagerPluginChannel.setMethodCallHandler(this); + mBackgroundCallbackChannel.setMethodCallHandler(this); // The AlarmService expects to hold a static reference to the plugin's background // method channel. @@ -86,13 +105,20 @@ public static void registerWith(Registrar registrar) { // can exist at a time. Moreover, calling registerWith() a 2nd time would // seem to overwrite the previously registered background channel without // notice. - AlarmService.setBackgroundChannel(backgroundCallbackChannel); + AlarmService.setBackgroundChannel(mBackgroundCallbackChannel); } - private Context mContext; + @Override + public void onDetachedFromEngine(FlutterPluginBinding binding) { + mContext = null; + mAlarmManagerPluginChannel.setMethodCallHandler(null); + mAlarmManagerPluginChannel = null; + + mBackgroundCallbackChannel.setMethodCallHandler(null); + mBackgroundCallbackChannel = null; + } - private AndroidAlarmManagerPlugin(Context context) { - this.mContext = context; + public AndroidAlarmManagerPlugin() { } /** Invoked when the Flutter side of this plugin sends a message to the Android side. */ diff --git a/packages/android_alarm_manager/example/android/app/build.gradle b/packages/android_alarm_manager/example/android/app/build.gradle index d296cafa8e7c..638987be0e70 100644 --- a/packages/android_alarm_manager/example/android/app/build.gradle +++ b/packages/android_alarm_manager/example/android/app/build.gradle @@ -58,3 +58,28 @@ dependencies { androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' } + +afterEvaluate { + def containsEmbeddingDependencies = false + for (def configuration : configurations.all) { + for (def dependency : configuration.dependencies) { + if (dependency.group == 'io.flutter' && + dependency.name.startsWith('flutter_embedding') && + dependency.isTransitive()) + { + containsEmbeddingDependencies = true + break + } + } + } + if (!containsEmbeddingDependencies) { + android { + dependencies { + // add lifecycle dependencies + def lifecycle_version = "2.1.0" + api "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" + api "androidx.lifecycle:lifecycle-runtime:$lifecycle_version" + } + } + } +} 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 7d87c6e1aae0..c67a8dd8f4f8 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 @@ -9,6 +9,15 @@ android:name=".Application" android:label="android_alarm_manager_example" android:icon="@mipmap/ic_launcher"> + + - diff --git a/packages/android_alarm_manager/example/android/app/src/main/java/io/flutter/plugins/androidalarmmanagerexample/EmbeddingV1Activity.java b/packages/android_alarm_manager/example/android/app/src/main/java/io/flutter/plugins/androidalarmmanagerexample/EmbeddingV1Activity.java new file mode 100644 index 000000000000..17a37b8dd156 --- /dev/null +++ b/packages/android_alarm_manager/example/android/app/src/main/java/io/flutter/plugins/androidalarmmanagerexample/EmbeddingV1Activity.java @@ -0,0 +1,19 @@ +// 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. + +package io.flutter.plugins.androidalarmmanagerexample; + +import android.os.Bundle; +import io.flutter.app.FlutterActivity; +import io.flutter.plugins.GeneratedPluginRegistrant; + +public class EmbeddingV1Activity extends FlutterActivity { + public static final String TAG = "AlarmExampleMainActivity"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + GeneratedPluginRegistrant.registerWith(this); + } +} diff --git a/packages/android_alarm_manager/example/android/app/src/main/java/io/flutter/plugins/androidalarmmanagerexample/MainActivity.java b/packages/android_alarm_manager/example/android/app/src/main/java/io/flutter/plugins/androidalarmmanagerexample/MainActivity.java index 3d9afa5235c3..b2fe7874abe8 100644 --- a/packages/android_alarm_manager/example/android/app/src/main/java/io/flutter/plugins/androidalarmmanagerexample/MainActivity.java +++ b/packages/android_alarm_manager/example/android/app/src/main/java/io/flutter/plugins/androidalarmmanagerexample/MainActivity.java @@ -1,15 +1,16 @@ +// 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.androidalarmmanagerexample; -import android.os.Bundle; -import io.flutter.app.FlutterActivity; -import io.flutter.plugins.GeneratedPluginRegistrant; +import io.flutter.embedding.android.FlutterActivity; +import io.flutter.embedding.engine.FlutterEngine; +import io.flutter.plugins.androidalarmmanager.AndroidAlarmManagerPlugin; public class MainActivity extends FlutterActivity { - public static final String TAG = "AlarmExampleMainActivity"; - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - GeneratedPluginRegistrant.registerWith(this); + public void configureFlutterEngine(FlutterEngine flutterEngine) { + flutterEngine.getPlugins().add(new AndroidAlarmManagerPlugin()); } } diff --git a/packages/android_alarm_manager/example/android/gradle.properties b/packages/android_alarm_manager/example/android/gradle.properties index 53ae0ae470eb..b6e61b62b903 100644 --- a/packages/android_alarm_manager/example/android/gradle.properties +++ b/packages/android_alarm_manager/example/android/gradle.properties @@ -1,3 +1,4 @@ android.enableJetifier=true android.useAndroidX=true org.gradle.jvmargs=-Xmx1536M +android.enableR8=true diff --git a/packages/android_alarm_manager/lib/android_alarm_manager.dart b/packages/android_alarm_manager/lib/android_alarm_manager.dart index b90823e5c9d4..79b4f876985a 100644 --- a/packages/android_alarm_manager/lib/android_alarm_manager.dart +++ b/packages/android_alarm_manager/lib/android_alarm_manager.dart @@ -67,6 +67,9 @@ class AndroidAlarmManager { /// Returns a [Future] that resolves to `true` on success and `false` on /// failure. static Future initialize() async { + // Setup Flutter state needed for MethodChannels just in case runApp hasn't + // been called yet. + WidgetsFlutterBinding.ensureInitialized(); final CallbackHandle handle = PluginUtilities.getCallbackHandle(_alarmManagerCallbackDispatcher); if (handle == null) { From 7323dac19da13e08e543ffbe4e58cb6bfc5f3c4a Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Tue, 15 Oct 2019 09:31:46 -0700 Subject: [PATCH 02/23] Update change log and version --- packages/android_alarm_manager/CHANGELOG.md | 3 +++ packages/android_alarm_manager/pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/android_alarm_manager/CHANGELOG.md b/packages/android_alarm_manager/CHANGELOG.md index 1506fd304cf7..65d9d7c83bf2 100644 --- a/packages/android_alarm_manager/CHANGELOG.md +++ b/packages/android_alarm_manager/CHANGELOG.md @@ -1,3 +1,6 @@ +## 0.4.5 +* Add support for Flutter Android embedding V2 + ## 0.4.4+2 * Remove AndroidX warning. diff --git a/packages/android_alarm_manager/pubspec.yaml b/packages/android_alarm_manager/pubspec.yaml index 6fe4ed944d55..6c3d489936be 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.4+2 +version: 0.4.5 author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/android_alarm_manager From df1849f62b3a28d11d6def09d08acf57cc8d3386 Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Tue, 15 Oct 2019 09:39:01 -0700 Subject: [PATCH 03/23] Formatting --- .../AndroidAlarmManagerPlugin.java | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) 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 8c5dc9cc6603..9edd36e2b64e 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 @@ -6,8 +6,8 @@ import android.content.Context; import io.flutter.embedding.engine.plugins.FlutterPlugin; -import io.flutter.plugin.common.JSONMethodCodec; import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.JSONMethodCodec; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel.MethodCallHandler; @@ -40,7 +40,8 @@ * Dart is ready to execute tasks. * */ -public class AndroidAlarmManagerPlugin implements FlutterPlugin, MethodCallHandler, ViewDestroyListener { +public class AndroidAlarmManagerPlugin + implements FlutterPlugin, MethodCallHandler, ViewDestroyListener { private Context mContext; private MethodChannel mAlarmManagerPluginChannel; private MethodChannel mBackgroundCallbackChannel; @@ -62,8 +63,8 @@ public static void registerWith(Registrar registrar) { @Override public void onAttachedToEngine(FlutterPluginBinding binding) { - onAttachedToEngine(binding.getApplicationContext(), - binding.getFlutterEngine().getDartExecutor()); + onAttachedToEngine( + binding.getApplicationContext(), binding.getFlutterEngine().getDartExecutor()); } public void onAttachedToEngine(Context applicationContext, BinaryMessenger messenger) { @@ -77,9 +78,7 @@ public void onAttachedToEngine(Context applicationContext, BinaryMessenger messe // - "Alarm.cancel" mAlarmManagerPluginChannel = new MethodChannel( - messenger, - "plugins.flutter.io/android_alarm_manager", - JSONMethodCodec.INSTANCE); + messenger, "plugins.flutter.io/android_alarm_manager", JSONMethodCodec.INSTANCE); // mBackgroundCallbackChannel is the channel responsible for receiving the following messages // from the background isolate that was setup by this plugin: @@ -118,8 +117,7 @@ public void onDetachedFromEngine(FlutterPluginBinding binding) { mBackgroundCallbackChannel = null; } - public AndroidAlarmManagerPlugin() { - } + public AndroidAlarmManagerPlugin() {} /** Invoked when the Flutter side of this plugin sends a message to the Android side. */ @Override From 2c71ccbef2792e43b9c2a8217463f93172601072 Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Thu, 17 Oct 2019 09:30:11 -0700 Subject: [PATCH 04/23] Address review comments --- packages/android_alarm_manager/CHANGELOG.md | 1 + .../androidalarmmanager/AlarmService.java | 9 --- .../AndroidAlarmManagerPlugin.java | 63 +++++++------------ .../example/android/settings_aar.gradle | 1 + .../example/lib/main.dart | 2 +- .../lib/android_alarm_manager.dart | 4 -- packages/android_alarm_manager/pubspec.yaml | 2 +- 7 files changed, 26 insertions(+), 56 deletions(-) create mode 100644 packages/android_alarm_manager/example/android/settings_aar.gradle diff --git a/packages/android_alarm_manager/CHANGELOG.md b/packages/android_alarm_manager/CHANGELOG.md index 65d9d7c83bf2..87bf194f4d61 100644 --- a/packages/android_alarm_manager/CHANGELOG.md +++ b/packages/android_alarm_manager/CHANGELOG.md @@ -1,4 +1,5 @@ ## 0.4.5 + * Add support for Flutter Android embedding V2 ## 0.4.4+2 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 bb3d0c8db102..74c98bc6a17a 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 @@ -152,15 +152,6 @@ public static void setCallbackDispatcher(Context context, long callbackHandle) { prefs.edit().putLong(CALLBACK_HANDLE_KEY, callbackHandle).apply(); } - public static boolean setBackgroundFlutterView(FlutterNativeView view) { - if (sBackgroundFlutterView != null && sBackgroundFlutterView != view) { - Log.i(TAG, "setBackgroundFlutterView tried to overwrite an existing FlutterNativeView"); - return false; - } - sBackgroundFlutterView = view; - return true; - } - public static void setPluginRegistrant(PluginRegistrantCallback callback) { sPluginRegistrantCallback = callback; } 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 9edd36e2b64e..3d5a6b8343d8 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 @@ -13,7 +13,6 @@ import io.flutter.plugin.common.MethodChannel.MethodCallHandler; import io.flutter.plugin.common.MethodChannel.Result; import io.flutter.plugin.common.PluginRegistry.Registrar; -import io.flutter.plugin.common.PluginRegistry.ViewDestroyListener; import io.flutter.view.FlutterNativeView; import org.json.JSONArray; import org.json.JSONException; @@ -41,10 +40,10 @@ * */ public class AndroidAlarmManagerPlugin - implements FlutterPlugin, MethodCallHandler, ViewDestroyListener { - private Context mContext; - private MethodChannel mAlarmManagerPluginChannel; - private MethodChannel mBackgroundCallbackChannel; + implements FlutterPlugin, MethodCallHandler { + private Context context; + private MethodChannel alarmManagerPluginChannel; + private MethodChannel backgroundCallbackChannel; /** * Registers this plugin with an associated Flutter execution context, represented by the given @@ -55,9 +54,6 @@ public class AndroidAlarmManagerPlugin */ public static void registerWith(Registrar registrar) { final AndroidAlarmManagerPlugin plugin = new AndroidAlarmManagerPlugin(); - // Listen for FlutterView destruction so that this plugin can move itself - // to background mode. - registrar.addViewDestroyListener(plugin); plugin.onAttachedToEngine(registrar.context(), registrar.messenger()); } @@ -68,26 +64,26 @@ public void onAttachedToEngine(FlutterPluginBinding binding) { } public void onAttachedToEngine(Context applicationContext, BinaryMessenger messenger) { - this.mContext = applicationContext; + this.context = applicationContext; - // mAlarmManagerPluginChannel is the channel responsible for receiving the following messages + // alarmManagerPluginChannel is the channel responsible for receiving the following messages // from the main Flutter app: // - "AlarmService.start" // - "Alarm.oneShotAt" // - "Alarm.periodic" // - "Alarm.cancel" - mAlarmManagerPluginChannel = + alarmManagerPluginChannel = new MethodChannel( messenger, "plugins.flutter.io/android_alarm_manager", JSONMethodCodec.INSTANCE); - // mBackgroundCallbackChannel is the channel responsible for receiving the following messages + // backgroundCallbackChannel is the channel responsible for receiving the following messages // from the background isolate that was setup by this plugin: // - "AlarmService.initialized" // // This channel is also responsible for sending requests from Android to Dart to execute Dart // callbacks in the background isolate. Those messages are sent with an empty method name because // they are the only messages that this channel sends to Dart. - mBackgroundCallbackChannel = + backgroundCallbackChannel = new MethodChannel( messenger, "plugins.flutter.io/android_alarm_manager_background", @@ -95,8 +91,8 @@ public void onAttachedToEngine(Context applicationContext, BinaryMessenger messe // Instantiate a new AndroidAlarmManagerPlugin and connect the primary and background // method channels for Android/Flutter communication. - mAlarmManagerPluginChannel.setMethodCallHandler(this); - mBackgroundCallbackChannel.setMethodCallHandler(this); + alarmManagerPluginChannel.setMethodCallHandler(this); + backgroundCallbackChannel.setMethodCallHandler(this); // The AlarmService expects to hold a static reference to the plugin's background // method channel. @@ -104,17 +100,17 @@ public void onAttachedToEngine(Context applicationContext, BinaryMessenger messe // can exist at a time. Moreover, calling registerWith() a 2nd time would // seem to overwrite the previously registered background channel without // notice. - AlarmService.setBackgroundChannel(mBackgroundCallbackChannel); + AlarmService.setBackgroundChannel(backgroundCallbackChannel); } @Override public void onDetachedFromEngine(FlutterPluginBinding binding) { - mContext = null; - mAlarmManagerPluginChannel.setMethodCallHandler(null); - mAlarmManagerPluginChannel = null; + context = null; + alarmManagerPluginChannel.setMethodCallHandler(null); + alarmManagerPluginChannel = null; - mBackgroundCallbackChannel.setMethodCallHandler(null); - mBackgroundCallbackChannel = null; + backgroundCallbackChannel.setMethodCallHandler(null); + backgroundCallbackChannel = null; } public AndroidAlarmManagerPlugin() {} @@ -133,8 +129,8 @@ public void onMethodCall(MethodCall call, Result result) { // method channel to communicate with the new background isolate. Once completed, // this onMethodCall() method will receive messages from both the primary and background // method channels. - AlarmService.setCallbackDispatcher(mContext, callbackHandle); - AlarmService.startBackgroundIsolate(mContext, callbackHandle); + AlarmService.setCallbackDispatcher(context, callbackHandle); + AlarmService.startBackgroundIsolate(context, callbackHandle); result.success(true); } else if (method.equals("AlarmService.initialized")) { // This message is sent by the background method channel as soon as the background isolate @@ -147,19 +143,19 @@ public void onMethodCall(MethodCall call, Result result) { // This message indicates that the Flutter app would like to schedule a periodic // task. PeriodicRequest periodicRequest = PeriodicRequest.fromJson((JSONArray) arguments); - AlarmService.setPeriodic(mContext, periodicRequest); + AlarmService.setPeriodic(context, periodicRequest); result.success(true); } else if (method.equals("Alarm.oneShotAt")) { // This message indicates that the Flutter app would like to schedule a one-time // task. OneShotRequest oneShotRequest = OneShotRequest.fromJson((JSONArray) arguments); - AlarmService.setOneShot(mContext, oneShotRequest); + AlarmService.setOneShot(context, oneShotRequest); result.success(true); } else if (method.equals("Alarm.cancel")) { // This message indicates that the Flutter app would like to cancel a previously // scheduled task. int requestCode = ((JSONArray) arguments).getInt(0); - AlarmService.cancel(mContext, requestCode); + AlarmService.cancel(context, requestCode); result.success(true); } else { result.notImplemented(); @@ -171,21 +167,6 @@ public void onMethodCall(MethodCall call, Result result) { } } - /** - * Transitions the Flutter execution context that owns this plugin from foreground execution to - * background execution. - * - *

Invoked when the {@link FlutterView} connected to the given {@link FlutterNativeView} is - * destroyed. - * - *

Returns true if the given {@code nativeView} was successfully stored by this plugin, or - * false if a different {@link FlutterNativeView} was already registered with this plugin. - */ - @Override - public boolean onViewDestroy(FlutterNativeView nativeView) { - return AlarmService.setBackgroundFlutterView(nativeView); - } - /** A request to schedule a one-shot Dart task. */ static final class OneShotRequest { static OneShotRequest fromJson(JSONArray json) throws JSONException { diff --git a/packages/android_alarm_manager/example/android/settings_aar.gradle b/packages/android_alarm_manager/example/android/settings_aar.gradle new file mode 100644 index 000000000000..e7b4def49cb5 --- /dev/null +++ b/packages/android_alarm_manager/example/android/settings_aar.gradle @@ -0,0 +1 @@ +include ':app' diff --git a/packages/android_alarm_manager/example/lib/main.dart b/packages/android_alarm_manager/example/lib/main.dart index e68735a75085..5e432d3c6d5d 100644 --- a/packages/android_alarm_manager/example/lib/main.dart +++ b/packages/android_alarm_manager/example/lib/main.dart @@ -25,7 +25,7 @@ Future main() async { Text('See device log for output', textDirection: TextDirection.ltr))); await AndroidAlarmManager.periodic( const Duration(seconds: 5), periodicID, printPeriodic, - wakeup: true); + wakeup: true, exact: true); await AndroidAlarmManager.oneShot( 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 79b4f876985a..d62dcb281f4d 100644 --- a/packages/android_alarm_manager/lib/android_alarm_manager.dart +++ b/packages/android_alarm_manager/lib/android_alarm_manager.dart @@ -20,10 +20,6 @@ const String _backgroundName = 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 { diff --git a/packages/android_alarm_manager/pubspec.yaml b/packages/android_alarm_manager/pubspec.yaml index 6c3d489936be..6fe4ed944d55 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.5 +version: 0.4.4+2 author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/android_alarm_manager From 03519ea207ccca4fe7788de93d12886966f922cd Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Thu, 17 Oct 2019 12:07:58 -0700 Subject: [PATCH 05/23] Formatting and added MainActivityTest.java --- .../AndroidAlarmManagerPlugin.java | 3 +-- .../androidalarmmanager/MainActivityTest.java | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 packages/android_alarm_manager/example/android/app/src/androidTest/java/io/plugins/androidalarmmanager/MainActivityTest.java 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 3d5a6b8343d8..0569b58285d9 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 @@ -39,8 +39,7 @@ * Dart is ready to execute tasks. * */ -public class AndroidAlarmManagerPlugin - implements FlutterPlugin, MethodCallHandler { +public class AndroidAlarmManagerPlugin implements FlutterPlugin, MethodCallHandler { private Context context; private MethodChannel alarmManagerPluginChannel; private MethodChannel backgroundCallbackChannel; diff --git a/packages/android_alarm_manager/example/android/app/src/androidTest/java/io/plugins/androidalarmmanager/MainActivityTest.java b/packages/android_alarm_manager/example/android/app/src/androidTest/java/io/plugins/androidalarmmanager/MainActivityTest.java new file mode 100644 index 000000000000..6b69d39de003 --- /dev/null +++ b/packages/android_alarm_manager/example/android/app/src/androidTest/java/io/plugins/androidalarmmanager/MainActivityTest.java @@ -0,0 +1,15 @@ +// 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.androidalarmmanagerexample; + +import androidx.test.rule.ActivityTestRule; +import dev.flutter.plugins.e2e.FlutterRunner; +import org.junit.Rule; +import org.junit.runner.RunWith; + +@RunWith(FlutterRunner.class) +public class MainActivityTest { + @Rule public ActivityTestRule rule = new ActivityTestRule<>(MainActivity.class); +} From 60f3fe01dc8707e813696142d7699fdd3f0dbdcb Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Fri, 18 Oct 2019 14:10:15 -0700 Subject: [PATCH 06/23] Added tests --- .../android/build.gradle | 31 +++++- .../example/android/app/build.gradle | 25 ----- .../MainActivity.java | 8 ++ .../example/lib/main.dart | 2 + .../example/pubspec.yaml | 5 + .../android_alarm_manager_e2e.dart | 100 ++++++++++++++++++ .../android_alarm_manager_e2e_test.dart | 28 +++++ .../lib/android_alarm_manager.dart | 8 +- 8 files changed, 177 insertions(+), 30 deletions(-) create mode 100644 packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e.dart create mode 100644 packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e_test.dart diff --git a/packages/android_alarm_manager/android/build.gradle b/packages/android_alarm_manager/android/build.gradle index d711772c2bb8..52da12479a40 100644 --- a/packages/android_alarm_manager/android/build.gradle +++ b/packages/android_alarm_manager/android/build.gradle @@ -23,7 +23,10 @@ apply plugin: 'com.android.library' android { compileSdkVersion 28 - + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } defaultConfig { minSdkVersion 16 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -37,3 +40,29 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.0.0' api 'androidx.core:core:1.0.1' } + +// TODO(bkonyi): Remove this hack once androidx.lifecycle is included on stable. https://github.com/flutter/flutter/issues/42348 +afterEvaluate { + def containsEmbeddingDependencies = false + for (def configuration : configurations.all) { + for (def dependency : configuration.dependencies) { + if (dependency.group == 'io.flutter' && + dependency.name.startsWith('flutter_embedding') && + dependency.isTransitive()) + { + containsEmbeddingDependencies = true + break + } + } + } + if (!containsEmbeddingDependencies) { + android { + dependencies { + def lifecycle_version = "1.1.1" + api 'android.arch.lifecycle:runtime:$lifecycle_version' + api 'android.arch.lifecycle:common:$lifecycle_version' + api 'android.arch.lifecycle:common-java8:$lifecycle_version' + } + } + } +} diff --git a/packages/android_alarm_manager/example/android/app/build.gradle b/packages/android_alarm_manager/example/android/app/build.gradle index 638987be0e70..d296cafa8e7c 100644 --- a/packages/android_alarm_manager/example/android/app/build.gradle +++ b/packages/android_alarm_manager/example/android/app/build.gradle @@ -58,28 +58,3 @@ dependencies { androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' } - -afterEvaluate { - def containsEmbeddingDependencies = false - for (def configuration : configurations.all) { - for (def dependency : configuration.dependencies) { - if (dependency.group == 'io.flutter' && - dependency.name.startsWith('flutter_embedding') && - dependency.isTransitive()) - { - containsEmbeddingDependencies = true - break - } - } - } - if (!containsEmbeddingDependencies) { - android { - dependencies { - // add lifecycle dependencies - def lifecycle_version = "2.1.0" - api "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" - api "androidx.lifecycle:lifecycle-runtime:$lifecycle_version" - } - } - } -} diff --git a/packages/android_alarm_manager/example/android/app/src/main/java/io/flutter/plugins/androidalarmmanagerexample/MainActivity.java b/packages/android_alarm_manager/example/android/app/src/main/java/io/flutter/plugins/androidalarmmanagerexample/MainActivity.java index b2fe7874abe8..2c80708c4e94 100644 --- a/packages/android_alarm_manager/example/android/app/src/main/java/io/flutter/plugins/androidalarmmanagerexample/MainActivity.java +++ b/packages/android_alarm_manager/example/android/app/src/main/java/io/flutter/plugins/androidalarmmanagerexample/MainActivity.java @@ -4,13 +4,21 @@ package io.flutter.plugins.androidalarmmanagerexample; +import dev.flutter.plugins.e2e.E2EPlugin; import io.flutter.embedding.android.FlutterActivity; import io.flutter.embedding.engine.FlutterEngine; +import io.flutter.embedding.engine.plugins.shim.ShimPluginRegistry; import io.flutter.plugins.androidalarmmanager.AndroidAlarmManagerPlugin; +import io.flutter.plugins.pathprovider.PathProviderPlugin; public class MainActivity extends FlutterActivity { + // TODO(bkonyi): Remove this once v2 of GeneratedPluginRegistrant rolls to stable. https://github.com/flutter/flutter/issues/42694 @Override public void configureFlutterEngine(FlutterEngine flutterEngine) { + ShimPluginRegistry shimPluginRegistry = new ShimPluginRegistry(flutterEngine); flutterEngine.getPlugins().add(new AndroidAlarmManagerPlugin()); + flutterEngine.getPlugins().add(new E2EPlugin()); + PathProviderPlugin.registerWith( + shimPluginRegistry.registrarFor("io.flutter.plugins.pathprovider.PathProviderPlugin")); } } diff --git a/packages/android_alarm_manager/example/lib/main.dart b/packages/android_alarm_manager/example/lib/main.dart index 5e432d3c6d5d..9b9b102bc2ca 100644 --- a/packages/android_alarm_manager/example/lib/main.dart +++ b/packages/android_alarm_manager/example/lib/main.dart @@ -16,6 +16,8 @@ Future main() async { final int periodicID = 0; final int oneShotID = 1; + WidgetsFlutterBinding.ensureInitialized(); + // Start the AlarmManager service. await AndroidAlarmManager.initialize(); diff --git a/packages/android_alarm_manager/example/pubspec.yaml b/packages/android_alarm_manager/example/pubspec.yaml index 392c03dc8902..3046f16a1405 100644 --- a/packages/android_alarm_manager/example/pubspec.yaml +++ b/packages/android_alarm_manager/example/pubspec.yaml @@ -6,8 +6,13 @@ dependencies: sdk: flutter android_alarm_manager: path: ../ + e2e: ^0.2.1 + path_provider: ^1.3.1 + dev_dependencies: + flutter_driver: + sdk: flutter flutter_test: sdk: flutter diff --git a/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e.dart b/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e.dart new file mode 100644 index 000000000000..7102762534a2 --- /dev/null +++ b/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e.dart @@ -0,0 +1,100 @@ +// Copyright 2019, the Chromium project authors. Please see the AUTHORS file +// for details. 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 'package:android_alarm_manager/android_alarm_manager.dart'; +import 'package:e2e/e2e.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:path_provider/path_provider.dart'; + +// From https://flutter.dev/docs/cookbook/persistence/reading-writing-files +Future get _localPath async { + final directory = await getTemporaryDirectory(); + return directory.path; +} + +Future get _localFile async { + final path = await _localPath; + return File('$path/counter.txt'); +} + +Future writeCounter(int counter) async { + final file = await _localFile; + + // Write the file. + return file.writeAsString('$counter'); +} + +Future readCounter() async { + try { + final file = await _localFile; + + // Read the file. + String contents = await file.readAsString(); + + return int.parse(contents); + } on FileSystemException catch (e) { + // If encountering an error, return 0. + return 0; + } +} + +Future incrementCounter() async { + int value = await readCounter(); + print('incrementCounter to: ${value + 1}'); + await writeCounter(value + 1); +} + +void main() { + E2EWidgetsFlutterBinding.ensureInitialized(); + + print('main'); + setUp(() async { + await AndroidAlarmManager.initialize(); + }); + + group('oneshot', () { + testWidgets('cancelled before it fires', (WidgetTester tester) async { + final int alarmId = 0; + final int startingValue = await readCounter(); + await AndroidAlarmManager.oneShot( + const Duration(seconds: 1), alarmId, incrementCounter); + expect(await AndroidAlarmManager.cancel(alarmId), isTrue); + await new Future.delayed(const Duration(seconds: 4)); + expect(await readCounter(), startingValue); + }); + + testWidgets('cancelled after it fires', (WidgetTester tester) async { + final int alarmId = 1; + final int startingValue = await readCounter(); + await AndroidAlarmManager.oneShot( + const Duration(seconds: 1), alarmId, incrementCounter, + exact: true, wakeup: true); + await new Future.delayed(const Duration(seconds: 2)); + // poll until file is updated + for (int i = 0; i < 10 && await readCounter() == startingValue; i++) { + await new Future.delayed(const Duration(seconds: 1)); + } + expect(await readCounter(), startingValue + 1); + expect(await AndroidAlarmManager.cancel(alarmId), isTrue); + }); + }); + + testWidgets('periodic', (WidgetTester tester) async { + final int alarmId = 2; + final int startingValue = await readCounter(); + await AndroidAlarmManager.periodic( + const Duration(seconds: 1), alarmId, incrementCounter, + wakeup: true, exact: true); + // poll until file is updated + for (int i = 0; i < 100 && await readCounter() < startingValue + 2; i++) { + await new Future.delayed(const Duration(seconds: 1)); + } + expect(await readCounter(), startingValue + 2); + expect(await AndroidAlarmManager.cancel(alarmId), isTrue); + await new Future.delayed(const Duration(seconds: 3)); + expect(await readCounter(), startingValue + 2); + }); +} diff --git a/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e_test.dart b/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e_test.dart new file mode 100644 index 000000000000..0bbfc8943126 --- /dev/null +++ b/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e_test.dart @@ -0,0 +1,28 @@ +// Copyright 2019, the Chromium project authors. Please see the AUTHORS file +// for details. 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:io'; + +import 'package:flutter_driver/flutter_driver.dart'; + +Future main() async { + final FlutterDriver driver = await FlutterDriver.connect(); + print('SETTING UP DRIVER'); + final subscription = driver.serviceClient.onIsolateRunnable + .asBroadcastStream() + .listen((isolateRef) async { + print('isolate started: ${isolateRef.name}'); + final isolate = await isolateRef.load(); + if (isolate.isPaused) { + isolate.resume(); + print( + 'resuming isolate: ${isolateRef.numberAsString}:${isolateRef.name}'); + } + }); + final String result = + await driver.requestData(null, timeout: const Duration(minutes: 1)); + subscription.cancel(); + driver.close(); + exit(result == 'pass' ? 0 : 1); +} diff --git a/packages/android_alarm_manager/lib/android_alarm_manager.dart b/packages/android_alarm_manager/lib/android_alarm_manager.dart index d62dcb281f4d..67bff61bc05e 100644 --- a/packages/android_alarm_manager/lib/android_alarm_manager.dart +++ b/packages/android_alarm_manager/lib/android_alarm_manager.dart @@ -13,11 +13,14 @@ 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 +// an isolate once, we setup a MethodChannel to listen for method invocations // 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() { + // Initialize state necessary for MethodChannels. + WidgetsFlutterBinding.ensureInitialized(); + const MethodChannel _channel = MethodChannel(_backgroundName, JSONMethodCodec()); // This is where the magic happens and we handle background events from the @@ -63,9 +66,6 @@ class AndroidAlarmManager { /// Returns a [Future] that resolves to `true` on success and `false` on /// failure. static Future initialize() async { - // Setup Flutter state needed for MethodChannels just in case runApp hasn't - // been called yet. - WidgetsFlutterBinding.ensureInitialized(); final CallbackHandle handle = PluginUtilities.getCallbackHandle(_alarmManagerCallbackDispatcher); if (handle == null) { From 458d81e3623940aef576017b3000de63ab226286 Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Fri, 18 Oct 2019 14:37:20 -0700 Subject: [PATCH 07/23] Fixed test analysis failures --- .../android_alarm_manager_e2e.dart | 23 ++++++++++--------- .../android_alarm_manager_e2e_test.dart | 13 ----------- 2 files changed, 12 insertions(+), 24 deletions(-) diff --git a/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e.dart b/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e.dart index 7102762534a2..1309b84d1b5e 100644 --- a/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e.dart +++ b/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e.dart @@ -11,17 +11,17 @@ import 'package:path_provider/path_provider.dart'; // From https://flutter.dev/docs/cookbook/persistence/reading-writing-files Future get _localPath async { - final directory = await getTemporaryDirectory(); + final Directory directory = await getTemporaryDirectory(); return directory.path; } Future get _localFile async { - final path = await _localPath; + final String path = await _localPath; return File('$path/counter.txt'); } Future writeCounter(int counter) async { - final file = await _localFile; + final File file = await _localFile; // Write the file. return file.writeAsString('$counter'); @@ -29,12 +29,13 @@ Future writeCounter(int counter) async { Future readCounter() async { try { - final file = await _localFile; + final File file = await _localFile; // Read the file. - String contents = await file.readAsString(); + final String contents = await file.readAsString(); return int.parse(contents); + // ignore: unused_catch_clause } on FileSystemException catch (e) { // If encountering an error, return 0. return 0; @@ -42,7 +43,7 @@ Future readCounter() async { } Future incrementCounter() async { - int value = await readCounter(); + final int value = await readCounter(); print('incrementCounter to: ${value + 1}'); await writeCounter(value + 1); } @@ -62,7 +63,7 @@ void main() { await AndroidAlarmManager.oneShot( const Duration(seconds: 1), alarmId, incrementCounter); expect(await AndroidAlarmManager.cancel(alarmId), isTrue); - await new Future.delayed(const Duration(seconds: 4)); + await Future.delayed(const Duration(seconds: 4)); expect(await readCounter(), startingValue); }); @@ -72,10 +73,10 @@ void main() { await AndroidAlarmManager.oneShot( const Duration(seconds: 1), alarmId, incrementCounter, exact: true, wakeup: true); - await new Future.delayed(const Duration(seconds: 2)); + await Future.delayed(const Duration(seconds: 2)); // poll until file is updated for (int i = 0; i < 10 && await readCounter() == startingValue; i++) { - await new Future.delayed(const Duration(seconds: 1)); + await Future.delayed(const Duration(seconds: 1)); } expect(await readCounter(), startingValue + 1); expect(await AndroidAlarmManager.cancel(alarmId), isTrue); @@ -90,11 +91,11 @@ void main() { wakeup: true, exact: true); // poll until file is updated for (int i = 0; i < 100 && await readCounter() < startingValue + 2; i++) { - await new Future.delayed(const Duration(seconds: 1)); + await Future.delayed(const Duration(seconds: 1)); } expect(await readCounter(), startingValue + 2); expect(await AndroidAlarmManager.cancel(alarmId), isTrue); - await new Future.delayed(const Duration(seconds: 3)); + await Future.delayed(const Duration(seconds: 3)); expect(await readCounter(), startingValue + 2); }); } diff --git a/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e_test.dart b/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e_test.dart index 0bbfc8943126..ac4ea11482e2 100644 --- a/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e_test.dart +++ b/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e_test.dart @@ -8,21 +8,8 @@ import 'package:flutter_driver/flutter_driver.dart'; Future main() async { final FlutterDriver driver = await FlutterDriver.connect(); - print('SETTING UP DRIVER'); - final subscription = driver.serviceClient.onIsolateRunnable - .asBroadcastStream() - .listen((isolateRef) async { - print('isolate started: ${isolateRef.name}'); - final isolate = await isolateRef.load(); - if (isolate.isPaused) { - isolate.resume(); - print( - 'resuming isolate: ${isolateRef.numberAsString}:${isolateRef.name}'); - } - }); final String result = await driver.requestData(null, timeout: const Duration(minutes: 1)); - subscription.cancel(); driver.close(); exit(result == 'pass' ? 0 : 1); } From 6a6cea7df00499f81124fdbcf881b6af9ef1c001 Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Mon, 4 Nov 2019 16:22:13 -0800 Subject: [PATCH 08/23] Additional migration work --- .../androidalarmmanager/AlarmService.java | 182 +++------------ .../AndroidAlarmManagerPlugin.java | 80 +++---- .../BackgroundExecutionContext.java | 208 ++++++++++++++++++ packages/android_alarm_manager/pubspec.yaml | 1 + 4 files changed, 270 insertions(+), 201 deletions(-) create mode 100644 packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/BackgroundExecutionContext.java 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 74c98bc6a17a..84fdfdd1588f 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 @@ -32,178 +32,60 @@ import org.json.JSONObject; public class AlarmService extends JobIntentService { - // TODO(mattcarroll): tags should be private. Make private if no public usage. - public static final String TAG = "AlarmService"; - private static final String CALLBACK_HANDLE_KEY = "callback_handle"; + private static final String TAG = "AlarmService"; private static final String PERSISTENT_ALARMS_SET_KEY = "persistent_alarm_ids"; - private static final String SHARED_PREFERENCES_KEY = "io.flutter.android_alarm_manager_plugin"; + protected 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 final Object persistentAlarmsLock = new Object(); - // TODO(mattcarroll): make sIsIsolateRunning per-instance, not static. - private static AtomicBoolean sIsIsolateRunning = new AtomicBoolean(false); - - // TODO(mattcarroll): make sAlarmQueue per-instance, not static. - private static List sAlarmQueue = Collections.synchronizedList(new LinkedList()); + // TODO(mattcarroll): make alarmQueue per-instance, not static. + private static List alarmQueue = Collections.synchronizedList(new LinkedList()); /** Background Dart execution context. */ - private static FlutterNativeView sBackgroundFlutterView; - - /** - * The {@link MethodChannel} that connects the Android side of this plugin with the background - * Dart isolate that was created by this plugin. - */ - private static MethodChannel sBackgroundChannel; - - private static PluginRegistrantCallback sPluginRegistrantCallback; + private static BackgroundExecutionContext backgroundExecutionContext; // Schedule the alarm to be handled by the AlarmService. public static void enqueueAlarmProcessing(Context context, Intent alarmContext) { enqueueWork(context, AlarmService.class, JOB_ID, alarmContext); } - /** - * Starts running a background Dart isolate within a new {@link FlutterNativeView}. - * - *

The isolate is configured as follows: - * - *

    - *
  • Bundle Path: {@code FlutterMain.findAppBundlePath(context)}. - *
  • Entrypoint: The Dart method represented by {@code callbackHandle}. - *
  • Run args: none. - *
- * - *

Preconditions: - * - *

    - *
  • The given {@code callbackHandle} must correspond to a registered Dart callback. If the - * handle does not resolve to a Dart callback then this method does nothing. - *
  • A static {@link #sPluginRegistrantCallback} must exist, otherwise a {@link - * PluginRegistrantException} will be thrown. - *
- */ public static void startBackgroundIsolate(Context context, long callbackHandle) { - // TODO(mattcarroll): re-arrange order of operations. The order is strange - there are 3 - // conditions that must be met for this method to do anything but they're split up for no - // apparent reason. Do the qualification checks first, then execute the method's logic. - FlutterMain.ensureInitializationComplete(context, null); - String mAppBundlePath = FlutterMain.findAppBundlePath(context); - FlutterCallbackInformation flutterCallback = - FlutterCallbackInformation.lookupCallbackInformation(callbackHandle); - if (flutterCallback == null) { - Log.e(TAG, "Fatal: failed to find callback"); - return; - } - - // 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 && !sIsIsolateRunning.get()) { - if (sPluginRegistrantCallback == null) { - throw new PluginRegistrantException(); - } - Log.i(TAG, "Starting AlarmService..."); - FlutterRunArguments args = new FlutterRunArguments(); - args.bundlePath = mAppBundlePath; - args.entrypoint = flutterCallback.callbackName; - args.libraryPath = flutterCallback.callbackLibraryPath; - sBackgroundFlutterView.runFromBundle(args); - sPluginRegistrantCallback.registerWith(sBackgroundFlutterView.getPluginRegistry()); - } + assert(backgroundExecutionContext == null); + backgroundExecutionContext = new BackgroundExecutionContext(); + backgroundExecutionContext.startBackgroundIsolate(context, callbackHandle); } /** - * Called once the Dart isolate ({@code sBackgroundFlutterView}) has finished initializing. + * Called once the Dart isolate ({@code backgroundFlutterView}) has finished initializing. * *

Invoked by {@link AndroidAlarmManagerPlugin} when it receives the {@code * AlarmService.initialized} message. Processes all alarm events that came in while the isolate * was starting. */ - // TODO(mattcarroll): consider making this method package private - public static void onInitialized() { + static void onInitialized() { Log.i(TAG, "AlarmService started!"); - sIsIsolateRunning.set(true); - synchronized (sAlarmQueue) { + synchronized (alarmQueue) { // Handle all the alarm events received before the Dart isolate was // initialized, then clear the queue. - Iterator i = sAlarmQueue.iterator(); + Iterator i = alarmQueue.iterator(); while (i.hasNext()) { - executeDartCallbackInBackgroundIsolate(i.next(), null); + backgroundExecutionContext.executeDartCallbackInBackgroundIsolate(i.next(), null); } - sAlarmQueue.clear(); + alarmQueue.clear(); } } - /** - * Sets the {@link MethodChannel} that is used to communicate with Dart callbacks that are invoked - * in the background by the android_alarm_manager plugin. - */ - public static void setBackgroundChannel(MethodChannel channel) { - sBackgroundChannel = channel; - } - /** * Sets the Dart callback handle for the Dart method that is responsible for initializing the * background Dart isolate, preparing it to receive Dart callback tasks requests. */ public static void setCallbackDispatcher(Context context, long callbackHandle) { - SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFERENCES_KEY, 0); - prefs.edit().putLong(CALLBACK_HANDLE_KEY, callbackHandle).apply(); + BackgroundExecutionContext.setCallbackDispatcher(context, callbackHandle); } public static void setPluginRegistrant(PluginRegistrantCallback callback) { - sPluginRegistrantCallback = callback; - } - - /** - * Executes the desired Dart callback in a background Dart isolate. - * - *

The given {@code intent} should contain a {@code long} extra called "callbackHandle", which - * corresponds to a callback registered with the Dart VM. - */ - private static void executeDartCallbackInBackgroundIsolate( - Intent intent, final CountDownLatch latch) { - // 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( - TAG, - "setBackgroundChannel was not called before alarms were scheduled." + " Bailing out."); - return; - } - - // If another thread is waiting, then wake that thread when the callback returns a result. - MethodChannel.Result result = null; - if (latch != null) { - result = - new MethodChannel.Result() { - @Override - public void success(Object result) { - latch.countDown(); - } - - @Override - public void error(String errorCode, String errorMessage, Object errorDetails) { - latch.countDown(); - } - - @Override - public void notImplemented() { - latch.countDown(); - } - }; - } - - // 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. - // TODO(mattcarroll): consider giving a method name anyway for the purpose of developer discoverability - // when reading the source code. Especially on the Dart side. - sBackgroundChannel.invokeMethod( - "", new Object[] {callbackHandle, intent.getIntExtra("id", -1)}, result); + // Indirectly set in BackgroundExecutionContext for backwards compatibility. + BackgroundExecutionContext.setPluginRegistrant(callback); } private static void scheduleAlarm( @@ -355,7 +237,7 @@ private static void addPersistentAlarm( String key = getPersistentAlarmKey(requestCode); SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFERENCES_KEY, 0); - synchronized (sPersistentAlarmsLock) { + synchronized (persistentAlarmsLock) { Set persistentAlarms = prefs.getStringSet(PERSISTENT_ALARMS_SET_KEY, null); if (persistentAlarms == null) { persistentAlarms = new HashSet<>(); @@ -374,7 +256,7 @@ private static void addPersistentAlarm( private static void clearPersistentAlarm(Context context, int requestCode) { SharedPreferences p = context.getSharedPreferences(SHARED_PREFERENCES_KEY, 0); - synchronized (sPersistentAlarmsLock) { + synchronized (persistentAlarmsLock) { Set persistentAlarms = p.getStringSet(PERSISTENT_ALARMS_SET_KEY, null); if ((persistentAlarms == null) || !persistentAlarms.contains(requestCode)) { return; @@ -390,7 +272,7 @@ private static void clearPersistentAlarm(Context context, int requestCode) { } public static void reschedulePersistentAlarms(Context context) { - synchronized (sPersistentAlarmsLock) { + synchronized (persistentAlarmsLock) { SharedPreferences p = context.getSharedPreferences(SHARED_PREFERENCES_KEY, 0); Set persistentAlarms = p.getStringSet(PERSISTENT_ALARMS_SET_KEY, null); // No alarms to reschedule. @@ -440,15 +322,11 @@ public static void reschedulePersistentAlarms(Context context) { @Override public void onCreate() { super.onCreate(); - - Context context = getApplicationContext(); - FlutterMain.ensureInitializationComplete(context, null); - - if (!sIsIsolateRunning.get()) { - SharedPreferences p = context.getSharedPreferences(SHARED_PREFERENCES_KEY, 0); - long callbackHandle = p.getLong(CALLBACK_HANDLE_KEY, 0); - startBackgroundIsolate(context, callbackHandle); + if (backgroundExecutionContext == null) { + backgroundExecutionContext = new BackgroundExecutionContext(); } + Context context = getApplicationContext(); + backgroundExecutionContext.startBackgroundIsolate(context); } /** @@ -461,17 +339,17 @@ public void onCreate() { * intent}, then the desired Dart callback is invoked immediately. * *

If there are any pre-existing callback requests that have yet to be executed, the incoming - * {@code intent} is added to the {@link #sAlarmQueue} to invoked later, after all pre-existing + * {@code intent} is added to the {@link #alarmQueue} to invoked later, after all pre-existing * callbacks have been executed. */ @Override protected void onHandleWork(final Intent intent) { // If we're in the middle of processing queued alarms, add the incoming // intent to the queue and return. - synchronized (sAlarmQueue) { - if (!sIsIsolateRunning.get()) { + synchronized (alarmQueue) { + if (!backgroundExecutionContext.isRunning()) { Log.i(TAG, "AlarmService has not yet started."); - sAlarmQueue.add(intent); + alarmQueue.add(intent); return; } } @@ -484,7 +362,7 @@ protected void onHandleWork(final Intent intent) { new Runnable() { @Override public void run() { - executeDartCallbackInBackgroundIsolate(intent, latch); + backgroundExecutionContext.executeDartCallbackInBackgroundIsolate(intent, latch); } }); 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 0569b58285d9..1d13b2465d87 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 @@ -5,6 +5,7 @@ package io.flutter.plugins.androidalarmmanager; import android.content.Context; +import android.util.Log; import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.JSONMethodCodec; @@ -40,9 +41,12 @@ * */ public class AndroidAlarmManagerPlugin implements FlutterPlugin, MethodCallHandler { + private final String TAG = "AndroidAlarmManagerPlugin"; private Context context; + private Object initializationLock = new Object(); private MethodChannel alarmManagerPluginChannel; - private MethodChannel backgroundCallbackChannel; + + private static AndroidAlarmManagerPlugin singleton; /** * Registers this plugin with an associated Flutter execution context, represented by the given @@ -52,8 +56,10 @@ public class AndroidAlarmManagerPlugin implements FlutterPlugin, MethodCallHandl * connected to, and running against, the associated Flutter execution context. */ public static void registerWith(Registrar registrar) { - final AndroidAlarmManagerPlugin plugin = new AndroidAlarmManagerPlugin(); - plugin.onAttachedToEngine(registrar.context(), registrar.messenger()); + if (singleton == null) { + singleton = new AndroidAlarmManagerPlugin(); + } + singleton.onAttachedToEngine(registrar.context(), registrar.messenger()); } @Override @@ -63,53 +69,36 @@ public void onAttachedToEngine(FlutterPluginBinding binding) { } public void onAttachedToEngine(Context applicationContext, BinaryMessenger messenger) { - this.context = applicationContext; - - // alarmManagerPluginChannel is the channel responsible for receiving the following messages - // from the main Flutter app: - // - "AlarmService.start" - // - "Alarm.oneShotAt" - // - "Alarm.periodic" - // - "Alarm.cancel" - alarmManagerPluginChannel = - new MethodChannel( - messenger, "plugins.flutter.io/android_alarm_manager", JSONMethodCodec.INSTANCE); - - // backgroundCallbackChannel is the channel responsible for receiving the following messages - // from the background isolate that was setup by this plugin: - // - "AlarmService.initialized" - // - // This channel is also responsible for sending requests from Android to Dart to execute Dart - // callbacks in the background isolate. Those messages are sent with an empty method name because - // they are the only messages that this channel sends to Dart. - backgroundCallbackChannel = - new MethodChannel( - messenger, - "plugins.flutter.io/android_alarm_manager_background", - JSONMethodCodec.INSTANCE); - - // Instantiate a new AndroidAlarmManagerPlugin and connect the primary and background - // method channels for Android/Flutter communication. - alarmManagerPluginChannel.setMethodCallHandler(this); - backgroundCallbackChannel.setMethodCallHandler(this); + synchronized(initializationLock) { + if (alarmManagerPluginChannel != null) { + return; + } - // The AlarmService expects to hold a static reference to the plugin's background - // method channel. - // TODO(mattcarroll): this static reference implies that only one instance of this plugin - // can exist at a time. Moreover, calling registerWith() a 2nd time would - // seem to overwrite the previously registered background channel without - // notice. - AlarmService.setBackgroundChannel(backgroundCallbackChannel); + Log.i(TAG, "onAttachedToEngine"); + this.context = applicationContext; + + // alarmManagerPluginChannel is the channel responsible for receiving the following messages + // from the main Flutter app: + // - "AlarmService.start" + // - "Alarm.oneShotAt" + // - "Alarm.periodic" + // - "Alarm.cancel" + alarmManagerPluginChannel = + new MethodChannel( + messenger, "plugins.flutter.io/android_alarm_manager", JSONMethodCodec.INSTANCE); + + // Instantiate a new AndroidAlarmManagerPlugin and connect the primary method channel for + // Android/Flutter communication. + alarmManagerPluginChannel.setMethodCallHandler(this); + } } @Override public void onDetachedFromEngine(FlutterPluginBinding binding) { + Log.i(TAG, "onDetachedFromEngine"); context = null; alarmManagerPluginChannel.setMethodCallHandler(null); alarmManagerPluginChannel = null; - - backgroundCallbackChannel.setMethodCallHandler(null); - backgroundCallbackChannel = null; } public AndroidAlarmManagerPlugin() {} @@ -131,13 +120,6 @@ public void onMethodCall(MethodCall call, Result result) { AlarmService.setCallbackDispatcher(context, callbackHandle); AlarmService.startBackgroundIsolate(context, callbackHandle); result.success(true); - } else if (method.equals("AlarmService.initialized")) { - // This message is sent by the background method channel as soon as the background isolate - // is running. From this point forward, the Android side of this plugin can send - // callback handles through the background method channel, and the Dart side will execute - // the Dart methods corresponding to those callback handles. - AlarmService.onInitialized(); - result.success(true); } else if (method.equals("Alarm.periodic")) { // This message indicates that the Flutter app would like to schedule a periodic // task. diff --git a/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/BackgroundExecutionContext.java b/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/BackgroundExecutionContext.java new file mode 100644 index 000000000000..bc945f7b0d3f --- /dev/null +++ b/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/BackgroundExecutionContext.java @@ -0,0 +1,208 @@ +// 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.Context; +import android.content.Intent; +import android.content.res.AssetManager; +import android.util.Log; +import io.flutter.embedding.engine.FlutterEngine; +import io.flutter.embedding.engine.dart.DartExecutor; +import io.flutter.embedding.engine.dart.DartExecutor.DartCallback; +import io.flutter.embedding.engine.plugins.shim.ShimPluginRegistry; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.JSONMethodCodec; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.MethodChannel.MethodCallHandler; +import io.flutter.plugin.common.MethodChannel.Result; +import io.flutter.plugin.common.PluginRegistry.Registrar; +import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback; +import io.flutter.view.FlutterCallbackInformation; +import io.flutter.view.FlutterMain; +import io.flutter.view.FlutterNativeView; +import io.flutter.view.FlutterRunArguments; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; +import org.json.JSONArray; +import org.json.JSONException; + +public class BackgroundExecutionContext implements MethodCallHandler { + private static final String TAG = "BackgroundExecutionContext"; + private static final String CALLBACK_HANDLE_KEY = "callback_handle"; + + /** + * The {@link MethodChannel} that connects the Android side of this plugin with the background + * Dart isolate that was created by this plugin. + */ + private MethodChannel backgroundChannel; + + private FlutterEngine backgroundFlutterEngine; + + private AtomicBoolean isIsolateRunning = new AtomicBoolean(false); + + private static PluginRegistrantCallback pluginRegistrantCallback; + + public static void setPluginRegistrant(PluginRegistrantCallback callback) { + pluginRegistrantCallback = callback; + } + + /** + * Sets the Dart callback handle for the Dart method that is responsible for initializing the + * background Dart isolate, preparing it to receive Dart callback tasks requests. + */ + public static void setCallbackDispatcher(Context context, long callbackHandle) { + SharedPreferences prefs = context.getSharedPreferences(AlarmService.SHARED_PREFERENCES_KEY, 0); + prefs.edit().putLong(CALLBACK_HANDLE_KEY, callbackHandle).apply(); + } + + /** + * Returns true when the background isolate has started. + */ + public boolean isRunning() { + return isIsolateRunning.get(); + } + + private void onInitialized() { + isIsolateRunning.set(true); + AlarmService.onInitialized(); + } + + @Override + public void onMethodCall(MethodCall call, Result result) { + String method = call.method; + Object arguments = call.arguments; + try { + if (method.equals("AlarmService.initialized")) { + // This message is sent by the background method channel as soon as the background isolate + // is running. From this point forward, the Android side of this plugin can send + // callback handles through the background method channel, and the Dart side will execute + // the Dart methods corresponding to those callback handles. + onInitialized(); + result.success(true); + } else { + result.notImplemented(); + } + } catch (PluginRegistrantException e) { + result.error("error", "AlarmManager error: " + e.getMessage(), null); + } + } + + public void startBackgroundIsolate(Context context) { + if (!isRunning()) { + SharedPreferences p = context.getSharedPreferences(AlarmService.SHARED_PREFERENCES_KEY, 0); + long callbackHandle = p.getLong(CALLBACK_HANDLE_KEY, 0); + startBackgroundIsolate(context, callbackHandle); + } + } + + /** + * Starts running a background Dart isolate within a new {@link FlutterEngine}. + * + *

The isolate is configured as follows: + * + *

    + *
  • Bundle Path: {@code FlutterMain.findAppBundlePath(context)}. + *
  • Entrypoint: The Dart method represented by {@code callback}. + *
  • Run args: none. + *
+ * + *

Preconditions: + * + *

    + *
  • The given {@code callback} must correspond to a registered Dart callback. If the + * handle does not resolve to a Dart callback then this method does nothing. + *
  • A static {@link #pluginRegistrantCallback} must exist, otherwise a {@link + * PluginRegistrantException} will be thrown. + *
+ */ + public void startBackgroundIsolate(Context context, long callbackHandle) { + if (backgroundFlutterEngine != null) { + Log.e(TAG, "Background isolate already started"); + return; + } + + FlutterMain.ensureInitializationComplete(context, null); + FlutterCallbackInformation flutterCallback = + FlutterCallbackInformation.lookupCallbackInformation(callbackHandle); + if (flutterCallback == null) { + Log.e(TAG, "Fatal: failed to find callback"); + return; + } + Log.i(TAG, "Starting AlarmService..."); + String appBundlePath = FlutterMain.findAppBundlePath(context); + AssetManager assets = context.getAssets(); + if (appBundlePath != null && !isRunning()) { + backgroundFlutterEngine = new FlutterEngine(context); + DartExecutor executor = backgroundFlutterEngine.getDartExecutor(); + initializeMethodChannel(executor); + DartCallback dartCallback = new DartCallback(assets, appBundlePath, flutterCallback); + + executor.executeDartCallback(dartCallback); + + // TODO(bkonyi): handle registration in V2 embedding. + // The pluginRegistrantCallback should only be set in the V1 embedding. + if (pluginRegistrantCallback != null) { + pluginRegistrantCallback.registerWith(new ShimPluginRegistry(backgroundFlutterEngine)); + } + } + } + + /** + * Executes the desired Dart callback in a background Dart isolate. + * + *

The given {@code intent} should contain a {@code long} extra called "callbackHandle", which + * corresponds to a callback registered with the Dart VM. + */ + public void executeDartCallbackInBackgroundIsolate( + Intent intent, final CountDownLatch latch) { + // 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 another thread is waiting, then wake that thread when the callback returns a result. + MethodChannel.Result result = null; + if (latch != null) { + result = + new MethodChannel.Result() { + @Override + public void success(Object result) { + latch.countDown(); + } + + @Override + public void error(String errorCode, String errorMessage, Object errorDetails) { + latch.countDown(); + } + + @Override + public void notImplemented() { + latch.countDown(); + } + }; + } + + // 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. + backgroundChannel.invokeMethod( + "invokeAlarmManagerCallback", new Object[] {callbackHandle, intent.getIntExtra("id", -1)}, result); + } + + private void initializeMethodChannel(BinaryMessenger isolate) { + // backgroundChannel is the channel responsible for receiving the following messages from + // the background isolate that was setup by this plugin: + // - "AlarmService.initialized" + // + // This channel is also responsible for sending requests from Android to Dart to execute Dart + // callbacks in the background isolate. + backgroundChannel = new MethodChannel( + isolate, + "plugins.flutter.io/android_alarm_manager_background", + JSONMethodCodec.INSTANCE); + backgroundChannel.setMethodCallHandler(this); + } +} diff --git a/packages/android_alarm_manager/pubspec.yaml b/packages/android_alarm_manager/pubspec.yaml index 6fe4ed944d55..7049e14e19d3 100644 --- a/packages/android_alarm_manager/pubspec.yaml +++ b/packages/android_alarm_manager/pubspec.yaml @@ -17,4 +17,5 @@ flutter: environment: sdk: ">=2.0.0-dev.28.0 <3.0.0" + # TODO(bkonyi): set minimum Flutter version to next stable release flutter: ">=1.2.0 <2.0.0" From 1e73ec4f7a86def7c8711ff8047d5ec97f13ad82 Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Mon, 4 Nov 2019 16:25:36 -0800 Subject: [PATCH 09/23] Formatting --- .../androidalarmmanager/AlarmService.java | 8 +---- .../AndroidAlarmManagerPlugin.java | 2 +- .../BackgroundExecutionContext.java | 29 ++++++++----------- .../android_alarm_manager_e2e.dart | 2 +- 4 files changed, 15 insertions(+), 26 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 84fdfdd1588f..d2671f93bda6 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 @@ -13,12 +13,7 @@ import android.util.Log; import androidx.core.app.AlarmManagerCompat; import androidx.core.app.JobIntentService; -import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback; -import io.flutter.view.FlutterCallbackInformation; -import io.flutter.view.FlutterMain; -import io.flutter.view.FlutterNativeView; -import io.flutter.view.FlutterRunArguments; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -27,7 +22,6 @@ import java.util.List; import java.util.Set; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicBoolean; import org.json.JSONException; import org.json.JSONObject; @@ -50,7 +44,7 @@ public static void enqueueAlarmProcessing(Context context, Intent alarmContext) } public static void startBackgroundIsolate(Context context, long callbackHandle) { - assert(backgroundExecutionContext == null); + assert (backgroundExecutionContext == null); backgroundExecutionContext = new BackgroundExecutionContext(); backgroundExecutionContext.startBackgroundIsolate(context, 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 1d13b2465d87..49c8bd611d18 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 @@ -69,7 +69,7 @@ public void onAttachedToEngine(FlutterPluginBinding binding) { } public void onAttachedToEngine(Context applicationContext, BinaryMessenger messenger) { - synchronized(initializationLock) { + synchronized (initializationLock) { if (alarmManagerPluginChannel != null) { return; } diff --git a/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/BackgroundExecutionContext.java b/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/BackgroundExecutionContext.java index bc945f7b0d3f..00e179db1f38 100644 --- a/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/BackgroundExecutionContext.java +++ b/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/BackgroundExecutionContext.java @@ -18,16 +18,11 @@ import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel.MethodCallHandler; import io.flutter.plugin.common.MethodChannel.Result; -import io.flutter.plugin.common.PluginRegistry.Registrar; import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback; import io.flutter.view.FlutterCallbackInformation; import io.flutter.view.FlutterMain; -import io.flutter.view.FlutterNativeView; -import io.flutter.view.FlutterRunArguments; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; -import org.json.JSONArray; -import org.json.JSONException; public class BackgroundExecutionContext implements MethodCallHandler { private static final String TAG = "BackgroundExecutionContext"; @@ -58,9 +53,7 @@ public static void setCallbackDispatcher(Context context, long callbackHandle) { prefs.edit().putLong(CALLBACK_HANDLE_KEY, callbackHandle).apply(); } - /** - * Returns true when the background isolate has started. - */ + /** Returns true when the background isolate has started. */ public boolean isRunning() { return isIsolateRunning.get(); } @@ -112,8 +105,8 @@ public void startBackgroundIsolate(Context context) { *

Preconditions: * *

    - *
  • The given {@code callback} must correspond to a registered Dart callback. If the - * handle does not resolve to a Dart callback then this method does nothing. + *
  • The given {@code callback} must correspond to a registered Dart callback. If the handle + * does not resolve to a Dart callback then this method does nothing. *
  • A static {@link #pluginRegistrantCallback} must exist, otherwise a {@link * PluginRegistrantException} will be thrown. *
@@ -156,8 +149,7 @@ public void startBackgroundIsolate(Context context, long callbackHandle) { *

The given {@code intent} should contain a {@code long} extra called "callbackHandle", which * corresponds to a callback registered with the Dart VM. */ - public void executeDartCallbackInBackgroundIsolate( - Intent intent, final CountDownLatch latch) { + public void executeDartCallbackInBackgroundIsolate(Intent intent, final CountDownLatch latch) { // 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. @@ -189,7 +181,9 @@ public void notImplemented() { // care about the method name as we simply lookup and invoke the callback // provided. backgroundChannel.invokeMethod( - "invokeAlarmManagerCallback", new Object[] {callbackHandle, intent.getIntExtra("id", -1)}, result); + "invokeAlarmManagerCallback", + new Object[] {callbackHandle, intent.getIntExtra("id", -1)}, + result); } private void initializeMethodChannel(BinaryMessenger isolate) { @@ -199,10 +193,11 @@ private void initializeMethodChannel(BinaryMessenger isolate) { // // This channel is also responsible for sending requests from Android to Dart to execute Dart // callbacks in the background isolate. - backgroundChannel = new MethodChannel( - isolate, - "plugins.flutter.io/android_alarm_manager_background", - JSONMethodCodec.INSTANCE); + backgroundChannel = + new MethodChannel( + isolate, + "plugins.flutter.io/android_alarm_manager_background", + JSONMethodCodec.INSTANCE); backgroundChannel.setMethodCallHandler(this); } } diff --git a/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e.dart b/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e.dart index 1309b84d1b5e..db5ee3f961ce 100644 --- a/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e.dart +++ b/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e.dart @@ -35,7 +35,7 @@ Future readCounter() async { final String contents = await file.readAsString(); return int.parse(contents); - // ignore: unused_catch_clause + // ignore: unused_catch_clause } on FileSystemException catch (e) { // If encountering an error, return 0. return 0; From 6bd80ee2c373d770af2f4f1abbbffd9875a65fd1 Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Tue, 5 Nov 2019 12:24:26 -0800 Subject: [PATCH 10/23] Fixed publishable failures --- .../androidalarmmanager/BackgroundExecutionContext.java | 1 + packages/android_alarm_manager/pubspec.yaml | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/BackgroundExecutionContext.java b/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/BackgroundExecutionContext.java index 00e179db1f38..655fa0feb69c 100644 --- a/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/BackgroundExecutionContext.java +++ b/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/BackgroundExecutionContext.java @@ -7,6 +7,7 @@ import android.content.Context; import android.content.Intent; import android.content.res.AssetManager; +import android.content.SharedPreferences; import android.util.Log; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.dart.DartExecutor; diff --git a/packages/android_alarm_manager/pubspec.yaml b/packages/android_alarm_manager/pubspec.yaml index 7049e14e19d3..5187adf43e07 100644 --- a/packages/android_alarm_manager/pubspec.yaml +++ b/packages/android_alarm_manager/pubspec.yaml @@ -11,9 +11,12 @@ dependencies: flutter: plugin: - androidPackage: io.flutter.plugins.androidalarmmanager - pluginClass: AndroidAlarmManagerPlugin - iosPrefix: FLT + platforms: + android: + package: io.flutter.plugins.androidalarmmanager + pluginClass: AndroidAlarmManagerPlugin + ios: + prefix: FLT environment: sdk: ">=2.0.0-dev.28.0 <3.0.0" From 010b7ea0b9bbe8fc024b99b87792be5e05824b76 Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Tue, 5 Nov 2019 12:46:11 -0800 Subject: [PATCH 11/23] Update pubspec --- packages/android_alarm_manager/pubspec.yaml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/android_alarm_manager/pubspec.yaml b/packages/android_alarm_manager/pubspec.yaml index 5187adf43e07..862adfd08727 100644 --- a/packages/android_alarm_manager/pubspec.yaml +++ b/packages/android_alarm_manager/pubspec.yaml @@ -1,8 +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.4+2 -author: Flutter Team +version: 0.4.5 homepage: https://github.com/flutter/plugins/tree/master/packages/android_alarm_manager dependencies: @@ -15,10 +14,8 @@ flutter: android: package: io.flutter.plugins.androidalarmmanager pluginClass: AndroidAlarmManagerPlugin - ios: - prefix: FLT environment: sdk: ">=2.0.0-dev.28.0 <3.0.0" # TODO(bkonyi): set minimum Flutter version to next stable release - flutter: ">=1.2.0 <2.0.0" + flutter: ">=1.10.0 <2.0.0" From 2c28c5b17bdaf2443f4ac1be220c5aee8d56e98a Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Tue, 5 Nov 2019 12:57:46 -0800 Subject: [PATCH 12/23] Update Flutter version lower bound --- packages/android_alarm_manager/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/android_alarm_manager/pubspec.yaml b/packages/android_alarm_manager/pubspec.yaml index 862adfd08727..83dbc048805d 100644 --- a/packages/android_alarm_manager/pubspec.yaml +++ b/packages/android_alarm_manager/pubspec.yaml @@ -18,4 +18,4 @@ flutter: environment: sdk: ">=2.0.0-dev.28.0 <3.0.0" # TODO(bkonyi): set minimum Flutter version to next stable release - flutter: ">=1.10.0 <2.0.0" + flutter: ">=1.2.0 <2.0.0" From acc5d62506b83bc06ebe42aab3de563a92037677 Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Tue, 5 Nov 2019 13:03:01 -0800 Subject: [PATCH 13/23] Add back authors --- packages/android_alarm_manager/pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/android_alarm_manager/pubspec.yaml b/packages/android_alarm_manager/pubspec.yaml index 83dbc048805d..0fd43cd7378e 100644 --- a/packages/android_alarm_manager/pubspec.yaml +++ b/packages/android_alarm_manager/pubspec.yaml @@ -2,6 +2,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.5 +author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/android_alarm_manager dependencies: From cf85ae02efa9c042ab9eb3313a25bdd6964e2192 Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Tue, 5 Nov 2019 13:04:46 -0800 Subject: [PATCH 14/23] Formatting --- .../plugins/androidalarmmanager/BackgroundExecutionContext.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/BackgroundExecutionContext.java b/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/BackgroundExecutionContext.java index 655fa0feb69c..782cca8a0181 100644 --- a/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/BackgroundExecutionContext.java +++ b/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/BackgroundExecutionContext.java @@ -6,8 +6,8 @@ import android.content.Context; import android.content.Intent; -import android.content.res.AssetManager; import android.content.SharedPreferences; +import android.content.res.AssetManager; import android.util.Log; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.dart.DartExecutor; From 6a147707c3adc6b6816153814e89d943119f33f1 Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Mon, 18 Nov 2019 15:35:37 -0800 Subject: [PATCH 15/23] Addressed comments --- .../androidalarmmanager/AlarmService.java | 59 +++++++++++++----- .../AndroidAlarmManagerPlugin.java | 9 ++- ...xt.java => FlutterBackgroundExecutor.java} | 60 ++++++++++++++----- 3 files changed, 93 insertions(+), 35 deletions(-) rename packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/{BackgroundExecutionContext.java => FlutterBackgroundExecutor.java} (78%) 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 d2671f93bda6..fb6e7f85b317 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 @@ -36,34 +36,49 @@ public class AlarmService extends JobIntentService { private static List alarmQueue = Collections.synchronizedList(new LinkedList()); /** Background Dart execution context. */ - private static BackgroundExecutionContext backgroundExecutionContext; + private static FlutterBackgroundExecutor flutterBackgroundExecutor; - // Schedule the alarm to be handled by the AlarmService. + /** Schedule the alarm to be handled by the {@link AlarmService}. */ public static void enqueueAlarmProcessing(Context context, Intent alarmContext) { enqueueWork(context, AlarmService.class, JOB_ID, alarmContext); } + /** + * Starts the background isolate for the {@link AlarmService}. + * + *

Preconditions: + * + *

    + *
  • The given {@code callbackHandle} must correspond to a registered Dart callback. If the + * handle does not resolve to a Dart callback then this method does nothing. + *
  • A static {@link #pluginRegistrantCallback} must exist, otherwise a {@link + * PluginRegistrantException} will be thrown. + *
+ */ public static void startBackgroundIsolate(Context context, long callbackHandle) { - assert (backgroundExecutionContext == null); - backgroundExecutionContext = new BackgroundExecutionContext(); - backgroundExecutionContext.startBackgroundIsolate(context, callbackHandle); + if (flutterBackgroundExecutor != null) { + Log.w(TAG, "Attempted to start a duplicate background isolate. Returning..."); + return; + } + flutterBackgroundExecutor = new FlutterBackgroundExecutor(); + flutterBackgroundExecutor.startBackgroundIsolate(context, callbackHandle); } /** - * Called once the Dart isolate ({@code backgroundFlutterView}) has finished initializing. + * Called once the Dart isolate ({@code flutterBackgroundExecutor}) has finished initializing. * *

Invoked by {@link AndroidAlarmManagerPlugin} when it receives the {@code * AlarmService.initialized} message. Processes all alarm events that came in while the isolate * was starting. */ - static void onInitialized() { + /* package */ static void onInitialized() { Log.i(TAG, "AlarmService started!"); synchronized (alarmQueue) { // Handle all the alarm events received before the Dart isolate was // initialized, then clear the queue. Iterator i = alarmQueue.iterator(); while (i.hasNext()) { - backgroundExecutionContext.executeDartCallbackInBackgroundIsolate(i.next(), null); + flutterBackgroundExecutor.executeDartCallbackInBackgroundIsolate(i.next(), null); } alarmQueue.clear(); } @@ -74,12 +89,21 @@ static void onInitialized() { * background Dart isolate, preparing it to receive Dart callback tasks requests. */ public static void setCallbackDispatcher(Context context, long callbackHandle) { - BackgroundExecutionContext.setCallbackDispatcher(context, callbackHandle); + FlutterBackgroundExecutor.setCallbackDispatcher(context, callbackHandle); } + /** + * Sets the {@link PluginRegistrantCallback} used to register the plugins used by an application + * with the newly spawned background isolate. + * + *

This should be invoked in {@link Application.onCreate} with {@link + * GeneratedPluginRegistrant} in applications using the V1 embedding API in order to use other + * plugins in the background isolate. For applications using the V2 embedding API, it is not + * necessary to set a {@link PluginRegistrantCallback} as plugins are registered automatically. + */ public static void setPluginRegistrant(PluginRegistrantCallback callback) { - // Indirectly set in BackgroundExecutionContext for backwards compatibility. - BackgroundExecutionContext.setPluginRegistrant(callback); + // Indirectly set in FlutterBackgroundExecutor for backwards compatibility. + FlutterBackgroundExecutor.setPluginRegistrant(callback); } private static void scheduleAlarm( @@ -152,6 +176,7 @@ private static void scheduleAlarm( } } + /** Schedules a one-shot alarm to be executed once in the future. */ public static void setOneShot(Context context, AndroidAlarmManagerPlugin.OneShotRequest request) { final boolean repeating = false; scheduleAlarm( @@ -168,6 +193,7 @@ public static void setOneShot(Context context, AndroidAlarmManagerPlugin.OneShot request.callbackHandle); } + /** Schedules a periodic alarm to be executed repeatedly in the future. */ public static void setPeriodic( Context context, AndroidAlarmManagerPlugin.PeriodicRequest request) { final boolean repeating = true; @@ -187,6 +213,7 @@ public static void setPeriodic( request.callbackHandle); } + /** Cancels an alarm with ID {@code requestCode}. */ public static void cancel(Context context, int requestCode) { // Clear the alarm if it was set to be rescheduled after reboots. clearPersistentAlarm(context, requestCode); @@ -316,11 +343,11 @@ public static void reschedulePersistentAlarms(Context context) { @Override public void onCreate() { super.onCreate(); - if (backgroundExecutionContext == null) { - backgroundExecutionContext = new BackgroundExecutionContext(); + if (flutterBackgroundExecutor == null) { + flutterBackgroundExecutor = new FlutterBackgroundExecutor(); } Context context = getApplicationContext(); - backgroundExecutionContext.startBackgroundIsolate(context); + flutterBackgroundExecutor.startBackgroundIsolate(context); } /** @@ -341,7 +368,7 @@ protected void onHandleWork(final Intent intent) { // If we're in the middle of processing queued alarms, add the incoming // intent to the queue and return. synchronized (alarmQueue) { - if (!backgroundExecutionContext.isRunning()) { + if (!flutterBackgroundExecutor.isRunning()) { Log.i(TAG, "AlarmService has not yet started."); alarmQueue.add(intent); return; @@ -356,7 +383,7 @@ protected void onHandleWork(final Intent intent) { new Runnable() { @Override public void run() { - backgroundExecutionContext.executeDartCallbackInBackgroundIsolate(intent, latch); + flutterBackgroundExecutor.executeDartCallbackInBackgroundIsolate(intent, latch); } }); 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 49c8bd611d18..9db627022f72 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 @@ -41,13 +41,12 @@ * */ public class AndroidAlarmManagerPlugin implements FlutterPlugin, MethodCallHandler { + private static AndroidAlarmManagerPlugin instance; private final String TAG = "AndroidAlarmManagerPlugin"; private Context context; private Object initializationLock = new Object(); private MethodChannel alarmManagerPluginChannel; - private static AndroidAlarmManagerPlugin singleton; - /** * Registers this plugin with an associated Flutter execution context, represented by the given * {@link Registrar}. @@ -56,10 +55,10 @@ public class AndroidAlarmManagerPlugin implements FlutterPlugin, MethodCallHandl * connected to, and running against, the associated Flutter execution context. */ public static void registerWith(Registrar registrar) { - if (singleton == null) { - singleton = new AndroidAlarmManagerPlugin(); + if (instance == null) { + instance = new AndroidAlarmManagerPlugin(); } - singleton.onAttachedToEngine(registrar.context(), registrar.messenger()); + instance.onAttachedToEngine(registrar.context(), registrar.messenger()); } @Override diff --git a/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/BackgroundExecutionContext.java b/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/FlutterBackgroundExecutor.java similarity index 78% rename from packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/BackgroundExecutionContext.java rename to packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/FlutterBackgroundExecutor.java index 782cca8a0181..c7eebc2332bf 100644 --- a/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/BackgroundExecutionContext.java +++ b/packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/FlutterBackgroundExecutor.java @@ -25,9 +25,14 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; -public class BackgroundExecutionContext implements MethodCallHandler { - private static final String TAG = "BackgroundExecutionContext"; +/** + * An background execution abstraction which handles initializing a background isolate running a + * callback dispatcher, used to invoke Dart callbacks while backgrounded. + */ +public class FlutterBackgroundExecutor implements MethodCallHandler { + private static final String TAG = "FlutterBackgroundExecutor"; private static final String CALLBACK_HANDLE_KEY = "callback_handle"; + private static PluginRegistrantCallback pluginRegistrantCallback; /** * The {@link MethodChannel} that connects the Android side of this plugin with the background @@ -37,10 +42,16 @@ public class BackgroundExecutionContext implements MethodCallHandler { private FlutterEngine backgroundFlutterEngine; - private AtomicBoolean isIsolateRunning = new AtomicBoolean(false); - - private static PluginRegistrantCallback pluginRegistrantCallback; + private AtomicBoolean isCallbackDispatcherReady = new AtomicBoolean(false); + /** + * Sets the {@code PluginRegistrantCallback} used to register plugins with the newly spawned + * isolate. + * + *

Note: this is only necessary for applications using the V1 engine embedding API as plugins + * are automatically registered via reflection in the V2 engine embedding API. If not set, alarm + * callbacks will not be able to utilize functionality from other plugins. + */ public static void setPluginRegistrant(PluginRegistrantCallback callback) { pluginRegistrantCallback = callback; } @@ -54,13 +65,13 @@ public static void setCallbackDispatcher(Context context, long callbackHandle) { prefs.edit().putLong(CALLBACK_HANDLE_KEY, callbackHandle).apply(); } - /** Returns true when the background isolate has started. */ + /** Returns true when the background isolate has started and is ready to handle alarms. */ public boolean isRunning() { - return isIsolateRunning.get(); + return isCallbackDispatcherReady.get(); } private void onInitialized() { - isIsolateRunning.set(true); + isCallbackDispatcherReady.set(true); AlarmService.onInitialized(); } @@ -84,6 +95,28 @@ public void onMethodCall(MethodCall call, Result result) { } } + /** + * Starts running a background Dart isolate within a new {@link FlutterEngine} using a previously + * used entrypoint. + * + *

The isolate is configured as follows: + * + *

    + *
  • Bundle Path: {@code FlutterMain.findAppBundlePath(context)}. + *
  • Entrypoint: The Dart method used the last time this plugin was initialized in the + * foreground. + *
  • Run args: none. + *
+ * + *

Preconditions: + * + *

    + *
  • The given callback must correspond to a registered Dart callback. If the handle does not + * resolve to a Dart callback then this method does nothing. + *
  • A static {@link #pluginRegistrantCallback} must exist, otherwise a {@link + * PluginRegistrantException} will be thrown. + *
+ */ public void startBackgroundIsolate(Context context) { if (!isRunning()) { SharedPreferences p = context.getSharedPreferences(AlarmService.SHARED_PREFERENCES_KEY, 0); @@ -99,15 +132,15 @@ public void startBackgroundIsolate(Context context) { * *
    *
  • Bundle Path: {@code FlutterMain.findAppBundlePath(context)}. - *
  • Entrypoint: The Dart method represented by {@code callback}. + *
  • Entrypoint: The Dart method represented by {@code callbackHandle}. *
  • Run args: none. *
* *

Preconditions: * *

    - *
  • The given {@code callback} must correspond to a registered Dart callback. If the handle - * does not resolve to a Dart callback then this method does nothing. + *
  • The given {@code callbackHandle} must correspond to a registered Dart callback. If the + * handle does not resolve to a Dart callback then this method does nothing. *
  • A static {@link #pluginRegistrantCallback} must exist, otherwise a {@link * PluginRegistrantException} will be thrown. *
@@ -118,7 +151,6 @@ public void startBackgroundIsolate(Context context, long callbackHandle) { return; } - FlutterMain.ensureInitializationComplete(context, null); FlutterCallbackInformation flutterCallback = FlutterCallbackInformation.lookupCallbackInformation(callbackHandle); if (flutterCallback == null) { @@ -136,8 +168,8 @@ public void startBackgroundIsolate(Context context, long callbackHandle) { executor.executeDartCallback(dartCallback); - // TODO(bkonyi): handle registration in V2 embedding. - // The pluginRegistrantCallback should only be set in the V1 embedding. + // The pluginRegistrantCallback should only be set in the V1 embedding as + // plugin registration is done via reflection in the V2 embedding. if (pluginRegistrantCallback != null) { pluginRegistrantCallback.registerWith(new ShimPluginRegistry(backgroundFlutterEngine)); } From 59a31acaf75e909eb5d387b6af3efa6c2f6f529c Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Thu, 21 Nov 2019 16:32:53 -0800 Subject: [PATCH 16/23] Resume background isolate for driver test --- .../android_alarm_manager_e2e_test.dart | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e_test.dart b/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e_test.dart index ac4ea11482e2..31a55f7b2983 100644 --- a/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e_test.dart +++ b/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e_test.dart @@ -2,14 +2,38 @@ // for details. 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 'package:flutter_driver/flutter_driver.dart'; +Future resumeIsolatesOnPause(FlutterDriver driver) async { + final vm = await driver.serviceClient.getVM(); + // // unpause any paused isolated + for (final isolateRef in vm.isolates) { + final isolate = await isolateRef.load(); + if (isolate.isPaused) { + isolate.resume(); + } + } + return driver.serviceClient.onIsolateRunnable + .asBroadcastStream() + .listen((isolateRef) async { + final isolate = await isolateRef.load(); + if (isolate.isPaused) { + isolate.resume(); + } + }); +} + Future main() async { final FlutterDriver driver = await FlutterDriver.connect(); + // flutter drive causes isolates to be paused on spawn. The background isolate + // for this plugin will need to be resumed for the test to pass. + final subscription = await resumeIsolatesOnPause(driver); final String result = - await driver.requestData(null, timeout: const Duration(minutes: 1)); + await driver.requestData(null, timeout: const Duration(minutes: 2)); driver.close(); + subscription.cancel(); exit(result == 'pass' ? 0 : 1); } From c6b467bae2e4dffa5e224c282eaaae2cba65e52f Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Thu, 21 Nov 2019 16:33:16 -0800 Subject: [PATCH 17/23] Fixed comment --- .../example/test_driver/android_alarm_manager_e2e_test.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e_test.dart b/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e_test.dart index 31a55f7b2983..8929a03d5a3e 100644 --- a/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e_test.dart +++ b/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e_test.dart @@ -9,7 +9,6 @@ import 'package:flutter_driver/flutter_driver.dart'; Future resumeIsolatesOnPause(FlutterDriver driver) async { final vm = await driver.serviceClient.getVM(); - // // unpause any paused isolated for (final isolateRef in vm.isolates) { final isolate = await isolateRef.load(); if (isolate.isPaused) { From 26639832958bfe4ebc2c59bb7404b4335edb2b65 Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Thu, 21 Nov 2019 16:44:53 -0800 Subject: [PATCH 18/23] Bump timeout --- .../example/test_driver/android_alarm_manager_e2e_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e_test.dart b/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e_test.dart index 8929a03d5a3e..a77649dd9114 100644 --- a/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e_test.dart +++ b/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e_test.dart @@ -31,7 +31,7 @@ Future main() async { // for this plugin will need to be resumed for the test to pass. final subscription = await resumeIsolatesOnPause(driver); final String result = - await driver.requestData(null, timeout: const Duration(minutes: 2)); + await driver.requestData(null, timeout: const Duration(minutes: 5)); driver.close(); subscription.cancel(); exit(result == 'pass' ? 0 : 1); From b03c818c06fb7c014310bb52d94110636f2b9022 Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Thu, 21 Nov 2019 17:00:17 -0800 Subject: [PATCH 19/23] Formatting --- .../android_alarm_manager_e2e_test.dart | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e_test.dart b/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e_test.dart index a77649dd9114..5c5397fe02d2 100644 --- a/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e_test.dart +++ b/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e_test.dart @@ -6,30 +6,33 @@ import 'dart:async'; import 'dart:io'; import 'package:flutter_driver/flutter_driver.dart'; +import 'package:vm_service_client/vm_service_client.dart'; -Future resumeIsolatesOnPause(FlutterDriver driver) async { - final vm = await driver.serviceClient.getVM(); - for (final isolateRef in vm.isolates) { - final isolate = await isolateRef.load(); - if (isolate.isPaused) { - isolate.resume(); - } +Future> resumeIsolatesOnPause( + FlutterDriver driver) async { + final VM vm = await driver.serviceClient.getVM(); + for (VMIsolateRef isolateRef in vm.isolates) { + final VMIsolate isolate = await isolateRef.load(); + if (isolate.isPaused) { + isolate.resume(); } - return driver.serviceClient.onIsolateRunnable - .asBroadcastStream() - .listen((isolateRef) async { - final isolate = await isolateRef.load(); - if (isolate.isPaused) { - isolate.resume(); - } - }); + } + return driver.serviceClient.onIsolateRunnable + .asBroadcastStream() + .listen((VMIsolateRef isolateRef) async { + final VMIsolate isolate = await isolateRef.load(); + if (isolate.isPaused) { + isolate.resume(); + } + }); } Future main() async { final FlutterDriver driver = await FlutterDriver.connect(); // flutter drive causes isolates to be paused on spawn. The background isolate // for this plugin will need to be resumed for the test to pass. - final subscription = await resumeIsolatesOnPause(driver); + final StreamSubscription subscription = + await resumeIsolatesOnPause(driver); final String result = await driver.requestData(null, timeout: const Duration(minutes: 5)); driver.close(); From e557d5870360cad925255b131e9c7e185a4f615a Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Mon, 25 Nov 2019 16:08:04 -0800 Subject: [PATCH 20/23] Bump Firebase timeout --- .cirrus.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.cirrus.yml b/.cirrus.yml index a3fc1382cc04..ba3c84721a60 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -10,7 +10,9 @@ task: - flutter channel master - flutter upgrade - git fetch origin master - activate_script: pub global activate flutter_plugin_tools + # activate_script: pub global activate flutter_plugin_tools + # TODO(bkonyi): remove before landing + activate_script: git clone https://github.com/bkonyi/plugin_tools.git flutter_plugin_tools && pub global activate flutter_plugin_tools -s path matrix: - name: publishable script: From d036c04cd869b2d09ad00835bbd54f6dd3261aec Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Mon, 25 Nov 2019 16:53:57 -0800 Subject: [PATCH 21/23] Remove loop limit in test --- .../example/test_driver/android_alarm_manager_e2e.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e.dart b/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e.dart index db5ee3f961ce..0ce469f85aaf 100644 --- a/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e.dart +++ b/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e.dart @@ -75,7 +75,7 @@ void main() { exact: true, wakeup: true); await Future.delayed(const Duration(seconds: 2)); // poll until file is updated - for (int i = 0; i < 10 && await readCounter() == startingValue; i++) { + while (await readCounter() == startingValue) { await Future.delayed(const Duration(seconds: 1)); } expect(await readCounter(), startingValue + 1); @@ -90,7 +90,7 @@ void main() { const Duration(seconds: 1), alarmId, incrementCounter, wakeup: true, exact: true); // poll until file is updated - for (int i = 0; i < 100 && await readCounter() < startingValue + 2; i++) { + while(await readCounter() < startingValue + 2) { await Future.delayed(const Duration(seconds: 1)); } expect(await readCounter(), startingValue + 2); From 50a38dcd101ba4f375cd39f37b3d1ead022e3d69 Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Tue, 26 Nov 2019 07:51:11 -0800 Subject: [PATCH 22/23] Formatting --- .cirrus.yml | 4 +--- .../example/test_driver/android_alarm_manager_e2e.dart | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.cirrus.yml b/.cirrus.yml index ba3c84721a60..a3fc1382cc04 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -10,9 +10,7 @@ task: - flutter channel master - flutter upgrade - git fetch origin master - # activate_script: pub global activate flutter_plugin_tools - # TODO(bkonyi): remove before landing - activate_script: git clone https://github.com/bkonyi/plugin_tools.git flutter_plugin_tools && pub global activate flutter_plugin_tools -s path + activate_script: pub global activate flutter_plugin_tools matrix: - name: publishable script: diff --git a/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e.dart b/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e.dart index 0ce469f85aaf..8359bfd59ef2 100644 --- a/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e.dart +++ b/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e.dart @@ -90,7 +90,7 @@ void main() { const Duration(seconds: 1), alarmId, incrementCounter, wakeup: true, exact: true); // poll until file is updated - while(await readCounter() < startingValue + 2) { + while (await readCounter() < startingValue + 2) { await Future.delayed(const Duration(seconds: 1)); } expect(await readCounter(), startingValue + 2); From 0e4c63989f64b09ded3bc608fec863bfa48fec9a Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Tue, 26 Nov 2019 14:30:48 -0800 Subject: [PATCH 23/23] Fix pedantic analysis errors --- .../test_driver/android_alarm_manager_e2e_test.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e_test.dart b/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e_test.dart index 5c5397fe02d2..eea5e8abc15f 100644 --- a/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e_test.dart +++ b/packages/android_alarm_manager/example/test_driver/android_alarm_manager_e2e_test.dart @@ -14,7 +14,7 @@ Future> resumeIsolatesOnPause( for (VMIsolateRef isolateRef in vm.isolates) { final VMIsolate isolate = await isolateRef.load(); if (isolate.isPaused) { - isolate.resume(); + await isolate.resume(); } } return driver.serviceClient.onIsolateRunnable @@ -22,7 +22,7 @@ Future> resumeIsolatesOnPause( .listen((VMIsolateRef isolateRef) async { final VMIsolate isolate = await isolateRef.load(); if (isolate.isPaused) { - isolate.resume(); + await isolate.resume(); } }); } @@ -35,7 +35,7 @@ Future main() async { await resumeIsolatesOnPause(driver); final String result = await driver.requestData(null, timeout: const Duration(minutes: 5)); - driver.close(); - subscription.cancel(); + await driver.close(); + await subscription.cancel(); exit(result == 'pass' ? 0 : 1); }