From 971b425e177f31d65f2852325c4421c84516c471 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 8 Sep 2020 12:28:29 +0200 Subject: [PATCH 01/15] F-Droid background sync modes --- .../matrix/android/sdk/api/session/Session.kt | 2 +- .../sdk/internal/session/DefaultSession.kt | 4 +- .../sdk/internal/session/sync/SyncTask.kt | 2 +- .../internal/session/sync/job/SyncService.kt | 105 +++++++---- .../internal/session/sync/job/SyncWorker.kt | 37 ++-- vector/src/fdroid/AndroidManifest.xml | 5 + .../receiver/AlarmSyncBroadcastReceiver.kt | 39 ++-- .../java/im/vector/app/push/fcm/FcmHelper.kt | 28 ++- .../java/im/vector/app/push/fcm/FcmHelper.kt | 2 +- .../java/im/vector/app/VectorApplication.kt | 2 +- .../im/vector/app/core/extensions/Session.kt | 3 +- .../app/core/services/VectorSyncService.kt | 31 +++- .../features/settings/BackgroundSyncMode.kt | 28 +++ .../BackgroundSyncModeChooserDialog.kt | 172 ++++++++++++++++++ .../features/settings/VectorPreferences.kt | 52 ++++++ ...rSettingsNotificationPreferenceFragment.kt | 107 ++++++++++- .../layout/dialog_background_sync_mode.xml | 13 ++ .../layout/item_custom_dialog_radio_line.xml | 57 ++++++ vector/src/main/res/values/strings.xml | 2 +- .../main/res/xml/network_security_config.xml | 7 + .../res/xml/vector_settings_notifications.xml | 39 ++-- 21 files changed, 637 insertions(+), 100 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/settings/BackgroundSyncMode.kt create mode 100644 vector/src/main/java/im/vector/app/features/settings/BackgroundSyncModeChooserDialog.kt create mode 100644 vector/src/main/res/layout/dialog_background_sync_mode.xml create mode 100644 vector/src/main/res/layout/item_custom_dialog_radio_line.xml diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt index cfddf73363..f5b10a5645 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt @@ -110,7 +110,7 @@ interface Session : * This does not work in doze mode :/ * If battery optimization is on it can work in app standby but that's all :/ */ - fun startAutomaticBackgroundSync(repeatDelay: Long = 30_000L) + fun startAutomaticBackgroundSync(timeOutInSeconds: Long, repeatDelayInSeconds: Long = 30L) fun stopAnyBackgroundSync() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt index f8ba625947..004c5afe8f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt @@ -166,8 +166,8 @@ internal class DefaultSession @Inject constructor( SyncWorker.requireBackgroundSync(workManagerProvider, sessionId) } - override fun startAutomaticBackgroundSync(repeatDelay: Long) { - SyncWorker.automaticallyBackgroundSync(workManagerProvider, sessionId, 0, repeatDelay) + override fun startAutomaticBackgroundSync(timeOutInSeconds: Long, repeatDelayInSeconds: Long) { + SyncWorker.automaticallyBackgroundSync(workManagerProvider, sessionId, timeOutInSeconds, repeatDelayInSeconds) } override fun stopAnyBackgroundSync() { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt index 02afd53908..9924d44764 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt @@ -32,7 +32,7 @@ import javax.inject.Inject internal interface SyncTask : Task { - data class Params(var timeout: Long = 30_000L) + data class Params(var timeout: Long = 6_000L) } internal class DefaultSyncTask @Inject constructor( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncService.kt index 20aa409336..0ba1a672f9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncService.kt @@ -19,6 +19,12 @@ package org.matrix.android.sdk.internal.session.sync.job import android.app.Service import android.content.Intent import android.os.IBinder +import android.os.PowerManager +import androidx.core.content.getSystemService +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancelChildren +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.Matrix import org.matrix.android.sdk.api.failure.isTokenError import org.matrix.android.sdk.api.session.Session @@ -28,10 +34,6 @@ import org.matrix.android.sdk.internal.session.sync.SyncTask import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.cancelChildren -import kotlinx.coroutines.launch import timber.log.Timber import java.util.concurrent.atomic.AtomicBoolean @@ -46,6 +48,11 @@ abstract class SyncService : Service() { private var sessionId: String? = null private var mIsSelfDestroyed: Boolean = false + private var syncTimeoutSeconds: Int = 6 + private var syncDelaySeconds: Int = 60 + private var periodic: Boolean = false + private var preventReschedule: Boolean = false + private var isInitialSync: Boolean = false private lateinit var session: Session private lateinit var syncTask: SyncTask @@ -59,27 +66,51 @@ abstract class SyncService : Service() { private val serviceScope = CoroutineScope(SupervisorJob()) override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - Timber.i("onStartCommand $intent") - val isInit = initialize(intent) - if (isInit) { - onStart(isInitialSync) - doSyncIfNotAlreadyRunning() - } else { - // We should start and stop as we have to ensure to call Service.startForeground() - onStart(isInitialSync) - stopMe() + Timber.i("## Sync: onStartCommand [$this] $intent with action: ${intent?.action}") + + // We should start we have to ensure we fulfill contract to show notification + // for foreground service (as per design for this service) + // TODO can we check if it's really in foreground + onStart(isInitialSync) + when (intent?.action) { + ACTION_STOP -> { + Timber.i("## Sync: stop command received") + // If it was periodic we ensure that it will not reschedule itself + preventReschedule = true + // we don't want to cancel initial syncs, let it finish + if (!isInitialSync) { + stopMe() + } + } + else -> { + val isInit = initialize(intent) + if (isInit) { + periodic = intent?.getBooleanExtra(EXTRA_PERIODIC, false) ?: false + Timber.i("## Sync: command received, periodic: $periodic") + // default is syncing + doSyncIfNotAlreadyRunning() + } else { + Timber.i("## Sync: Failed to initialize service") + stopMe() + } + } } - // No intent just start the service, an alarm will should call with intent + return START_STICKY } override fun onDestroy() { - Timber.i("## onDestroy() : $this") + Timber.i("## Sync: onDestroy() [$this] periodic:$periodic preventReschedule:$preventReschedule") if (!mIsSelfDestroyed) { - Timber.w("## Destroy by the system : $this") + Timber.d("## Sync: Destroy by the system : $this") } - serviceScope.coroutineContext.cancelChildren() isRunning.set(false) + // Cancelling the context will trigger the catch close the doSync try + serviceScope.coroutineContext.cancelChildren() + if (!preventReschedule && periodic && sessionId != null) { + Timber.d("## Sync: Reschedule service in $syncDelaySeconds sec") + onRescheduleAsked(sessionId ?: "", false, syncTimeoutSeconds, syncDelaySeconds) + } super.onDestroy() } @@ -90,9 +121,15 @@ abstract class SyncService : Service() { private fun doSyncIfNotAlreadyRunning() { if (isRunning.get()) { - Timber.i("Received a start while was already syncing... ignore") + Timber.i("## Sync: Received a start while was already syncing... ignore") } else { isRunning.set(true) + // Acquire a lock to give enough time for the sync :/ + getSystemService()?.run { + newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "riotx:fdroidSynclock").apply { + acquire((syncTimeoutSeconds * 1000L + 10_000L)) + } + } serviceScope.launch(coroutineDispatchers.io) { doSync() } @@ -100,8 +137,8 @@ abstract class SyncService : Service() { } private suspend fun doSync() { - Timber.v("Execute sync request with timeout 0") - val params = SyncTask.Params(TIME_OUT) + Timber.v("## Sync: Execute sync request with timeout $syncTimeoutSeconds seconds") + val params = SyncTask.Params(syncTimeoutSeconds * 1000L) try { syncTask.execute(params) // Start sync if we were doing an initial sync and the syncThread is not launched yet @@ -111,28 +148,28 @@ abstract class SyncService : Service() { } stopMe() } catch (throwable: Throwable) { - Timber.e(throwable) + Timber.e(throwable, "## Sync: sync service did fail ${isRunning.get()}") if (throwable.isTokenError()) { - stopMe() - } else { - Timber.v("Should be rescheduled to avoid wasting resources") - sessionId?.also { - onRescheduleAsked(it, isInitialSync, delay = 10_000L) - } - stopMe() + // no need to retry + preventReschedule = true } + // JobCancellation could be caught here when onDestroy cancels the coroutine context + if (isRunning.get()) stopMe() } } private fun initialize(intent: Intent?): Boolean { if (intent == null) { + Timber.d("## Sync: initialize intent is null") return false } val matrix = Matrix.getInstance(applicationContext) val safeSessionId = intent.getStringExtra(EXTRA_SESSION_ID) ?: return false + syncTimeoutSeconds = intent.getIntExtra(EXTRA_TIMEOUT_SECONDS, 6) + syncDelaySeconds = intent.getIntExtra(EXTRA_DELAY_SECONDS, 60) try { val sessionComponent = matrix.sessionManager.getSessionComponent(safeSessionId) - ?: throw IllegalStateException("You should have a session to make it work") + ?: throw IllegalStateException("## Sync: You should have a session to make it work") session = sessionComponent.session() sessionId = safeSessionId syncTask = sessionComponent.syncTask() @@ -143,14 +180,14 @@ abstract class SyncService : Service() { backgroundDetectionObserver = matrix.backgroundDetectionObserver return true } catch (exception: Exception) { - Timber.e(exception, "An exception occurred during initialisation") + Timber.e(exception, "## Sync: An exception occurred during initialisation") return false } } abstract fun onStart(isInitialSync: Boolean) - abstract fun onRescheduleAsked(sessionId: String, isInitialSync: Boolean, delay: Long) + abstract fun onRescheduleAsked(sessionId: String, isInitialSync: Boolean, timeout: Int, delay: Int) override fun onBind(intent: Intent?): IBinder? { return null @@ -158,6 +195,10 @@ abstract class SyncService : Service() { companion object { const val EXTRA_SESSION_ID = "EXTRA_SESSION_ID" - private const val TIME_OUT = 0L + const val EXTRA_TIMEOUT_SECONDS = "EXTRA_TIMEOUT_SECONDS" + const val EXTRA_DELAY_SECONDS = "EXTRA_DELAY_SECONDS" + const val EXTRA_PERIODIC = "EXTRA_PERIODIC" + + const val ACTION_STOP = "ACTION_STOP" } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt index e702de3573..3e0a29ba72 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt @@ -34,7 +34,8 @@ import timber.log.Timber import java.util.concurrent.TimeUnit import javax.inject.Inject -private const val DEFAULT_LONG_POOL_TIMEOUT = 0L +private const val DEFAULT_LONG_POOL_TIMEOUT = 6L +private const val DEFAULT_DELAY_TIMEOUT = 30_000L /** * Possible previous worker: None @@ -48,13 +49,15 @@ internal class SyncWorker(context: Context, internal data class Params( override val sessionId: String, val timeout: Long = DEFAULT_LONG_POOL_TIMEOUT, - val automaticallyRetry: Boolean = false, + val delay: Long = DEFAULT_DELAY_TIMEOUT, + val periodic: Boolean = false, override val lastFailureMessage: String? = null ) : SessionWorkerParams @Inject lateinit var syncTask: SyncTask @Inject lateinit var taskExecutor: TaskExecutor @Inject lateinit var networkConnectivityChecker: NetworkConnectivityChecker + @Inject lateinit var workManagerProvider: WorkManagerProvider override suspend fun doWork(): Result { Timber.i("Sync work starting") @@ -67,11 +70,21 @@ internal class SyncWorker(context: Context, return runCatching { doSync(params.timeout) }.fold( - { Result.success() }, + { + Result.success().also { + if (params.periodic) { + // we want to schedule another one after delay + automaticallyBackgroundSync(workManagerProvider, params.sessionId, params.timeout, params.delay) + } + } + }, { failure -> - if (failure.isTokenError() || !params.automaticallyRetry) { + if (failure.isTokenError()) { Result.failure() } else { + // If the worker was stopped (when going back in foreground), a JobCancellation exception is sent + // but in this case the result is ignored, as the work is considered stopped, + // so don't worry of the retry here for this case Result.retry() } } @@ -79,7 +92,7 @@ internal class SyncWorker(context: Context, } private suspend fun doSync(timeout: Long) { - val taskParams = SyncTask.Params(timeout) + val taskParams = SyncTask.Params(timeout * 1000) syncTask.execute(taskParams) } @@ -87,25 +100,27 @@ internal class SyncWorker(context: Context, private const val BG_SYNC_WORK_NAME = "BG_SYNCP" fun requireBackgroundSync(workManagerProvider: WorkManagerProvider, sessionId: String, serverTimeout: Long = 0) { - val data = WorkerParamsFactory.toData(Params(sessionId, serverTimeout, false)) + val data = WorkerParamsFactory.toData(Params(sessionId, serverTimeout, 0L, false)) val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder() .setConstraints(WorkManagerProvider.workConstraints) .setBackoffCriteria(BackoffPolicy.LINEAR, 1_000, TimeUnit.MILLISECONDS) .setInputData(data) .build() workManagerProvider.workManager - .enqueueUniqueWork(BG_SYNC_WORK_NAME, ExistingWorkPolicy.REPLACE, workRequest) + .enqueueUniqueWork(BG_SYNC_WORK_NAME, ExistingWorkPolicy.APPEND_OR_REPLACE, workRequest) } - fun automaticallyBackgroundSync(workManagerProvider: WorkManagerProvider, sessionId: String, serverTimeout: Long = 0, delay: Long = 30_000) { - val data = WorkerParamsFactory.toData(Params(sessionId, serverTimeout, true)) + fun automaticallyBackgroundSync(workManagerProvider: WorkManagerProvider, sessionId: String, serverTimeout: Long = 0, delayInSeconds: Long = 30) { + val data = WorkerParamsFactory.toData(Params(sessionId, serverTimeout, delayInSeconds, true)) val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder() .setConstraints(WorkManagerProvider.workConstraints) .setInputData(data) - .setBackoffCriteria(BackoffPolicy.LINEAR, delay, TimeUnit.MILLISECONDS) + .setBackoffCriteria(BackoffPolicy.LINEAR, 1_000, TimeUnit.MILLISECONDS) + .setInitialDelay(delayInSeconds, TimeUnit.SECONDS) .build() + workManagerProvider.workManager - .enqueueUniqueWork(BG_SYNC_WORK_NAME, ExistingWorkPolicy.REPLACE, workRequest) + .enqueueUniqueWork(BG_SYNC_WORK_NAME, ExistingWorkPolicy.APPEND_OR_REPLACE, workRequest) } fun stopAnyBackgroundSync(workManagerProvider: WorkManagerProvider) { diff --git a/vector/src/fdroid/AndroidManifest.xml b/vector/src/fdroid/AndroidManifest.xml index 55f745ddfa..3a7c107138 100644 --- a/vector/src/fdroid/AndroidManifest.xml +++ b/vector/src/fdroid/AndroidManifest.xml @@ -4,6 +4,11 @@ + + diff --git a/vector/src/fdroid/java/im/vector/app/fdroid/receiver/AlarmSyncBroadcastReceiver.kt b/vector/src/fdroid/java/im/vector/app/fdroid/receiver/AlarmSyncBroadcastReceiver.kt index 1af92ff387..674e7dfef5 100644 --- a/vector/src/fdroid/java/im/vector/app/fdroid/receiver/AlarmSyncBroadcastReceiver.kt +++ b/vector/src/fdroid/java/im/vector/app/fdroid/receiver/AlarmSyncBroadcastReceiver.kt @@ -22,16 +22,18 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.os.Build -import android.os.PowerManager import androidx.core.content.ContextCompat +import androidx.core.content.getSystemService import im.vector.app.core.di.HasVectorInjector import im.vector.app.core.services.VectorSyncService -import androidx.core.content.getSystemService +import im.vector.app.features.settings.VectorPreferences import org.matrix.android.sdk.internal.session.sync.job.SyncService import timber.log.Timber class AlarmSyncBroadcastReceiver : BroadcastReceiver() { + lateinit var vectorPreferences: VectorPreferences + override fun onReceive(context: Context, intent: Intent) { val appContext = context.applicationContext if (appContext is HasVectorInjector) { @@ -40,41 +42,35 @@ class AlarmSyncBroadcastReceiver : BroadcastReceiver() { Timber.v("No active session don't launch sync service.") return } - } - - // Acquire a lock to give enough time for the sync :/ - context.getSystemService()!!.run { - newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "riotx:fdroidSynclock").apply { - acquire((10_000).toLong()) - } + vectorPreferences = appContext.injector().vectorPreferences() } val sessionId = intent.getStringExtra(SyncService.EXTRA_SESSION_ID) ?: return // This method is called when the BroadcastReceiver is receiving an Intent broadcast. Timber.d("RestartBroadcastReceiver received intent") - VectorSyncService.newIntent(context, sessionId).let { + VectorSyncService.newPeriodicIntent(context, sessionId, vectorPreferences.backgroundSyncTimeOut(), vectorPreferences.backgroundSyncDelay()).let { try { ContextCompat.startForegroundService(context, it) } catch (ex: Throwable) { - // TODO + Timber.i("## Sync: Failed to start service, Alarm scheduled to restart service") + scheduleAlarm(context, sessionId, vectorPreferences.backgroundSyncDelay()) Timber.e(ex) } } - - scheduleAlarm(context, sessionId, 30_000L) - Timber.i("Alarm scheduled to restart service") } companion object { private const val REQUEST_CODE = 0 - fun scheduleAlarm(context: Context, sessionId: String, delay: Long) { + fun scheduleAlarm(context: Context, sessionId: String, delayInSeconds: Int) { // Reschedule + Timber.v("## Sync: Scheduling alarm for background sync in $delayInSeconds seconds") val intent = Intent(context, AlarmSyncBroadcastReceiver::class.java).apply { putExtra(SyncService.EXTRA_SESSION_ID, sessionId) + putExtra(SyncService.EXTRA_PERIODIC, true) } val pIntent = PendingIntent.getBroadcast(context, REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT) - val firstMillis = System.currentTimeMillis() + delay + val firstMillis = System.currentTimeMillis() + delayInSeconds * 1000L val alarmMgr = context.getSystemService()!! if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { alarmMgr.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, firstMillis, pIntent) @@ -84,11 +80,20 @@ class AlarmSyncBroadcastReceiver : BroadcastReceiver() { } fun cancelAlarm(context: Context) { - Timber.v("Cancel alarm") + Timber.v("## Sync: Cancel alarm for background sync") val intent = Intent(context, AlarmSyncBroadcastReceiver::class.java) val pIntent = PendingIntent.getBroadcast(context, REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT) val alarmMgr = context.getSystemService()!! alarmMgr.cancel(pIntent) + + // Stop current service to restart + VectorSyncService.stopIntent(context).let { + try { + ContextCompat.startForegroundService(context, it) + } catch (ex: Throwable) { + Timber.i("## Sync: Cancel sync") + } + } } } } diff --git a/vector/src/fdroid/java/im/vector/app/push/fcm/FcmHelper.kt b/vector/src/fdroid/java/im/vector/app/push/fcm/FcmHelper.kt index 7a8c5fa134..7874d0237f 100755 --- a/vector/src/fdroid/java/im/vector/app/push/fcm/FcmHelper.kt +++ b/vector/src/fdroid/java/im/vector/app/push/fcm/FcmHelper.kt @@ -23,6 +23,7 @@ import android.content.Context import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.pushers.PushersManager import im.vector.app.fdroid.receiver.AlarmSyncBroadcastReceiver +import im.vector.app.features.settings.BackgroundSyncMode import im.vector.app.features.settings.VectorPreferences import timber.log.Timber @@ -61,16 +62,35 @@ object FcmHelper { // No op } - fun onEnterForeground(context: Context) { + fun onEnterForeground(context: Context, activeSessionHolder: ActiveSessionHolder) { + // try to stop all regardless of background mode + activeSessionHolder.getSafeActiveSession()?.stopAnyBackgroundSync() AlarmSyncBroadcastReceiver.cancelAlarm(context) } fun onEnterBackground(context: Context, vectorPreferences: VectorPreferences, activeSessionHolder: ActiveSessionHolder) { // We need to use alarm in this mode if (vectorPreferences.areNotificationEnabledForDevice() && activeSessionHolder.hasActiveSession()) { - val currentSession = activeSessionHolder.getActiveSession() - AlarmSyncBroadcastReceiver.scheduleAlarm(context, currentSession.sessionId, 4_000L) - Timber.i("Alarm scheduled to restart service") + when (vectorPreferences.getFdroidSyncBackgroundMode()) { + BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_BATTERY -> { + // we rely on periodic worker + Timber.i("## Sync: Work scheduled to periodically sync") + activeSessionHolder + .getSafeActiveSession() + ?.startAutomaticBackgroundSync( + vectorPreferences.backgroundSyncTimeOut().toLong(), + vectorPreferences.backgroundSyncDelay().toLong() + ) + } + BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME -> { + val currentSession = activeSessionHolder.getActiveSession() + AlarmSyncBroadcastReceiver.scheduleAlarm(context, currentSession.sessionId, vectorPreferences.backgroundSyncDelay()) + Timber.i("## Sync: Alarm scheduled to start syncing") + } + BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_DISABLED -> { + // we do nothing + } + } } } } diff --git a/vector/src/gplay/java/im/vector/app/push/fcm/FcmHelper.kt b/vector/src/gplay/java/im/vector/app/push/fcm/FcmHelper.kt index 84ca392c45..d139cad9d3 100755 --- a/vector/src/gplay/java/im/vector/app/push/fcm/FcmHelper.kt +++ b/vector/src/gplay/java/im/vector/app/push/fcm/FcmHelper.kt @@ -102,7 +102,7 @@ object FcmHelper { } @Suppress("UNUSED_PARAMETER") - fun onEnterForeground(context: Context) { + fun onEnterForeground(context: Context, activeSessionHolder: ActiveSessionHolder) { // No op } diff --git a/vector/src/main/java/im/vector/app/VectorApplication.kt b/vector/src/main/java/im/vector/app/VectorApplication.kt index c9d2c96223..4b0ef66459 100644 --- a/vector/src/main/java/im/vector/app/VectorApplication.kt +++ b/vector/src/main/java/im/vector/app/VectorApplication.kt @@ -146,7 +146,7 @@ class VectorApplication : @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun entersForeground() { Timber.i("App entered foreground") - FcmHelper.onEnterForeground(appContext) + FcmHelper.onEnterForeground(appContext, activeSessionHolder) activeSessionHolder.getSafeActiveSession()?.also { it.stopAnyBackgroundSync() } diff --git a/vector/src/main/java/im/vector/app/core/extensions/Session.kt b/vector/src/main/java/im/vector/app/core/extensions/Session.kt index 6f0fd51525..cb87947612 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/Session.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/Session.kt @@ -37,7 +37,8 @@ fun Session.configureAndStart(context: Context) { fun Session.startSyncing(context: Context) { val applicationContext = context.applicationContext if (!hasAlreadySynced()) { - VectorSyncService.newIntent(applicationContext, sessionId).also { + // initial sync is done as a service so it can continue below app lifecycle + VectorSyncService.newOneShotIntent(applicationContext, sessionId, 0).also { try { ContextCompat.startForegroundService(applicationContext, it) } catch (ex: Throwable) { diff --git a/vector/src/main/java/im/vector/app/core/services/VectorSyncService.kt b/vector/src/main/java/im/vector/app/core/services/VectorSyncService.kt index 20ceb07fb5..bbd8487ae5 100644 --- a/vector/src/main/java/im/vector/app/core/services/VectorSyncService.kt +++ b/vector/src/main/java/im/vector/app/core/services/VectorSyncService.kt @@ -31,9 +31,26 @@ class VectorSyncService : SyncService() { companion object { - fun newIntent(context: Context, sessionId: String): Intent { + fun newOneShotIntent(context: Context, sessionId: String, timeoutSeconds: Int): Intent { return Intent(context, VectorSyncService::class.java).also { it.putExtra(EXTRA_SESSION_ID, sessionId) + it.putExtra(EXTRA_TIMEOUT_SECONDS, timeoutSeconds) + it.putExtra(EXTRA_PERIODIC, false) + } + } + + fun newPeriodicIntent(context: Context, sessionId: String, timeoutSeconds: Int, delayInSeconds: Int): Intent { + return Intent(context, VectorSyncService::class.java).also { + it.putExtra(EXTRA_SESSION_ID, sessionId) + it.putExtra(EXTRA_TIMEOUT_SECONDS, timeoutSeconds) + it.putExtra(EXTRA_PERIODIC, true) + it.putExtra(EXTRA_DELAY_SECONDS, delayInSeconds) + } + } + + fun stopIntent(context: Context): Intent { + return Intent(context, VectorSyncService::class.java).also { + it.action = ACTION_STOP } } } @@ -55,8 +72,8 @@ class VectorSyncService : SyncService() { startForeground(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE, notification) } - override fun onRescheduleAsked(sessionId: String, isInitialSync: Boolean, delay: Long) { - reschedule(sessionId, delay) + override fun onRescheduleAsked(sessionId: String, isInitialSync: Boolean, timeout: Int, delay: Int) { + reschedule(sessionId, timeout, delay) } override fun onDestroy() { @@ -69,13 +86,13 @@ class VectorSyncService : SyncService() { notificationManager.cancel(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE) } - private fun reschedule(sessionId: String, delay: Long) { + private fun reschedule(sessionId: String, timeout: Int, delay: Int) { val pendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - PendingIntent.getForegroundService(this, 0, newIntent(this, sessionId), 0) + PendingIntent.getForegroundService(this, 0, newPeriodicIntent(this, sessionId, timeout, delay), 0) } else { - PendingIntent.getService(this, 0, newIntent(this, sessionId), 0) + PendingIntent.getService(this, 0, newPeriodicIntent(this, sessionId, timeout, delay), 0) } - val firstMillis = System.currentTimeMillis() + delay + val firstMillis = System.currentTimeMillis() + delay * 1000L val alarmMgr = getSystemService()!! if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { alarmMgr.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, firstMillis, pendingIntent) diff --git a/vector/src/main/java/im/vector/app/features/settings/BackgroundSyncMode.kt b/vector/src/main/java/im/vector/app/features/settings/BackgroundSyncMode.kt new file mode 100644 index 0000000000..f266060053 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/BackgroundSyncMode.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.settings + +enum class BackgroundSyncMode { + FDROID_BACKGROUND_SYNC_MODE_FOR_BATTERY, + FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME, + FDROID_BACKGROUND_SYNC_MODE_DISABLED; + + companion object { + fun fromString(value: String?): BackgroundSyncMode = values().firstOrNull { it.name == value } + ?: FDROID_BACKGROUND_SYNC_MODE_DISABLED + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/BackgroundSyncModeChooserDialog.kt b/vector/src/main/java/im/vector/app/features/settings/BackgroundSyncModeChooserDialog.kt new file mode 100644 index 0000000000..079be58fbc --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/settings/BackgroundSyncModeChooserDialog.kt @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.settings + +import android.app.Dialog +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ArrayAdapter +import android.widget.ListView +import android.widget.RadioButton +import android.widget.TextView +import androidx.appcompat.app.AlertDialog +import androidx.core.view.isVisible +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.FragmentActivity +import im.vector.app.R + +class BackgroundSyncModeChooserDialog : DialogFragment() { + + var interactionListener: InteractionListener? = null + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return activity?.let { activity: FragmentActivity -> + val builder = AlertDialog.Builder(activity) + // Get the layout inflater + val inflater = activity.layoutInflater + + // Inflate and set the layout for the dialog + // Pass null as the parent view because its going in the dialog layout + val view = inflater.inflate(R.layout.dialog_background_sync_mode, null) + view.findViewById(R.id.dialog_background_sync_list)?.let { + it.adapter = Adapter( + activity, + BackgroundSyncMode.fromString(arguments?.getString(ARG_INITIAL_MODE)) + ) + } + builder.setView(view) + // Add action buttons + .setPositiveButton(R.string.ok) { dialog, _ -> + val mode = getSelectedOption() + if (mode.name == arguments?.getString(ARG_INITIAL_MODE)) { + // it's like a cancel, no changes + dialog.cancel() + } else { + interactionListener?.onOptionSelected(mode) + dialog.dismiss() + } + } + .setNegativeButton(R.string.cancel) { dialog, _ -> + interactionListener?.onCancel() + dialog.cancel() + } + builder.create() + } ?: throw IllegalStateException("Activity cannot be null") + } + + private fun getSelectedOption(): BackgroundSyncMode { + options.forEach { + if (it.isSelected) return it.mode + } + // an item is always selected, should not happen + return options[0].mode + } + + data class SyncMode(val mode: BackgroundSyncMode, val title: Int, val description: Int, var isSelected: Boolean) + + private class Adapter(context: Context, val initialMode: BackgroundSyncMode) : ArrayAdapter(context, 0, options) { + init { + // mark the currently selected option + var initialModeFound = false + options.forEach { + it.isSelected = initialMode == it.mode + initialModeFound = true + } + if (!initialModeFound) { + options[0].isSelected = true + } + } + + override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { + val syncMode = getItem(position)!! + + // Only 3 items, let's keep it like that + val itemView = convertView + ?: LayoutInflater.from(context).inflate(R.layout.item_custom_dialog_radio_line, parent, false) + + // Lookup view for data population + itemView?.findViewById(R.id.item_generic_title_text)?.let { + it.text = context.getString(syncMode.title) + } + itemView?.findViewById(R.id.item_generic_description_text)?.let { + it.text = context.getString(syncMode.description) + it.isVisible = true + } + itemView?.findViewById(R.id.item_generic_radio)?.let { + it.isChecked = syncMode.isSelected + it.isVisible = true + // let the item click handle that + it.setOnClickListener { + toggleChangeAtPosition(position) + } + } + + itemView?.setOnClickListener { + toggleChangeAtPosition(position) + } + + // Populate the data into the template view using the data object + + return itemView + } + + private fun toggleChangeAtPosition(position: Int) { + if (getItem(position)?.isSelected == true) { + // nop + } else { + for (i in 0 until count) { + // we change the single selection + getItem(i)?.isSelected = i == position + } + notifyDataSetChanged() + } + } + } + + interface InteractionListener { + fun onCancel() {} + fun onOptionSelected(mode: BackgroundSyncMode) {} + } + + companion object { + private val options = listOf( + SyncMode(BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_BATTERY, + R.string.settings_background_fdroid_sync_mode_battery, + R.string.settings_background_fdroid_sync_mode_battery_description, false), + SyncMode(BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME, + R.string.settings_background_fdroid_sync_mode_real_time, + R.string.settings_background_fdroid_sync_mode_real_time_description, false), + SyncMode(BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_DISABLED, + R.string.settings_background_fdroid_sync_mode_disabled, + R.string.settings_background_fdroid_sync_mode_disabled_description, false) + ) + + private const val ARG_INITIAL_MODE = "ARG_INITIAL_MODE" + + fun newInstance(selectedMode: BackgroundSyncMode, interactionListener: InteractionListener): BackgroundSyncModeChooserDialog { + val frag = BackgroundSyncModeChooserDialog() + frag.interactionListener = interactionListener + val args = Bundle() + args.putString(ARG_INITIAL_MODE, selectedMode.name) + frag.arguments = args + return frag + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt index 886395c1f7..2239efa992 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt @@ -55,6 +55,7 @@ class VectorPreferences @Inject constructor(private val context: Context) { const val SETTINGS_CONTACT_PREFERENCE_KEYS = "SETTINGS_CONTACT_PREFERENCE_KEYS" const val SETTINGS_NOTIFICATIONS_TARGETS_PREFERENCE_KEY = "SETTINGS_NOTIFICATIONS_TARGETS_PREFERENCE_KEY" const val SETTINGS_NOTIFICATIONS_TARGET_DIVIDER_PREFERENCE_KEY = "SETTINGS_NOTIFICATIONS_TARGET_DIVIDER_PREFERENCE_KEY" + const val SETTINGS_FDROID_BACKGROUND_SYNC_MODE = "SETTINGS_FDROID_BACKGROUND_SYNC_MODE" const val SETTINGS_BACKGROUND_SYNC_PREFERENCE_KEY = "SETTINGS_BACKGROUND_SYNC_PREFERENCE_KEY" const val SETTINGS_BACKGROUND_SYNC_DIVIDER_PREFERENCE_KEY = "SETTINGS_BACKGROUND_SYNC_DIVIDER_PREFERENCE_KEY" const val SETTINGS_LABS_PREFERENCE_KEY = "SETTINGS_LABS_PREFERENCE_KEY" @@ -182,6 +183,8 @@ class VectorPreferences @Inject constructor(private val context: Context) { private const val SETTINGS_UNKNOWN_DEVICE_DISMISSED_LIST = "SETTINGS_UNKNWON_DEVICE_DISMISSED_LIST" + // Background sync modes + // some preferences keys must be kept after a logout private val mKeysToKeepAfterLogout = listOf( SETTINGS_DEFAULT_MEDIA_COMPRESSION_KEY, @@ -830,4 +833,53 @@ class VectorPreferences @Inject constructor(private val context: Context) { fun useFlagPinCode(): Boolean { return defaultPrefs.getBoolean(SETTINGS_SECURITY_USE_PIN_CODE_FLAG, false) } + + fun backgroundSyncTimeOut(): Int { + return tryThis { + // The xml pref is saved as a string so use getString and parse + defaultPrefs.getString(SETTINGS_SET_SYNC_TIMEOUT_PREFERENCE_KEY, "6")?.toInt() + } ?: 6 + } + + fun setBackgroundSyncTimeout(timeInSecond: Int) { + defaultPrefs + .edit() + .putString(SETTINGS_SET_SYNC_TIMEOUT_PREFERENCE_KEY, timeInSecond.toString()) + .apply() + } + + fun backgroundSyncDelay(): Int { + return tryThis { + // The xml pref is saved as a string so use getString and parse + defaultPrefs.getString(SETTINGS_SET_SYNC_DELAY_PREFERENCE_KEY, "60")?.toInt() + } ?: 60 + } + + fun setBackgroundSyncDelay(timeInSecond: Int) { + defaultPrefs + .edit() + .putString(SETTINGS_SET_SYNC_DELAY_PREFERENCE_KEY, timeInSecond.toString()) + .apply() + } + + fun isBackgroundSyncEnabled(): Boolean { + return getFdroidSyncBackgroundMode() != BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_DISABLED + } + + fun setFdroidSyncBackgroundMode(mode: BackgroundSyncMode) { + defaultPrefs + .edit() + .putString(SETTINGS_FDROID_BACKGROUND_SYNC_MODE, mode.name) + .apply() + } + + fun getFdroidSyncBackgroundMode(): BackgroundSyncMode { + return try { + val strPref = defaultPrefs + .getString(SETTINGS_FDROID_BACKGROUND_SYNC_MODE, BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_BATTERY.name) + BackgroundSyncMode.values().firstOrNull { it.name == strPref } ?: BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_BATTERY + } catch (e: Throwable) { + BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_BATTERY + } + } } diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationPreferenceFragment.kt index 1cf9b0dcfa..21895e15b6 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationPreferenceFragment.kt @@ -25,16 +25,21 @@ import android.os.Parcelable import android.widget.Toast import androidx.preference.Preference import androidx.preference.SwitchPreference -import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.pushrules.RuleIds -import org.matrix.android.sdk.api.pushrules.RuleKind import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder +import im.vector.app.core.preference.VectorEditTextPreference import im.vector.app.core.preference.VectorPreference +import im.vector.app.core.preference.VectorPreferenceCategory import im.vector.app.core.preference.VectorSwitchPreference import im.vector.app.core.pushers.PushersManager +import im.vector.app.core.utils.isIgnoringBatteryOptimizations +import im.vector.app.core.utils.requestDisablingBatteryOptimization import im.vector.app.features.notifications.NotificationUtils import im.vector.app.push.fcm.FcmHelper +import org.matrix.android.sdk.api.MatrixCallback +import org.matrix.android.sdk.api.extensions.tryThis +import org.matrix.android.sdk.api.pushrules.RuleIds +import org.matrix.android.sdk.api.pushrules.RuleKind import javax.inject.Inject // Referenced in vector_settings_preferences_root.xml @@ -65,9 +70,104 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor( (pref as SwitchPreference).isChecked = areNotifEnabledAtAccountLevel } + findPreference(VectorPreferences.SETTINGS_FDROID_BACKGROUND_SYNC_MODE)?.let { + it.onPreferenceClickListener = Preference.OnPreferenceClickListener { + val initialMode = vectorPreferences.getFdroidSyncBackgroundMode() + val dialogFragment = BackgroundSyncModeChooserDialog.newInstance( + initialMode, + object : BackgroundSyncModeChooserDialog.InteractionListener { + override fun onOptionSelected(mode: BackgroundSyncMode) { + // option has change, need to act + if (mode == BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME) { + // Important, Battery optim white listing is needed in this mode; + // Even if using foreground service with foreground notif, it stops to work + // in doze mode for certain devices :/ + if (!isIgnoringBatteryOptimizations(requireContext())) { + requestDisablingBatteryOptimization(requireActivity(), + this@VectorSettingsNotificationPreferenceFragment, + REQUEST_BATTERY_OPTIMIZATION) + } + } + vectorPreferences.setFdroidSyncBackgroundMode(mode) + refreshBackgroundSyncPrefs() + } + } + ) + activity?.supportFragmentManager?.let { + dialogFragment.show(it, "syncDialog") + } + true + } + } + + findPreference(VectorPreferences.SETTINGS_SET_SYNC_TIMEOUT_PREFERENCE_KEY)?.let { + it.isEnabled = vectorPreferences.isBackgroundSyncEnabled() + it.summary = secondsToText(vectorPreferences.backgroundSyncTimeOut()) + it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> + if (newValue is String) { + val syncTimeout = tryThis { Integer.parseInt(newValue) } ?: 6 + vectorPreferences.setBackgroundSyncTimeout(maxOf(0, syncTimeout)) + refreshBackgroundSyncPrefs() + } + true + } + } + + findPreference(VectorPreferences.SETTINGS_SET_SYNC_DELAY_PREFERENCE_KEY)?.let { + it.isEnabled = vectorPreferences.isBackgroundSyncEnabled() + it.summary = secondsToText(vectorPreferences.backgroundSyncDelay()) + it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> + if (newValue is String) { + val syncDelay = tryThis { Integer.parseInt(newValue) } ?: 6 + vectorPreferences.setBackgroundSyncDelay(maxOf(0, syncDelay)) + refreshBackgroundSyncPrefs() + } + true + } + } + + refreshBackgroundSyncPrefs() + handleSystemPreference() } + private fun refreshBackgroundSyncPrefs() { + findPreference(VectorPreferences.SETTINGS_FDROID_BACKGROUND_SYNC_MODE)?.let { + it.summary = when (vectorPreferences.getFdroidSyncBackgroundMode()) { + BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_BATTERY -> getString(R.string.settings_background_fdroid_sync_mode_battery) + BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME -> getString(R.string.settings_background_fdroid_sync_mode_real_time) + BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_DISABLED -> getString(R.string.settings_background_fdroid_sync_mode_disabled) + } + } + + findPreference(VectorPreferences.SETTINGS_BACKGROUND_SYNC_PREFERENCE_KEY)?.let { + it.isVisible = !FcmHelper.isPushSupported() + } + + findPreference(VectorPreferences.SETTINGS_SET_SYNC_TIMEOUT_PREFERENCE_KEY)?.let { + it.isEnabled = vectorPreferences.isBackgroundSyncEnabled() + it.summary = secondsToText(vectorPreferences.backgroundSyncTimeOut()) + } + findPreference(VectorPreferences.SETTINGS_SET_SYNC_DELAY_PREFERENCE_KEY)?.let { + it.isEnabled = vectorPreferences.isBackgroundSyncEnabled() + it.summary = secondsToText(vectorPreferences.backgroundSyncDelay()) + } + } + + /** + * Convert a delay in seconds to string + * + * @param seconds the delay in seconds + * @return the text + */ + private fun secondsToText(seconds: Int): String { + return if (seconds > 1) { + seconds.toString() + " " + getString(R.string.settings_seconds) + } else { + seconds.toString() + " " + getString(R.string.settings_second) + } + } + private fun handleSystemPreference() { val callNotificationsSystemOptions = findPreference(VectorPreferences.SETTINGS_SYSTEM_CALL_NOTIFICATION_PREFERENCE_KEY)!! if (NotificationUtils.supportNotificationChannels()) { @@ -234,5 +334,6 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor( companion object { private const val REQUEST_NOTIFICATION_RINGTONE = 888 + private const val REQUEST_BATTERY_OPTIMIZATION = 500 } } diff --git a/vector/src/main/res/layout/dialog_background_sync_mode.xml b/vector/src/main/res/layout/dialog_background_sync_mode.xml new file mode 100644 index 0000000000..89b2aaefde --- /dev/null +++ b/vector/src/main/res/layout/dialog_background_sync_mode.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/vector/src/main/res/layout/item_custom_dialog_radio_line.xml b/vector/src/main/res/layout/item_custom_dialog_radio_line.xml new file mode 100644 index 0000000000..17d7be08ed --- /dev/null +++ b/vector/src/main/res/layout/item_custom_dialog_radio_line.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index a0abc28a4d..c191b17f7e 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -797,7 +797,7 @@ Messages sent by bot Background synchronization - Background Sync Mode (Experimental) + Background Sync Mode Optimized for battery Element will sync in background in way that preserves the device’s limited resources (battery).\nDepending on your device resource state, the sync may be deferred by the operating system. Optimized for real time diff --git a/vector/src/main/res/xml/network_security_config.xml b/vector/src/main/res/xml/network_security_config.xml index e40c61c229..1f323dffd1 100644 --- a/vector/src/main/res/xml/network_security_config.xml +++ b/vector/src/main/res/xml/network_security_config.xml @@ -13,4 +13,11 @@ 10.0.2.2 + + + + + + + diff --git a/vector/src/main/res/xml/vector_settings_notifications.xml b/vector/src/main/res/xml/vector_settings_notifications.xml index 3e19c9e9e5..52333dd81a 100644 --- a/vector/src/main/res/xml/vector_settings_notifications.xml +++ b/vector/src/main/res/xml/vector_settings_notifications.xml @@ -71,34 +71,37 @@ - From 43c24e55abfd0db2882609b3630a720a8f46bc4a Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 9 Sep 2020 10:33:34 +0200 Subject: [PATCH 02/15] Restart fdroid sync on application boot --- .../OnApplicationUpgradeOrRebootReceiver.kt | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/vector/src/fdroid/java/im/vector/app/fdroid/receiver/OnApplicationUpgradeOrRebootReceiver.kt b/vector/src/fdroid/java/im/vector/app/fdroid/receiver/OnApplicationUpgradeOrRebootReceiver.kt index a8a394576b..2460e149a5 100644 --- a/vector/src/fdroid/java/im/vector/app/fdroid/receiver/OnApplicationUpgradeOrRebootReceiver.kt +++ b/vector/src/fdroid/java/im/vector/app/fdroid/receiver/OnApplicationUpgradeOrRebootReceiver.kt @@ -21,6 +21,8 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import im.vector.app.core.di.HasVectorInjector +import im.vector.app.core.extensions.vectorComponent +import im.vector.app.features.settings.BackgroundSyncMode import timber.log.Timber class OnApplicationUpgradeOrRebootReceiver : BroadcastReceiver() { @@ -30,8 +32,23 @@ class OnApplicationUpgradeOrRebootReceiver : BroadcastReceiver() { val appContext = context.applicationContext if (appContext is HasVectorInjector) { val activeSession = appContext.injector().activeSessionHolder().getSafeActiveSession() + val preferences = appContext.vectorComponent().vectorPreferences() if (activeSession != null) { - AlarmSyncBroadcastReceiver.scheduleAlarm(context, activeSession.sessionId, 10) + when (preferences.getFdroidSyncBackgroundMode()) { + BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_BATTERY -> { + Timber.i("## Sync: OnBoot Work scheduled to periodically sync") + activeSession.startAutomaticBackgroundSync( + preferences.backgroundSyncTimeOut().toLong(), + preferences.backgroundSyncDelay().toLong() + ) + } + BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME -> { + AlarmSyncBroadcastReceiver.scheduleAlarm(context, activeSession.sessionId, preferences.backgroundSyncDelay()) + } + BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_DISABLED -> { + // nop + } + } } } } From 3ff475af7aecd6349010ebe0fadfa593c6389f62 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 9 Sep 2020 11:33:36 +0200 Subject: [PATCH 03/15] Avoid scheduling alarm until network is back --- .../internal/session/sync/job/SyncService.kt | 32 +++++++-- .../java/im/vector/app/push/fcm/FcmHelper.kt | 2 +- .../app/core/services/VectorSyncService.kt | 66 +++++++++++++++++++ 3 files changed, 93 insertions(+), 7 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncService.kt index 0ba1a672f9..485eca6f74 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncService.kt @@ -26,6 +26,7 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.launch import org.matrix.android.sdk.api.Matrix +import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.isTokenError import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.sync.SyncState @@ -86,17 +87,26 @@ abstract class SyncService : Service() { val isInit = initialize(intent) if (isInit) { periodic = intent?.getBooleanExtra(EXTRA_PERIODIC, false) ?: false - Timber.i("## Sync: command received, periodic: $periodic") - // default is syncing - doSyncIfNotAlreadyRunning() + val onNetworkBack = intent?.getBooleanExtra(EXTRA_NETWORK_BACK_RESTART, false) ?: false + Timber.d("## Sync: command received, periodic: $periodic networkBack: $onNetworkBack") + if (onNetworkBack && !backgroundDetectionObserver.isInBackground) { + // the restart after network occurs while the app is in foreground + // so just stop. It will be restarted when entering background + preventReschedule = true + stopMe() + } else { + // default is syncing + doSyncIfNotAlreadyRunning() + } } else { - Timber.i("## Sync: Failed to initialize service") + Timber.d("## Sync: Failed to initialize service") stopMe() } } } - return START_STICKY + // It's ok to be not sticky because we will explicitly start it again on the next alarm? + return START_NOT_STICKY } override fun onDestroy() { @@ -107,7 +117,7 @@ abstract class SyncService : Service() { isRunning.set(false) // Cancelling the context will trigger the catch close the doSync try serviceScope.coroutineContext.cancelChildren() - if (!preventReschedule && periodic && sessionId != null) { + if (!preventReschedule && periodic && sessionId != null && backgroundDetectionObserver.isInBackground) { Timber.d("## Sync: Reschedule service in $syncDelaySeconds sec") onRescheduleAsked(sessionId ?: "", false, syncTimeoutSeconds, syncDelaySeconds) } @@ -140,6 +150,7 @@ abstract class SyncService : Service() { Timber.v("## Sync: Execute sync request with timeout $syncTimeoutSeconds seconds") val params = SyncTask.Params(syncTimeoutSeconds * 1000L) try { + // never do that in foreground, let the syncThread work syncTask.execute(params) // Start sync if we were doing an initial sync and the syncThread is not launched yet if (isInitialSync && session.getSyncState() == SyncState.Idle) { @@ -153,6 +164,12 @@ abstract class SyncService : Service() { // no need to retry preventReschedule = true } + if (throwable is Failure.NetworkConnection) { + // Network is off, no need to reschedule endless alarms :/ + preventReschedule = true + // Instead start a work to restart background sync when network is back + onNetworkError(sessionId ?: "", isInitialSync, syncTimeoutSeconds, syncDelaySeconds) + } // JobCancellation could be caught here when onDestroy cancels the coroutine context if (isRunning.get()) stopMe() } @@ -189,6 +206,8 @@ abstract class SyncService : Service() { abstract fun onRescheduleAsked(sessionId: String, isInitialSync: Boolean, timeout: Int, delay: Int) + abstract fun onNetworkError(sessionId: String, isInitialSync: Boolean, timeout: Int, delay: Int) + override fun onBind(intent: Intent?): IBinder? { return null } @@ -198,6 +217,7 @@ abstract class SyncService : Service() { const val EXTRA_TIMEOUT_SECONDS = "EXTRA_TIMEOUT_SECONDS" const val EXTRA_DELAY_SECONDS = "EXTRA_DELAY_SECONDS" const val EXTRA_PERIODIC = "EXTRA_PERIODIC" + const val EXTRA_NETWORK_BACK_RESTART = "EXTRA_NETWORK_BACK_RESTART" const val ACTION_STOP = "ACTION_STOP" } diff --git a/vector/src/fdroid/java/im/vector/app/push/fcm/FcmHelper.kt b/vector/src/fdroid/java/im/vector/app/push/fcm/FcmHelper.kt index 7874d0237f..169fd9ecac 100755 --- a/vector/src/fdroid/java/im/vector/app/push/fcm/FcmHelper.kt +++ b/vector/src/fdroid/java/im/vector/app/push/fcm/FcmHelper.kt @@ -74,7 +74,7 @@ object FcmHelper { when (vectorPreferences.getFdroidSyncBackgroundMode()) { BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_BATTERY -> { // we rely on periodic worker - Timber.i("## Sync: Work scheduled to periodically sync") + Timber.i("## Sync: Work scheduled to periodically sync in ${vectorPreferences.backgroundSyncDelay()} sec") activeSessionHolder .getSafeActiveSession() ?.startAutomaticBackgroundSync( diff --git a/vector/src/main/java/im/vector/app/core/services/VectorSyncService.kt b/vector/src/main/java/im/vector/app/core/services/VectorSyncService.kt index bbd8487ae5..bf78d5b7fb 100644 --- a/vector/src/main/java/im/vector/app/core/services/VectorSyncService.kt +++ b/vector/src/main/java/im/vector/app/core/services/VectorSyncService.kt @@ -21,11 +21,21 @@ import android.app.PendingIntent import android.content.Context import android.content.Intent import android.os.Build +import androidx.core.content.ContextCompat.getSystemService import androidx.core.content.getSystemService +import androidx.work.Constraints +import androidx.work.Data +import androidx.work.NetworkType +import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.WorkManager +import androidx.work.WorkRequest +import androidx.work.Worker +import androidx.work.WorkerParameters import im.vector.app.R import im.vector.app.core.extensions.vectorComponent import im.vector.app.features.notifications.NotificationUtils import org.matrix.android.sdk.internal.session.sync.job.SyncService +import timber.log.Timber class VectorSyncService : SyncService() { @@ -48,6 +58,16 @@ class VectorSyncService : SyncService() { } } + fun newPeriodicNetworkBackIntent(context: Context, sessionId: String, timeoutSeconds: Int, delayInSeconds: Int): Intent { + return Intent(context, VectorSyncService::class.java).also { + it.putExtra(EXTRA_SESSION_ID, sessionId) + it.putExtra(EXTRA_TIMEOUT_SECONDS, timeoutSeconds) + it.putExtra(EXTRA_PERIODIC, true) + it.putExtra(EXTRA_DELAY_SECONDS, delayInSeconds) + it.putExtra(EXTRA_NETWORK_BACK_RESTART, true) + } + } + fun stopIntent(context: Context): Intent { return Intent(context, VectorSyncService::class.java).also { it.action = ACTION_STOP @@ -76,6 +96,28 @@ class VectorSyncService : SyncService() { reschedule(sessionId, timeout, delay) } + override fun onNetworkError(sessionId: String, isInitialSync: Boolean, timeout: Int, delay: Int) { + Timber.d("## Sync: A network error occured during sync") + val uploadWorkRequest: WorkRequest = + OneTimeWorkRequestBuilder() + .setInputData(Data.Builder() + .putString("sessionId", sessionId) + .putInt("timeout", timeout) + .putInt("delay", delay) + .build() + ) + .setConstraints(Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build() + ) + .build() + + Timber.d("## Sync: Schedule a work to restart service when network will be on") + WorkManager + .getInstance(applicationContext) + .enqueue(uploadWorkRequest) + } + override fun onDestroy() { removeForegroundNotification() super.onDestroy() @@ -100,4 +142,28 @@ class VectorSyncService : SyncService() { alarmMgr.set(AlarmManager.RTC_WAKEUP, firstMillis, pendingIntent) } } + + class RestartWhenNetworkOn(appContext: Context, workerParams: WorkerParameters) : + Worker(appContext, workerParams) { + override fun doWork(): Result { + val sessionId = inputData.getString("sessionId") ?: return Result.failure() + val timeout = inputData.getInt("timeout", 6) + val delay = inputData.getInt("delay", 60) + + val pendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + PendingIntent.getForegroundService(applicationContext, 0, newPeriodicNetworkBackIntent(applicationContext, sessionId, timeout, delay), 0) + } else { + PendingIntent.getService(applicationContext, 0, newPeriodicNetworkBackIntent(applicationContext, sessionId, timeout, delay), 0) + } + val firstMillis = System.currentTimeMillis() + delay * 1000L + val alarmMgr = getSystemService(applicationContext, AlarmManager::class.java)!! + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + alarmMgr.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, firstMillis, pendingIntent) + } else { + alarmMgr.set(AlarmManager.RTC_WAKEUP, firstMillis, pendingIntent) + } + // Indicate whether the work finished successfully with the Result + return Result.success() + } + } } From 8ac3af327b16745283d05b424f269c8522e176a9 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 9 Sep 2020 11:35:33 +0200 Subject: [PATCH 04/15] Update change log --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index eada6082b9..00966ef2d5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,7 @@ Features ✨: Improvements 🙌: - Handle date formatting properly (show time am/pm if needed, display year when needed) + - Improve F-Droid Notification (#2055) Bugfix 🐛: - Clear the notification when the event is read elsewhere (#1822) From f4c4e84ffef55ea999fe612ea267464b61f80e86 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 10 Sep 2020 17:13:11 +0200 Subject: [PATCH 05/15] Fix false positive from code quality analysis --- tools/check/forbidden_strings_in_code.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/check/forbidden_strings_in_code.txt b/tools/check/forbidden_strings_in_code.txt index ebe877b301..3ced7de7e2 100644 --- a/tools/check/forbidden_strings_in_code.txt +++ b/tools/check/forbidden_strings_in_code.txt @@ -151,7 +151,7 @@ android\.app\.AlertDialog new Gson\(\) ### Use matrixOneTimeWorkRequestBuilder -import androidx.work.OneTimeWorkRequestBuilder===1 +import androidx.work.OneTimeWorkRequestBuilder===2 ### Use TextUtils.formatFileSize Formatter\.formatFileSize===1 @@ -164,7 +164,7 @@ Formatter\.formatShortFileSize===1 # android\.text\.TextUtils ### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If it is ok, change the value in file forbidden_strings_in_code.txt -enum class===77 +enum class===78 ### Do not import temporary legacy classes import org.matrix.android.sdk.internal.legacy.riot===3 From 23f13b092ff82a15a1c3eebac8e64eefc3e5d870 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 10 Sep 2020 18:00:06 +0200 Subject: [PATCH 06/15] Plurals --- .../VectorSettingsNotificationPreferenceFragment.kt | 6 +----- vector/src/main/res/values/strings.xml | 4 ++++ 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationPreferenceFragment.kt index 21895e15b6..81036e64e1 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationPreferenceFragment.kt @@ -161,11 +161,7 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor( * @return the text */ private fun secondsToText(seconds: Int): String { - return if (seconds > 1) { - seconds.toString() + " " + getString(R.string.settings_seconds) - } else { - seconds.toString() + " " + getString(R.string.settings_second) - } + return resources.getQuantityString(R.plurals.seconds, seconds, seconds) } private fun handleSystemPreference() { diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index c191b17f7e..1ed36e4228 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -815,6 +815,10 @@ Delay between each Sync second seconds + + %d second + %d seconds + Version olm version From 4dc28a9d6287378aca74562b9f504fe8ef9a8259 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 10 Sep 2020 18:08:35 +0200 Subject: [PATCH 07/15] Reorder settings --- .../res/xml/vector_settings_notifications.xml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/vector/src/main/res/xml/vector_settings_notifications.xml b/vector/src/main/res/xml/vector_settings_notifications.xml index 52333dd81a..983ddf36f2 100644 --- a/vector/src/main/res/xml/vector_settings_notifications.xml +++ b/vector/src/main/res/xml/vector_settings_notifications.xml @@ -62,15 +62,6 @@ - - - - - - + + + + + + From db977b8109a8e305b31202327a88e71836ba7467 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 11 Sep 2020 09:43:10 +0200 Subject: [PATCH 08/15] Simplify Dialog UI and code --- .../BackgroundSyncModeChooserDialog.kt | 145 +++--------------- .../layout/dialog_background_sync_mode.xml | 84 +++++++++- .../layout/item_custom_dialog_radio_line.xml | 57 ------- 3 files changed, 103 insertions(+), 183 deletions(-) delete mode 100644 vector/src/main/res/layout/item_custom_dialog_radio_line.xml diff --git a/vector/src/main/java/im/vector/app/features/settings/BackgroundSyncModeChooserDialog.kt b/vector/src/main/java/im/vector/app/features/settings/BackgroundSyncModeChooserDialog.kt index 079be58fbc..8b45a06f23 100644 --- a/vector/src/main/java/im/vector/app/features/settings/BackgroundSyncModeChooserDialog.kt +++ b/vector/src/main/java/im/vector/app/features/settings/BackgroundSyncModeChooserDialog.kt @@ -17,147 +17,52 @@ package im.vector.app.features.settings import android.app.Dialog -import android.content.Context import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup -import android.widget.ArrayAdapter -import android.widget.ListView -import android.widget.RadioButton -import android.widget.TextView import androidx.appcompat.app.AlertDialog -import androidx.core.view.isVisible import androidx.fragment.app.DialogFragment -import androidx.fragment.app.FragmentActivity import im.vector.app.R class BackgroundSyncModeChooserDialog : DialogFragment() { - var interactionListener: InteractionListener? = null + private var interactionListener: InteractionListener? = null override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - return activity?.let { activity: FragmentActivity -> - val builder = AlertDialog.Builder(activity) - // Get the layout inflater - val inflater = activity.layoutInflater + val initialMode = BackgroundSyncMode.fromString(arguments?.getString(ARG_INITIAL_MODE)) - // Inflate and set the layout for the dialog - // Pass null as the parent view because its going in the dialog layout - val view = inflater.inflate(R.layout.dialog_background_sync_mode, null) - view.findViewById(R.id.dialog_background_sync_list)?.let { - it.adapter = Adapter( - activity, - BackgroundSyncMode.fromString(arguments?.getString(ARG_INITIAL_MODE)) - ) - } - builder.setView(view) - // Add action buttons - .setPositiveButton(R.string.ok) { dialog, _ -> - val mode = getSelectedOption() - if (mode.name == arguments?.getString(ARG_INITIAL_MODE)) { - // it's like a cancel, no changes - dialog.cancel() - } else { - interactionListener?.onOptionSelected(mode) - dialog.dismiss() - } - } - .setNegativeButton(R.string.cancel) { dialog, _ -> - interactionListener?.onCancel() - dialog.cancel() - } - builder.create() - } ?: throw IllegalStateException("Activity cannot be null") - } + val view = requireActivity().layoutInflater.inflate(R.layout.dialog_background_sync_mode, null) + val dialog = AlertDialog.Builder(requireActivity()) + .setTitle(R.string.settings_background_fdroid_sync_mode) + .setView(view) + .setPositiveButton(R.string.cancel, null) + .create() - private fun getSelectedOption(): BackgroundSyncMode { - options.forEach { - if (it.isSelected) return it.mode + view.findViewById(R.id.backgroundSyncModeBattery).setOnClickListener { + interactionListener + ?.takeIf { initialMode != BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_BATTERY } + ?.onOptionSelected(BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_BATTERY) + dialog.dismiss() } - // an item is always selected, should not happen - return options[0].mode - } - - data class SyncMode(val mode: BackgroundSyncMode, val title: Int, val description: Int, var isSelected: Boolean) - - private class Adapter(context: Context, val initialMode: BackgroundSyncMode) : ArrayAdapter(context, 0, options) { - init { - // mark the currently selected option - var initialModeFound = false - options.forEach { - it.isSelected = initialMode == it.mode - initialModeFound = true - } - if (!initialModeFound) { - options[0].isSelected = true - } + view.findViewById(R.id.backgroundSyncModeReal).setOnClickListener { + interactionListener + ?.takeIf { initialMode != BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME } + ?.onOptionSelected(BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME) + dialog.dismiss() } - - override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { - val syncMode = getItem(position)!! - - // Only 3 items, let's keep it like that - val itemView = convertView - ?: LayoutInflater.from(context).inflate(R.layout.item_custom_dialog_radio_line, parent, false) - - // Lookup view for data population - itemView?.findViewById(R.id.item_generic_title_text)?.let { - it.text = context.getString(syncMode.title) - } - itemView?.findViewById(R.id.item_generic_description_text)?.let { - it.text = context.getString(syncMode.description) - it.isVisible = true - } - itemView?.findViewById(R.id.item_generic_radio)?.let { - it.isChecked = syncMode.isSelected - it.isVisible = true - // let the item click handle that - it.setOnClickListener { - toggleChangeAtPosition(position) - } - } - - itemView?.setOnClickListener { - toggleChangeAtPosition(position) - } - - // Populate the data into the template view using the data object - - return itemView - } - - private fun toggleChangeAtPosition(position: Int) { - if (getItem(position)?.isSelected == true) { - // nop - } else { - for (i in 0 until count) { - // we change the single selection - getItem(i)?.isSelected = i == position - } - notifyDataSetChanged() - } + view.findViewById(R.id.backgroundSyncModeOff).setOnClickListener { + interactionListener + ?.takeIf { initialMode != BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_DISABLED } + ?.onOptionSelected(BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_DISABLED) + dialog.dismiss() } + return dialog } interface InteractionListener { - fun onCancel() {} - fun onOptionSelected(mode: BackgroundSyncMode) {} + fun onOptionSelected(mode: BackgroundSyncMode) } companion object { - private val options = listOf( - SyncMode(BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_BATTERY, - R.string.settings_background_fdroid_sync_mode_battery, - R.string.settings_background_fdroid_sync_mode_battery_description, false), - SyncMode(BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME, - R.string.settings_background_fdroid_sync_mode_real_time, - R.string.settings_background_fdroid_sync_mode_real_time_description, false), - SyncMode(BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_DISABLED, - R.string.settings_background_fdroid_sync_mode_disabled, - R.string.settings_background_fdroid_sync_mode_disabled_description, false) - ) - private const val ARG_INITIAL_MODE = "ARG_INITIAL_MODE" fun newInstance(selectedMode: BackgroundSyncMode, interactionListener: InteractionListener): BackgroundSyncModeChooserDialog { diff --git a/vector/src/main/res/layout/dialog_background_sync_mode.xml b/vector/src/main/res/layout/dialog_background_sync_mode.xml index 89b2aaefde..1ddaa3a3f6 100644 --- a/vector/src/main/res/layout/dialog_background_sync_mode.xml +++ b/vector/src/main/res/layout/dialog_background_sync_mode.xml @@ -1,13 +1,85 @@ - - + android:orientation="vertical"> - + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vector/src/main/res/layout/item_custom_dialog_radio_line.xml b/vector/src/main/res/layout/item_custom_dialog_radio_line.xml deleted file mode 100644 index 17d7be08ed..0000000000 --- a/vector/src/main/res/layout/item_custom_dialog_radio_line.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - From c2be97741e78780339dfe399abd862596d5500e3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 11 Sep 2020 10:12:56 +0200 Subject: [PATCH 09/15] Restore listener after device rotation --- .../BackgroundSyncModeChooserDialog.kt | 5 +- ...rSettingsNotificationPreferenceFragment.kt | 49 ++++++++++--------- 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/settings/BackgroundSyncModeChooserDialog.kt b/vector/src/main/java/im/vector/app/features/settings/BackgroundSyncModeChooserDialog.kt index 8b45a06f23..59b8569c1e 100644 --- a/vector/src/main/java/im/vector/app/features/settings/BackgroundSyncModeChooserDialog.kt +++ b/vector/src/main/java/im/vector/app/features/settings/BackgroundSyncModeChooserDialog.kt @@ -25,7 +25,7 @@ import im.vector.app.R class BackgroundSyncModeChooserDialog : DialogFragment() { - private var interactionListener: InteractionListener? = null + var interactionListener: InteractionListener? = null override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val initialMode = BackgroundSyncMode.fromString(arguments?.getString(ARG_INITIAL_MODE)) @@ -65,9 +65,8 @@ class BackgroundSyncModeChooserDialog : DialogFragment() { companion object { private const val ARG_INITIAL_MODE = "ARG_INITIAL_MODE" - fun newInstance(selectedMode: BackgroundSyncMode, interactionListener: InteractionListener): BackgroundSyncModeChooserDialog { + fun newInstance(selectedMode: BackgroundSyncMode): BackgroundSyncModeChooserDialog { val frag = BackgroundSyncModeChooserDialog() - frag.interactionListener = interactionListener val args = Bundle() args.putString(ARG_INITIAL_MODE, selectedMode.name) frag.arguments = args diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationPreferenceFragment.kt index 81036e64e1..62ecc606c4 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationPreferenceFragment.kt @@ -47,7 +47,8 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor( private val pushManager: PushersManager, private val activeSessionHolder: ActiveSessionHolder, private val vectorPreferences: VectorPreferences -) : VectorSettingsBaseFragment() { +) : VectorSettingsBaseFragment(), + BackgroundSyncModeChooserDialog.InteractionListener { override var titleRes: Int = R.string.settings_notifications override val preferenceXmlRes = R.xml.vector_settings_notifications @@ -73,28 +74,10 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor( findPreference(VectorPreferences.SETTINGS_FDROID_BACKGROUND_SYNC_MODE)?.let { it.onPreferenceClickListener = Preference.OnPreferenceClickListener { val initialMode = vectorPreferences.getFdroidSyncBackgroundMode() - val dialogFragment = BackgroundSyncModeChooserDialog.newInstance( - initialMode, - object : BackgroundSyncModeChooserDialog.InteractionListener { - override fun onOptionSelected(mode: BackgroundSyncMode) { - // option has change, need to act - if (mode == BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME) { - // Important, Battery optim white listing is needed in this mode; - // Even if using foreground service with foreground notif, it stops to work - // in doze mode for certain devices :/ - if (!isIgnoringBatteryOptimizations(requireContext())) { - requestDisablingBatteryOptimization(requireActivity(), - this@VectorSettingsNotificationPreferenceFragment, - REQUEST_BATTERY_OPTIMIZATION) - } - } - vectorPreferences.setFdroidSyncBackgroundMode(mode) - refreshBackgroundSyncPrefs() - } - } - ) - activity?.supportFragmentManager?.let { - dialogFragment.show(it, "syncDialog") + val dialogFragment = BackgroundSyncModeChooserDialog.newInstance(initialMode) + dialogFragment.interactionListener = this + activity?.supportFragmentManager?.let {fm -> + dialogFragment.show(fm, "syncDialog") } true } @@ -131,6 +114,23 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor( handleSystemPreference() } + // BackgroundSyncModeChooserDialog.InteractionListener + override fun onOptionSelected(mode: BackgroundSyncMode) { + // option has change, need to act + if (mode == BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME) { + // Important, Battery optim white listing is needed in this mode; + // Even if using foreground service with foreground notif, it stops to work + // in doze mode for certain devices :/ + if (!isIgnoringBatteryOptimizations(requireContext())) { + requestDisablingBatteryOptimization(requireActivity(), + this@VectorSettingsNotificationPreferenceFragment, + REQUEST_BATTERY_OPTIMIZATION) + } + } + vectorPreferences.setFdroidSyncBackgroundMode(mode) + refreshBackgroundSyncPrefs() + } + private fun refreshBackgroundSyncPrefs() { findPreference(VectorPreferences.SETTINGS_FDROID_BACKGROUND_SYNC_MODE)?.let { it.summary = when (vectorPreferences.getFdroidSyncBackgroundMode()) { @@ -251,6 +251,9 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor( if (context is VectorSettingsFragmentInteractionListener) { interactionListener = context } + (activity?.supportFragmentManager + ?.findFragmentByTag("syncDialog") as BackgroundSyncModeChooserDialog?) + ?.interactionListener = this } override fun onDetach() { From e997610ef2e1111c758f440d2e4d94ce5ef7667d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 11 Sep 2020 10:33:20 +0200 Subject: [PATCH 10/15] Add documentation from PR description --- .../app/features/settings/BackgroundSyncMode.kt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/settings/BackgroundSyncMode.kt b/vector/src/main/java/im/vector/app/features/settings/BackgroundSyncMode.kt index f266060053..2dcb663fc1 100644 --- a/vector/src/main/java/im/vector/app/features/settings/BackgroundSyncMode.kt +++ b/vector/src/main/java/im/vector/app/features/settings/BackgroundSyncMode.kt @@ -16,9 +16,26 @@ package im.vector.app.features.settings +/** + * Different strategies for Background sync, only applicable to F-Droid version of the app + */ enum class BackgroundSyncMode { + /** + * In this mode background syncs are scheduled via Workers, meaning that the system will have control on the periodicity + * of syncs when battery is low or when the phone is idle (sync will occur in allowed maintenance windows). After completion + * the sync work will schedule another one. + */ FDROID_BACKGROUND_SYNC_MODE_FOR_BATTERY, + + /** + * This mode requires the app to be exempted from battery optimization. Alarms will be launched and will wake up the app + * in order to perform the background sync as a foreground service. After completion the service will schedule another alarm + */ FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME, + + /** + * The app won't sync in background + */ FDROID_BACKGROUND_SYNC_MODE_DISABLED; companion object { From da09df0e4227acc07f28080e38685bd6fe50a6c0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 11 Sep 2020 10:49:54 +0200 Subject: [PATCH 11/15] format --- .../settings/VectorSettingsNotificationPreferenceFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationPreferenceFragment.kt index 62ecc606c4..61dbe1705e 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationPreferenceFragment.kt @@ -76,7 +76,7 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor( val initialMode = vectorPreferences.getFdroidSyncBackgroundMode() val dialogFragment = BackgroundSyncModeChooserDialog.newInstance(initialMode) dialogFragment.interactionListener = this - activity?.supportFragmentManager?.let {fm -> + activity?.supportFragmentManager?.let { fm -> dialogFragment.show(fm, "syncDialog") } true From 7efc58cb4256be2d3a36cd141705df00e2c057b3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 11 Sep 2020 11:02:54 +0200 Subject: [PATCH 12/15] Avoid duplication of code and fix issue on OnApplicationUpgradeOrRebootReceiver: background starts even if notification are disabled --- .../matrix/android/sdk/api/session/Session.kt | 2 +- .../app/fdroid/BackgroundSyncStarter.kt | 52 +++++++++++++++++++ .../OnApplicationUpgradeOrRebootReceiver.kt | 26 +++------- .../java/im/vector/app/push/fcm/FcmHelper.kt | 27 +--------- 4 files changed, 61 insertions(+), 46 deletions(-) create mode 100644 vector/src/fdroid/java/im/vector/app/fdroid/BackgroundSyncStarter.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt index f5b10a5645..4dfc24ddae 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt @@ -110,7 +110,7 @@ interface Session : * This does not work in doze mode :/ * If battery optimization is on it can work in app standby but that's all :/ */ - fun startAutomaticBackgroundSync(timeOutInSeconds: Long, repeatDelayInSeconds: Long = 30L) + fun startAutomaticBackgroundSync(timeOutInSeconds: Long, repeatDelayInSeconds: Long) fun stopAnyBackgroundSync() diff --git a/vector/src/fdroid/java/im/vector/app/fdroid/BackgroundSyncStarter.kt b/vector/src/fdroid/java/im/vector/app/fdroid/BackgroundSyncStarter.kt new file mode 100644 index 0000000000..7221e2b065 --- /dev/null +++ b/vector/src/fdroid/java/im/vector/app/fdroid/BackgroundSyncStarter.kt @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.fdroid + +import android.content.Context +import im.vector.app.core.di.ActiveSessionHolder +import im.vector.app.fdroid.receiver.AlarmSyncBroadcastReceiver +import im.vector.app.features.settings.BackgroundSyncMode +import im.vector.app.features.settings.VectorPreferences +import timber.log.Timber + +object BackgroundSyncStarter { + fun start(context: Context, vectorPreferences: VectorPreferences, activeSessionHolder: ActiveSessionHolder) { + if (vectorPreferences.areNotificationEnabledForDevice()) { + val activeSession = activeSessionHolder.getSafeActiveSession() ?: return + + when (vectorPreferences.getFdroidSyncBackgroundMode()) { + BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_BATTERY -> { + // we rely on periodic worker + Timber.i("## Sync: Work scheduled to periodically sync in ${vectorPreferences.backgroundSyncDelay()}s") + activeSession.startAutomaticBackgroundSync( + vectorPreferences.backgroundSyncTimeOut().toLong(), + vectorPreferences.backgroundSyncDelay().toLong() + ) + } + BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME -> { + // We need to use alarm in this mode + AlarmSyncBroadcastReceiver.scheduleAlarm(context, activeSession.sessionId, vectorPreferences.backgroundSyncDelay()) + Timber.i("## Sync: Alarm scheduled to start syncing") + } + BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_DISABLED -> { + // we do nothing + Timber.i("## Sync: background sync is disabled") + } + } + } + } +} diff --git a/vector/src/fdroid/java/im/vector/app/fdroid/receiver/OnApplicationUpgradeOrRebootReceiver.kt b/vector/src/fdroid/java/im/vector/app/fdroid/receiver/OnApplicationUpgradeOrRebootReceiver.kt index 2460e149a5..797b5734a2 100644 --- a/vector/src/fdroid/java/im/vector/app/fdroid/receiver/OnApplicationUpgradeOrRebootReceiver.kt +++ b/vector/src/fdroid/java/im/vector/app/fdroid/receiver/OnApplicationUpgradeOrRebootReceiver.kt @@ -22,7 +22,7 @@ import android.content.Context import android.content.Intent import im.vector.app.core.di.HasVectorInjector import im.vector.app.core.extensions.vectorComponent -import im.vector.app.features.settings.BackgroundSyncMode +import im.vector.app.fdroid.BackgroundSyncStarter import timber.log.Timber class OnApplicationUpgradeOrRebootReceiver : BroadcastReceiver() { @@ -31,25 +31,11 @@ class OnApplicationUpgradeOrRebootReceiver : BroadcastReceiver() { Timber.v("## onReceive() ${intent.action}") val appContext = context.applicationContext if (appContext is HasVectorInjector) { - val activeSession = appContext.injector().activeSessionHolder().getSafeActiveSession() - val preferences = appContext.vectorComponent().vectorPreferences() - if (activeSession != null) { - when (preferences.getFdroidSyncBackgroundMode()) { - BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_BATTERY -> { - Timber.i("## Sync: OnBoot Work scheduled to periodically sync") - activeSession.startAutomaticBackgroundSync( - preferences.backgroundSyncTimeOut().toLong(), - preferences.backgroundSyncDelay().toLong() - ) - } - BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME -> { - AlarmSyncBroadcastReceiver.scheduleAlarm(context, activeSession.sessionId, preferences.backgroundSyncDelay()) - } - BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_DISABLED -> { - // nop - } - } - } + BackgroundSyncStarter.start( + context, + appContext.vectorComponent().vectorPreferences(), + appContext.injector().activeSessionHolder() + ) } } } diff --git a/vector/src/fdroid/java/im/vector/app/push/fcm/FcmHelper.kt b/vector/src/fdroid/java/im/vector/app/push/fcm/FcmHelper.kt index 169fd9ecac..5f0ee396ee 100755 --- a/vector/src/fdroid/java/im/vector/app/push/fcm/FcmHelper.kt +++ b/vector/src/fdroid/java/im/vector/app/push/fcm/FcmHelper.kt @@ -22,10 +22,9 @@ import android.app.Activity import android.content.Context import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.pushers.PushersManager +import im.vector.app.fdroid.BackgroundSyncStarter import im.vector.app.fdroid.receiver.AlarmSyncBroadcastReceiver -import im.vector.app.features.settings.BackgroundSyncMode import im.vector.app.features.settings.VectorPreferences -import timber.log.Timber /** * This class has an alter ego in the gplay variant. @@ -69,28 +68,6 @@ object FcmHelper { } fun onEnterBackground(context: Context, vectorPreferences: VectorPreferences, activeSessionHolder: ActiveSessionHolder) { - // We need to use alarm in this mode - if (vectorPreferences.areNotificationEnabledForDevice() && activeSessionHolder.hasActiveSession()) { - when (vectorPreferences.getFdroidSyncBackgroundMode()) { - BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_BATTERY -> { - // we rely on periodic worker - Timber.i("## Sync: Work scheduled to periodically sync in ${vectorPreferences.backgroundSyncDelay()} sec") - activeSessionHolder - .getSafeActiveSession() - ?.startAutomaticBackgroundSync( - vectorPreferences.backgroundSyncTimeOut().toLong(), - vectorPreferences.backgroundSyncDelay().toLong() - ) - } - BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME -> { - val currentSession = activeSessionHolder.getActiveSession() - AlarmSyncBroadcastReceiver.scheduleAlarm(context, currentSession.sessionId, vectorPreferences.backgroundSyncDelay()) - Timber.i("## Sync: Alarm scheduled to start syncing") - } - BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_DISABLED -> { - // we do nothing - } - } - } + BackgroundSyncStarter.start(context, vectorPreferences, activeSessionHolder) } } From 2ed7be243b7ecc034fc66e08e2175f5f50137c3e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 11 Sep 2020 11:21:37 +0200 Subject: [PATCH 13/15] Restore TestBatteryOptimization --- .../troubleshoot/TestBatteryOptimization.kt | 21 ++++++++++--------- ...ificationTroubleshootTestManagerFactory.kt | 5 ++++- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestBatteryOptimization.kt b/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestBatteryOptimization.kt index 19a6870b2b..510ade0a33 100644 --- a/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestBatteryOptimization.kt +++ b/vector/src/fdroid/java/im/vector/app/fdroid/features/settings/troubleshoot/TestBatteryOptimization.kt @@ -15,29 +15,30 @@ */ package im.vector.app.fdroid.features.settings.troubleshoot -import androidx.fragment.app.Fragment +import androidx.appcompat.app.AppCompatActivity import im.vector.app.R +import im.vector.app.core.resources.StringProvider import im.vector.app.core.utils.isIgnoringBatteryOptimizations import im.vector.app.core.utils.requestDisablingBatteryOptimization import im.vector.app.features.settings.troubleshoot.NotificationTroubleshootTestManager import im.vector.app.features.settings.troubleshoot.TroubleshootTest +import javax.inject.Inject -// Not used anymore -class TestBatteryOptimization(val fragment: Fragment) : TroubleshootTest(R.string.settings_troubleshoot_test_battery_title) { +class TestBatteryOptimization @Inject constructor( + private val context: AppCompatActivity, + private val stringProvider: StringProvider +) : TroubleshootTest(R.string.settings_troubleshoot_test_battery_title) { override fun perform() { - val context = fragment.context - if (context != null && isIgnoringBatteryOptimizations(context)) { - description = fragment.getString(R.string.settings_troubleshoot_test_battery_success) + if (isIgnoringBatteryOptimizations(context)) { + description = stringProvider.getString(R.string.settings_troubleshoot_test_battery_success) status = TestStatus.SUCCESS quickFix = null } else { - description = fragment.getString(R.string.settings_troubleshoot_test_battery_failed) + description = stringProvider.getString(R.string.settings_troubleshoot_test_battery_failed) quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_battery_quickfix) { override fun doFix() { - fragment.activity?.let { - requestDisablingBatteryOptimization(it, fragment, NotificationTroubleshootTestManager.REQ_CODE_FIX) - } + requestDisablingBatteryOptimization(context, null, NotificationTroubleshootTestManager.REQ_CODE_FIX) } } status = TestStatus.FAILED diff --git a/vector/src/fdroid/java/im/vector/app/push/fcm/NotificationTroubleshootTestManagerFactory.kt b/vector/src/fdroid/java/im/vector/app/push/fcm/NotificationTroubleshootTestManagerFactory.kt index ce9bad6c26..65b8609446 100644 --- a/vector/src/fdroid/java/im/vector/app/push/fcm/NotificationTroubleshootTestManagerFactory.kt +++ b/vector/src/fdroid/java/im/vector/app/push/fcm/NotificationTroubleshootTestManagerFactory.kt @@ -18,6 +18,7 @@ package im.vector.app.push.fcm import androidx.fragment.app.Fragment import im.vector.app.fdroid.features.settings.troubleshoot.TestAutoStartBoot import im.vector.app.fdroid.features.settings.troubleshoot.TestBackgroundRestrictions +import im.vector.app.fdroid.features.settings.troubleshoot.TestBatteryOptimization import im.vector.app.features.settings.troubleshoot.NotificationTroubleshootTestManager import im.vector.app.features.settings.troubleshoot.TestAccountSettings import im.vector.app.features.settings.troubleshoot.TestDeviceSettings @@ -30,7 +31,8 @@ class NotificationTroubleshootTestManagerFactory @Inject constructor(private val private val testDeviceSettings: TestDeviceSettings, private val testPushRulesSettings: TestPushRulesSettings, private val testAutoStartBoot: TestAutoStartBoot, - private val testBackgroundRestrictions: TestBackgroundRestrictions) { + private val testBackgroundRestrictions: TestBackgroundRestrictions, + private val testBatteryOptimization: TestBatteryOptimization) { fun create(fragment: Fragment): NotificationTroubleshootTestManager { val mgr = NotificationTroubleshootTestManager(fragment) @@ -40,6 +42,7 @@ class NotificationTroubleshootTestManagerFactory @Inject constructor(private val mgr.addTest(testPushRulesSettings) mgr.addTest(testAutoStartBoot) mgr.addTest(testBackgroundRestrictions) + mgr.addTest(testBatteryOptimization) return mgr } } From b26d379d20603a9150a095fe02898e57e88c8572 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 11 Sep 2020 11:49:14 +0200 Subject: [PATCH 14/15] Refresh push pref after diagnostic --- ...VectorSettingsNotificationPreferenceFragment.kt | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationPreferenceFragment.kt index 61dbe1705e..e7b9c3cb2a 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationPreferenceFragment.kt @@ -88,7 +88,7 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor( it.summary = secondsToText(vectorPreferences.backgroundSyncTimeOut()) it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> if (newValue is String) { - val syncTimeout = tryThis { Integer.parseInt(newValue) } ?: 6 + val syncTimeout = tryThis { Integer.parseInt(newValue) } ?: 6 vectorPreferences.setBackgroundSyncTimeout(maxOf(0, syncTimeout)) refreshBackgroundSyncPrefs() } @@ -101,7 +101,7 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor( it.summary = secondsToText(vectorPreferences.backgroundSyncDelay()) it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> if (newValue is String) { - val syncDelay = tryThis { Integer.parseInt(newValue) } ?: 6 + val syncDelay = tryThis { Integer.parseInt(newValue) } ?: 6 vectorPreferences.setBackgroundSyncDelay(maxOf(0, syncDelay)) refreshBackgroundSyncPrefs() } @@ -244,6 +244,16 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor( val preference = findPreference(key) preference?.isHighlighted = true } + + refreshPref() + } + + private fun refreshPref() { + // This pref may have change from troubleshoot pref fragment + if (!FcmHelper.isPushSupported()) { + findPreference(VectorPreferences.SETTINGS_START_ON_BOOT_PREFERENCE_KEY) + ?.isChecked = vectorPreferences.autoStartOnBoot() + } } override fun onAttach(context: Context) { From 8abff412d05229477c1560ac9e39ee007ee77b25 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 11 Sep 2020 11:56:25 +0200 Subject: [PATCH 15/15] Create constant for default value and so fix a bug when setting bad value for delay. --- .../im/vector/app/features/settings/BackgroundSyncMode.kt | 3 +++ .../im/vector/app/features/settings/VectorPreferences.kt | 8 ++++---- .../VectorSettingsNotificationPreferenceFragment.kt | 4 ++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/settings/BackgroundSyncMode.kt b/vector/src/main/java/im/vector/app/features/settings/BackgroundSyncMode.kt index 2dcb663fc1..9d8b5755b0 100644 --- a/vector/src/main/java/im/vector/app/features/settings/BackgroundSyncMode.kt +++ b/vector/src/main/java/im/vector/app/features/settings/BackgroundSyncMode.kt @@ -39,6 +39,9 @@ enum class BackgroundSyncMode { FDROID_BACKGROUND_SYNC_MODE_DISABLED; companion object { + const val DEFAULT_SYNC_DELAY_SECONDS = 60 + const val DEFAULT_SYNC_TIMEOUT_SECONDS = 6 + fun fromString(value: String?): BackgroundSyncMode = values().firstOrNull { it.name == value } ?: FDROID_BACKGROUND_SYNC_MODE_DISABLED } diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt index 2239efa992..7415b57310 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt @@ -837,8 +837,8 @@ class VectorPreferences @Inject constructor(private val context: Context) { fun backgroundSyncTimeOut(): Int { return tryThis { // The xml pref is saved as a string so use getString and parse - defaultPrefs.getString(SETTINGS_SET_SYNC_TIMEOUT_PREFERENCE_KEY, "6")?.toInt() - } ?: 6 + defaultPrefs.getString(SETTINGS_SET_SYNC_TIMEOUT_PREFERENCE_KEY, null)?.toInt() + } ?: BackgroundSyncMode.DEFAULT_SYNC_TIMEOUT_SECONDS } fun setBackgroundSyncTimeout(timeInSecond: Int) { @@ -851,8 +851,8 @@ class VectorPreferences @Inject constructor(private val context: Context) { fun backgroundSyncDelay(): Int { return tryThis { // The xml pref is saved as a string so use getString and parse - defaultPrefs.getString(SETTINGS_SET_SYNC_DELAY_PREFERENCE_KEY, "60")?.toInt() - } ?: 60 + defaultPrefs.getString(SETTINGS_SET_SYNC_DELAY_PREFERENCE_KEY, null)?.toInt() + } ?: BackgroundSyncMode.DEFAULT_SYNC_DELAY_SECONDS } fun setBackgroundSyncDelay(timeInSecond: Int) { diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationPreferenceFragment.kt index e7b9c3cb2a..3f74c4db75 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationPreferenceFragment.kt @@ -88,7 +88,7 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor( it.summary = secondsToText(vectorPreferences.backgroundSyncTimeOut()) it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> if (newValue is String) { - val syncTimeout = tryThis { Integer.parseInt(newValue) } ?: 6 + val syncTimeout = tryThis { Integer.parseInt(newValue) } ?: BackgroundSyncMode.DEFAULT_SYNC_TIMEOUT_SECONDS vectorPreferences.setBackgroundSyncTimeout(maxOf(0, syncTimeout)) refreshBackgroundSyncPrefs() } @@ -101,7 +101,7 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor( it.summary = secondsToText(vectorPreferences.backgroundSyncDelay()) it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> if (newValue is String) { - val syncDelay = tryThis { Integer.parseInt(newValue) } ?: 6 + val syncDelay = tryThis { Integer.parseInt(newValue) } ?: BackgroundSyncMode.DEFAULT_SYNC_DELAY_SECONDS vectorPreferences.setBackgroundSyncDelay(maxOf(0, syncDelay)) refreshBackgroundSyncPrefs() }