Add Android task scheduler.

This commit is contained in:
akwizgran
2020-07-31 17:09:01 +01:00
parent dfefb88b32
commit ac80a90ef3
19 changed files with 358 additions and 21 deletions

View File

@@ -16,6 +16,8 @@
android:label="@string/app_name"
android:supportsRtl="true">
<receiver android:name=".system.AlarmReceiver" />
</application>
</manifest>

View File

@@ -6,7 +6,7 @@ import org.briarproject.bramble.plugin.tor.CircumventionModule;
import org.briarproject.bramble.reporting.ReportingModule;
import org.briarproject.bramble.socks.SocksModule;
import org.briarproject.bramble.system.AndroidSystemModule;
import org.briarproject.bramble.system.DefaultTaskSchedulerModule;
import org.briarproject.bramble.system.AndroidTaskSchedulerModule;
import dagger.Module;
@@ -14,8 +14,8 @@ import dagger.Module;
AndroidBatteryModule.class,
AndroidNetworkModule.class,
AndroidSystemModule.class,
AndroidTaskSchedulerModule.class,
CircumventionModule.class,
DefaultTaskSchedulerModule.class,
ReportingModule.class,
SocksModule.class
})

View File

@@ -0,0 +1,8 @@
package org.briarproject.bramble;
import org.briarproject.bramble.api.system.AlarmListener;
public interface BrambleAppComponent {
AlarmListener alarmListener();
}

View File

@@ -0,0 +1,6 @@
package org.briarproject.bramble;
public interface BrambleApplication {
BrambleAppComponent getBrambleAppComponent();
}

View File

@@ -0,0 +1,11 @@
package org.briarproject.bramble.api.system;
import android.content.Intent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
public interface AlarmListener {
void onAlarm(Intent intent);
}

View File

@@ -0,0 +1,18 @@
package org.briarproject.bramble.system;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@NotNullByDefault
interface AlarmConstants {
/**
* Request code for the broadcast intent attached to the periodic alarm.
*/
int REQUEST_ALARM = 1;
/**
* Key for storing the process ID in the extras of the periodic alarm's
* intent. This allows us to ignore alarms scheduled by dead processes.
*/
String EXTRA_PID = "org.briarproject.bramble.EXTRA_PID";
}

View File

@@ -0,0 +1,17 @@
package org.briarproject.bramble.system;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import org.briarproject.bramble.BrambleApplication;
public class AlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context ctx, Intent intent) {
BrambleApplication app =
(BrambleApplication) ctx.getApplicationContext();
app.getBrambleAppComponent().alarmListener().onAlarm(intent);
}
}

View File

