F-Droid background sync modes
This commit is contained in:
parent
b9e8d7187c
commit
971b425e17
@ -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()
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -32,7 +32,7 @@ import javax.inject.Inject
|
||||
|
||||
internal interface SyncTask : Task<SyncTask.Params, Unit> {
|
||||
|
||||
data class Params(var timeout: Long = 30_000L)
|
||||
data class Params(var timeout: Long = 6_000L)
|
||||
}
|
||||
|
||||
internal class DefaultSyncTask @Inject constructor(
|
||||
|
@ -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<PowerManager>()?.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"
|
||||
}
|
||||
}
|
||||
|
@ -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<SyncWorker>()
|
||||
.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<SyncWorker>()
|
||||
.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) {
|
||||
|
@ -4,6 +4,11 @@
|
||||
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<!--
|
||||
Required for long polling account synchronisation in background.
|
||||
If not present ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS intent action won't work
|
||||
-->
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||
|
||||
<application>
|
||||
|
||||
|
@ -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<PowerManager>()!!.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<AlarmManager>()!!
|
||||
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<AlarmManager>()!!
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -102,7 +102,7 @@ object FcmHelper {
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun onEnterForeground(context: Context) {
|
||||
fun onEnterForeground(context: Context, activeSessionHolder: ActiveSessionHolder) {
|
||||
// No op
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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<AlarmManager>()!!
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
alarmMgr.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, firstMillis, pendingIntent)
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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<ListView>(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<SyncMode>(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<TextView>(R.id.item_generic_title_text)?.let {
|
||||
it.text = context.getString(syncMode.title)
|
||||
}
|
||||
itemView?.findViewById<TextView>(R.id.item_generic_description_text)?.let {
|
||||
it.text = context.getString(syncMode.description)
|
||||
it.isVisible = true
|
||||
}
|
||||
itemView?.findViewById<RadioButton>(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
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<VectorPreference>(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<VectorEditTextPreference>(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<VectorEditTextPreference>(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<VectorPreference>(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<VectorPreferenceCategory>(VectorPreferences.SETTINGS_BACKGROUND_SYNC_PREFERENCE_KEY)?.let {
|
||||
it.isVisible = !FcmHelper.isPushSupported()
|
||||
}
|
||||
|
||||
findPreference<VectorEditTextPreference>(VectorPreferences.SETTINGS_SET_SYNC_TIMEOUT_PREFERENCE_KEY)?.let {
|
||||
it.isEnabled = vectorPreferences.isBackgroundSyncEnabled()
|
||||
it.summary = secondsToText(vectorPreferences.backgroundSyncTimeOut())
|
||||
}
|
||||
findPreference<VectorEditTextPreference>(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<VectorPreference>(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
|
||||
}
|
||||
}
|
||||
|
13
vector/src/main/res/layout/dialog_background_sync_mode.xml
Normal file
13
vector/src/main/res/layout/dialog_background_sync_mode.xml
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ListView
|
||||
android:id="@+id/dialog_background_sync_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:listitem="@layout/item_custom_dialog_radio_line" />
|
||||
|
||||
</LinearLayout>
|
57
vector/src/main/res/layout/item_custom_dialog_radio_line.xml
Normal file
57
vector/src/main/res/layout/item_custom_dialog_radio_line.xml
Normal file
@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/colorBackground"
|
||||
android:minHeight="50dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/item_generic_title_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="2dp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toTopOf="@+id/item_generic_description_text"
|
||||
app:layout_constraintEnd_toStartOf="@+id/item_generic_radio"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Item Title"
|
||||
tools:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/item_generic_description_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:paddingTop="2dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/item_generic_radio"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/item_generic_title_text"
|
||||
tools:text="At totam delectus et aliquid dolorem. Consectetur voluptas tempore et non blanditiis id optio. Dolorum impedit quidem minus nihil. "
|
||||
tools:visibility="visible" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/item_generic_radio"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/item_generic_description_text"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/item_generic_title_text" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -797,7 +797,7 @@
|
||||
<string name="settings_messages_sent_by_bot">Messages sent by bot</string>
|
||||
|
||||
<string name="settings_background_sync">Background synchronization</string>
|
||||
<string name="settings_background_fdroid_sync_mode">Background Sync Mode (Experimental)</string>
|
||||
<string name="settings_background_fdroid_sync_mode">Background Sync Mode</string>
|
||||
<string name="settings_background_fdroid_sync_mode_battery">Optimized for battery</string>
|
||||
<string name="settings_background_fdroid_sync_mode_battery_description">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.</string>
|
||||
<string name="settings_background_fdroid_sync_mode_real_time">Optimized for real time</string>
|
||||
|
@ -13,4 +13,11 @@
|
||||
<domain includeSubdomains="true">10.0.2.2</domain>
|
||||
</domain-config>
|
||||
|
||||
<debug-overrides>
|
||||
<trust-anchors>
|
||||
<certificates src="system" />
|
||||
<certificates src="user" />
|
||||
</trust-anchors>
|
||||
</debug-overrides>
|
||||
|
||||
</network-security-config>
|
||||
|
@ -71,34 +71,37 @@
|
||||
|
||||
</im.vector.app.core.preference.VectorPreferenceCategory>
|
||||
|
||||
<!--im.vector.app.core.preference.VectorPreferenceCategory
|
||||
<im.vector.app.core.preference.VectorPreferenceCategory
|
||||
android:key="SETTINGS_BACKGROUND_SYNC_PREFERENCE_KEY"
|
||||
android:title="@string/settings_background_sync">
|
||||
android:title="@string/settings_background_sync"
|
||||
app:isPreferenceVisible="false">
|
||||
|
||||
<im.vector.app.core.preference.VectorPreference
|
||||
android:key="SETTINGS_FDROID_BACKGROUND_SYNC_MODE"
|
||||
android:persistent="false"
|
||||
android:title="@string/settings_background_fdroid_sync_mode" />
|
||||
|
||||
<im.vector.app.core.preference.VectorEditTextPreference
|
||||
android:inputType="numberDecimal"
|
||||
android:persistent="false"
|
||||
android:key="SETTINGS_SET_SYNC_DELAY_PREFERENCE_KEY"
|
||||
android:title="@string/settings_set_sync_delay" />
|
||||
|
||||
<im.vector.app.core.preference.VectorEditTextPreference
|
||||
android:inputType="numberDecimal"
|
||||
android:persistent="false"
|
||||
android:key="SETTINGS_SET_SYNC_TIMEOUT_PREFERENCE_KEY"
|
||||
android:title="@string/settings_set_sync_timeout" />
|
||||
|
||||
<im.vector.app.core.preference.VectorSwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:key="SETTINGS_START_ON_BOOT_PREFERENCE_KEY"
|
||||
android:title="@string/settings_start_on_boot" />
|
||||
|
||||
<im.vector.app.core.preference.VectorSwitchPreference
|
||||
android:key="SETTINGS_ENABLE_BACKGROUND_SYNC_PREFERENCE_KEY"
|
||||
android:title="@string/settings_enable_background_sync" />
|
||||
|
||||
<im.vector.app.core.preference.VectorEditTextPreference
|
||||
android:dependency="SETTINGS_ENABLE_BACKGROUND_SYNC_PREFERENCE_KEY"
|
||||
android:key="SETTINGS_SET_SYNC_TIMEOUT_PREFERENCE_KEY"
|
||||
android:numeric="integer"
|
||||
android:title="@string/settings_set_sync_timeout" />
|
||||
|
||||
<im.vector.app.core.preference.VectorEditTextPreference
|
||||
android:dependency="SETTINGS_ENABLE_BACKGROUND_SYNC_PREFERENCE_KEY"
|
||||
android:key="SETTINGS_SET_SYNC_DELAY_PREFERENCE_KEY"
|
||||
android:numeric="integer"
|
||||
android:title="@string/settings_set_sync_delay" />
|
||||
|
||||
</im.vector.app.core.preference.VectorPreferenceCategory>
|
||||
|
||||
<im.vector.app.core.preference.VectorPreferenceCategory
|
||||
<!--im.vector.app.core.preference.VectorPreferenceCategory
|
||||
android:key="SETTINGS_NOTIFICATIONS_TARGETS_PREFERENCE_KEY"
|
||||
android:title="@string/settings_notifications_targets" /-->
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user