44
55package io .flutter .plugins .androidalarmmanager ;
66
7- import android .app .Activity ;
87import android .app .AlarmManager ;
9- import android .app .Application ;
108import android .app .PendingIntent ;
119import android .app .Service ;
1210import android .content .Context ;
1311import android .content .Intent ;
1412import android .os .IBinder ;
1513import android .util .Log ;
16- import io .flutter .app .FlutterActivity ;
17- import io .flutter .app .FlutterApplication ;
14+ import io .flutter .plugin .common .MethodChannel ;
1815import io .flutter .plugin .common .PluginRegistry .PluginRegistrantCallback ;
16+ import io .flutter .view .FlutterCallbackInformation ;
1917import io .flutter .view .FlutterMain ;
2018import io .flutter .view .FlutterNativeView ;
19+ import io .flutter .view .FlutterRunArguments ;
20+ import java .util .concurrent .atomic .AtomicBoolean ;
2121
2222public class AlarmService extends Service {
2323 public static final String TAG = "AlarmService" ;
24- private static FlutterNativeView sSharedFlutterView ;
24+ private static AtomicBoolean sStarted = new AtomicBoolean (false );
25+ private static FlutterNativeView sBackgroundFlutterView ;
26+ private static MethodChannel sBackgroundChannel ;
2527 private static PluginRegistrantCallback sPluginRegistrantCallback ;
2628
27- private FlutterNativeView mFlutterView ;
28- private String appBundlePath ;
29+ private String mAppBundlePath ;
30+
31+ public static void onInitialized () {
32+ sStarted .set (true );
33+ }
34+
35+ // Here we start the AlarmService. This method does a few things:
36+ // - Retrieves the callback information for the handle associated with the
37+ // callback dispatcher in the Dart portion of the plugin.
38+ // - Builds the arguments object for running in a new FlutterNativeView.
39+ // - Enters the isolate owned by the FlutterNativeView at the callback
40+ // represented by `callbackHandle` and initializes the callback
41+ // dispatcher.
42+ // - Registers the FlutterNativeView's PluginRegistry to receive
43+ // MethodChannel messages.
44+ public static void startAlarmService (Context context , long callbackHandle ) {
45+ FlutterMain .ensureInitializationComplete (context , null );
46+ String mAppBundlePath = FlutterMain .findAppBundlePath (context );
47+ FlutterCallbackInformation cb =
48+ FlutterCallbackInformation .lookupCallbackInformation (callbackHandle );
49+ if (cb == null ) {
50+ Log .e (TAG , "Fatal: failed to find callback" );
51+ return ;
52+ }
53+
54+ // Note that we're passing `true` as the second argument to our
55+ // FlutterNativeView constructor. This specifies the FlutterNativeView
56+ // as a background view and does not create a drawing surface.
57+ sBackgroundFlutterView = new FlutterNativeView (context , true );
58+ if (mAppBundlePath != null && !sStarted .get ()) {
59+ Log .i (TAG , "Starting AlarmService..." );
60+ FlutterRunArguments args = new FlutterRunArguments ();
61+ args .bundlePath = mAppBundlePath ;
62+ args .entrypoint = cb .callbackName ;
63+ args .libraryPath = cb .callbackLibraryPath ;
64+ sBackgroundFlutterView .runFromBundle (args );
65+ sPluginRegistrantCallback .registerWith (sBackgroundFlutterView .getPluginRegistry ());
66+ }
67+ }
68+
69+ public static void setBackgroundChannel (MethodChannel channel ) {
70+ sBackgroundChannel = channel ;
71+ }
2972
3073 public static void setOneShot (
3174 Context context ,
3275 int requestCode ,
3376 boolean exact ,
3477 boolean wakeup ,
3578 long startMillis ,
36- String entrypoint ) {
79+ long callbackHandle ) {
3780 final boolean repeating = false ;
38- scheduleAlarm (context , requestCode , repeating , exact , wakeup , startMillis , 0 , entrypoint );
81+ scheduleAlarm (context , requestCode , repeating , exact , wakeup , startMillis , 0 , callbackHandle );
3982 }
4083
4184 public static void setPeriodic (
@@ -45,10 +88,17 @@ public static void setPeriodic(
4588 boolean wakeup ,
4689 long startMillis ,
4790 long intervalMillis ,
48- String entrypoint ) {
91+ long callbackHandle ) {
4992 final boolean repeating = true ;
5093 scheduleAlarm (
51- context , requestCode , repeating , exact , wakeup , startMillis , intervalMillis , entrypoint );
94+ context ,
95+ requestCode ,
96+ repeating ,
97+ exact ,
98+ wakeup ,
99+ startMillis ,
100+ intervalMillis ,
101+ callbackHandle );
52102 }
53103
54104 public static void cancel (Context context , int requestCode ) {
@@ -64,109 +114,53 @@ public static void cancel(Context context, int requestCode) {
64114 }
65115
66116 public static FlutterNativeView getSharedFlutterView () {
67- return sSharedFlutterView ;
117+ return sBackgroundFlutterView ;
68118 }
69119
70- public static boolean setSharedFlutterView (FlutterNativeView view ) {
71- if (sSharedFlutterView != null && sSharedFlutterView != view ) {
72- Log .i (TAG , "setSharedFlutterView tried to overwrite an existing FlutterNativeView" );
120+ public static boolean setBackgroundFlutterView (FlutterNativeView view ) {
121+ if (sBackgroundFlutterView != null && sBackgroundFlutterView != view ) {
122+ Log .i (TAG , "setBackgroundFlutterView tried to overwrite an existing FlutterNativeView" );
73123 return false ;
74124 }
75- Log .i (TAG , "setSharedFlutterView set" );
76- sSharedFlutterView = view ;
125+ sBackgroundFlutterView = view ;
77126 return true ;
78127 }
79128
80129 public static void setPluginRegistrant (PluginRegistrantCallback callback ) {
81130 sPluginRegistrantCallback = callback ;
82131 }
83132
84- private void ensureFlutterView () {
85- if (mFlutterView != null ) {
86- return ;
87- }
88-
89- if (sSharedFlutterView != null ) {
90- mFlutterView = sSharedFlutterView ;
91- return ;
92- }
93-
94- // mFlutterView and sSharedFlutterView are both null. That likely means that
95- // no FlutterView has ever been created in this process before. So, we'll
96- // make one, and assign it to both mFlutterView and sSharedFlutterView.
97- mFlutterView = new FlutterNativeView (getApplicationContext ());
98- sSharedFlutterView = mFlutterView ;
99-
100- // If there was no FlutterNativeView before now, then we also must
101- // initialize the PluginRegistry.
102- sPluginRegistrantCallback .registerWith (mFlutterView .getPluginRegistry ());
103- return ;
104- }
105-
106- // This returns the FlutterView for the main FlutterActivity if there is one.
107- private static FlutterNativeView viewFromAppContext (Context context ) {
108- Application app = (Application ) context ;
109- if (!(app instanceof FlutterApplication )) {
110- Log .i (TAG , "viewFromAppContext app not a FlutterApplication" );
111- return null ;
112- }
113- FlutterApplication flutterApp = (FlutterApplication ) app ;
114- Activity activity = flutterApp .getCurrentActivity ();
115- if (activity == null ) {
116- Log .i (TAG , "viewFromAppContext activity is null" );
117- return null ;
118- }
119- if (!(activity instanceof FlutterActivity )) {
120- Log .i (TAG , "viewFromAppContext activity is not a FlutterActivity" );
121- return null ;
122- }
123- FlutterActivity flutterActivity = (FlutterActivity ) activity ;
124- return flutterActivity .getFlutterView ().getFlutterNativeView ();
125- }
126-
127133 @ Override
128134 public void onCreate () {
129135 super .onCreate ();
130136 Context context = getApplicationContext ();
131- mFlutterView = viewFromAppContext (context );
132137 FlutterMain .ensureInitializationComplete (context , null );
133- if (appBundlePath == null ) {
134- appBundlePath = FlutterMain .findAppBundlePath (context );
135- }
136- }
137-
138- @ Override
139- public void onDestroy () {
140- // Try to find the native view of the main activity if there is one.
141- Context context = getApplicationContext ();
142- FlutterNativeView nativeView = viewFromAppContext (context );
143-
144- // Don't destroy mFlutterView if it is the same as the native view for the
145- // main activity, or the same as the shared native view.
146- if (mFlutterView != nativeView && mFlutterView != sSharedFlutterView ) {
147- mFlutterView .destroy ();
148- }
149- mFlutterView = null ;
150-
151- // Don't destroy the shared native view if it is the same native view as
152- // for the main activity.
153- if (sSharedFlutterView != nativeView ) {
154- sSharedFlutterView .destroy ();
155- }
156- sSharedFlutterView = null ;
138+ mAppBundlePath = FlutterMain .findAppBundlePath (context );
157139 }
158140
141+ // This is where we handle alarm events before sending them to our callback
142+ // dispatcher in Dart.
159143 @ Override
160144 public int onStartCommand (Intent intent , int flags , int startId ) {
161- ensureFlutterView ();
162- String entrypoint = intent .getStringExtra ("entrypoint" );
163- if (entrypoint == null ) {
164- Log .i (TAG , "onStartCommand got a null entrypoint. Bailing out" );
145+ if (!sStarted .get ()) {
146+ Log .i (TAG , "AlarmService has not yet started." );
147+ // TODO(bkonyi): queue up alarm events.
165148 return START_NOT_STICKY ;
166149 }
167- if (appBundlePath != null ) {
168- mFlutterView .runFromBundle (appBundlePath , null , entrypoint , true );
150+ // Grab the handle for the callback associated with this alarm. Pay close
151+ // attention to the type of the callback handle as storing this value in a
152+ // variable of the wrong size will cause the callback lookup to fail.
153+ long callbackHandle = intent .getLongExtra ("callbackHandle" , 0 );
154+ if (sBackgroundChannel == null ) {
155+ Log .e (
156+ TAG ,
157+ "setBackgroundChannel was not called before alarms were scheduled." + " Bailing out." );
158+ return START_NOT_STICKY ;
169159 }
160+ // Handle the alarm event in Dart. Note that for this plugin, we don't
161+ // care about the method name as we simply lookup and invoke the callback
162+ // provided.
163+ sBackgroundChannel .invokeMethod ("" , new Object [] {callbackHandle });
170164 return START_NOT_STICKY ;
171165 }
172166
@@ -183,10 +177,10 @@ private static void scheduleAlarm(
183177 boolean wakeup ,
184178 long startMillis ,
185179 long intervalMillis ,
186- String entrypoint ) {
187- // Create an Intent for the alarm and set the desired Dart entrypoint .
180+ long callbackHandle ) {
181+ // Create an Intent for the alarm and set the desired Dart callback handle .
188182 Intent alarm = new Intent (context , AlarmService .class );
189- alarm .putExtra ("entrypoint " , entrypoint );
183+ alarm .putExtra ("callbackHandle " , callbackHandle );
190184 PendingIntent pendingIntent =
191185 PendingIntent .getService (context , requestCode , alarm , PendingIntent .FLAG_UPDATE_CURRENT );
192186
0 commit comments