@@ -0,0 +1,204 @@
package org.briarproject.bramble.system;
import android.annotation.TargetApi;
import android.app.AlarmManager;
import android.app.Application;
import android.app.PendingIntent;
import android.content.Intent;
import android.os.Process;
import android.os.SystemClock;
import org.briarproject.bramble.api.lifecycle.Service;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.AlarmListener;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.TaskScheduler;
import java.util.ArrayList;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
import static android.app.AlarmManager.INTERVAL_FIFTEEN_MINUTES;
import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
import static android.content.Context.ALARM_SERVICE;
import static android.os.Build.VERSION.SDK_INT;
import static java.util.Objects.requireNonNull;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.system.AlarmConstants.EXTRA_PID;
import static org.briarproject.bramble.system.AlarmConstants.REQUEST_ALARM;
@ThreadSafe
@NotNullByDefault
class AndroidTaskScheduler implements TaskScheduler, Service, AlarmListener {
private static final Logger LOG =
getLogger(AndroidTaskScheduler.class.getName());
private static final long TICK_MS = SECONDS.toMillis(10);
private static final long ALARM_MS = INTERVAL_FIFTEEN_MINUTES;
private final Application app;
private final Clock clock;
private final ScheduledExecutorService scheduledExecutorService;
private final AlarmManager alarmManager;
private final Object lock = new Object();
@GuardedBy("lock")
private final Queue<ScheduledTask> tasks = new PriorityQueue<>();
AndroidTaskScheduler(Application app, Clock clock,
ScheduledExecutorService scheduledExecutorService) {
this.app = app;
this.clock = clock;
this.scheduledExecutorService = scheduledExecutorService;
alarmManager = (AlarmManager)
requireNonNull(app.getSystemService(ALARM_SERVICE));
}
@Override
public void startService() {
scheduledExecutorService.scheduleAtFixedRate(this::runDueTasks,
TICK_MS, TICK_MS, MILLISECONDS);
scheduleAlarm();
}
@Override
public void stopService() {
cancelAlarm();
}
@Override
public Future<?> schedule(Runnable task, long delay, TimeUnit unit) {
long now = clock.currentTimeMillis();
long dueMillis = now + MILLISECONDS.convert(delay, unit);
ScheduledTask s = new ScheduledTask(task, dueMillis);
if (dueMillis <= now) {
scheduledExecutorService.execute(s);
} else {
synchronized (lock) {
tasks.add(s);
}
}
return s;
}
@Override
public Future<?> scheduleAtFixedRate(Runnable task, long delay,
long interval, TimeUnit unit) {
Runnable wrapped = () -> {
scheduleAtFixedRate(task, interval, interval, unit);
task.run();
};
return schedule(wrapped, delay, unit);
}
@Override
public Future<?> scheduleWithFixedDelay(Runnable task, long delay,
long interval, TimeUnit unit) {
Runnable wrapped = () -> {
task.run();
scheduleWithFixedDelay(task, interval, interval, unit);
};
return schedule(wrapped, delay, unit);
}
@Override
public void onAlarm(Intent intent) {
int extraPid = intent.getIntExtra(EXTRA_PID, -1);
int currentPid = Process.myPid();
if (extraPid == currentPid) {
LOG.info("Alarm");
rescheduleAlarm();
runDueTasks();
} else {
LOG.info("Ignoring alarm with PID " + extraPid
+ ", current PID is " + currentPid);
}
}
private void runDueTasks() {
long now = clock.currentTimeMillis();
List<ScheduledTask> due = new ArrayList<>();
synchronized (lock) {
while (true) {
ScheduledTask s = tasks.peek();
if (s == null || s.dueMillis > now) break;
due.add(tasks.remove());
}
}
if (LOG.isLoggable(INFO)) {
LOG.info("Running " + due.size() + " due tasks");
}
for (ScheduledTask s : due) {
if (LOG.isLoggable(INFO)) {
LOG.info("Task is " + (now - s.dueMillis) + " ms overdue");
}
s.run();
}
}
private void scheduleAlarm() {
if (SDK_INT >= 23) scheduleIdleAlarm();
else scheduleInexactRepeatingAlarm();
}
private void rescheduleAlarm() {
if (SDK_INT >= 23) scheduleIdleAlarm();
}
private void cancelAlarm() {
alarmManager.cancel(getAlarmPendingIntent());
}
private void scheduleInexactRepeatingAlarm() {
alarmManager.setInexactRepeating(ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + ALARM_MS, ALARM_MS,
getAlarmPendingIntent());
}
@TargetApi(23)
private void scheduleIdleAlarm() {
alarmManager.setAndAllowWhileIdle(ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + ALARM_MS,
getAlarmPendingIntent());
}
private PendingIntent getAlarmPendingIntent() {
Intent i = new Intent(app, AlarmReceiver.class);
i.putExtra(EXTRA_PID, android.os.Process.myPid());
return PendingIntent.getBroadcast(app, REQUEST_ALARM, i,
FLAG_CANCEL_CURRENT);
}
private static class ScheduledTask extends FutureTask<Void>
implements Comparable<ScheduledTask> {
private final long dueMillis;
public ScheduledTask(Runnable runnable, long dueMillis) {
super(runnable, null);
this.dueMillis = dueMillis;
}
@Override
public int compareTo(ScheduledTask s) {
//noinspection UseCompareMethod
if (dueMillis < s.dueMillis) return -1;
if (dueMillis > s.dueMillis) return 1;
return 0;
}
}
}

View File

