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

Commit

Permalink
Merge remote-tracking branch 'upstream/master' (CONTAINS CONFLICTS)
Browse files Browse the repository at this point in the history
  • Loading branch information
axel-op committed Nov 28, 2019
2 parents 2388916 + 3d57cff commit 66aa50d
Show file tree
Hide file tree
Showing 37 changed files with 957 additions and 520 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ keystore.properties
gradlew
gradlew.bat
gradle-wrapper.jar
.flutter-plugins-dependencies
*.iml

GeneratedPluginRegistrant.h
Expand Down
4 changes: 4 additions & 0 deletions packages/android_alarm_manager/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.4.5

* Add support for Flutter Android embedding V2

## 0.4.4+3

* Add unit tests and DartDocs.
Expand Down
31 changes: 30 additions & 1 deletion packages/android_alarm_manager/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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'
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -27,192 +22,88 @@
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;

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<Intent> sAlarmQueue = Collections.synchronizedList(new LinkedList<Intent>());
// TODO(mattcarroll): make alarmQueue per-instance, not static.
private static List<Intent> alarmQueue = Collections.synchronizedList(new LinkedList<Intent>());

/** 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 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 running a background Dart isolate within a new {@link FlutterNativeView}.
*
* <p>The isolate is configured as follows:
*
* <ul>
* <li>Bundle Path: {@code FlutterMain.findAppBundlePath(context)}.
* <li>Entrypoint: The Dart method represented by {@code callbackHandle}.
* <li>Run args: none.
* </ul>
* Starts the background isolate for the {@link AlarmService}.
*
* <p>Preconditions:
*
* <ul>
* <li>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.
* <li>A static {@link #sPluginRegistrantCallback} must exist, otherwise a {@link
* <li>A static {@link #pluginRegistrantCallback} must exist, otherwise a {@link
* PluginRegistrantException} will be thrown.
* </ul>
*/
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");
if (flutterBackgroundExecutor != null) {
Log.w(TAG, "Attempted to start a duplicate background isolate. Returning...");
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());
}
flutterBackgroundExecutor = new FlutterBackgroundExecutor();
flutterBackgroundExecutor.startBackgroundIsolate(context, callbackHandle);
}

/**
* Called once the Dart isolate ({@code sBackgroundFlutterView}) has finished initializing.
* Called once the Dart isolate ({@code flutterBackgroundExecutor}) has finished initializing.
*
* <p>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() {
/* package */ 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<Intent> i = sAlarmQueue.iterator();
Iterator<Intent> i = alarmQueue.iterator();
while (i.hasNext()) {
executeDartCallbackInBackgroundIsolate(i.next(), null);
flutterBackgroundExecutor.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();
}

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;
FlutterBackgroundExecutor.setCallbackDispatcher(context, callbackHandle);
}

/**
* Executes the desired Dart callback in a background Dart isolate.
* Sets the {@link PluginRegistrantCallback} used to register the plugins used by an application
* with the newly spawned background isolate.
*
* <p>The given {@code intent} should contain a {@code long} extra called "callbackHandle", which
* corresponds to a callback registered with the Dart VM.
* <p>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.
*/
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);
public static void setPluginRegistrant(PluginRegistrantCallback callback) {
// Indirectly set in FlutterBackgroundExecutor for backwards compatibility.
FlutterBackgroundExecutor.setPluginRegistrant(callback);
}

private static void scheduleAlarm(
Expand Down Expand Up @@ -285,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(
Expand All @@ -301,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;
Expand All @@ -320,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);
Expand Down Expand Up @@ -364,7 +258,7 @@ private static void addPersistentAlarm(
String key = getPersistentAlarmKey(requestCode);
SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFERENCES_KEY, 0);

synchronized (sPersistentAlarmsLock) {
synchronized (persistentAlarmsLock) {
Set<String> persistentAlarms = prefs.getStringSet(PERSISTENT_ALARMS_SET_KEY, null);
if (persistentAlarms == null) {
persistentAlarms = new HashSet<>();
Expand All @@ -383,7 +277,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<String> persistentAlarms = p.getStringSet(PERSISTENT_ALARMS_SET_KEY, null);
if ((persistentAlarms == null) || !persistentAlarms.contains(requestCode)) {
return;
Expand All @@ -399,7 +293,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<String> persistentAlarms = p.getStringSet(PERSISTENT_ALARMS_SET_KEY, null);
// No alarms to reschedule.
Expand Down Expand Up @@ -449,15 +343,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 (flutterBackgroundExecutor == null) {
flutterBackgroundExecutor = new FlutterBackgroundExecutor();
}
Context context = getApplicationContext();
flutterBackgroundExecutor.startBackgroundIsolate(context);
}

/**
Expand All @@ -470,17 +360,17 @@ public void onCreate() {
* intent}, then the desired Dart callback is invoked immediately.
*
* <p>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 (!flutterBackgroundExecutor.isRunning()) {
Log.i(TAG, "AlarmService has not yet started.");
sAlarmQueue.add(intent);
alarmQueue.add(intent);
return;
}
}
Expand All @@ -493,7 +383,7 @@ protected void onHandleWork(final Intent intent) {
new Runnable() {
@Override
public void run() {
executeDartCallbackInBackgroundIsolate(intent, latch);
flutterBackgroundExecutor.executeDartCallbackInBackgroundIsolate(intent, latch);
}
});

Expand Down
Loading

0 comments on commit 66aa50d

Please sign in to comment.