diff --git a/mailbox-android/src/main/AndroidManifest.xml b/mailbox-android/src/main/AndroidManifest.xml index aa9cb5779b2173a559165fbe0a1249064042fdfe..6d9518fb7ca00122be0b766fa72166ee2604b987 100644 --- a/mailbox-android/src/main/AndroidManifest.xml +++ b/mailbox-android/src/main/AndroidManifest.xml @@ -26,6 +26,9 @@ <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> + + <receiver android:name=".core.system.AlarmReceiver" /> + </application> </manifest> \ No newline at end of file diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/AppModule.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/AppModule.kt index 68239a115ae4568bbdf781cf29e6be7174051d97..d4e4de7a15408e80a2b98a5a77e116fd4555511f 100644 --- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/AppModule.kt +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/AppModule.kt @@ -4,12 +4,11 @@ import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import org.briarproject.mailbox.core.CoreModule -import org.briarproject.mailbox.core.tor.AndroidTorModule @Module( includes = [ CoreModule::class, - AndroidTorModule::class, + // Hilt modules from this gradle module are included automatically somehow ] ) @InstallIn(SingletonComponent::class) diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxService.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxService.kt index 8ff1823fb288124e5a52df349298d701c5bcd04b..62fbc8d1a41a409b09321c636cc2279fd175ecc9 100644 --- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxService.kt +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxService.kt @@ -9,14 +9,13 @@ import android.os.IBinder import androidx.core.content.ContextCompat import dagger.hilt.android.AndroidEntryPoint import org.briarproject.mailbox.android.MailboxNotificationManager.Companion.NOTIFICATION_MAIN_ID -import org.briarproject.mailbox.android.api.system.AndroidWakeLockManager import org.briarproject.mailbox.core.lifecycle.LifecycleManager import org.briarproject.mailbox.core.lifecycle.LifecycleManager.StartResult import org.briarproject.mailbox.core.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING import org.briarproject.mailbox.core.lifecycle.LifecycleManager.StartResult.SUCCESS +import org.briarproject.mailbox.core.system.AndroidWakeLockManager import org.slf4j.LoggerFactory.getLogger import java.util.concurrent.atomic.AtomicBoolean - import javax.inject.Inject import kotlin.system.exitProcess diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/system/AndroidSystemModule.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/system/AndroidSystemModule.kt deleted file mode 100644 index 917feb9037102c346ac4dd0df7c4ee1ecd72da7f..0000000000000000000000000000000000000000 --- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/system/AndroidSystemModule.kt +++ /dev/null @@ -1,60 +0,0 @@ -package org.briarproject.mailbox.android.system - -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import org.briarproject.mailbox.android.api.system.AndroidExecutor -import org.briarproject.mailbox.android.api.system.AndroidWakeLockManager -import org.briarproject.mailbox.core.lifecycle.LifecycleManager -import java.util.concurrent.Executor -import java.util.concurrent.RejectedExecutionHandler -import java.util.concurrent.ScheduledExecutorService -import java.util.concurrent.ScheduledThreadPoolExecutor -import java.util.concurrent.ThreadPoolExecutor -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -internal class AndroidSystemModule { - - private var scheduledExecutorService: ScheduledExecutorService - - init { - // Discard tasks that are submitted during shutdown - val policy: RejectedExecutionHandler = ThreadPoolExecutor.DiscardPolicy() - scheduledExecutorService = ScheduledThreadPoolExecutor(1, policy) - } - - @Provides - @Singleton - fun provideScheduledExecutorService( - lifecycleManager: LifecycleManager, - ): ScheduledExecutorService { - lifecycleManager.registerForShutdown(scheduledExecutorService) - return scheduledExecutorService - } - - @Provides - @Singleton - fun provideWakeLockManager( - wakeLockManager: AndroidWakeLockManagerImpl, - ): AndroidWakeLockManager { - return wakeLockManager - } - - @Provides - @Singleton - fun provideAndroidExecutor(androidExecutor: AndroidExecutorImpl): AndroidExecutor { - return androidExecutor - } - - @Provides - @Singleton - fun provideEventExecutor(androidExecutor: AndroidExecutor): Executor { - return Executor { - androidExecutor.runOnUiThread(it) - } - } - -} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/core/AndroidEagerSingletons.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/core/AndroidEagerSingletons.kt index baae3905f77ffafb0ce3273466c1149b7614b6d1..1269bb461bb447eb2be2d10f035fcd14a55ac9d4 100644 --- a/mailbox-android/src/main/java/org/briarproject/mailbox/core/AndroidEagerSingletons.kt +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/core/AndroidEagerSingletons.kt @@ -1,9 +1,13 @@ package org.briarproject.mailbox.core +import org.briarproject.mailbox.core.system.AndroidTaskScheduler +import org.briarproject.mailbox.core.tor.AndroidNetworkManager import org.briarproject.mailbox.core.tor.AndroidTorPlugin import javax.inject.Inject @Suppress("unused") internal class AndroidEagerSingletons @Inject constructor( + val androidTaskScheduler: AndroidTaskScheduler, + val androidNetworkManager: AndroidNetworkManager, val androidTorPlugin: AndroidTorPlugin, ) diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AlarmConstants.java b/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AlarmConstants.java new file mode 100644 index 0000000000000000000000000000000000000000..6338e44f82cc3eb3c47db8701855c6ac7984191a --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AlarmConstants.java @@ -0,0 +1,15 @@ +package org.briarproject.mailbox.core.system; + +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"; +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AlarmReceiver.java b/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AlarmReceiver.java new file mode 100644 index 0000000000000000000000000000000000000000..8a099905582428c82eb3febd1f5ecb048c7a124d --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AlarmReceiver.java @@ -0,0 +1,17 @@ +package org.briarproject.mailbox.core.system; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import org.briarproject.mailbox.android.MailboxApplication; + +public class AlarmReceiver extends BroadcastReceiver { + + @Override + public void onReceive(Context ctx, Intent intent) { + MailboxApplication app = + (MailboxApplication) ctx.getApplicationContext(); + app.androidEagerSingletons.getAndroidTaskScheduler().onAlarm(intent); + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/api/system/AndroidExecutor.java b/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidExecutor.java similarity index 94% rename from mailbox-android/src/main/java/org/briarproject/mailbox/android/api/system/AndroidExecutor.java rename to mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidExecutor.java index 8e9ea64c11cd4d392fae23ef3a04faa259938ffb..a97d710516acd20efa7948574f42da058076a6ad 100644 --- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/api/system/AndroidExecutor.java +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidExecutor.java @@ -1,4 +1,4 @@ -package org.briarproject.mailbox.android.api.system; +package org.briarproject.mailbox.core.system; import java.util.concurrent.Callable; import java.util.concurrent.Future; diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/system/AndroidExecutorImpl.java b/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidExecutorImpl.java similarity index 94% rename from mailbox-android/src/main/java/org/briarproject/mailbox/android/system/AndroidExecutorImpl.java rename to mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidExecutorImpl.java index 14a66f08868d961b21aaea616b66bc1a3a2832ad..3544757c604beb91832a6f6bb3931ba9d80baeab 100644 --- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/system/AndroidExecutorImpl.java +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidExecutorImpl.java @@ -1,11 +1,9 @@ -package org.briarproject.mailbox.android.system; +package org.briarproject.mailbox.core.system; import android.app.Application; import android.os.Handler; import android.os.Looper; -import org.briarproject.mailbox.android.api.system.AndroidExecutor; - import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidSystemModule.java b/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidSystemModule.java new file mode 100644 index 0000000000000000000000000000000000000000..7e348623bbb388690e8b27bb4120329dfd2b5a8f --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidSystemModule.java @@ -0,0 +1,59 @@ +package org.briarproject.mailbox.core.system; + +import org.briarproject.mailbox.core.event.EventExecutor; +import org.briarproject.mailbox.core.lifecycle.LifecycleManager; + +import java.util.concurrent.Executor; +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; +import dagger.hilt.InstallIn; +import dagger.hilt.components.SingletonComponent; + +@Module +@InstallIn(SingletonComponent.class) +public class AndroidSystemModule { + + private final ScheduledExecutorService scheduledExecutorService; + + public AndroidSystemModule() { + // Discard tasks that are submitted during shutdown + RejectedExecutionHandler policy = + new ScheduledThreadPoolExecutor.DiscardPolicy(); + scheduledExecutorService = new ScheduledThreadPoolExecutor(1, policy); + } + + @Provides + @Singleton + ScheduledExecutorService provideScheduledExecutorService( + LifecycleManager lifecycleManager) { + lifecycleManager.registerForShutdown(scheduledExecutorService); + return scheduledExecutorService; + } + + @Provides + @Singleton + AndroidExecutor provideAndroidExecutor( + AndroidExecutorImpl androidExecutor) { + return androidExecutor; + } + + @Provides + @Singleton + @EventExecutor + Executor provideEventExecutor(AndroidExecutor androidExecutor) { + return androidExecutor::runOnUiThread; + } + + @Provides + @Singleton + AndroidWakeLockManager provideWakeLockManager( + AndroidWakeLockManagerImpl wakeLockManager) { + return wakeLockManager; + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidTaskScheduler.java b/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidTaskScheduler.java new file mode 100644 index 0000000000000000000000000000000000000000..770b9ef7f806547bf61a99fff0080ecc4df6f4a8 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidTaskScheduler.java @@ -0,0 +1,231 @@ +package org.briarproject.mailbox.core.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.mailbox.core.lifecycle.Service; +import org.slf4j.Logger; + +import java.util.ArrayList; +import java.util.List; +import java.util.PriorityQueue; +import java.util.Queue; +import java.util.concurrent.Executor; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +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 org.briarproject.mailbox.core.system.AlarmConstants.EXTRA_PID; +import static org.briarproject.mailbox.core.system.AlarmConstants.REQUEST_ALARM; +import static org.briarproject.mailbox.core.util.LogUtils.info; +import static org.slf4j.LoggerFactory.getLogger; + +@ThreadSafe +public class AndroidTaskScheduler implements TaskScheduler, Service { + + private static final Logger LOG = getLogger(AndroidTaskScheduler.class); + + private static final long ALARM_MS = INTERVAL_FIFTEEN_MINUTES; + + private final Application app; + private final AndroidWakeLockManager wakeLockManager; + 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, + AndroidWakeLockManager wakeLockManager, + ScheduledExecutorService scheduledExecutorService) { + this.app = app; + this.wakeLockManager = wakeLockManager; + this.scheduledExecutorService = scheduledExecutorService; + alarmManager = (AlarmManager) requireNonNull( + app.getSystemService(ALARM_SERVICE)); + } + + @Override + public void startService() { + scheduleAlarm(); + } + + @Override + public void stopService() { + cancelAlarm(); + } + + @Override + public Cancellable schedule(Runnable task, Executor executor, long delay, + TimeUnit unit) { + AtomicBoolean cancelled = new AtomicBoolean(false); + return schedule(task, executor, delay, unit, cancelled); + } + + @Override + public Cancellable scheduleWithFixedDelay(Runnable task, Executor executor, + long delay, long interval, TimeUnit unit) { + AtomicBoolean cancelled = new AtomicBoolean(false); + return scheduleWithFixedDelay(task, executor, delay, interval, unit, + cancelled); + } + + public void onAlarm(Intent intent) { + wakeLockManager.runWakefully(() -> { + int extraPid = intent.getIntExtra(EXTRA_PID, -1); + int currentPid = Process.myPid(); + if (extraPid == currentPid) { + LOG.info("Alarm"); + rescheduleAlarm(); + runDueTasks(); + } else { + info(LOG, () -> "Ignoring alarm with PID " + extraPid + + ", current PID is " + + currentPid); + } + }, "TaskAlarm"); + } + + private Cancellable schedule(Runnable task, Executor executor, long delay, + TimeUnit unit, AtomicBoolean cancelled) { + long now = SystemClock.elapsedRealtime(); + long dueMillis = now + MILLISECONDS.convert(delay, unit); + Runnable wakeful = () -> + wakeLockManager.executeWakefully(task, executor, "TaskHandoff"); + Future<?> check = scheduleCheckForDueTasks(delay, unit); + ScheduledTask s = new ScheduledTask(wakeful, dueMillis, check, + cancelled); + synchronized (lock) { + tasks.add(s); + } + return s; + } + + private Cancellable scheduleWithFixedDelay(Runnable task, Executor executor, + long delay, long interval, TimeUnit unit, AtomicBoolean cancelled) { + // All executions of this periodic task share a cancelled flag + Runnable wrapped = () -> { + task.run(); + scheduleWithFixedDelay(task, executor, interval, interval, unit, + cancelled); + }; + return schedule(wrapped, executor, delay, unit, cancelled); + } + + private Future<?> scheduleCheckForDueTasks(long delay, TimeUnit unit) { + Runnable wakeful = () -> wakeLockManager.runWakefully( + this::runDueTasks, "TaskScheduler"); + return scheduledExecutorService.schedule(wakeful, delay, unit); + } + + @Wakeful + private void runDueTasks() { + long now = SystemClock.elapsedRealtime(); + List<ScheduledTask> due = new ArrayList<>(); + synchronized (lock) { + while (true) { + ScheduledTask s = tasks.peek(); + if (s == null || s.dueMillis > now) break; + due.add(tasks.remove()); + } + } + info(LOG, () -> "Running " + due.size() + " due tasks"); + for (ScheduledTask s : due) { + info(LOG, () -> "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 the alarm repeats automatically + 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, Process.myPid()); + return PendingIntent + .getBroadcast(app, REQUEST_ALARM, i, FLAG_CANCEL_CURRENT); + } + + private class ScheduledTask + implements Runnable, Cancellable, Comparable<ScheduledTask> { + + private final Runnable task; + private final long dueMillis; + private final Future<?> check; + private final AtomicBoolean cancelled; + + public ScheduledTask(Runnable task, long dueMillis, + Future<?> check, AtomicBoolean cancelled) { + this.task = task; + this.dueMillis = dueMillis; + this.check = check; + this.cancelled = cancelled; + } + + @Override + public void run() { + if (!cancelled.get()) task.run(); + } + + @Override + public void cancel() { + // Cancel any future executions of this task + cancelled.set(true); + // Cancel the scheduled check for due tasks + check.cancel(false); + // Remove the task from the queue + synchronized (lock) { + tasks.remove(this); + } + } + + @Override + public int compareTo(ScheduledTask s) { + //noinspection UseCompareMethod + if (dueMillis < s.dueMillis) return -1; + if (dueMillis > s.dueMillis) return 1; + return 0; + } + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidTaskSchedulerModule.java b/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidTaskSchedulerModule.java new file mode 100644 index 0000000000000000000000000000000000000000..18074c14205d79d87a07c252347c2de356b9e422 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidTaskSchedulerModule.java @@ -0,0 +1,37 @@ +package org.briarproject.mailbox.core.system; + +import android.app.Application; + +import org.briarproject.mailbox.core.lifecycle.LifecycleManager; + +import java.util.concurrent.ScheduledExecutorService; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; +import dagger.hilt.InstallIn; +import dagger.hilt.components.SingletonComponent; + +@Module +@InstallIn(SingletonComponent.class) +public class AndroidTaskSchedulerModule { + + @Provides + @Singleton + AndroidTaskScheduler provideAndroidTaskScheduler( + LifecycleManager lifecycleManager, Application app, + AndroidWakeLockManager wakeLockManager, + ScheduledExecutorService scheduledExecutorService) { + AndroidTaskScheduler scheduler = new AndroidTaskScheduler(app, + wakeLockManager, scheduledExecutorService); + lifecycleManager.registerService(scheduler); + return scheduler; + } + + @Provides + @Singleton + TaskScheduler provideTaskScheduler(AndroidTaskScheduler scheduler) { + return scheduler; + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/api/system/AndroidWakeLock.java b/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidWakeLock.java similarity index 85% rename from mailbox-android/src/main/java/org/briarproject/mailbox/android/api/system/AndroidWakeLock.java rename to mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidWakeLock.java index f1577efd080a858ecadfbe4cd8d8416a9c793e2d..dd0c8f8fffc910279d50e0e7caa0a9dd750e0b30 100644 --- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/api/system/AndroidWakeLock.java +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidWakeLock.java @@ -1,4 +1,4 @@ -package org.briarproject.mailbox.android.api.system; +package org.briarproject.mailbox.core.system; public interface AndroidWakeLock { diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/system/AndroidWakeLockImpl.java b/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidWakeLockImpl.java similarity index 93% rename from mailbox-android/src/main/java/org/briarproject/mailbox/android/system/AndroidWakeLockImpl.java rename to mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidWakeLockImpl.java index 6eaa65eb325613973ed73079f64338f800836c9c..32b67fc62c955005a172217fac729d6c9d722cc3 100644 --- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/system/AndroidWakeLockImpl.java +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidWakeLockImpl.java @@ -1,9 +1,5 @@ -package org.briarproject.mailbox.android.system; +package org.briarproject.mailbox.core.system; -import static org.briarproject.mailbox.core.util.LogUtils.trace; -import static org.slf4j.LoggerFactory.getLogger; - -import org.briarproject.mailbox.android.api.system.AndroidWakeLock; import org.slf4j.Logger; import java.util.concurrent.atomic.AtomicInteger; @@ -11,6 +7,9 @@ import java.util.concurrent.atomic.AtomicInteger; import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.ThreadSafe; +import static org.briarproject.mailbox.core.util.LogUtils.trace; +import static org.slf4j.LoggerFactory.getLogger; + /** * A wrapper around a {@link SharedWakeLock} that provides the more convenient * semantics of {@link AndroidWakeLock} (i.e. calls to acquire() and release() diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/api/system/AndroidWakeLockManager.java b/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidWakeLockManager.java similarity index 95% rename from mailbox-android/src/main/java/org/briarproject/mailbox/android/api/system/AndroidWakeLockManager.java rename to mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidWakeLockManager.java index 917d10b191d21421151d2d98b300b7c1408e4bc6..7b2cd233b9d0044b8b93b81184f0321ecd279a03 100644 --- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/api/system/AndroidWakeLockManager.java +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidWakeLockManager.java @@ -1,4 +1,4 @@ -package org.briarproject.mailbox.android.api.system; +package org.briarproject.mailbox.core.system; import java.util.concurrent.Executor; diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/system/AndroidWakeLockManagerImpl.java b/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidWakeLockManagerImpl.java similarity index 95% rename from mailbox-android/src/main/java/org/briarproject/mailbox/android/system/AndroidWakeLockManagerImpl.java rename to mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidWakeLockManagerImpl.java index bb590249d6affd5abebd7404e70a63f600e300c2..0cbd9a087b454789a7727d6c20f945f96d4782a5 100644 --- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/system/AndroidWakeLockManagerImpl.java +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidWakeLockManagerImpl.java @@ -1,24 +1,21 @@ -package org.briarproject.mailbox.android.system; - -import static android.content.Context.POWER_SERVICE; -import static android.os.PowerManager.PARTIAL_WAKE_LOCK; -import static java.util.Objects.requireNonNull; -import static java.util.concurrent.TimeUnit.MINUTES; -import static java.util.concurrent.TimeUnit.SECONDS; +package org.briarproject.mailbox.core.system; import android.app.Application; import android.content.Context; import android.content.pm.PackageManager; import android.os.PowerManager; -import org.briarproject.mailbox.android.api.system.AndroidWakeLock; -import org.briarproject.mailbox.android.api.system.AndroidWakeLockManager; - import java.util.concurrent.Executor; import java.util.concurrent.ScheduledExecutorService; import javax.inject.Inject; +import static android.content.Context.POWER_SERVICE; +import static android.os.PowerManager.PARTIAL_WAKE_LOCK; +import static java.util.Objects.requireNonNull; +import static java.util.concurrent.TimeUnit.MINUTES; +import static java.util.concurrent.TimeUnit.SECONDS; + class AndroidWakeLockManagerImpl implements AndroidWakeLockManager { /** diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/system/RenewableWakeLock.java b/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/RenewableWakeLock.java similarity index 98% rename from mailbox-android/src/main/java/org/briarproject/mailbox/android/system/RenewableWakeLock.java rename to mailbox-android/src/main/java/org/briarproject/mailbox/core/system/RenewableWakeLock.java index a39cbf5f53ff7759762707f4ae8a08af2edc74dd..0cd45bc6de686534a1c3b0e3d28d2a419bb35618 100644 --- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/system/RenewableWakeLock.java +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/RenewableWakeLock.java @@ -1,11 +1,4 @@ -package org.briarproject.mailbox.android.system; - -import static org.briarproject.mailbox.core.util.LogUtils.info; -import static org.briarproject.mailbox.core.util.LogUtils.trace; -import static org.briarproject.mailbox.core.util.LogUtils.warn; -import static org.slf4j.LoggerFactory.getLogger; -import static java.util.Objects.requireNonNull; -import static java.util.concurrent.TimeUnit.MILLISECONDS; +package org.briarproject.mailbox.core.system; import android.os.PowerManager; import android.os.PowerManager.WakeLock; @@ -19,6 +12,13 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.ThreadSafe; +import static java.util.Objects.requireNonNull; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.briarproject.mailbox.core.util.LogUtils.info; +import static org.briarproject.mailbox.core.util.LogUtils.trace; +import static org.briarproject.mailbox.core.util.LogUtils.warn; +import static org.slf4j.LoggerFactory.getLogger; + @ThreadSafe class RenewableWakeLock implements SharedWakeLock { diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/system/SharedWakeLock.java b/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/SharedWakeLock.java similarity index 82% rename from mailbox-android/src/main/java/org/briarproject/mailbox/android/system/SharedWakeLock.java rename to mailbox-android/src/main/java/org/briarproject/mailbox/core/system/SharedWakeLock.java index a4e353de45c2e690a75299f698b3371968ff2f9f..d193a7806cbe6a0974eae6cb44fb848bfeda4031 100644 --- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/system/SharedWakeLock.java +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/SharedWakeLock.java @@ -1,6 +1,4 @@ -package org.briarproject.mailbox.android.system; - -import org.briarproject.mailbox.android.api.system.AndroidWakeLock; +package org.briarproject.mailbox.core.system; interface SharedWakeLock { diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/core/tor/AndroidLocationUtils.java b/mailbox-android/src/main/java/org/briarproject/mailbox/core/tor/AndroidLocationUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..b189c3d43f20b0cafeba86d04c93e096e16d9ad3 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/core/tor/AndroidLocationUtils.java @@ -0,0 +1,70 @@ +package org.briarproject.mailbox.core.tor; + +import static android.content.Context.TELEPHONY_SERVICE; + +import android.annotation.SuppressLint; +import android.app.Application; +import android.content.Context; +import android.telephony.TelephonyManager; +import android.text.TextUtils; + +import org.briarproject.mailbox.core.system.LocationUtils; + +import java.util.Locale; +import java.util.logging.Logger; + +import javax.inject.Inject; + +class AndroidLocationUtils implements LocationUtils { + + private static final Logger LOG = + Logger.getLogger(AndroidLocationUtils.class.getName()); + + private final Context appContext; + + @Inject + AndroidLocationUtils(Application app) { + appContext = app.getApplicationContext(); + } + + /** + * This guesses the current country from the first of these sources that + * succeeds (also in order of likelihood of being correct): + * + * <ul> + * <li>Phone network. This works even when no SIM card is inserted, or a + * foreign SIM card is inserted.</li> + * <li>SIM card. This is only an heuristic and assumes the user is not + * roaming.</li> + * <li>User locale. This is an even worse heuristic.</li> + * </ul> + * <p> + * Note: this is very similar to <a href="https://android.googlesource.com/platform/frameworks/base/+/cd92588%5E/location/java/android/location/CountryDetector.java"> + * this API</a> except it seems that Google doesn't want us to use it for + * some reason - both that class and {@code Context.COUNTRY_CODE} are + * annotated {@code @hide}. + */ + @Override + @SuppressLint("DefaultLocale") + public String getCurrentCountry() { + String countryCode = getCountryFromPhoneNetwork(); + if (!TextUtils.isEmpty(countryCode)) return countryCode.toUpperCase(); + LOG.info("Falling back to SIM card country"); + countryCode = getCountryFromSimCard(); + if (!TextUtils.isEmpty(countryCode)) return countryCode.toUpperCase(); + LOG.info("Falling back to user-defined locale"); + return Locale.getDefault().getCountry(); + } + + private String getCountryFromPhoneNetwork() { + Object o = appContext.getSystemService(TELEPHONY_SERVICE); + TelephonyManager tm = (TelephonyManager) o; + return tm == null ? "" : tm.getNetworkCountryIso(); + } + + private String getCountryFromSimCard() { + Object o = appContext.getSystemService(TELEPHONY_SERVICE); + TelephonyManager tm = (TelephonyManager) o; + return tm == null ? "" : tm.getSimCountryIso(); + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/core/tor/AndroidNetworkManager.java b/mailbox-android/src/main/java/org/briarproject/mailbox/core/tor/AndroidNetworkManager.java new file mode 100644 index 0000000000000000000000000000000000000000..1743c3c477f40bb4dd90bf6003fc800fe9cd1a74 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/core/tor/AndroidNetworkManager.java @@ -0,0 +1,215 @@ +package org.briarproject.mailbox.core.tor; + +import static android.content.Context.CONNECTIVITY_SERVICE; +import static android.content.Intent.ACTION_SCREEN_OFF; +import static android.content.Intent.ACTION_SCREEN_ON; +import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; +import static android.net.ConnectivityManager.TYPE_WIFI; +import static android.net.wifi.p2p.WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION; +import static android.os.Build.VERSION.SDK_INT; +import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED; +import static org.briarproject.mailbox.core.util.LogUtils.info; +import static org.briarproject.mailbox.core.util.LogUtils.logException; +import static org.slf4j.LoggerFactory.getLogger; +import static java.net.NetworkInterface.getNetworkInterfaces; +import static java.util.Collections.list; +import static java.util.Objects.requireNonNull; +import static java.util.concurrent.TimeUnit.MINUTES; +import static java.util.concurrent.TimeUnit.SECONDS; + +import android.annotation.TargetApi; +import android.app.Application; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.net.LinkAddress; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkInfo; + +import org.briarproject.mailbox.core.event.EventBus; +import org.briarproject.mailbox.core.event.EventExecutor; +import org.briarproject.mailbox.core.lifecycle.Service; +import org.briarproject.mailbox.core.system.TaskScheduler; +import org.briarproject.mailbox.core.system.TaskScheduler.Cancellable; +import org.slf4j.Logger; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.Enumeration; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import javax.annotation.Nullable; +import javax.inject.Inject; + +public class AndroidNetworkManager implements NetworkManager, Service { + + private static final Logger LOG = getLogger(AndroidNetworkManager.class); + + // See android.net.wifi.WifiManager + private static final String WIFI_AP_STATE_CHANGED_ACTION = + "android.net.wifi.WIFI_AP_STATE_CHANGED"; + + private final TaskScheduler scheduler; + private final EventBus eventBus; + private final Executor eventExecutor; + private final Application app; + private final ConnectivityManager connectivityManager; + private final AtomicReference<Cancellable> connectivityCheck = + new AtomicReference<>(); + private final AtomicBoolean used = new AtomicBoolean(false); + + private volatile BroadcastReceiver networkStateReceiver = null; + + @Inject + AndroidNetworkManager(TaskScheduler scheduler, EventBus eventBus, + @EventExecutor Executor eventExecutor, Application app) { + this.scheduler = scheduler; + this.eventBus = eventBus; + this.eventExecutor = eventExecutor; + this.app = app; + connectivityManager = (ConnectivityManager) + requireNonNull(app.getSystemService(CONNECTIVITY_SERVICE)); + } + + @Override + public void startService() { + if (used.getAndSet(true)) throw new IllegalStateException(); + // Register to receive network status events + networkStateReceiver = new NetworkStateReceiver(); + IntentFilter filter = new IntentFilter(); + filter.addAction(CONNECTIVITY_ACTION); + filter.addAction(ACTION_SCREEN_ON); + filter.addAction(ACTION_SCREEN_OFF); + filter.addAction(WIFI_AP_STATE_CHANGED_ACTION); + filter.addAction(WIFI_P2P_THIS_DEVICE_CHANGED_ACTION); + if (SDK_INT >= 23) filter.addAction(ACTION_DEVICE_IDLE_MODE_CHANGED); + app.registerReceiver(networkStateReceiver, filter); + } + + @Override + public void stopService() { + if (networkStateReceiver != null) + app.unregisterReceiver(networkStateReceiver); + } + + @Override + public NetworkStatus getNetworkStatus() { + NetworkInfo net = connectivityManager.getActiveNetworkInfo(); + boolean connected = net != null && net.isConnected(); + boolean wifi = false, ipv6Only = false; + if (connected) { + wifi = net.getType() == TYPE_WIFI; + if (SDK_INT >= 23) ipv6Only = isActiveNetworkIpv6Only(); + else ipv6Only = areAllAvailableNetworksIpv6Only(); + } + return new NetworkStatus(connected, wifi, ipv6Only); + } + + /** + * Returns true if the + * {@link ConnectivityManager#getActiveNetwork() active network} has an + * IPv6 unicast address and no IPv4 addresses. The active network is + * assumed not to be a loopback interface. + */ + @TargetApi(23) + private boolean isActiveNetworkIpv6Only() { + Network net = connectivityManager.getActiveNetwork(); + if (net == null) { + LOG.info("No active network"); + return false; + } + LinkProperties props = connectivityManager.getLinkProperties(net); + if (props == null) { + LOG.info("No link properties for active network"); + return false; + } + boolean hasIpv6Unicast = false; + for (LinkAddress linkAddress : props.getLinkAddresses()) { + InetAddress addr = linkAddress.getAddress(); + if (addr instanceof Inet4Address) return false; + if (!addr.isMulticastAddress()) hasIpv6Unicast = true; + } + return hasIpv6Unicast; + } + + /** + * Returns true if the device has at least one network interface with an + * IPv6 unicast address and no interfaces with IPv4 addresses, excluding + * loopback interfaces and interfaces that are + * {@link NetworkInterface#isUp() down}. If this method returns true and + * the device has internet access then it's via IPv6 only. + */ + private boolean areAllAvailableNetworksIpv6Only() { + try { + Enumeration<NetworkInterface> interfaces = getNetworkInterfaces(); + if (interfaces == null) { + LOG.info("No network interfaces"); + return false; + } + boolean hasIpv6Unicast = false; + for (NetworkInterface i : list(interfaces)) { + if (i.isLoopback() || !i.isUp()) continue; + for (InetAddress addr : list(i.getInetAddresses())) { + if (addr instanceof Inet4Address) return false; + if (!addr.isMulticastAddress()) hasIpv6Unicast = true; + } + } + return hasIpv6Unicast; + } catch (SocketException e) { + logException(LOG, e); + return false; + } + } + + private void updateConnectionStatus() { + eventBus.broadcast(new NetworkStatusEvent(getNetworkStatus())); + } + + private void scheduleConnectionStatusUpdate(int delay, TimeUnit unit) { + Cancellable newConnectivityCheck = + scheduler.schedule(this::updateConnectionStatus, eventExecutor, + delay, unit); + Cancellable oldConnectivityCheck = + connectivityCheck.getAndSet(newConnectivityCheck); + if (oldConnectivityCheck != null) oldConnectivityCheck.cancel(); + } + + private class NetworkStateReceiver extends BroadcastReceiver { + + @Override + public void onReceive(Context ctx, Intent i) { + String action = i.getAction(); + info(LOG, () -> "Received broadcast " + action); + updateConnectionStatus(); + if (isSleepOrDozeEvent(action)) { + // Allow time for the network to be enabled or disabled + scheduleConnectionStatusUpdate(1, MINUTES); + } else if (isApEvent(action)) { + // The state change may be broadcast before the AP address is + // visible, so delay handling the event + scheduleConnectionStatusUpdate(5, SECONDS); + } + } + + private boolean isSleepOrDozeEvent(@Nullable String action) { + boolean isSleep = ACTION_SCREEN_ON.equals(action) || + ACTION_SCREEN_OFF.equals(action); + boolean isDoze = SDK_INT >= 23 && + ACTION_DEVICE_IDLE_MODE_CHANGED.equals(action); + return isSleep || isDoze; + } + + private boolean isApEvent(@Nullable String action) { + return WIFI_AP_STATE_CHANGED_ACTION.equals(action) || + WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action); + } + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/core/tor/AndroidTorModule.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/core/tor/AndroidTorModule.kt index d6072abadb9a6247f81e62a96070a0d54d89753e..383759435419f726ff5778d6c9d7f30abebb3cf8 100644 --- a/mailbox-android/src/main/java/org/briarproject/mailbox/core/tor/AndroidTorModule.kt +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/core/tor/AndroidTorModule.kt @@ -7,9 +7,10 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent -import org.briarproject.mailbox.android.api.system.AndroidWakeLockManager +import org.briarproject.mailbox.core.event.EventBus import org.briarproject.mailbox.core.lifecycle.IoExecutor import org.briarproject.mailbox.core.lifecycle.LifecycleManager +import org.briarproject.mailbox.core.system.AndroidWakeLockManager import org.briarproject.mailbox.core.system.Clock import org.briarproject.mailbox.core.system.LocationUtils import org.briarproject.mailbox.core.system.ResourceProvider @@ -50,6 +51,7 @@ internal class AndroidTorModule { androidWakeLockManager: AndroidWakeLockManager, backoff: Backoff, lifecycleManager: LifecycleManager, + eventBus: EventBus, ) = AndroidTorPlugin( ioExecutor, app, @@ -62,7 +64,10 @@ internal class AndroidTorModule { backoff, architecture, app.getDir("tor", Context.MODE_PRIVATE), - ).also { lifecycleManager.registerService(it) } + ).also { + lifecycleManager.registerService(it) + eventBus.addListener(it) + } private val architecture: String? get() { @@ -79,4 +84,19 @@ internal class AndroidTorModule { return null } + @Provides + fun provideLocationUtils(locationUtils: AndroidLocationUtils): LocationUtils { + return locationUtils + } + + @Provides + @Singleton + fun provideNetworkManager( + lifecycleManager: LifecycleManager, + networkManager: AndroidNetworkManager, + ): NetworkManager { + lifecycleManager.registerService(networkManager) + return networkManager + } + } diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/core/tor/AndroidTorPlugin.java b/mailbox-android/src/main/java/org/briarproject/mailbox/core/tor/AndroidTorPlugin.java index 4c46231e0fd2c43aca82f842b1bc75ecd0aee70e..0992a4674e118abd9157f8abc4b1b52e4631a76b 100644 --- a/mailbox-android/src/main/java/org/briarproject/mailbox/core/tor/AndroidTorPlugin.java +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/core/tor/AndroidTorPlugin.java @@ -11,8 +11,8 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Build; -import org.briarproject.mailbox.android.api.system.AndroidWakeLock; -import org.briarproject.mailbox.android.api.system.AndroidWakeLockManager; +import org.briarproject.mailbox.core.system.AndroidWakeLock; +import org.briarproject.mailbox.core.system.AndroidWakeLockManager; import org.briarproject.mailbox.core.system.Clock; import org.briarproject.mailbox.core.system.LocationUtils; import org.briarproject.mailbox.core.system.ResourceProvider; diff --git a/mailbox-cli/src/main/java/org/briarproject/mailbox/cli/JavaCliModule.kt b/mailbox-cli/src/main/java/org/briarproject/mailbox/cli/JavaCliModule.kt index ee6c354a3c2cf83efe9b2bfed34d1beef61d547c..3638ea3a6125418665f874ae26bc1d29e49c7049 100644 --- a/mailbox-cli/src/main/java/org/briarproject/mailbox/cli/JavaCliModule.kt +++ b/mailbox-cli/src/main/java/org/briarproject/mailbox/cli/JavaCliModule.kt @@ -4,11 +4,15 @@ import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import org.briarproject.mailbox.core.CoreModule +import org.briarproject.mailbox.core.event.DefaultEventExecutorModule +import org.briarproject.mailbox.core.system.DefaultTaskSchedulerModule import org.briarproject.mailbox.core.tor.JavaTorModule @Module( includes = [ CoreModule::class, + DefaultEventExecutorModule::class, + DefaultTaskSchedulerModule::class, JavaTorModule::class, ] ) diff --git a/mailbox-cli/src/main/java/org/briarproject/mailbox/core/JavaCliEagerSingletons.kt b/mailbox-cli/src/main/java/org/briarproject/mailbox/core/JavaCliEagerSingletons.kt index ff5328b0bfca3e9b1fef791e12212a8f704ec039..446a1cc524401b85bd76e56e518c5c7f84d652bf 100644 --- a/mailbox-cli/src/main/java/org/briarproject/mailbox/core/JavaCliEagerSingletons.kt +++ b/mailbox-cli/src/main/java/org/briarproject/mailbox/core/JavaCliEagerSingletons.kt @@ -1,9 +1,11 @@ package org.briarproject.mailbox.core +import org.briarproject.mailbox.core.system.TaskScheduler import org.briarproject.mailbox.core.tor.JavaTorPlugin import javax.inject.Inject @Suppress("unused") internal class JavaCliEagerSingletons @Inject constructor( + val taskScheduler: TaskScheduler, val javaTorPlugin: JavaTorPlugin, ) diff --git a/mailbox-cli/src/main/java/org/briarproject/mailbox/core/tor/JavaCliNetworkManager.java b/mailbox-cli/src/main/java/org/briarproject/mailbox/core/tor/JavaCliNetworkManager.java new file mode 100644 index 0000000000000000000000000000000000000000..133055c3ec926b5941ca639d7214e3d4687c1c25 --- /dev/null +++ b/mailbox-cli/src/main/java/org/briarproject/mailbox/core/tor/JavaCliNetworkManager.java @@ -0,0 +1,46 @@ +package org.briarproject.mailbox.core.tor; + +import org.slf4j.Logger; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; + +import javax.inject.Inject; + +import static java.util.Collections.list; +import static org.briarproject.mailbox.core.util.LogUtils.logException; +import static org.briarproject.mailbox.core.util.NetworkUtils.getNetworkInterfaces; +import static org.slf4j.LoggerFactory.getLogger; + +class JavaCliNetworkManager implements NetworkManager { + + private static final Logger LOG = getLogger(JavaCliNetworkManager.class); + + @Inject + JavaCliNetworkManager() { + } + + @Override + public NetworkStatus getNetworkStatus() { + boolean connected = false, hasIpv4 = false, hasIpv6Unicast = false; + try { + for (NetworkInterface i : getNetworkInterfaces()) { + if (i.isLoopback() || !i.isUp()) continue; + for (InetAddress addr : list(i.getInetAddresses())) { + connected = true; + if (addr instanceof Inet4Address) { + hasIpv4 = true; + } else if (!addr.isMulticastAddress()) { + hasIpv6Unicast = true; + } + } + } + } catch (SocketException e) { + logException(LOG, e); + } + return new NetworkStatus(connected, false, !hasIpv4 && hasIpv6Unicast); + } + +} diff --git a/mailbox-cli/src/main/java/org/briarproject/mailbox/core/tor/JavaTorModule.kt b/mailbox-cli/src/main/java/org/briarproject/mailbox/core/tor/JavaTorModule.kt index c70b23b72d271b2bc59c610fb455dbd359d6a4b8..4454de7df29d6b2a23ddd196d631faeb490bb982 100644 --- a/mailbox-cli/src/main/java/org/briarproject/mailbox/core/tor/JavaTorModule.kt +++ b/mailbox-cli/src/main/java/org/briarproject/mailbox/core/tor/JavaTorModule.kt @@ -4,6 +4,7 @@ import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import org.briarproject.mailbox.core.event.EventBus import org.briarproject.mailbox.core.lifecycle.IoExecutor import org.briarproject.mailbox.core.lifecycle.LifecycleManager import org.briarproject.mailbox.core.system.Clock @@ -11,8 +12,9 @@ import org.briarproject.mailbox.core.system.LocationUtils import org.briarproject.mailbox.core.system.ResourceProvider import org.briarproject.mailbox.core.util.OsUtils.isLinux import org.slf4j.Logger -import org.slf4j.LoggerFactory +import org.slf4j.LoggerFactory.getLogger import java.io.File +import java.util.Locale import java.util.concurrent.Executor import javax.inject.Singleton @@ -21,7 +23,7 @@ import javax.inject.Singleton internal class JavaTorModule { companion object { - private val LOG: Logger = LoggerFactory.getLogger(JavaTorModule::class.java) + private val LOG: Logger = getLogger(JavaTorModule::class.java) } @Provides @@ -42,6 +44,7 @@ internal class JavaTorModule { circumventionProvider: CircumventionProvider, backoff: Backoff, lifecycleManager: LifecycleManager, + eventBus: EventBus, ): JavaTorPlugin { val configDir = File(System.getProperty("user.home") + File.separator + ".config") val mailboxDir = File(configDir, ".briar-mailbox") @@ -56,7 +59,10 @@ internal class JavaTorModule { backoff, architecture, torDir, - ).also { lifecycleManager.registerService(it) } + ).also { + lifecycleManager.registerService(it) + eventBus.addListener(it) + } } private val architecture: String? @@ -81,4 +87,14 @@ internal class JavaTorModule { return null } + @Provides + @Singleton + fun provideNetworkManager(networkManager: JavaCliNetworkManager): NetworkManager { + return networkManager + } + + @Provides + @Singleton + fun provideLocationUtils() = LocationUtils { Locale.getDefault().country } + } diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/CoreModule.kt b/mailbox-core/src/main/java/org/briarproject/mailbox/core/CoreModule.kt index b0c37df01a5b6bfe334310af7385ac377b8b8d17..229da7f21a7e0cf4d8916406a3e7556b2faf13e8 100644 --- a/mailbox-core/src/main/java/org/briarproject/mailbox/core/CoreModule.kt +++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/CoreModule.kt @@ -5,6 +5,7 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import org.briarproject.mailbox.core.db.DatabaseModule +import org.briarproject.mailbox.core.event.EventModule import org.briarproject.mailbox.core.lifecycle.LifecycleModule import org.briarproject.mailbox.core.server.WebServerModule import org.briarproject.mailbox.core.system.Clock @@ -13,6 +14,7 @@ import javax.inject.Singleton @Module( includes = [ + EventModule::class, LifecycleModule::class, DatabaseModule::class, WebServerModule::class, diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/PoliteExecutor.java b/mailbox-core/src/main/java/org/briarproject/mailbox/core/PoliteExecutor.java index 671842ce53737f946533ce9d418d28c37260efe4..4149077b7c878a6c494b8e561db5f0421909b2c6 100644 --- a/mailbox-core/src/main/java/org/briarproject/mailbox/core/PoliteExecutor.java +++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/PoliteExecutor.java @@ -1,8 +1,5 @@ package org.briarproject.mailbox.core; -import static org.briarproject.mailbox.core.util.LogUtils.now; -import static java.util.logging.Level.FINE; - import java.util.LinkedList; import java.util.Queue; import java.util.concurrent.Executor; @@ -10,6 +7,9 @@ import java.util.logging.Logger; import javax.annotation.concurrent.GuardedBy; +import static java.util.logging.Level.FINE; +import static org.briarproject.mailbox.core.util.LogUtils.now; + /** * An {@link Executor} that delegates its tasks to another {@link Executor} * while limiting the number of tasks that are delegated concurrently. Tasks diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/event/DefaultEventExecutorModule.java b/mailbox-core/src/main/java/org/briarproject/mailbox/core/event/DefaultEventExecutorModule.java new file mode 100644 index 0000000000000000000000000000000000000000..675d81de60b0ed93ef4d2d75bb18324f669e7060 --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/event/DefaultEventExecutorModule.java @@ -0,0 +1,33 @@ +package org.briarproject.mailbox.core.event; + +import java.util.concurrent.Executor; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; +import dagger.hilt.InstallIn; +import dagger.hilt.components.SingletonComponent; + +import static java.util.concurrent.Executors.newSingleThreadExecutor; + +/** + * Default implementation of {@link EventExecutor} that uses a dedicated thread + * to notify listeners of events. Applications may prefer to supply an + * implementation that uses an existing thread, such as the UI thread. + */ +@Module +@InstallIn(SingletonComponent.class) +public class DefaultEventExecutorModule { + + @Provides + @Singleton + @EventExecutor + Executor provideEventExecutor() { + return newSingleThreadExecutor(r -> { + Thread t = new Thread(r); + t.setDaemon(true); + return t; + }); + } +} diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/event/Event.java b/mailbox-core/src/main/java/org/briarproject/mailbox/core/event/Event.java new file mode 100644 index 0000000000000000000000000000000000000000..8faa67ecb985885402ab5db6deadede77323d915 --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/event/Event.java @@ -0,0 +1,7 @@ +package org.briarproject.mailbox.core.event; + +/** + * An abstract superclass for events. + */ +public abstract class Event { +} diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/event/EventBus.java b/mailbox-core/src/main/java/org/briarproject/mailbox/core/event/EventBus.java new file mode 100644 index 0000000000000000000000000000000000000000..b5fe6a10b12309a4440105236bdbe83fe0b84177 --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/event/EventBus.java @@ -0,0 +1,22 @@ +package org.briarproject.mailbox.core.event; + +public interface EventBus { + + /** + * Adds a listener to be notified when events occur. + */ + void addListener(EventListener l); + + /** + * Removes a listener. + */ + void removeListener(EventListener l); + + /** + * Asynchronously notifies all listeners of an event. Listeners are + * notified on the {@link EventExecutor}. + * <p> + * This method can safely be called while holding a lock. + */ + void broadcast(Event e); +} diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/event/EventBusImpl.java b/mailbox-core/src/main/java/org/briarproject/mailbox/core/event/EventBusImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..ae357248b7584837ebbfb510904eff82855ef20d --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/event/EventBusImpl.java @@ -0,0 +1,38 @@ +package org.briarproject.mailbox.core.event; + +import java.util.Collection; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executor; + +import javax.annotation.concurrent.ThreadSafe; +import javax.inject.Inject; + +@ThreadSafe +class EventBusImpl implements EventBus { + + private final Collection<EventListener> listeners = + new CopyOnWriteArrayList<>(); + private final Executor eventExecutor; + + @Inject + EventBusImpl(@EventExecutor Executor eventExecutor) { + this.eventExecutor = eventExecutor; + } + + @Override + public void addListener(EventListener l) { + listeners.add(l); + } + + @Override + public void removeListener(EventListener l) { + listeners.remove(l); + } + + @Override + public void broadcast(Event e) { + eventExecutor.execute(() -> { + for (EventListener l : listeners) l.eventOccurred(e); + }); + } +} diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/event/EventExecutor.java b/mailbox-core/src/main/java/org/briarproject/mailbox/core/event/EventExecutor.java new file mode 100644 index 0000000000000000000000000000000000000000..b8c03f18042207133dff78c52c0468cb4ba3d000 --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/event/EventExecutor.java @@ -0,0 +1,26 @@ +package org.briarproject.mailbox.core.event; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import javax.inject.Qualifier; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Annotation for injecting the executor for broadcasting events and running + * tasks that need to run in a defined order with respect to events. Also used + * for annotating methods that should run on the event executor. + * <p> + * The contract of this executor is that tasks are run in the order they're + * submitted, tasks are not run concurrently, and submitting a task will never + * block. Tasks must not block. Tasks submitted during shutdown are discarded. + */ +@Qualifier +@Target({FIELD, METHOD, PARAMETER}) +@Retention(RUNTIME) +public @interface EventExecutor { +} diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/event/EventListener.java b/mailbox-core/src/main/java/org/briarproject/mailbox/core/event/EventListener.java new file mode 100644 index 0000000000000000000000000000000000000000..97b423852d54752cccc5115beaa2020870427385 --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/event/EventListener.java @@ -0,0 +1,14 @@ +package org.briarproject.mailbox.core.event; + +/** + * An interface for receiving notifications when events occur. + */ +public interface EventListener { + + /** + * Called when an event is broadcast. Implementations of this method must + * not block. + */ + @EventExecutor + void eventOccurred(Event e); +} diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/event/EventModule.java b/mailbox-core/src/main/java/org/briarproject/mailbox/core/event/EventModule.java new file mode 100644 index 0000000000000000000000000000000000000000..8319a16cea839335a6af057cda49490bc50809b8 --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/event/EventModule.java @@ -0,0 +1,19 @@ +package org.briarproject.mailbox.core.event; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; +import dagger.hilt.InstallIn; +import dagger.hilt.components.SingletonComponent; + +@Module +@InstallIn(SingletonComponent.class) +public class EventModule { + + @Provides + @Singleton + EventBus provideEventBus(EventBusImpl eventBus) { + return eventBus; + } +} diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/IoExecutor.java b/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/IoExecutor.java index 45e8c51f15bc79ca99d84cd20c1e10ef055718d7..6008a696a0998255dede80e994415ce93a380bf5 100644 --- a/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/IoExecutor.java +++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/IoExecutor.java @@ -1,15 +1,15 @@ package org.briarproject.mailbox.core.lifecycle; -import static java.lang.annotation.ElementType.FIELD; -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.ElementType.PARAMETER; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - import java.lang.annotation.Retention; import java.lang.annotation.Target; import javax.inject.Qualifier; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + /** * Annotation for injecting the executor for long-running IO tasks. Also used * for annotating methods that should run on the IO executor. diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/system/DefaultTaskSchedulerModule.java b/mailbox-core/src/main/java/org/briarproject/mailbox/core/system/DefaultTaskSchedulerModule.java new file mode 100644 index 0000000000000000000000000000000000000000..1bbb56cf51559bd09ec9a40a2bfa5ac720e06dda --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/system/DefaultTaskSchedulerModule.java @@ -0,0 +1,32 @@ +package org.briarproject.mailbox.core.system; + +import org.briarproject.mailbox.core.lifecycle.LifecycleManager; + +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; + +@Module +public class DefaultTaskSchedulerModule { + + private final ScheduledExecutorService scheduledExecutorService; + + public DefaultTaskSchedulerModule() { + // Discard tasks that are submitted during shutdown + RejectedExecutionHandler policy = + new ScheduledThreadPoolExecutor.DiscardPolicy(); + scheduledExecutorService = new ScheduledThreadPoolExecutor(1, policy); + } + + @Provides + @Singleton + TaskScheduler provideTaskScheduler(LifecycleManager lifecycleManager) { + lifecycleManager.registerForShutdown(scheduledExecutorService); + return new TaskSchedulerImpl(scheduledExecutorService); + } +} diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/system/TaskScheduler.java b/mailbox-core/src/main/java/org/briarproject/mailbox/core/system/TaskScheduler.java new file mode 100644 index 0000000000000000000000000000000000000000..83972016ece1d036c57b713917762ea04a2af60d --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/system/TaskScheduler.java @@ -0,0 +1,40 @@ +package org.briarproject.mailbox.core.system; + +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; + +/** + * A service that can be used to schedule the execution of tasks. + */ +public interface TaskScheduler { + + /** + * Submits the given task to the given executor after the given delay. + * <p> + * If the platform supports wake locks, a wake lock will be held while + * submitting and running the task. + */ + Cancellable schedule(Runnable task, Executor executor, long delay, + TimeUnit unit); + + /** + * Submits the given task to the given executor after the given delay, + * and then repeatedly with the given interval between executions + * (measured from the end of one execution to the beginning of the next). + * <p> + * If the platform supports wake locks, a wake lock will be held while + * submitting and running the task. + */ + Cancellable scheduleWithFixedDelay(Runnable task, Executor executor, + long delay, long interval, TimeUnit unit); + + interface Cancellable { + + /** + * Cancels the task if it has not already started running. If the task + * is {@link #scheduleWithFixedDelay(Runnable, Executor, long, long, TimeUnit) periodic}, + * all future executions of the task are cancelled. + */ + void cancel(); + } +} diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/system/TaskSchedulerImpl.java b/mailbox-core/src/main/java/org/briarproject/mailbox/core/system/TaskSchedulerImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..80117a82d265d396f76b300ec8ff40a6c99c52df --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/system/TaskSchedulerImpl.java @@ -0,0 +1,39 @@ +package org.briarproject.mailbox.core.system; + +import java.util.concurrent.Executor; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import javax.annotation.concurrent.ThreadSafe; + +/** + * A {@link TaskScheduler} that uses a {@link ScheduledExecutorService}. + */ +@ThreadSafe +class TaskSchedulerImpl implements TaskScheduler { + + private final ScheduledExecutorService scheduledExecutorService; + + TaskSchedulerImpl(ScheduledExecutorService scheduledExecutorService) { + this.scheduledExecutorService = scheduledExecutorService; + } + + @Override + public Cancellable schedule(Runnable task, Executor executor, long delay, + TimeUnit unit) { + Runnable execute = () -> executor.execute(task); + ScheduledFuture<?> future = + scheduledExecutorService.schedule(execute, delay, unit); + return () -> future.cancel(false); + } + + @Override + public Cancellable scheduleWithFixedDelay(Runnable task, Executor executor, + long delay, long interval, TimeUnit unit) { + Runnable execute = () -> executor.execute(task); + ScheduledFuture<?> future = scheduledExecutorService. + scheduleWithFixedDelay(execute, delay, interval, unit); + return () -> future.cancel(false); + } +} diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/system/Wakeful.java b/mailbox-core/src/main/java/org/briarproject/mailbox/core/system/Wakeful.java index a34038db1ad182d519a92b2d94ff46d9328512ea..02a0218b984089454fb8e7a8a6a7211c02873684 100644 --- a/mailbox-core/src/main/java/org/briarproject/mailbox/core/system/Wakeful.java +++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/system/Wakeful.java @@ -1,13 +1,13 @@ package org.briarproject.mailbox.core.system; -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - import java.lang.annotation.Retention; import java.lang.annotation.Target; import javax.inject.Qualifier; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + /** * Annotation for methods that must be called while holding a wake lock, if * the platform supports wake locks. diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/tor/NetworkStatusEvent.java b/mailbox-core/src/main/java/org/briarproject/mailbox/core/tor/NetworkStatusEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..db9366a553263a5551ebc56bab4dc4eaa20ea3b6 --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/tor/NetworkStatusEvent.java @@ -0,0 +1,19 @@ +package org.briarproject.mailbox.core.tor; + +import org.briarproject.mailbox.core.event.Event; + +import javax.annotation.concurrent.Immutable; + +@Immutable +public class NetworkStatusEvent extends Event { + + private final NetworkStatus status; + + public NetworkStatusEvent(NetworkStatus status) { + this.status = status; + } + + public NetworkStatus getStatus() { + return status; + } +} \ No newline at end of file diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/tor/TorModule.kt b/mailbox-core/src/main/java/org/briarproject/mailbox/core/tor/TorModule.kt index 833f19012bbff51d4f09fcaab2ea1cb7d204dfcb..c1afee5d4b9cadbeabb5f68f61360e5fffdac7ca 100644 --- a/mailbox-core/src/main/java/org/briarproject/mailbox/core/tor/TorModule.kt +++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/tor/TorModule.kt @@ -5,7 +5,6 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import org.briarproject.mailbox.core.lifecycle.IoExecutor -import org.briarproject.mailbox.core.system.LocationUtils import java.util.concurrent.Executor import java.util.concurrent.SynchronousQueue import java.util.concurrent.ThreadPoolExecutor @@ -23,16 +22,6 @@ internal class TorModule { private const val BACKOFF_BASE = 1.2 } - @Provides - @Singleton - fun provideNetworkManager() = NetworkManager { - NetworkStatus(true, true, false) - } - - @Provides - @Singleton - fun provideLocationUtils() = LocationUtils { "" } - @Provides @Singleton fun provideCircumventionProvider(): CircumventionProvider = object : CircumventionProvider { diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/tor/TorPlugin.java b/mailbox-core/src/main/java/org/briarproject/mailbox/core/tor/TorPlugin.java index d58d7afca829ed5a2b8438ff31275771783fbe6c..1ec14a2e1e37ef77ae933afd69a912a9eddec728 100644 --- a/mailbox-core/src/main/java/org/briarproject/mailbox/core/tor/TorPlugin.java +++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/tor/TorPlugin.java @@ -1,29 +1,11 @@ package org.briarproject.mailbox.core.tor; -import static net.freehaven.tor.control.TorControlCommands.HS_ADDRESS; -import static net.freehaven.tor.control.TorControlCommands.HS_PRIVKEY; -import static org.briarproject.mailbox.core.tor.TorConstants.CONTROL_PORT; -import static org.briarproject.mailbox.core.tor.TorPlugin.State.ACTIVE; -import static org.briarproject.mailbox.core.tor.TorPlugin.State.DISABLED; -import static org.briarproject.mailbox.core.tor.TorPlugin.State.ENABLING; -import static org.briarproject.mailbox.core.tor.TorPlugin.State.INACTIVE; -import static org.briarproject.mailbox.core.tor.TorPlugin.State.STARTING_STOPPING; -import static org.briarproject.mailbox.core.util.IoUtils.copyAndClose; -import static org.briarproject.mailbox.core.util.IoUtils.tryToClose; -import static org.briarproject.mailbox.core.util.LogUtils.info; -import static org.briarproject.mailbox.core.util.LogUtils.logException; -import static org.briarproject.mailbox.core.util.LogUtils.warn; -import static org.briarproject.mailbox.core.util.PrivacyUtils.scrubOnion; -import static org.slf4j.LoggerFactory.getLogger; -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; -import static java.util.Collections.singletonMap; -import static java.util.Objects.requireNonNull; - import net.freehaven.tor.control.EventHandler; import net.freehaven.tor.control.TorControlConnection; import org.briarproject.mailbox.core.PoliteExecutor; +import org.briarproject.mailbox.core.event.Event; +import org.briarproject.mailbox.core.event.EventListener; import org.briarproject.mailbox.core.lifecycle.IoExecutor; import org.briarproject.mailbox.core.lifecycle.Service; import org.briarproject.mailbox.core.lifecycle.ServiceException; @@ -55,7 +37,27 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.ThreadSafe; -abstract class TorPlugin implements Service, EventHandler { +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; +import static java.util.Objects.requireNonNull; +import static net.freehaven.tor.control.TorControlCommands.HS_ADDRESS; +import static net.freehaven.tor.control.TorControlCommands.HS_PRIVKEY; +import static org.briarproject.mailbox.core.tor.TorConstants.CONTROL_PORT; +import static org.briarproject.mailbox.core.tor.TorPlugin.State.ACTIVE; +import static org.briarproject.mailbox.core.tor.TorPlugin.State.DISABLED; +import static org.briarproject.mailbox.core.tor.TorPlugin.State.ENABLING; +import static org.briarproject.mailbox.core.tor.TorPlugin.State.INACTIVE; +import static org.briarproject.mailbox.core.tor.TorPlugin.State.STARTING_STOPPING; +import static org.briarproject.mailbox.core.util.IoUtils.copyAndClose; +import static org.briarproject.mailbox.core.util.IoUtils.tryToClose; +import static org.briarproject.mailbox.core.util.LogUtils.info; +import static org.briarproject.mailbox.core.util.LogUtils.logException; +import static org.briarproject.mailbox.core.util.LogUtils.warn; +import static org.briarproject.mailbox.core.util.PrivacyUtils.scrubOnion; +import static org.slf4j.LoggerFactory.getLogger; + +abstract class TorPlugin implements Service, EventHandler, EventListener { private static final Logger LOG = getLogger(TorPlugin.class); @@ -420,8 +422,7 @@ abstract class TorPlugin implements Service, EventHandler { info(LOG, () -> "OR connection " + status + " " + orName); if (status.equals("CLOSED") || status.equals("FAILED")) { // Check whether we've lost connectivity - updateConnectionStatus(networkManager.getNetworkStatus() - ); + updateConnectionStatus(networkManager.getNetworkStatus()); } } @@ -449,8 +450,11 @@ abstract class TorPlugin implements Service, EventHandler { } } - public void onNetworkStatusChanged() { - updateConnectionStatus(networkManager.getNetworkStatus()); + @Override + public void eventOccurred(Event e) { + if (e instanceof NetworkStatusEvent) { + updateConnectionStatus(((NetworkStatusEvent) e).getStatus()); + } } private void disableNetwork() { diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/util/IoUtils.java b/mailbox-core/src/main/java/org/briarproject/mailbox/core/util/IoUtils.java index 5c9befdefb6dda6dbdfd46dfeea83e547e1a0cf8..f0f70a70ecb71355a14a69833ded9f33c6e395af 100644 --- a/mailbox-core/src/main/java/org/briarproject/mailbox/core/util/IoUtils.java +++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/util/IoUtils.java @@ -1,9 +1,5 @@ package org.briarproject.mailbox.core.util; -import static org.briarproject.mailbox.core.util.LogUtils.logException; -import static org.briarproject.mailbox.core.util.LogUtils.warn; -import static org.slf4j.LoggerFactory.getLogger; - import org.slf4j.Logger; import java.io.Closeable; @@ -17,6 +13,10 @@ import java.net.Socket; import javax.annotation.Nullable; +import static org.briarproject.mailbox.core.util.LogUtils.logException; +import static org.briarproject.mailbox.core.util.LogUtils.warn; +import static org.slf4j.LoggerFactory.getLogger; + public class IoUtils { private static final Logger LOG = getLogger(IoUtils.class); diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/util/NetworkUtils.java b/mailbox-core/src/main/java/org/briarproject/mailbox/core/util/NetworkUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..88b9306e8d692d0b5721832d6ff719da4f3649e6 --- /dev/null +++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/util/NetworkUtils.java @@ -0,0 +1,31 @@ +package org.briarproject.mailbox.core.util; + +import org.slf4j.Logger; + +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.Enumeration; +import java.util.List; + +import static java.util.Collections.emptyList; +import static java.util.Collections.list; +import static org.briarproject.mailbox.core.util.LogUtils.logException; +import static org.slf4j.LoggerFactory.getLogger; + +public class NetworkUtils { + + private static final Logger LOG = getLogger(NetworkUtils.class.getName()); + + public static List<NetworkInterface> getNetworkInterfaces() { + try { + Enumeration<NetworkInterface> ifaces = + NetworkInterface.getNetworkInterfaces(); + // Despite what the docs say, the return value can be null + //noinspection ConstantConditions + return ifaces == null ? emptyList() : list(ifaces); + } catch (SocketException e) { + logException(LOG, e); + return emptyList(); + } + } +} diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/util/PrivacyUtils.java b/mailbox-core/src/main/java/org/briarproject/mailbox/core/util/PrivacyUtils.java index 246d4d2c99e1f5b997bdfa839a3b281253ef7bc7..f0451e1bc53a1011cecc21bd2e6f9844cd14740e 100644 --- a/mailbox-core/src/main/java/org/briarproject/mailbox/core/util/PrivacyUtils.java +++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/util/PrivacyUtils.java @@ -1,9 +1,5 @@ package org.briarproject.mailbox.core.util; -import static org.briarproject.mailbox.core.util.StringUtils.isNullOrEmpty; -import static org.briarproject.mailbox.core.util.StringUtils.isValidMac; -import static org.briarproject.mailbox.core.util.StringUtils.toHexString; - import java.net.Inet4Address; import java.net.InetAddress; import java.net.InetSocketAddress; @@ -11,6 +7,10 @@ import java.net.SocketAddress; import javax.annotation.Nullable; +import static org.briarproject.mailbox.core.util.StringUtils.isNullOrEmpty; +import static org.briarproject.mailbox.core.util.StringUtils.isValidMac; +import static org.briarproject.mailbox.core.util.StringUtils.toHexString; + public class PrivacyUtils { public static String scrubOnion(String onion) { diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/util/StringUtils.java b/mailbox-core/src/main/java/org/briarproject/mailbox/core/util/StringUtils.java index 2a5cf7991ddecd9fa982300a824c78b03808fd75..5198a75d4c32a02ca46956351d8372754111a8ed 100644 --- a/mailbox-core/src/main/java/org/briarproject/mailbox/core/util/StringUtils.java +++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/util/StringUtils.java @@ -1,8 +1,5 @@ package org.briarproject.mailbox.core.util; -import static java.nio.charset.CodingErrorAction.IGNORE; -import static java.util.regex.Pattern.CASE_INSENSITIVE; - import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.charset.CharacterCodingException; @@ -14,6 +11,9 @@ import java.util.regex.Pattern; import javax.annotation.Nullable; +import static java.nio.charset.CodingErrorAction.IGNORE; +import static java.util.regex.Pattern.CASE_INSENSITIVE; + public class StringUtils { private static final Charset UTF_8 = Charset.forName("UTF-8");