@@ -0,0 +1,59 @@
package org.briarproject.bramble.system;
import android.app.Application;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.system.AlarmListener;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.TaskScheduler;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import javax.inject.Inject;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class AndroidTaskSchedulerModule {
public static class EagerSingletons {
@Inject
AndroidTaskScheduler scheduler;
}
private final ScheduledExecutorService scheduledExecutorService;
public AndroidTaskSchedulerModule() {
// Discard tasks that are submitted during shutdown
RejectedExecutionHandler policy =
new ScheduledThreadPoolExecutor.DiscardPolicy();
scheduledExecutorService = new ScheduledThreadPoolExecutor(1, policy);
}
@Provides
@Singleton
AndroidTaskScheduler provideAndroidTaskScheduler(
LifecycleManager lifecycleManager, Application app, Clock clock) {
lifecycleManager.registerForShutdown(scheduledExecutorService);
AndroidTaskScheduler scheduler =
new AndroidTaskScheduler(app, clock, scheduledExecutorService);
lifecycleManager.registerService(scheduler);
return scheduler;
}
@Provides
@Singleton
AlarmListener provideAlarmListener(AndroidTaskScheduler scheduler) {
return scheduler;
}
@Provides
@Singleton
TaskScheduler provideTaskScheduler(AndroidTaskScheduler scheduler) {
return scheduler;
}
}

View File

