diff --git a/mailbox-android/src/main/AndroidManifest.xml b/mailbox-android/src/main/AndroidManifest.xml index 2d7e4c828214f06da4492b43d79d6f3f8a24cc3c..ee9849f4d42f338fdbba1853db241dc546aeca6b 100644 --- a/mailbox-android/src/main/AndroidManifest.xml +++ b/mailbox-android/src/main/AndroidManifest.xml @@ -2,11 +2,16 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.briarproject.mailbox"> + <uses-feature + android:name="android.hardware.touchscreen" + android:required="false" /> + <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> + <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <application android:name=".android.MailboxApplication" @@ -19,7 +24,9 @@ <service android:name=".android.MailboxService" /> - <activity android:name=".android.MainActivity"> + <activity + android:name=".android.MainActivity" + android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> @@ -29,6 +36,15 @@ <receiver android:name=".core.system.AlarmReceiver" /> + <receiver + android:name=".android.StartReceiver" + android:exported="false"> + <intent-filter> + <action android:name="android.intent.action.BOOT_COMPLETED" /> + <action android:name="android.intent.action.MY_PACKAGE_REPLACED" /> + </intent-filter> + </receiver> + </application> </manifest> diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxNotificationManager.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxNotificationManager.kt index a259a3028f52f1c68ef15b1baca609495eb53eda..fa67a900c267f9c6c5aca638f52469ed4c759efe 100644 --- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxNotificationManager.kt +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxNotificationManager.kt @@ -74,7 +74,7 @@ class MailboxNotificationManager @Inject constructor( return NotificationCompat.Builder(ctx, CHANNEL_ID) .setContentTitle(ctx.getString(R.string.notification_mailbox_title)) .setContentText(ctx.getString(R.string.notification_mailbox_content)) - .setSmallIcon(R.drawable.ic_launcher_foreground) + .setSmallIcon(R.drawable.ic_notification_foreground) .setContentIntent(pendingIntent) .setPriority(PRIORITY_MIN) .build() 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 c13b1a5a0307fc9b256d17fe74c3ed37a5d7b2ed..6f47c9e9e8f0848aa3ccc97befbd83678229e42d 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 @@ -29,13 +29,13 @@ import androidx.core.content.ContextCompat import dagger.hilt.android.AndroidEntryPoint import org.briarproject.mailbox.android.MailboxNotificationManager.Companion.NOTIFICATION_MAIN_ID 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.AndroidWakeLock import org.briarproject.mailbox.core.system.AndroidWakeLockManager import org.slf4j.LoggerFactory.getLogger import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject +import kotlin.concurrent.thread import kotlin.system.exitProcess @AndroidEntryPoint @@ -70,52 +70,59 @@ class MailboxService : Service() { @Inject internal lateinit var notificationManager: MailboxNotificationManager + private lateinit var lifecycleWakeLock: AndroidWakeLock + override fun onCreate() { super.onCreate() LOG.info("Created") if (created.getAndSet(true)) { LOG.warn("Already created") - // FIXME when can this happen? Next line will kill app + // This is a canary to notify us about strange behavior concerning service creation + // in logs and bug reports. Calling stopSelf() kills the app. stopSelf() return } - // Hold a wake lock during startup - wakeLockManager.runWakefully({ - startForeground(NOTIFICATION_MAIN_ID, notificationManager.serviceNotification) - // Start the services in a background thread - wakeLockManager.executeWakefully({ - val result: StartResult = lifecycleManager.startServices() - when { - result === SUCCESS -> started = true - result === ALREADY_RUNNING -> { - LOG.warn("Already running") - // FIXME when can this happen? Next line will kill app - stopSelf() - } - else -> { - if (LOG.isWarnEnabled) LOG.warn("Startup failed: $result") - // TODO: implement this - // and start activity in new process, so we can kill this one - // showStartupFailure(result) - stopSelf() - } - } - }, "LifecycleStartup") - // Register for device shutdown broadcasts - receiver = object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - LOG.info("Device is shutting down") + // We hold a wake lock during the whole lifecycle. We have a one-to-one relationship + // between MailboxService and the LifecycleManager. As we do not support lifecycle restarts + // only a single MailboxService is allowed to start and stop with the LifecycleManager + // singleton. Should the service be killed and restarted, the LifecycleManager must also + // have been destroyed and there is no way to recover except via a restart of the app. + // So should a second MailboxService be started anytime, this is a unrecoverable situation + // and we stop the app. + // Acquiring the wakelock here and releasing it as the last thing before exitProcess() + // during onDestroy() makes sure it is being held during the whole lifecycle. + lifecycleWakeLock = wakeLockManager.createWakeLock("Lifecycle") + lifecycleWakeLock.acquire() + + startForeground(NOTIFICATION_MAIN_ID, notificationManager.serviceNotification) + // Start the services in a background thread + thread { + val result = lifecycleManager.startServices() + when { + result === SUCCESS -> started = true + else -> { + if (LOG.isWarnEnabled) LOG.warn("Startup failed: $result") + // TODO: implement this + // and start activity in new process, so we can kill this one + // showStartupFailure(result) stopSelf() } } - val filter = IntentFilter() - filter.addAction(Intent.ACTION_SHUTDOWN) - filter.addAction("android.intent.action.QUICKBOOT_POWEROFF") - filter.addAction("com.htc.intent.action.QUICKBOOT_POWEROFF") - registerReceiver(receiver, filter) - }, "LifecycleStartup") + } + // Register for device shutdown broadcasts + receiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + LOG.info("Device is shutting down") + stopSelf() + } + } + val filter = IntentFilter() + filter.addAction(Intent.ACTION_SHUTDOWN) + filter.addAction("android.intent.action.QUICKBOOT_POWEROFF") + filter.addAction("com.htc.intent.action.QUICKBOOT_POWEROFF") + registerReceiver(receiver, filter) } override fun onBind(intent: Intent): IBinder? { @@ -123,23 +130,23 @@ class MailboxService : Service() { } override fun onDestroy() { - wakeLockManager.runWakefully({ - super.onDestroy() - LOG.info("Destroyed") - stopForeground(true) - if (receiver != null) unregisterReceiver(receiver) - wakeLockManager.executeWakefully({ - try { - if (started) { - lifecycleManager.stopServices() - lifecycleManager.waitForShutdown() - } - } catch (e: InterruptedException) { - LOG.info("Interrupted while waiting for shutdown") - } - LOG.info("Exiting") - exitProcess(0) - }, "LifecycleShutdown") - }, "LifecycleShutdown") + super.onDestroy() + LOG.info("Destroyed") + stopForeground(true) + if (receiver != null) unregisterReceiver(receiver) + if (started) { + try { + lifecycleManager.stopServices() + lifecycleManager.waitForShutdown() + } catch (e: InterruptedException) { + LOG.info("Interrupted while waiting for shutdown") + } + } + // Do not exit within wakeful execution, otherwise we will never release the wake locks. + // Or maybe we want to do precisely that to make sure exiting really happens and the app + // doesn't get suspended before it gets a chance to exit? + lifecycleWakeLock.release() + LOG.info("Exiting") + exitProcess(0) } } diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxViewModel.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxViewModel.kt index 6730309c147abdb859c35a6ebd52057170b17d56..7cdf4dfa16a8b100f4af24653a055ceaae941278 100644 --- a/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxViewModel.kt +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/MailboxViewModel.kt @@ -30,9 +30,9 @@ import kotlinx.coroutines.flow.StateFlow import org.briarproject.android.dontkillmelib.DozeHelper import org.briarproject.mailbox.core.lifecycle.LifecycleManager import org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState -import org.briarproject.mailbox.core.system.AndroidWakeLockManager import org.briarproject.mailbox.core.system.DozeWatchdog import javax.inject.Inject +import kotlin.concurrent.thread @HiltViewModel class MailboxViewModel @Inject constructor( @@ -41,7 +41,6 @@ class MailboxViewModel @Inject constructor( private val dozeWatchdog: DozeWatchdog, handle: SavedStateHandle, private val lifecycleManager: LifecycleManager, - private val wakeLockManager: AndroidWakeLockManager, ) : AndroidViewModel(app) { val needToShowDoNotKillMeFragment get() = dozeHelper.needToShowDoNotKillMeFragment(app) @@ -68,10 +67,11 @@ class MailboxViewModel @Inject constructor( } fun wipe() { - wakeLockManager.executeWakefully({ + thread { + // TODO: handle return value lifecycleManager.wipeMailbox() MailboxService.stopService(getApplication()) - }, "LifecycleWipe") + } } fun getAndResetDozeFlag() = dozeWatchdog.andResetDozeFlag diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/android/StartReceiver.kt b/mailbox-android/src/main/java/org/briarproject/mailbox/android/StartReceiver.kt new file mode 100644 index 0000000000000000000000000000000000000000..9865d44008e9c131cd57ec6d083f8440b87ffabb --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/android/StartReceiver.kt @@ -0,0 +1,55 @@ +/* + * Briar Mailbox + * Copyright (C) 2021-2022 The Briar Project + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + */ + +package org.briarproject.mailbox.android + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.Intent.ACTION_BOOT_COMPLETED +import android.content.Intent.ACTION_MY_PACKAGE_REPLACED +import dagger.hilt.android.AndroidEntryPoint +import org.briarproject.mailbox.core.lifecycle.LifecycleManager +import org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState.NOT_STARTED +import org.briarproject.mailbox.core.util.LogUtils.debug +import org.slf4j.LoggerFactory.getLogger +import javax.inject.Inject + +private val LOG = getLogger(StartReceiver::class.java) + +@AndroidEntryPoint +class StartReceiver : BroadcastReceiver() { + + @Inject + internal lateinit var lifecycleManager: LifecycleManager + + override fun onReceive(context: Context, intent: Intent) { + val action = intent.action + if (action != ACTION_BOOT_COMPLETED && action != ACTION_MY_PACKAGE_REPLACED) return + + val lifecycleState = lifecycleManager.lifecycleStateFlow.value + LOG.debug { "Received $action in state ${lifecycleState.name}" } + if (lifecycleState == NOT_STARTED) { + // On API 31, we can still start a foreground service from background here: + // https://developer.android.com/guide/components/foreground-services#background-start-restriction-exemptions + MailboxService.startService(context) + } + } + +} 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 index 56df13486773ef9d0b0916a54723b972b1f51e20..c6bd6839db88a7a94c662eb97c5a7028f952459f 100644 --- 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 @@ -63,7 +63,6 @@ public class AndroidTaskScheduler implements TaskScheduler, Service { 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; @@ -72,10 +71,8 @@ public class AndroidTaskScheduler implements TaskScheduler, Service { 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)); @@ -107,29 +104,25 @@ public class AndroidTaskScheduler implements TaskScheduler, Service { } 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"); + 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); + } } 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"); + Runnable wrapped = () -> executor.execute(task); Future<?> check = scheduleCheckForDueTasks(delay, unit); - ScheduledTask s = new ScheduledTask(wakeful, dueMillis, check, + ScheduledTask s = new ScheduledTask(wrapped, dueMillis, check, cancelled); synchronized (lock) { tasks.add(s); @@ -149,12 +142,10 @@ public class AndroidTaskScheduler implements TaskScheduler, Service { } private Future<?> scheduleCheckForDueTasks(long delay, TimeUnit unit) { - Runnable wakeful = () -> wakeLockManager.runWakefully( - this::runDueTasks, "TaskScheduler"); - return scheduledExecutorService.schedule(wakeful, delay, unit); + return scheduledExecutorService + .schedule(this::runDueTasks, delay, unit); } - @Wakeful private void runDueTasks() { long now = SystemClock.elapsedRealtime(); List<ScheduledTask> due = new ArrayList<>(); 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 index 72733855e7bc5bef9cc55de6bf4d304826469179..0c5383f8fcb2c2e752207e1fe6e50d6fa38db03b 100644 --- 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 @@ -43,7 +43,7 @@ public class AndroidTaskSchedulerModule { AndroidWakeLockManager wakeLockManager, ScheduledExecutorService scheduledExecutorService) { AndroidTaskScheduler scheduler = new AndroidTaskScheduler(app, - wakeLockManager, scheduledExecutorService); + scheduledExecutorService); lifecycleManager.registerService(scheduler); return scheduler; } diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidWakeLockManager.java b/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidWakeLockManager.java index 449828b396ee77a736ba5c2950d0ce5d153c22c4..8d717149a29c837393a378cf1ac39e82544c86b2 100644 --- a/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidWakeLockManager.java +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidWakeLockManager.java @@ -19,8 +19,6 @@ package org.briarproject.mailbox.core.system; -import java.util.concurrent.Executor; - public interface AndroidWakeLockManager { /** @@ -28,27 +26,4 @@ public interface AndroidWakeLockManager { * logging; the underlying OS wake lock will use its own tag. */ AndroidWakeLock createWakeLock(String tag); - - /** - * Runs the given task while holding a wake lock. - */ - void runWakefully(Runnable r, String tag); - - /** - * Submits the given task to the given executor while holding a wake lock. - * The lock is released when the task completes, or if an exception is - * thrown while submitting or running the task. - */ - void executeWakefully(Runnable r, Executor executor, String tag); - - /** - * Starts a dedicated thread to run the given task asynchronously. A wake - * lock is acquired before starting the thread and released when the task - * completes, or if an exception is thrown while starting the thread or - * running the task. - * <p> - * This method should only be used for lifecycle management tasks that - * can't be run on an executor. - */ - void executeWakefully(Runnable r, String tag); } diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidWakeLockManagerImpl.java b/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidWakeLockManagerImpl.java index 13bbfbe4a44c7694a78b60247bbd8d3ae1b354f4..c00275eff38f81abe57be4cbf39db37d2073a64a 100644 --- a/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidWakeLockManagerImpl.java +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/core/system/AndroidWakeLockManagerImpl.java @@ -24,7 +24,6 @@ import android.content.Context; import android.content.pm.PackageManager; import android.os.PowerManager; -import java.util.concurrent.Executor; import java.util.concurrent.ScheduledExecutorService; import javax.inject.Inject; @@ -66,57 +65,6 @@ class AndroidWakeLockManagerImpl implements AndroidWakeLockManager { return new AndroidWakeLockImpl(sharedWakeLock, tag); } - @Override - public void runWakefully(Runnable r, String tag) { - AndroidWakeLock wakeLock = createWakeLock(tag); - wakeLock.acquire(); - try { - r.run(); - } finally { - wakeLock.release(); - } - } - - @Override - public void executeWakefully(Runnable r, Executor executor, String tag) { - AndroidWakeLock wakeLock = createWakeLock(tag); - wakeLock.acquire(); - try { - executor.execute(() -> { - try { - r.run(); - } finally { - // Release the wake lock if the task throws an exception - wakeLock.release(); - } - }); - } catch (Exception e) { - // Release the wake lock if the executor throws an exception when - // we submit the task (in which case the release() call above won't - // happen) - wakeLock.release(); - throw e; - } - } - - @Override - public void executeWakefully(Runnable r, String tag) { - AndroidWakeLock wakeLock = createWakeLock(tag); - wakeLock.acquire(); - try { - new Thread(() -> { - try { - r.run(); - } finally { - wakeLock.release(); - } - }).start(); - } catch (Exception e) { - wakeLock.release(); - throw e; - } - } - private String getWakeLockTag(Context ctx) { PackageManager pm = ctx.getPackageManager(); if (isInstalled(pm, "com.huawei.powergenie")) { diff --git a/mailbox-android/src/main/res/drawable-anydpi-v24/ic_notification_foreground.xml b/mailbox-android/src/main/res/drawable-anydpi-v24/ic_notification_foreground.xml new file mode 100644 index 0000000000000000000000000000000000000000..e6104ddb4a95553cf9442da1e96bb54c49416511 --- /dev/null +++ b/mailbox-android/src/main/res/drawable-anydpi-v24/ic_notification_foreground.xml @@ -0,0 +1,15 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <group + android:scaleX="0.64941174" + android:scaleY="0.64941174" + android:translateX="2.2588236" + android:translateY="0.96"> + <path + android:fillColor="#ffffff" + android:pathData="M3.334,0C2.4499,-0 1.6017,0.3579 0.9766,0.9941C0.3514,1.6304 0,2.4928 0,3.3926L0,25.1055C0,26.0053 0.3514,26.8696 0.9766,27.5059C1.6017,28.1421 2.4499,28.498 3.334,28.498L21.4902,28.498L27.5391,33.6426C28.5157,34.4732 30,33.766 30,32.4707L30,3.3926C30,2.4928 29.6485,1.6304 29.0234,0.9941C28.3983,0.3579 27.55,0 26.666,0L3.334,0zM5.334,5.4277L24.666,5.4277L24.666,15.0332L17.9121,15.0332L17.9121,18.3555L20.3828,18.3555C20.8283,18.3555 21.0512,18.904 20.7363,19.2246L15.4102,24.6445C15.2149,24.8432 14.8984,24.8432 14.7031,24.6445L9.377,19.2246C9.062,18.904 9.285,18.3555 9.7305,18.3555L12.2012,18.3555L12.2012,15.0332L5.334,15.0332L5.334,5.4277z" /> + </group> +</vector> diff --git a/mailbox-android/src/main/res/drawable-hdpi/ic_notification_foreground.png b/mailbox-android/src/main/res/drawable-hdpi/ic_notification_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..3a8fb15ba1a045dd1d4d6bbf356103eae1bd8d11 Binary files /dev/null and b/mailbox-android/src/main/res/drawable-hdpi/ic_notification_foreground.png differ diff --git a/mailbox-android/src/main/res/drawable-mdpi/ic_notification_foreground.png b/mailbox-android/src/main/res/drawable-mdpi/ic_notification_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..e2db859527dabf48159265d869503331ecf819a1 Binary files /dev/null and b/mailbox-android/src/main/res/drawable-mdpi/ic_notification_foreground.png differ diff --git a/mailbox-android/src/main/res/drawable-xhdpi/ic_notification_foreground.png b/mailbox-android/src/main/res/drawable-xhdpi/ic_notification_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..c85b6626b9e7726309dbbad7f83c7c388ff987c9 Binary files /dev/null and b/mailbox-android/src/main/res/drawable-xhdpi/ic_notification_foreground.png differ diff --git a/mailbox-android/src/main/res/drawable-xxhdpi/ic_notification_foreground.png b/mailbox-android/src/main/res/drawable-xxhdpi/ic_notification_foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..170f1d5ab39b5b45aeede7f546287831ffb6d294 Binary files /dev/null and b/mailbox-android/src/main/res/drawable-xxhdpi/ic_notification_foreground.png differ diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/db/JdbcDatabase.kt b/mailbox-core/src/main/java/org/briarproject/mailbox/core/db/JdbcDatabase.kt index b9039cd2ac6f2ee492ec71c19396a2bc4d76d030..73713e8015265d892f8dc6e853cc6935db7a5add 100644 --- a/mailbox-core/src/main/java/org/briarproject/mailbox/core/db/JdbcDatabase.kt +++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/db/JdbcDatabase.kt @@ -134,7 +134,7 @@ abstract class JdbcDatabase(private val dbTypes: DatabaseTypes, private val cloc listener?.onDatabaseCompaction() val start: Long = now() compactAndClose() - logDuration(LOG, { "Compacting database" }, start) + logDuration(LOG, start) { "Compacting database" } // Allow the next transaction to reopen the DB connectionsLock.withLock { closed = false @@ -221,12 +221,12 @@ abstract class JdbcDatabase(private val dbTypes: DatabaseTypes, private val cloc LOG.trace("locking read lock") lock.readLock().lock() LOG.trace("locked read lock") - logDuration(LOG, { "Waiting for read lock" }, start) + logDuration(LOG, start) { "Waiting for read lock" } } else { LOG.trace { "locking write lock" } lock.writeLock().lock() LOG.trace { "locked write lock" } - logDuration(LOG, { "Waiting for write lock" }, start) + logDuration(LOG, start) { "Waiting for write lock" } } return try { Transaction(startTransaction(), readOnly) diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManager.java b/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManager.java index af614491e37b9624e60829d67e776e1f50e33e93..4250ab95722cfd1c107407e25824fd2f5172e83a 100644 --- a/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManager.java +++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManager.java @@ -38,7 +38,6 @@ public interface LifecycleManager { * The result of calling {@link #startServices()}. */ enum StartResult { - ALREADY_RUNNING, SERVICE_ERROR, SUCCESS } diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManagerImpl.kt b/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManagerImpl.kt index 2e4781ed23032f3e6a7fcc1deec6717d0f5096f7..ab7a5703f57429de8b0df195699c3cd5fb59e58c 100644 --- a/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManagerImpl.kt +++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/lifecycle/LifecycleManagerImpl.kt @@ -20,10 +20,8 @@ package org.briarproject.mailbox.core.lifecycle import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow import org.briarproject.mailbox.core.db.Database import org.briarproject.mailbox.core.db.MigrationListener -import org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState import org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState.COMPACTING_DATABASE import org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState.MIGRATING_DATABASE import org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState.NOT_STARTED @@ -35,7 +33,6 @@ import org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState.S import org.briarproject.mailbox.core.lifecycle.LifecycleManager.LifecycleState.WIPING import org.briarproject.mailbox.core.lifecycle.LifecycleManager.OpenDatabaseHook 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.SERVICE_ERROR import org.briarproject.mailbox.core.lifecycle.LifecycleManager.StartResult.SUCCESS import org.briarproject.mailbox.core.setup.WipeManager @@ -85,33 +82,37 @@ internal class LifecycleManagerImpl @Inject constructor( } override fun registerService(s: Service) { - LOG.info { "Registering service ${s.javaClass.simpleName}" } + LOG.info { "Registering service ${s.name()}" } services.add(s) } override fun registerOpenDatabaseHook(hook: OpenDatabaseHook) { - LOG.info { "Registering open database hook ${hook.javaClass.simpleName}" } + LOG.info { "Registering open database hook ${hook.name()}" } openDatabaseHooks.add(hook) } override fun registerForShutdown(e: ExecutorService) { - LOG.info { "Registering executor ${e.javaClass.simpleName}" } + LOG.info { "Registering executor ${e.name()}" } executors.add(e) } @GuardedBy("startStopWipeSemaphore") override fun startServices(): StartResult { - if (!startStopWipeSemaphore.tryAcquire()) { - LOG.info("Already starting or stopping") - return ALREADY_RUNNING + try { + startStopWipeSemaphore.acquire() + } catch (e: InterruptedException) { + LOG.warn("Interrupted while waiting to stop services") + return SERVICE_ERROR + } + if (!state.compareAndSet(NOT_STARTED, STARTING)) { + return SERVICE_ERROR } - state.compareAndSet(NOT_STARTED, STARTING) return try { LOG.info("Opening database") var start = now() val reopened = db.open(this) - if (reopened) logDuration(LOG, { "Reopening database" }, start) - else logDuration(LOG, { "Creating database" }, start) + if (reopened) logDuration(LOG, start) { "Reopening database" } + else logDuration(LOG, start) { "Creating database" } // Inform hooks that DB was opened db.write { txn -> for (hook in openDatabaseHooks) { @@ -124,7 +125,7 @@ internal class LifecycleManagerImpl @Inject constructor( for (s in services) { start = now() s.startService() - logDuration(LOG, { "Starting service ${s.javaClass.simpleName}" }, start) + logDuration(LOG, start) { "Starting service ${s.name()}" } } state.compareAndSet(STARTING_SERVICES, RUNNING) startupLatch.countDown() @@ -166,15 +167,10 @@ internal class LifecycleManagerImpl @Inject constructor( val wiped = state.value == WIPING LOG.info("Stopping services") state.value = STOPPING - for (s in services) { - val start = now() - s.stopService() - logDuration(LOG, { "Stopping service " + s.javaClass.simpleName }, start) - } - for (e in executors) { - LOG.trace { "Stopping executor ${e.javaClass.simpleName}" } - e.shutdownNow() - } + + stopAllServices() + stopAllExecutors() + if (wiped) { // If we just wiped, the database has already been closed, so we should not call // close(). Since the services are being shut down after wiping (so that the web @@ -182,21 +178,46 @@ internal class LifecycleManagerImpl @Inject constructor( // API call created some files in the meantime. To make sure we delete those in // case of a wipe, repeat deletion of files here after the services have been // stopped. - wipeManager.wipe(wipeDatabase = false) + wipeFilesAgain() } else { - val start = now() - db.close() - logDuration(LOG, { "Closing database" }, start) + closeDatabase() } + shutdownLatch.countDown() - } catch (e: ServiceException) { - logException(LOG, e) } finally { state.compareAndSet(STOPPING, STOPPED) startStopWipeSemaphore.release() } } + @GuardedBy("startStopWipeSemaphore") + private fun stopAllServices() { + for (s in services) { + run("stopping service ${s.name()}") { + s.stopService() + } + } + } + + @GuardedBy("startStopWipeSemaphore") + private fun stopAllExecutors() { + for (e in executors) { + run("stopping executor ${e.name()}") { + e.shutdownNow() + } + } + } + + @GuardedBy("startStopWipeSemaphore") + private fun wipeFilesAgain() = run("wiping files again") { + wipeManager.wipeFilesOnly() + } + + @GuardedBy("startStopWipeSemaphore") + private fun closeDatabase() = run("closing database") { + db.close() + } + @GuardedBy("startStopWipeSemaphore") override fun wipeMailbox(): Boolean { try { @@ -209,8 +230,9 @@ internal class LifecycleManagerImpl @Inject constructor( return false } try { - wipeManager.wipe(wipeDatabase = true) - + run("wiping database and files") { + wipeManager.wipeDatabaseAndFiles() + } // We need to move this to a thread so that the webserver call can finish when it calls // this. Otherwise we'll end up in a deadlock: the same thread trying to stop the // webserver from within a call that wants to send a response on the very same webserver. @@ -219,7 +241,6 @@ internal class LifecycleManagerImpl @Inject constructor( thread { stopServices() } - return true } finally { startStopWipeSemaphore.release() @@ -227,25 +248,28 @@ internal class LifecycleManagerImpl @Inject constructor( } @Throws(InterruptedException::class) - override fun waitForDatabase() { - dbLatch.await() - } + override fun waitForDatabase() = dbLatch.await() @Throws(InterruptedException::class) - override fun waitForStartup() { - startupLatch.await() - } + override fun waitForStartup() = startupLatch.await() @Throws(InterruptedException::class) - override fun waitForShutdown() { - shutdownLatch.await() - } + override fun waitForShutdown() = shutdownLatch.await() - override fun getLifecycleState(): LifecycleState { - return state.value - } + override fun getLifecycleState() = state.value + + override fun getLifecycleStateFlow() = state - override fun getLifecycleStateFlow(): StateFlow<LifecycleState> { - return state + private fun run(name: String, task: () -> Unit) { + LOG.trace { name } + val start = now() + try { + task() + } catch (throwable: Throwable) { + logException(LOG, throwable) { "Error while $name" } + } + logDuration(LOG, start) { name } } + + private fun Any.name() = javaClass.simpleName } diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/setup/WipeManager.kt b/mailbox-core/src/main/java/org/briarproject/mailbox/core/setup/WipeManager.kt index 68250a4a1fd9b947503a25c4097b42f7724edabd..59629b3cd8459a2a03b562559ce2f84f13dba434 100644 --- a/mailbox-core/src/main/java/org/briarproject/mailbox/core/setup/WipeManager.kt +++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/setup/WipeManager.kt @@ -42,12 +42,17 @@ class WipeManager @Inject constructor( /* * This must only be called by the LifecycleManager */ - fun wipe(wipeDatabase: Boolean) { - if (wipeDatabase) { - db.dropAllTablesAndClose() - val dir = databaseConfig.getDatabaseDirectory() - IoUtils.deleteFileOrDir(dir) - } + fun wipeDatabaseAndFiles() { + db.dropAllTablesAndClose() + val dir = databaseConfig.getDatabaseDirectory() + IoUtils.deleteFileOrDir(dir) + fileManager.deleteAllFiles() + } + + /* + * This must only be called by the LifecycleManager + */ + fun wipeFilesOnly() { fileManager.deleteAllFiles() } diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/tor/TorConstants.java b/mailbox-core/src/main/java/org/briarproject/mailbox/core/tor/TorConstants.java index ef1ffd649c86e183bff5f317d989aa9288cae8d8..ca6b6633ecfe7eb554e85c5016f63bf91271f57d 100644 --- a/mailbox-core/src/main/java/org/briarproject/mailbox/core/tor/TorConstants.java +++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/tor/TorConstants.java @@ -26,7 +26,7 @@ public interface TorConstants { String HS_PRIVATE_KEY_V3 = "onionPrivKey3"; String HS_ADDRESS_V3 = "onionAddress3"; - int SOCKS_PORT = 59052; - int CONTROL_PORT = 59053; + int SOCKS_PORT = 59054; + int CONTROL_PORT = 59055; } diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/util/LogUtils.kt b/mailbox-core/src/main/java/org/briarproject/mailbox/core/util/LogUtils.kt index 7adf285c8aa598a7a6746914218c7d9b9f9c97f5..a0ae2f033abc2a1a5e54c76ad9bc72e241ffacf6 100644 --- a/mailbox-core/src/main/java/org/briarproject/mailbox/core/util/LogUtils.kt +++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/util/LogUtils.kt @@ -68,13 +68,18 @@ object LogUtils { * @param start the start time of the task, as returned by [now] */ @JvmStatic - fun logDuration(logger: Logger, msg: () -> String, start: Long) { + fun logDuration(logger: Logger, start: Long, msg: () -> String) { logger.trace { val duration = now() - start "${msg()} took $duration ms" } } + @JvmStatic + fun logException(logger: Logger, t: Throwable, message: () -> String) { + if (logger.isWarnEnabled) logger.warn(message(), t) + } + @JvmStatic fun logException(logger: Logger, t: Throwable) { if (logger.isWarnEnabled) logger.warn(t.toString(), t) diff --git a/mailbox-core/src/main/resources/torrc b/mailbox-core/src/main/resources/torrc index 23beef726a8a6ed0efc2f36e90b51fd536eb2aa1..5a99c8b36284031e47b5a90916c82af0df924608 100644 --- a/mailbox-core/src/main/resources/torrc +++ b/mailbox-core/src/main/resources/torrc @@ -1,6 +1,6 @@ -ControlPort 59053 +ControlPort 59055 CookieAuthentication 1 DisableNetwork 1 RunAsDaemon 1 SafeSocks 1 -SocksPort 59052 +SocksPort 59054