mirror of
https://github.com/element-hq/element-android
synced 2024-11-24 10:25:35 +03:00
Merge pull request #2080 from vector-im/feature/polling_work
Feature/polling work
This commit is contained in:
commit
61b91f4015
26 changed files with 744 additions and 132 deletions
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
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,7 +19,14 @@ 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.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
|
||||
|
@ -28,10 +35,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 +49,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 +67,60 @@ 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
|
||||
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.d("## Sync: Failed to initialize service")
|
||||
stopMe()
|
||||
}
|
||||
}
|
||||
}
|
||||
// No intent just start the service, an alarm will should call with intent
|
||||
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() {
|
||||
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 && backgroundDetectionObserver.isInBackground) {
|
||||
Timber.d("## Sync: Reschedule service in $syncDelaySeconds sec")
|
||||
onRescheduleAsked(sessionId ?: "", false, syncTimeoutSeconds, syncDelaySeconds)
|
||||
}
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
|
@ -90,9 +131,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,9 +147,10 @@ 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 {
|
||||
// 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) {
|
||||
|
@ -111,28 +159,34 @@ 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
|
||||
}
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
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 +197,16 @@ 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)
|
||||
|
||||
abstract fun onNetworkError(sessionId: String, isInitialSync: Boolean, timeout: Int, delay: Int)
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
return null
|
||||
|
@ -158,6 +214,11 @@ 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 EXTRA_NETWORK_BACK_RESTART = "EXTRA_NETWORK_BACK_RESTART"
|
||||
|
||||
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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.fdroid.BackgroundSyncStarter
|
||||
import timber.log.Timber
|
||||
|
||||
class OnApplicationUpgradeOrRebootReceiver : BroadcastReceiver() {
|
||||
|
@ -29,10 +31,11 @@ class OnApplicationUpgradeOrRebootReceiver : BroadcastReceiver() {
|
|||
Timber.v("## onReceive() ${intent.action}")
|
||||
val appContext = context.applicationContext
|
||||
if (appContext is HasVectorInjector) {
|
||||
val activeSession = appContext.injector().activeSessionHolder().getSafeActiveSession()
|
||||
if (activeSession != null) {
|
||||
AlarmSyncBroadcastReceiver.scheduleAlarm(context, activeSession.sessionId, 10)
|
||||
}
|
||||
BackgroundSyncStarter.start(
|
||||
context,
|
||||
appContext.vectorComponent().vectorPreferences(),
|
||||
appContext.injector().activeSessionHolder()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,9 +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.VectorPreferences
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* This class has an alter ego in the gplay variant.
|
||||
|
@ -61,16 +61,13 @@ 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")
|
||||
}
|
||||
BackgroundSyncStarter.start(context, vectorPreferences, activeSessionHolder)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -21,19 +21,56 @@ 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() {
|
||||
|
||||
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 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -55,8 +92,30 @@ 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 onNetworkError(sessionId: String, isInitialSync: Boolean, timeout: Int, delay: Int) {
|
||||
Timber.d("## Sync: A network error occured during sync")
|
||||
val uploadWorkRequest: WorkRequest =
|
||||
OneTimeWorkRequestBuilder<RestartWhenNetworkOn>()
|
||||
.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() {
|
||||
|
@ -69,13 +128,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)
|
||||
|
@ -83,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<AlarmManager>(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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* 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.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import im.vector.app.R
|
||||
|
||||
class BackgroundSyncModeChooserDialog : DialogFragment() {
|
||||
|
||||
var interactionListener: InteractionListener? = null
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val initialMode = BackgroundSyncMode.fromString(arguments?.getString(ARG_INITIAL_MODE))
|
||||
|
||||
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()
|
||||
|
||||
view.findViewById<View>(R.id.backgroundSyncModeBattery).setOnClickListener {
|
||||
interactionListener
|
||||
?.takeIf { initialMode != BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_BATTERY }
|
||||
?.onOptionSelected(BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_BATTERY)
|
||||
dialog.dismiss()
|
||||
}
|
||||
view.findViewById<View>(R.id.backgroundSyncModeReal).setOnClickListener {
|
||||
interactionListener
|
||||
?.takeIf { initialMode != BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME }
|
||||
?.onOptionSelected(BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME)
|
||||
dialog.dismiss()
|
||||
}
|
||||
view.findViewById<View>(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 onOptionSelected(mode: BackgroundSyncMode)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val ARG_INITIAL_MODE = "ARG_INITIAL_MODE"
|
||||
|
||||
fun newInstance(selectedMode: BackgroundSyncMode): BackgroundSyncModeChooserDialog {
|
||||
val frag = BackgroundSyncModeChooserDialog()
|
||||
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, null)?.toInt()
|
||||
} ?: BackgroundSyncMode.DEFAULT_SYNC_TIMEOUT_SECONDS
|
||||
}
|
||||
|
||||
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, null)?.toInt()
|
||||
} ?: BackgroundSyncMode.DEFAULT_SYNC_DELAY_SECONDS
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -42,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
|
||||
|
@ -65,9 +71,99 @@ 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)
|
||||
dialogFragment.interactionListener = this
|
||||
activity?.supportFragmentManager?.let { fm ->
|
||||
dialogFragment.show(fm, "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) } ?: BackgroundSyncMode.DEFAULT_SYNC_TIMEOUT_SECONDS
|
||||
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) } ?: BackgroundSyncMode.DEFAULT_SYNC_DELAY_SECONDS
|
||||
vectorPreferences.setBackgroundSyncDelay(maxOf(0, syncDelay))
|
||||
refreshBackgroundSyncPrefs()
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
refreshBackgroundSyncPrefs()
|
||||
|
||||
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<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 resources.getQuantityString(R.plurals.seconds, seconds, seconds)
|
||||
}
|
||||
|
||||
private fun handleSystemPreference() {
|
||||
val callNotificationsSystemOptions = findPreference<VectorPreference>(VectorPreferences.SETTINGS_SYSTEM_CALL_NOTIFICATION_PREFERENCE_KEY)!!
|
||||
if (NotificationUtils.supportNotificationChannels()) {
|
||||
|
@ -148,6 +244,16 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor(
|
|||
val preference = findPreference<VectorSwitchPreference>(key)
|
||||
preference?.isHighlighted = true
|
||||
}
|
||||
|
||||
refreshPref()
|
||||
}
|
||||
|
||||
private fun refreshPref() {
|
||||
// This pref may have change from troubleshoot pref fragment
|
||||
if (!FcmHelper.isPushSupported()) {
|
||||
findPreference<VectorSwitchPreference>(VectorPreferences.SETTINGS_START_ON_BOOT_PREFERENCE_KEY)
|
||||
?.isChecked = vectorPreferences.autoStartOnBoot()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
|
@ -155,6 +261,9 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor(
|
|||
if (context is VectorSettingsFragmentInteractionListener) {
|
||||
interactionListener = context
|
||||
}
|
||||
(activity?.supportFragmentManager
|
||||
?.findFragmentByTag("syncDialog") as BackgroundSyncModeChooserDialog?)
|
||||
?.interactionListener = this
|
||||
}
|
||||
|
||||
override fun onDetach() {
|
||||
|
@ -234,5 +343,6 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor(
|
|||
|
||||
companion object {
|
||||
private const val REQUEST_NOTIFICATION_RINGTONE = 888
|
||||
private const val REQUEST_BATTERY_OPTIMIZATION = 500
|
||||
}
|
||||
}
|
||||
|
|
85
vector/src/main/res/layout/dialog_background_sync_mode.xml
Normal file
85
vector/src/main/res/layout/dialog_background_sync_mode.xml
Normal file
|
@ -0,0 +1,85 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/backgroundSyncModeBattery"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/settings_background_fdroid_sync_mode_battery"
|
||||
android:textColor="?riotx_text_primary"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/settings_background_fdroid_sync_mode_battery_description"
|
||||
android:textColor="?riotx_text_secondary" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/backgroundSyncModeReal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/settings_background_fdroid_sync_mode_real_time"
|
||||
android:textColor="?riotx_text_primary"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/settings_background_fdroid_sync_mode_real_time_description"
|
||||
android:textColor="?riotx_text_secondary" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/backgroundSyncModeOff"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/settings_background_fdroid_sync_mode_disabled"
|
||||
android:textColor="?riotx_text_primary"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/settings_background_fdroid_sync_mode_disabled_description"
|
||||
android:textColor="?riotx_text_secondary" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
|
@ -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>
|
||||
|
@ -815,6 +815,10 @@
|
|||
<string name="settings_set_sync_delay">Delay between each Sync</string>
|
||||
<string name="settings_second">second</string>
|
||||
<string name="settings_seconds">seconds</string>
|
||||
<plurals name="seconds">
|
||||
<item quantity="one">%d second</item>
|
||||
<item quantity="other">%d seconds</item>
|
||||
</plurals>
|
||||
|
||||
<string name="settings_version">Version</string>
|
||||
<string name="settings_olm_version">olm version</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>
|
||||
|
|
|
@ -62,6 +62,36 @@
|
|||
|
||||
</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"
|
||||
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.VectorPreferenceCategory>
|
||||
|
||||
<im.vector.app.core.preference.VectorPreferenceCategory android:title="@string/settings_troubleshoot_title">
|
||||
|
||||
<im.vector.app.core.preference.VectorPreference
|
||||
|
@ -72,33 +102,6 @@
|
|||
</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">
|
||||
|
||||
<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
|
||||
android:key="SETTINGS_NOTIFICATIONS_TARGETS_PREFERENCE_KEY"
|
||||
android:title="@string/settings_notifications_targets" /-->
|
||||
|
||||
|
|
Loading…
Reference in a new issue