Skip to content

Commit

Permalink
Make our worker implementation function more like Worker, but inherit…
Browse files Browse the repository at this point in the history
…ing RemoteListenableWorker
  • Loading branch information
bjester committed Dec 1, 2023
1 parent 7c278aa commit 21de1b2
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 76 deletions.
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
package org.kivy.android;

import android.annotation.SuppressLint;
import android.content.Context;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.concurrent.futures.CallbackToFutureAdapter;
import androidx.work.ForegroundInfo;
import androidx.work.WorkerParameters;
import androidx.work.impl.utils.futures.SettableFuture;
import androidx.work.impl.utils.taskexecutor.WorkManagerTaskExecutor;
import androidx.work.multiprocess.RemoteListenableWorker;

import com.google.common.util.concurrent.ListenableFuture;

import java.util.concurrent.Executors;
import java.util.concurrent.Executor;
import java.util.concurrent.RunnableFuture;
import java.util.concurrent.ThreadPoolExecutor;

abstract public class PythonWorker extends RemoteListenableWorker {
private static final String TAG = "PythonWorker";
Expand Down Expand Up @@ -60,75 +65,89 @@ public boolean isLongRunning() {
return getTags().contains(TAG_LONG_RUNNING);
}

protected String getArgument() {
String dataArg = getInputData().getString(ARGUMENT_WORKER_ARGUMENT);
final String serviceArg;
if (dataArg != null) {
serviceArg = dataArg;
} else {
serviceArg = "";
}
return serviceArg;
}

protected Result doWork() {
String id = getId().toString();
String arg = getArgument();

Log.d(TAG, id + " Running with python worker argument: " + arg);

int res = nativeStart(
androidPrivate, androidArgument,
workerEntrypoint, pythonName,
pythonHome, pythonPath,
arg
);
Log.d(TAG, id + " Finished remote python work: " + res);

if (res == 0) {
return Result.success();
}

return Result.failure();
}

@SuppressLint("RestrictedApi")
@NonNull
@Override
public ListenableFuture<Result> startRemoteWork() {
return CallbackToFutureAdapter.getFuture(completer -> {
String id = getId().toString();
String dataArg = getInputData().getString(ARGUMENT_WORKER_ARGUMENT);

final String serviceArg;
if (dataArg != null) {
Log.d(TAG, id + " Setting python worker argument to " + dataArg);
serviceArg = dataArg;
} else {
serviceArg = "";
}

if (isLongRunning()) {
Log.d(TAG, id + " Enabling foreground service for long running task");
setForegroundAsync(getForegroundInfo());
}

// The python thread handling the work needs to be run in a
// separate thread so that future can be returned. Without
// it, any cancellation can't be processed.
final Thread pythonThread = new Thread(new Runnable() {
@Override
public void run() {
Log.d(TAG, id + " Running with python worker argument: " + serviceArg);

try {
int res = nativeStart(
androidPrivate, androidArgument,
workerEntrypoint, pythonName,
pythonHome, pythonPath,
serviceArg
);
Log.d(TAG, id + " Finished remote python work: " + res);

if (res == 0) {
completer.set(Result.success());
} else {
completer.set(Result.failure());
}
} catch (Exception e) {
if (getRunAttemptCount() > MAX_WORKER_RETRIES) {
Log.e(TAG, id + " Exception in remote python work", e);
completer.setException(e);
} else {
Log.w(TAG, id + " Exception in remote python work, scheduling retry", e);
completer.set(Result.retry());
}
} finally {
cleanup();
SettableFuture<Result> future = SettableFuture.create();
String id = getId().toString();

if (isLongRunning()) {
Log.d(TAG, id + " Enabling foreground service for long running task");
setForegroundAsync(getForegroundInfo());
}

// See executor defined in configuration
ThreadPoolExecutor executor = (ThreadPoolExecutor) getBackgroundExecutor();
// This is somewhat similar to what the plain `Worker` class does, except that we
// use `submit` instead of `execute` so we can propagate cancellation
// See https://android.googlesource.com/platform/frameworks/support/+/60ae0eec2a32396c22ad92502cde952c80d514a0/work/workmanager/src/main/java/androidx/work/Worker.java
RunnableFuture<?> threadFuture = (RunnableFuture<?>)executor.submit(new Runnable() {
@Override
public void run() {
try {
Result r = doWork();
future.set(r);
} catch (Exception e) {
if (getRunAttemptCount() > MAX_WORKER_RETRIES) {
Log.e(TAG, id + " Exception in remote python work", e);
future.setException(e);
} else {
Log.w(TAG, id + " Exception in remote python work, scheduling retry", e);
future.set(Result.retry());
}
} finally {
cleanup();
}
}, "python_worker_thread");
}
});

completer.addCancellationListener(new Runnable() {
@Override
public void run() {
Log.i(TAG, id + " Interrupting remote work");
pythonThread.interrupt();
// If `RunnableFuture` was a `ListenableFuture` we could simply use `future.setFuture` to
// propagate the result and cancellation, but instead add listener to propagate
// cancellation to python thread, using the task executor which should invoke this in the
// main thread (where this was originally called from)
future.addListener(new Runnable() {
@Override
public void run() {
if (future.isCancelled()) {
Log.i(TAG, "Interrupting python thread");
threadFuture.cancel(true);
}
}, Executors.newSingleThreadExecutor());

Log.i(TAG, id + " Starting remote python work");
pythonThread.start();

return TAG + " work thread";
});
}
}, getTaskExecutor().getMainThreadExecutor());
return future;
}

// Native part
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,15 @@
import androidx.core.app.NotificationChannelCompat;
import androidx.work.Configuration;

import org.learningequality.NotificationRef;

import java.util.concurrent.Executors;

public class App extends Application implements Configuration.Provider {
@Override
public void onCreate() {
super.onCreate();
NotificationRef.initialize(this);
createNotificationChannels();
}

Expand All @@ -25,6 +30,7 @@ public Configuration getWorkManagerConfiguration() {
return new Configuration.Builder()
.setDefaultProcessName(processName)
.setMinimumLoggingLevel(android.util.Log.DEBUG)
.setExecutor(Executors.newFixedThreadPool(20))
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public ForegroundInfo getForegroundInfo() {
ref = TaskworkerWorkerService.mService.getNotificationRef();
} else {
ref = getNotificationRef();
Log.w(TAG, "No service found, using worker notification for foreground");
}

NotificationBuilder builder = new NotificationBuilder(getApplicationContext(), ref);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,17 @@ public class TaskworkerWorkerService extends RemoteWorkerService implements Noti

@Override
public void onCreate() {
mService = this;
Context context = getApplicationContext();
Log.v(TAG, "Initializing WorkManager");
WorkManager.getInstance(getApplicationContext());
super.onCreate();
Log.v(TAG, "Initializing task worker service");
PythonUtil.loadLibraries(
new File(context.getApplicationInfo().nativeLibraryDir)
);
mService = this;
// Initialize the work manager
WorkManager.getInstance(getApplicationContext());
super.onCreate();
// We could potentially remove this and leave the notification up to long-running workers
// bound to the service
sendNotification();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package org.learningequality;

import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.provider.Settings;

import androidx.core.app.NotificationCompat;

Expand All @@ -17,21 +20,36 @@ public NotificationBuilder(Context context, String channelId) {
// Default title
String notificationTitle = context.getApplicationContext().getString(R.string.app_name);
setContentTitle(notificationTitle);
}

public NotificationBuilder(Context context, int channelRef) {
this(context, NotificationRef.getChannelId(context, channelRef));

// defaults for service notification channel
if (channelRef == NotificationRef.REF_CHANNEL_SERVICE) {
if (channelId.equals(NotificationRef.ID_CHANNEL_SERVICE)) {
setOngoing(true);
setCategory(NotificationCompat.CATEGORY_SERVICE);
setContentTitle(context.getString(R.string.notification_service_channel_content));
} else if (channelRef == NotificationRef.REF_CHANNEL_DEFAULT) {
setContentText(context.getString(R.string.notification_service_channel_content));
setTicker(context.getString(R.string.notification_service_channel_ticker));

// Add settings button to notification for quick access to the minimize setting for this
// foreground notification channel
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName());
intent.putExtra(Settings.EXTRA_CHANNEL_ID, channelId);
addAction(new NotificationCompat.Action.Builder(
R.drawable.baseline_notifications_paused_24,
context.getString(R.string.notification_service_channel_action),
PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
).build());
}
} else if (channelId.equals(NotificationRef.ID_CHANNEL_DEFAULT)) {
setCategory(NotificationCompat.CATEGORY_PROGRESS);
setTicker(context.getString(R.string.notification_default_channel_ticker));
}
}

public NotificationBuilder(Context context, int channelRef) {
this(context, NotificationRef.getChannelId(context, channelRef));
}

public NotificationBuilder(Context context, NotificationRef ref) {
this(context, ref.getChannelRef());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ public final class NotificationRef {
public static final int ID_DEFAULT = 1;
public static final int REF_CHANNEL_SERVICE = 1;
public static final int REF_CHANNEL_DEFAULT = 2;
public static String ID_CHANNEL_DEFAULT = null;
public static String ID_CHANNEL_SERVICE = null;
private static boolean initialized = false;
private final int channelRef;
private final String tag;
private final int id;
Expand Down Expand Up @@ -38,12 +41,22 @@ public String getTag() {
return tag;
}

public static void initialize(Context context) {
if (initialized) {
return;
}
ID_CHANNEL_DEFAULT = context.getString(R.string.notification_default_channel_id);
ID_CHANNEL_SERVICE = context.getString(R.string.notification_service_channel_id);
initialized = true;
}

public static String getChannelId(Context context, int channelRef) {
initialize(context);
switch (channelRef) {
case REF_CHANNEL_SERVICE:
return context.getString(R.string.notification_service_channel_id);
return ID_CHANNEL_SERVICE;
case REF_CHANNEL_DEFAULT:
return context.getString(R.string.notification_default_channel_id);
return ID_CHANNEL_DEFAULT;
default:
return null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.89,2 2,2zM18,16v-5c0,-3.07 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.63,5.36 6,7.93 6,11v5l-2,2v1h16v-1l-2,-2zM14.5,9.8l-2.8,3.4h2.8L14.5,15h-5v-1.8l2.8,-3.4L9.5,9.8L9.5,8h5v1.8z"/>
</vector>
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
<string name="notification_service_channel_id">background_notifications</string>
<string name="notification_service_channel_title">Background Notifications</string>
<string name="notification_service_channel_content">Background tasks</string>
<string name="notification_service_channel_ticker">A persistent notification of Kolibri\'s background processing</string>
<string name="notification_service_channel_action">Manage</string>
<string name="notification_default_channel_id">task_notifications</string>
<string name="notification_default_channel_title">Task Notifications</string>
<string name="notification_default_channel_ticker">A progress notification of Kolibri\'s background processing</string>
</resources>

0 comments on commit 21de1b2

Please sign in to comment.