@@ -5,7 +5,7 @@ import android.os.PowerManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.TaskScheduler;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
@@ -39,7 +39,7 @@ public class RenewableWakeLock {
@Nullable
private PowerManager.WakeLock wakeLock; // Locking: lock
@Nullable
private ScheduledFuture<?> future; // Locking: lock
private Future<?> future; // Locking: lock
public RenewableWakeLock(PowerManager powerManager,
TaskScheduler scheduler, int levelAndFlags, String tag,

View File

@@ -2,8 +2,8 @@ package org.briarproject.bramble.api.system;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
@@ -18,17 +18,17 @@ public interface TaskScheduler {
/**
* See {@link ScheduledExecutorService#schedule(Runnable, long, TimeUnit)}.
*/
ScheduledFuture<?> schedule(Runnable task, long delay, TimeUnit unit);
Future<?> schedule(Runnable task, long delay, TimeUnit unit);
/**
* See {@link ScheduledExecutorService#scheduleAtFixedRate(Runnable, long, long, TimeUnit)}.
*/
ScheduledFuture<?> scheduleAtFixedRate(Runnable task, long delay,
long interval, TimeUnit unit);
Future<?> scheduleAtFixedRate(Runnable task, long delay, long interval,
TimeUnit unit);
/**
* See {@link ScheduledExecutorService#scheduleWithFixedDelay(Runnable, long, long, TimeUnit)}.
*/
ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, long delay,
Future<?> scheduleWithFixedDelay(Runnable task, long delay,
long interval, TimeUnit unit);
}

View File

@@ -189,7 +189,7 @@ class PollerImpl implements Poller, EventListener {
// it will abort safely when it finds it's been replaced
if (scheduled != null) scheduled.future.cancel(false);
PollTask task = new PollTask(p, due, randomiseNext);
Future future = scheduler.schedule(() ->
Future<?> future = scheduler.schedule(() ->
ioExecutor.execute(task), delay, MILLISECONDS);
tasks.put(t, new ScheduledPollTask(task, future));
}

View File

@@ -3,8 +3,8 @@ package org.briarproject.bramble.system;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.TaskScheduler;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import javax.annotation.concurrent.ThreadSafe;
@@ -24,19 +24,18 @@ class TaskSchedulerImpl implements TaskScheduler {
}
@Override
public ScheduledFuture<?> schedule(Runnable task, long delay,
TimeUnit unit) {
public Future<?> schedule(Runnable task, long delay, TimeUnit unit) {
return delegate.schedule(task, delay, unit);
}
@Override
public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, long delay,
public Future<?> scheduleAtFixedRate(Runnable task, long delay,
long interval, TimeUnit unit) {
return delegate.scheduleAtFixedRate(task, delay, interval, unit);
}
@Override
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, long delay,
public Future<?> scheduleWithFixedDelay(Runnable task, long delay,
long interval, TimeUnit unit) {
return delegate.scheduleWithFixedDelay(task, delay, interval, unit);
}

View File

@@ -198,7 +198,7 @@ class TransportKeyManagerImpl implements TransportKeyManager {
private void scheduleKeyUpdate(long now) {
long delay = timePeriodLength - now % timePeriodLength;
scheduler.schedule((Runnable) this::updateKeys, delay, MILLISECONDS);
scheduler.schedule(this::updateKeys, delay, MILLISECONDS);
}
private void updateKeys() {

View File

@@ -31,7 +31,7 @@ import org.junit.Test;
import java.security.SecureRandom;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.Future;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
@@ -55,8 +55,7 @@ public class PollerImplTest extends BrambleMockTestCase {
private final TransportPropertyManager transportPropertyManager =
context.mock(TransportPropertyManager.class);
private final Clock clock = context.mock(Clock.class);
private final ScheduledFuture<?> future =
context.mock(ScheduledFuture.class);
private final Future<?> future = context.mock(Future.class);
private final SecureRandom random;
private final Executor ioExecutor = new ImmediateExecutor();

View File

@@ -2,6 +2,7 @@ package org.briarproject.briar.android;
import org.briarproject.bramble.BrambleAndroidEagerSingletons;
import org.briarproject.bramble.BrambleAndroidModule;
import org.briarproject.bramble.BrambleAppComponent;
import org.briarproject.bramble.BrambleCoreEagerSingletons;
import org.briarproject.bramble.BrambleCoreModule;
import org.briarproject.bramble.account.BriarAccountModule;
@@ -73,7 +74,7 @@ import dagger.Component;
})
public interface AndroidComponent
extends BrambleCoreEagerSingletons, BrambleAndroidEagerSingletons,
BriarCoreEagerSingletons, AndroidEagerSingletons {
BriarCoreEagerSingletons, AndroidEagerSingletons, BrambleAppComponent {
// Exposed objects
@CryptoExecutor

View File

@@ -3,6 +3,7 @@ package org.briarproject.briar.android;
import android.app.Activity;
import android.content.SharedPreferences;
import org.briarproject.bramble.BrambleApplication;
import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
import java.util.Collection;
@@ -12,7 +13,7 @@ import java.util.logging.LogRecord;
* This exists so that the Application object will not necessarily be cast
* directly to the Briar application object.
*/
public interface BriarApplication {
public interface BriarApplication extends BrambleApplication {
Class<? extends Activity> ENTRY_ACTIVITY = NavDrawerActivity.class;

View File

@@ -18,6 +18,7 @@ import org.acra.ACRA;
import org.acra.ReportingInteractionMode;
import org.acra.annotation.ReportsCrashes;
import org.briarproject.bramble.BrambleAndroidEagerSingletons;
import org.briarproject.bramble.BrambleAppComponent;
import org.briarproject.bramble.BrambleCoreEagerSingletons;
import org.briarproject.briar.BriarCoreEagerSingletons;
import org.briarproject.briar.BuildConfig;
@@ -170,6 +171,11 @@ public class BriarApplicationImpl extends Application
StrictMode.setVmPolicy(vmPolicy.build());
}
@Override
public BrambleAppComponent getBrambleAppComponent() {
return applicationComponent;
}
@Override
public Collection<LogRecord> getRecentLogRecords() {
return logHandler.getRecentLogRecords();

View File

@@ -8,6 +8,7 @@ import com.vanniktech.emoji.EmojiManager;
import com.vanniktech.emoji.google.GoogleEmojiProvider;
import org.briarproject.bramble.BrambleAndroidEagerSingletons;
import org.briarproject.bramble.BrambleAppComponent;
import org.briarproject.bramble.BrambleCoreEagerSingletons;
import org.briarproject.briar.BriarCoreEagerSingletons;
@@ -53,6 +54,11 @@ public class TestBriarApplication extends Application
EmojiManager.install(new GoogleEmojiProvider());
}
@Override
public BrambleAppComponent getBrambleAppComponent() {
return applicationComponent;
}
@Override
public Collection<LogRecord> getRecentLogRecords() {
return emptyList();