Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit 66aa50d

Browse files
committed
Merge remote-tracking branch 'upstream/master' (CONTAINS CONFLICTS)
2 parents 2388916 + 3d57cff commit 66aa50d

37 files changed

+957
-520
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ keystore.properties
3030
gradlew
3131
gradlew.bat
3232
gradle-wrapper.jar
33+
.flutter-plugins-dependencies
3334
*.iml
3435

3536
GeneratedPluginRegistrant.h

packages/android_alarm_manager/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.4.5
2+
3+
* Add support for Flutter Android embedding V2
4+
15
## 0.4.4+3
26

37
* Add unit tests and DartDocs.

packages/android_alarm_manager/android/build.gradle

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ apply plugin: 'com.android.library'
2323

2424
android {
2525
compileSdkVersion 28
26-
26+
compileOptions {
27+
sourceCompatibility JavaVersion.VERSION_1_8
28+
targetCompatibility JavaVersion.VERSION_1_8
29+
}
2730
defaultConfig {
2831
minSdkVersion 16
2932
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -37,3 +40,29 @@ dependencies {
3740
implementation 'androidx.appcompat:appcompat:1.0.0'
3841
api 'androidx.core:core:1.0.1'
3942
}
43+
44+
// TODO(bkonyi): Remove this hack once androidx.lifecycle is included on stable. https://github.com/flutter/flutter/issues/42348
45+
afterEvaluate {
46+
def containsEmbeddingDependencies = false
47+
for (def configuration : configurations.all) {
48+
for (def dependency : configuration.dependencies) {
49+
if (dependency.group == 'io.flutter' &&
50+
dependency.name.startsWith('flutter_embedding') &&
51+
dependency.isTransitive())
52+
{
53+
containsEmbeddingDependencies = true
54+
break
55+
}
56+
}
57+
}
58+
if (!containsEmbeddingDependencies) {
59+
android {
60+
dependencies {
61+
def lifecycle_version = "1.1.1"
62+
api 'android.arch.lifecycle:runtime:$lifecycle_version'
63+
api 'android.arch.lifecycle:common:$lifecycle_version'
64+
api 'android.arch.lifecycle:common-java8:$lifecycle_version'
65+
}
66+
}
67+
}
68+
}

packages/android_alarm_manager/android/src/main/java/io/flutter/plugins/androidalarmmanager/AlarmService.java

Lines changed: 44 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,7 @@
1313
import android.util.Log;
1414
import androidx.core.app.AlarmManagerCompat;
1515
import androidx.core.app.JobIntentService;
16-
import io.flutter.plugin.common.MethodChannel;
1716
import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback;
18-
import io.flutter.view.FlutterCallbackInformation;
19-
import io.flutter.view.FlutterMain;
20-
import io.flutter.view.FlutterNativeView;
21-
import io.flutter.view.FlutterRunArguments;
2217
import java.util.Collections;
2318
import java.util.HashMap;
2419
import java.util.HashSet;
@@ -27,192 +22,88 @@
2722
import java.util.List;
2823
import java.util.Set;
2924
import java.util.concurrent.CountDownLatch;
30-
import java.util.concurrent.atomic.AtomicBoolean;
3125
import org.json.JSONException;
3226
import org.json.JSONObject;
3327

3428
public class AlarmService extends JobIntentService {
35-
// TODO(mattcarroll): tags should be private. Make private if no public usage.
36-
public static final String TAG = "AlarmService";
37-
private static final String CALLBACK_HANDLE_KEY = "callback_handle";
29+
private static final String TAG = "AlarmService";
3830
private static final String PERSISTENT_ALARMS_SET_KEY = "persistent_alarm_ids";
39-
private static final String SHARED_PREFERENCES_KEY = "io.flutter.android_alarm_manager_plugin";
31+
protected static final String SHARED_PREFERENCES_KEY = "io.flutter.android_alarm_manager_plugin";
4032
private static final int JOB_ID = 1984; // Random job ID.
41-
private static final Object sPersistentAlarmsLock = new Object();
33+
private static final Object persistentAlarmsLock = new Object();
4234

43-
// TODO(mattcarroll): make sIsIsolateRunning per-instance, not static.
44-
private static AtomicBoolean sIsIsolateRunning = new AtomicBoolean(false);
45-
46-
// TODO(mattcarroll): make sAlarmQueue per-instance, not static.
47-
private static List<Intent> sAlarmQueue = Collections.synchronizedList(new LinkedList<Intent>());
35+
// TODO(mattcarroll): make alarmQueue per-instance, not static.
36+
private static List<Intent> alarmQueue = Collections.synchronizedList(new LinkedList<Intent>());
4837

4938
/** Background Dart execution context. */
50-
private static FlutterNativeView sBackgroundFlutterView;
51-
52-
/**
53-
* The {@link MethodChannel} that connects the Android side of this plugin with the background
54-
* Dart isolate that was created by this plugin.
55-
*/
56-
private static MethodChannel sBackgroundChannel;
57-
58-
private static PluginRegistrantCallback sPluginRegistrantCallback;
39+
private static FlutterBackgroundExecutor flutterBackgroundExecutor;
5940

60-
// Schedule the alarm to be handled by the AlarmService.
41+
/** Schedule the alarm to be handled by the {@link AlarmService}. */
6142
public static void enqueueAlarmProcessing(Context context, Intent alarmContext) {
6243
enqueueWork(context, AlarmService.class, JOB_ID, alarmContext);
6344
}
6445

6546
/**
66-
* Starts running a background Dart isolate within a new {@link FlutterNativeView}.
67-
*
68-
* <p>The isolate is configured as follows:
69-
*
70-
* <ul>
71-
* <li>Bundle Path: {@code FlutterMain.findAppBundlePath(context)}.
72-
* <li>Entrypoint: The Dart method represented by {@code callbackHandle}.
73-
* <li>Run args: none.
74-
* </ul>
47+
* Starts the background isolate for the {@link AlarmService}.
7548
*
7649
* <p>Preconditions:
7750
*
7851
* <ul>
7952
* <li>The given {@code callbackHandle} must correspond to a registered Dart callback. If the
8053
* handle does not resolve to a Dart callback then this method does nothing.
81-
* <li>A static {@link #sPluginRegistrantCallback} must exist, otherwise a {@link
54+
* <li>A static {@link #pluginRegistrantCallback} must exist, otherwise a {@link
8255
* PluginRegistrantException} will be thrown.
8356
* </ul>
8457
*/
8558
public static void startBackgroundIsolate(Context context, long callbackHandle) {
86-
// TODO(mattcarroll): re-arrange order of operations. The order is strange - there are 3
87-
// conditions that must be met for this method to do anything but they're split up for no
88-
// apparent reason. Do the qualification checks first, then execute the method's logic.
89-
FlutterMain.ensureInitializationComplete(context, null);
90-
String mAppBundlePath = FlutterMain.findAppBundlePath(context);
91-
FlutterCallbackInformation flutterCallback =
92-
FlutterCallbackInformation.lookupCallbackInformation(callbackHandle);
93-
if (flutterCallback == null) {
94-
Log.e(TAG, "Fatal: failed to find callback");
59+
if (flutterBackgroundExecutor != null) {
60+
Log.w(TAG, "Attempted to start a duplicate background isolate. Returning...");
9561
return;
9662
}
97-
98-
// Note that we're passing `true` as the second argument to our
99-
// FlutterNativeView constructor. This specifies the FlutterNativeView
100-
// as a background view and does not create a drawing surface.
101-
sBackgroundFlutterView = new FlutterNativeView(context, true);
102-
if (mAppBundlePath != null && !sIsIsolateRunning.get()) {
103-
if (sPluginRegistrantCallback == null) {
104-
throw new PluginRegistrantException();
105-
}
106-
Log.i(TAG, "Starting AlarmService...");
107-
FlutterRunArguments args = new FlutterRunArguments();
108-
args.bundlePath = mAppBundlePath;
109-
args.entrypoint = flutterCallback.callbackName;
110-
args.libraryPath = flutterCallback.callbackLibraryPath;
111-
sBackgroundFlutterView.runFromBundle(args);
112-
sPluginRegistrantCallback.registerWith(sBackgroundFlutterView.getPluginRegistry());
113-
}
63+
flutterBackgroundExecutor = new FlutterBackgroundExecutor();
64+
flutterBackgroundExecutor.startBackgroundIsolate(context, callbackHandle);
11465
}
11566

11667
/**
117-
* Called once the Dart isolate ({@code sBackgroundFlutterView}) has finished initializing.
68+
* Called once the Dart isolate ({@code flutterBackgroundExecutor}) has finished initializing.
11869
*
11970
* <p>Invoked by {@link AndroidAlarmManagerPlugin} when it receives the {@code
12071
* AlarmService.initialized} message. Processes all alarm events that came in while the isolate
12172
* was starting.
12273
*/
123-
// TODO(mattcarroll): consider making this method package private
124-
public static void onInitialized() {
74+
/* package */ static void onInitialized() {
12575
Log.i(TAG, "AlarmService started!");
126-
sIsIsolateRunning.set(true);
127-
synchronized (sAlarmQueue) {
76+
synchronized (alarmQueue) {
12877
// Handle all the alarm events received before the Dart isolate was
12978
// initialized, then clear the queue.
130-
Iterator<Intent> i = sAlarmQueue.iterator();
79+
Iterator<Intent> i = alarmQueue.iterator();
13180
while (i.hasNext()) {
132-
executeDartCallbackInBackgroundIsolate(i.next(), null);
81+
flutterBackgroundExecutor.executeDartCallbackInBackgroundIsolate(i.next(), null);
13382
}
134-
sAlarmQueue.clear();
83+
alarmQueue.clear();
13584
}
13685
}
13786

138-
/**
139-
* Sets the {@link MethodChannel} that is used to communicate with Dart callbacks that are invoked
140-
* in the background by the android_alarm_manager plugin.
141-
*/
142-
public static void setBackgroundChannel(MethodChannel channel) {
143-
sBackgroundChannel = channel;
144-
}
145-
14687
/**
14788
* Sets the Dart callback handle for the Dart method that is responsible for initializing the
14889
* background Dart isolate, preparing it to receive Dart callback tasks requests.
14990
*/
15091
public static void setCallbackDispatcher(Context context, long callbackHandle) {
151-
SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFERENCES_KEY, 0);
152-
prefs.edit().putLong(CALLBACK_HANDLE_KEY, callbackHandle).apply();
153-
}
154-
155-
public static boolean setBackgroundFlutterView(FlutterNativeView view) {
156-
if (sBackgroundFlutterView != null && sBackgroundFlutterView != view) {
157-
Log.i(TAG, "setBackgroundFlutterView tried to overwrite an existing FlutterNativeView");
158-
return false;
159-
}
160-
sBackgroundFlutterView = view;
161-
return true;
162-
}
163-
164-
public static void setPluginRegistrant(PluginRegistrantCallback callback) {
165-
sPluginRegistrantCallback = callback;
92+
FlutterBackgroundExecutor.setCallbackDispatcher(context, callbackHandle);
16693
}
16794

16895
/**
169-
* Executes the desired Dart callback in a background Dart isolate.
96+
* Sets the {@link PluginRegistrantCallback} used to register the plugins used by an application
97+
* with the newly spawned background isolate.
17098
*
171-
* <p>The given {@code intent} should contain a {@code long} extra called "callbackHandle", which
172-
* corresponds to a callback registered with the Dart VM.
99+
* <p>This should be invoked in {@link Application.onCreate} with {@link
100+
* GeneratedPluginRegistrant} in applications using the V1 embedding API in order to use other
101+
* plugins in the background isolate. For applications using the V2 embedding API, it is not
102+
* necessary to set a {@link PluginRegistrantCallback} as plugins are registered automatically.
173103
*/
174-
private static void executeDartCallbackInBackgroundIsolate(
175-
Intent intent, final CountDownLatch latch) {
176-
// Grab the handle for the callback associated with this alarm. Pay close
177-
// attention to the type of the callback handle as storing this value in a
178-
// variable of the wrong size will cause the callback lookup to fail.
179-
long callbackHandle = intent.getLongExtra("callbackHandle", 0);
180-
if (sBackgroundChannel == null) {
181-
Log.e(
182-
TAG,
183-
"setBackgroundChannel was not called before alarms were scheduled." + " Bailing out.");
184-
return;
185-
}
186-
187-
// If another thread is waiting, then wake that thread when the callback returns a result.
188-
MethodChannel.Result result = null;
189-
if (latch != null) {
190-
result =
191-
new MethodChannel.Result() {
192-
@Override
193-
public void success(Object result) {
194-
latch.countDown();
195-
}
196-
197-
@Override
198-
public void error(String errorCode, String errorMessage, Object errorDetails) {
199-
latch.countDown();
200-
}
201-
202-
@Override
203-
public void notImplemented() {
204-
latch.countDown();
205-
}
206-
};
207-
}
208-
209-
// Handle the alarm event in Dart. Note that for this plugin, we don't
210-
// care about the method name as we simply lookup and invoke the callback
211-
// provided.
212-
// TODO(mattcarroll): consider giving a method name anyway for the purpose of developer discoverability
213-
// when reading the source code. Especially on the Dart side.
214-
sBackgroundChannel.invokeMethod(
215-
"", new Object[] {callbackHandle, intent.getIntExtra("id", -1)}, result);
104+
public static void setPluginRegistrant(PluginRegistrantCallback callback) {
105+
// Indirectly set in FlutterBackgroundExecutor for backwards compatibility.
106+
FlutterBackgroundExecutor.setPluginRegistrant(callback);
216107
}
217108

218109
private static void scheduleAlarm(
@@ -285,6 +176,7 @@ private static void scheduleAlarm(
285176
}
286177
}
287178

179+
/** Schedules a one-shot alarm to be executed once in the future. */
288180
public static void setOneShot(Context context, AndroidAlarmManagerPlugin.OneShotRequest request) {
289181
final boolean repeating = false;
290182
scheduleAlarm(
@@ -301,6 +193,7 @@ public static void setOneShot(Context context, AndroidAlarmManagerPlugin.OneShot
301193
request.callbackHandle);
302194
}
303195

196+
/** Schedules a periodic alarm to be executed repeatedly in the future. */
304197
public static void setPeriodic(
305198
Context context, AndroidAlarmManagerPlugin.PeriodicRequest request) {
306199
final boolean repeating = true;
@@ -320,6 +213,7 @@ public static void setPeriodic(
320213
request.callbackHandle);
321214
}
322215

216+
/** Cancels an alarm with ID {@code requestCode}. */
323217
public static void cancel(Context context, int requestCode) {
324218
// Clear the alarm if it was set to be rescheduled after reboots.
325219
clearPersistentAlarm(context, requestCode);
@@ -364,7 +258,7 @@ private static void addPersistentAlarm(
364258
String key = getPersistentAlarmKey(requestCode);
365259
SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFERENCES_KEY, 0);
366260

367-
synchronized (sPersistentAlarmsLock) {
261+
synchronized (persistentAlarmsLock) {
368262
Set<String> persistentAlarms = prefs.getStringSet(PERSISTENT_ALARMS_SET_KEY, null);
369263
if (persistentAlarms == null) {
370264
persistentAlarms = new HashSet<>();
@@ -383,7 +277,7 @@ private static void addPersistentAlarm(
383277

384278
private static void clearPersistentAlarm(Context context, int requestCode) {
385279
SharedPreferences p = context.getSharedPreferences(SHARED_PREFERENCES_KEY, 0);
386-
synchronized (sPersistentAlarmsLock) {
280+
synchronized (persistentAlarmsLock) {
387281
Set<String> persistentAlarms = p.getStringSet(PERSISTENT_ALARMS_SET_KEY, null);
388282
if ((persistentAlarms == null) || !persistentAlarms.contains(requestCode)) {
389283
return;
@@ -399,7 +293,7 @@ private static void clearPersistentAlarm(Context context, int requestCode) {
399293
}
400294

401295
public static void reschedulePersistentAlarms(Context context) {
402-
synchronized (sPersistentAlarmsLock) {
296+
synchronized (persistentAlarmsLock) {
403297
SharedPreferences p = context.getSharedPreferences(SHARED_PREFERENCES_KEY, 0);
404298
Set<String> persistentAlarms = p.getStringSet(PERSISTENT_ALARMS_SET_KEY, null);
405299
// No alarms to reschedule.
@@ -449,15 +343,11 @@ public static void reschedulePersistentAlarms(Context context) {
449343
@Override
450344
public void onCreate() {
451345
super.onCreate();
452-
453-
Context context = getApplicationContext();
454-
FlutterMain.ensureInitializationComplete(context, null);
455-
456-
if (!sIsIsolateRunning.get()) {
457-
SharedPreferences p = context.getSharedPreferences(SHARED_PREFERENCES_KEY, 0);
458-
long callbackHandle = p.getLong(CALLBACK_HANDLE_KEY, 0);
459-
startBackgroundIsolate(context, callbackHandle);
346+
if (flutterBackgroundExecutor == null) {
347+
flutterBackgroundExecutor = new FlutterBackgroundExecutor();
460348
}
349+
Context context = getApplicationContext();
350+
flutterBackgroundExecutor.startBackgroundIsolate(context);
461351
}
462352

463353
/**
@@ -470,17 +360,17 @@ public void onCreate() {
470360
* intent}, then the desired Dart callback is invoked immediately.
471361
*
472362
* <p>If there are any pre-existing callback requests that have yet to be executed, the incoming
473-
* {@code intent} is added to the {@link #sAlarmQueue} to invoked later, after all pre-existing
363+
* {@code intent} is added to the {@link #alarmQueue} to invoked later, after all pre-existing
474364
* callbacks have been executed.
475365
*/
476366
@Override
477367
protected void onHandleWork(final Intent intent) {
478368
// If we're in the middle of processing queued alarms, add the incoming
479369
// intent to the queue and return.
480-
synchronized (sAlarmQueue) {
481-
if (!sIsIsolateRunning.get()) {
370+
synchronized (alarmQueue) {
371+
if (!flutterBackgroundExecutor.isRunning()) {
482372
Log.i(TAG, "AlarmService has not yet started.");
483-
sAlarmQueue.add(intent);
373+
alarmQueue.add(intent);
484374
return;
485375
}
486376
}
@@ -493,7 +383,7 @@ protected void onHandleWork(final Intent intent) {
493383
new Runnable() {
494384
@Override
495385
public void run() {
496-
executeDartCallbackInBackgroundIsolate(intent, latch);
386+
flutterBackgroundExecutor.executeDartCallbackInBackgroundIsolate(intent, latch);
497387
}
498388
});
499389

0 commit comments

Comments
 (0)