Sync: use a foreground service for initialSync.

This commit is contained in:
ganfra 2019-12-10 19:52:12 +01:00
parent 79ef055bfb
commit 5338f93852
18 changed files with 177 additions and 148 deletions

View file

@ -44,3 +44,5 @@ sealed class Failure(cause: Throwable? = null) : Throwable(cause = cause) {
abstract class FeatureFailure : Failure() abstract class FeatureFailure : Failure()
} }
fun Throwable.isTokenError() = this is Failure.ServerError && (this.error.code == MatrixError.UNKNOWN_TOKEN || this.error.code == MatrixError.MISSING_TOKEN)

View file

@ -104,6 +104,11 @@ interface Session :
*/ */
fun syncState(): LiveData<SyncState> fun syncState(): LiveData<SyncState>
/**
* This methods return true if an initial sync has been processed
*/
fun hasAlreadySynced(): Boolean
/** /**
* This method allow to close a session. It does stop some services. * This method allow to close a session. It does stop some services.
*/ */

View file

@ -24,7 +24,7 @@ import im.vector.matrix.android.api.MatrixCallback
interface CacheService { interface CacheService {
/** /**
* Clear the whole cached data, except credentials. Once done, the session is closed and has to be opened again * Clear the whole cached data, except credentials. Once done, the sync has to be restarted by the sdk user.
*/ */
fun clearCache(callback: MatrixCallback<Unit>) fun clearCache(callback: MatrixCallback<Unit>)
} }

View file

@ -17,6 +17,7 @@
package im.vector.matrix.android.internal.network package im.vector.matrix.android.internal.network
import android.content.Context import android.content.Context
import androidx.annotation.WorkerThread
import com.novoda.merlin.Merlin import com.novoda.merlin.Merlin
import com.novoda.merlin.MerlinsBeard import com.novoda.merlin.MerlinsBeard
import im.vector.matrix.android.internal.di.MatrixScope import im.vector.matrix.android.internal.di.MatrixScope
@ -28,8 +29,8 @@ import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
@MatrixScope @MatrixScope
internal class NetworkConnectivityChecker @Inject constructor(context: Context, internal class NetworkConnectivityChecker @Inject constructor(private val context: Context,
backgroundDetectionObserver: BackgroundDetectionObserver) private val backgroundDetectionObserver: BackgroundDetectionObserver)
: BackgroundDetectionObserver.Listener { : BackgroundDetectionObserver.Listener {
private val merlin = Merlin.Builder() private val merlin = Merlin.Builder()
@ -37,19 +38,30 @@ internal class NetworkConnectivityChecker @Inject constructor(context: Context,
.withDisconnectableCallbacks() .withDisconnectableCallbacks()
.build(context) .build(context)
private val listeners = Collections.synchronizedSet(LinkedHashSet<Listener>()) private val merlinsBeard = MerlinsBeard.Builder().build(context)
// True when internet is available private val listeners = Collections.synchronizedSet(LinkedHashSet<Listener>())
var hasInternetAccess = MerlinsBeard.Builder().build(context).isConnected private var hasInternetAccess = merlinsBeard.isConnected
private set
init { init {
backgroundDetectionObserver.register(this) backgroundDetectionObserver.register(this)
} }
/**
* Returns true when internet is available
*/
@WorkerThread
fun hasInternetAccess(): Boolean {
// If we are in background we have unbound merlin, so we have to check
return if (backgroundDetectionObserver.isIsBackground) {
merlinsBeard.hasInternetAccess()
} else {
hasInternetAccess
}
}
override fun onMoveToForeground() { override fun onMoveToForeground() {
merlin.bind() merlin.bind()
merlin.registerDisconnectable { merlin.registerDisconnectable {
if (hasInternetAccess) { if (hasInternetAccess) {
Timber.v("On Disconnect") Timber.v("On Disconnect")
@ -76,14 +88,17 @@ internal class NetworkConnectivityChecker @Inject constructor(context: Context,
merlin.unbind() merlin.unbind()
} }
// In background you won't get notification as merlin is unbound
suspend fun waitUntilConnected() { suspend fun waitUntilConnected() {
if (hasInternetAccess) { if (hasInternetAccess) {
return return
} else { } else {
Timber.v("Waiting for network...")
suspendCoroutine<Unit> { continuation -> suspendCoroutine<Unit> { continuation ->
register(object : Listener { register(object : Listener {
override fun onConnect() { override fun onConnect() {
unregister(this) unregister(this)
Timber.v("Connected to network...")
continuation.resume(Unit) continuation.resume(Unit)
} }
}) })

View file

@ -44,6 +44,7 @@ import im.vector.matrix.android.api.session.sync.SyncState
import im.vector.matrix.android.api.session.user.UserService import im.vector.matrix.android.api.session.user.UserService
import im.vector.matrix.android.internal.crypto.DefaultCryptoService import im.vector.matrix.android.internal.crypto.DefaultCryptoService
import im.vector.matrix.android.internal.database.LiveEntityObserver import im.vector.matrix.android.internal.database.LiveEntityObserver
import im.vector.matrix.android.internal.session.sync.SyncTokenStore
import im.vector.matrix.android.internal.session.sync.job.SyncThread import im.vector.matrix.android.internal.session.sync.job.SyncThread
import im.vector.matrix.android.internal.session.sync.job.SyncWorker import im.vector.matrix.android.internal.session.sync.job.SyncWorker
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
@ -72,6 +73,7 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
private val secureStorageService: Lazy<SecureStorageService>, private val secureStorageService: Lazy<SecureStorageService>,
private val syncThreadProvider: Provider<SyncThread>, private val syncThreadProvider: Provider<SyncThread>,
private val contentUrlResolver: ContentUrlResolver, private val contentUrlResolver: ContentUrlResolver,
private val syncTokenStore: SyncTokenStore,
private val contentUploadProgressTracker: ContentUploadStateTracker, private val contentUploadProgressTracker: ContentUploadStateTracker,
private val initialSyncProgressService: Lazy<InitialSyncProgressService>, private val initialSyncProgressService: Lazy<InitialSyncProgressService>,
private val homeServerCapabilitiesService: Lazy<HomeServerCapabilitiesService>) private val homeServerCapabilitiesService: Lazy<HomeServerCapabilitiesService>)
@ -147,6 +149,10 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
return getSyncThread().liveState() return getSyncThread().liveState()
} }
override fun hasAlreadySynced(): Boolean {
return syncTokenStore.getLastToken() != null
}
private fun getSyncThread(): SyncThread { private fun getSyncThread(): SyncThread {
return syncThread ?: syncThreadProvider.get().also { return syncThread ?: syncThreadProvider.get().also {
syncThread = it syncThread = it
@ -156,17 +162,7 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
override fun clearCache(callback: MatrixCallback<Unit>) { override fun clearCache(callback: MatrixCallback<Unit>) {
stopSync() stopSync()
stopAnyBackgroundSync() stopAnyBackgroundSync()
cacheService.get().clearCache(object : MatrixCallback<Unit> { cacheService.get().clearCache(callback)
override fun onSuccess(data: Unit) {
startSync(true)
callback.onSuccess(data)
}
override fun onFailure(failure: Throwable) {
startSync(true)
callback.onFailure(failure)
}
})
} }
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)

View file

@ -46,6 +46,7 @@ import im.vector.matrix.android.internal.session.sync.job.SyncWorker
import im.vector.matrix.android.internal.session.user.UserModule import im.vector.matrix.android.internal.session.user.UserModule
import im.vector.matrix.android.internal.session.user.accountdata.AccountDataModule import im.vector.matrix.android.internal.session.user.accountdata.AccountDataModule
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
@Component(dependencies = [MatrixComponent::class], @Component(dependencies = [MatrixComponent::class],
modules = [ modules = [
@ -69,6 +70,8 @@ import im.vector.matrix.android.internal.task.TaskExecutor
@SessionScope @SessionScope
internal interface SessionComponent { internal interface SessionComponent {
fun coroutineDispatchers(): MatrixCoroutineDispatchers
fun session(): Session fun session(): Session
fun syncTask(): SyncTask fun syncTask(): SyncTask

View file

@ -21,6 +21,7 @@ import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.failure.MatrixError import im.vector.matrix.android.api.failure.MatrixError
import im.vector.matrix.android.internal.auth.SessionParamsStore import im.vector.matrix.android.internal.auth.SessionParamsStore
import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.DefaultInitialSyncProgressService import im.vector.matrix.android.internal.session.DefaultInitialSyncProgressService
import im.vector.matrix.android.internal.session.filter.FilterRepository import im.vector.matrix.android.internal.session.filter.FilterRepository
@ -28,6 +29,7 @@ import im.vector.matrix.android.internal.session.homeserver.GetHomeServerCapabil
import im.vector.matrix.android.internal.session.sync.model.SyncResponse import im.vector.matrix.android.internal.session.sync.model.SyncResponse
import im.vector.matrix.android.internal.session.user.UserStore import im.vector.matrix.android.internal.session.user.UserStore
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
internal interface SyncTask : Task<SyncTask.Params, Unit> { internal interface SyncTask : Task<SyncTask.Params, Unit> {
@ -47,6 +49,7 @@ internal class DefaultSyncTask @Inject constructor(private val syncAPI: SyncAPI,
) : SyncTask { ) : SyncTask {
override suspend fun execute(params: SyncTask.Params) { override suspend fun execute(params: SyncTask.Params) {
Timber.v("Sync task started on Thread: ${Thread.currentThread().name}")
// Maybe refresh the home server capabilities data we know // Maybe refresh the home server capabilities data we know
getHomeServerCapabilitiesTask.execute(Unit) getHomeServerCapabilitiesTask.execute(Unit)
@ -84,5 +87,6 @@ internal class DefaultSyncTask @Inject constructor(private val syncAPI: SyncAPI,
if (isInitialSync) { if (isInitialSync) {
initialSyncProgressService.endAll() initialSyncProgressService.endAll()
} }
Timber.v("Sync task finished on Thread: ${Thread.currentThread().name}")
} }
} }

View file

@ -18,21 +18,15 @@ package im.vector.matrix.android.internal.session.sync.job
import android.app.Service import android.app.Service
import android.content.Intent import android.content.Intent
import android.os.IBinder import android.os.IBinder
import com.squareup.moshi.JsonEncodingException
import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.failure.isTokenError
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.failure.MatrixError
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
import im.vector.matrix.android.internal.session.sync.SyncTask import im.vector.matrix.android.internal.session.sync.SyncTask
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.TaskThread import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import im.vector.matrix.android.internal.task.configureWith import kotlinx.coroutines.*
import timber.log.Timber import timber.log.Timber
import java.net.SocketTimeoutException import java.util.concurrent.atomic.AtomicBoolean
import java.util.Timer
import java.util.TimerTask
/** /**
* Can execute periodic sync task. * Can execute periodic sync task.
@ -43,13 +37,15 @@ import java.util.TimerTask
open class SyncService : Service() { open class SyncService : Service() {
private var mIsSelfDestroyed: Boolean = false private var mIsSelfDestroyed: Boolean = false
private var cancelableTask: Cancelable? = null
private lateinit var syncTask: SyncTask private lateinit var syncTask: SyncTask
private lateinit var networkConnectivityChecker: NetworkConnectivityChecker private lateinit var networkConnectivityChecker: NetworkConnectivityChecker
private lateinit var taskExecutor: TaskExecutor private lateinit var taskExecutor: TaskExecutor
private lateinit var coroutineDispatchers: MatrixCoroutineDispatchers
var timer = Timer() private val isRunning = AtomicBoolean(false)
private val serviceScope = CoroutineScope(SupervisorJob())
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Timber.i("onStartCommand $intent") Timber.i("onStartCommand $intent")
@ -60,13 +56,14 @@ open class SyncService : Service() {
syncTask = sessionComponent.syncTask() syncTask = sessionComponent.syncTask()
networkConnectivityChecker = sessionComponent.networkConnectivityChecker() networkConnectivityChecker = sessionComponent.networkConnectivityChecker()
taskExecutor = sessionComponent.taskExecutor() taskExecutor = sessionComponent.taskExecutor()
if (cancelableTask == null) { coroutineDispatchers = sessionComponent.coroutineDispatchers()
timer.cancel() if (isRunning.get()) {
timer = Timer() Timber.i("Received a start while was already syncing... ignore")
doSync(true)
} else { } else {
// Already syncing ignore isRunning.set(true)
Timber.i("Received a start while was already syncking... ignore") serviceScope.launch(coroutineDispatchers.sync) {
doSync()
}
} }
} }
// No intent just start the service, an alarm will should call with intent // No intent just start the service, an alarm will should call with intent
@ -75,86 +72,40 @@ open class SyncService : Service() {
override fun onDestroy() { override fun onDestroy() {
Timber.i("## onDestroy() : $this") Timber.i("## onDestroy() : $this")
if (!mIsSelfDestroyed) { if (!mIsSelfDestroyed) {
Timber.w("## Destroy by the system : $this") Timber.w("## Destroy by the system : $this")
} }
serviceScope.coroutineContext.cancelChildren()
cancelableTask?.cancel() isRunning.set(false)
super.onDestroy() super.onDestroy()
} }
fun stopMe() { private fun stopMe() {
timer.cancel()
timer = Timer()
cancelableTask?.cancel()
mIsSelfDestroyed = true mIsSelfDestroyed = true
stopSelf() stopSelf()
} }
fun doSync(once: Boolean = false) { private suspend fun doSync() {
if (!networkConnectivityChecker.hasInternetAccess) { if (!networkConnectivityChecker.hasInternetAccess()) {
Timber.v("No internet access. Waiting...") Timber.v("No network, try to sync again in 10s")
// TODO Retry in ? delay(DELAY_NO_NETWORK)
timer.schedule(object : TimerTask() { doSync()
override fun run() { return
doSync() }
} Timber.v("Execute sync request with timeout 0")
}, NO_NETWORK_DELAY) val params = SyncTask.Params(TIME_OUT)
} else { try {
Timber.v("Execute sync request with timeout 0") syncTask.execute(params)
val params = SyncTask.Params(TIME_OUT) stopMe()
cancelableTask = syncTask } catch (throwable: Throwable) {
.configureWith(params) { Timber.e(throwable)
callbackThread = TaskThread.SYNC if (throwable.isTokenError()) {
executionThread = TaskThread.SYNC stopMe()
callback = object : MatrixCallback<Unit> { } else {
override fun onSuccess(data: Unit) { Timber.v("Retry to sync in 5s")
cancelableTask = null delay(DELAY_FAILURE)
if (!once) { doSync()
timer.schedule(object : TimerTask() { }
override fun run() {
doSync()
}
}, NEXT_BATCH_DELAY)
} else {
// stop
stopMe()
}
}
override fun onFailure(failure: Throwable) {
Timber.e(failure)
cancelableTask = null
if (failure is Failure.NetworkConnection
&& failure.cause is SocketTimeoutException) {
// Timeout are not critical
timer.schedule(object : TimerTask() {
override fun run() {
doSync()
}
}, 5_000L)
}
if (failure !is Failure.NetworkConnection
|| failure.cause is JsonEncodingException) {
// Wait 10s before retrying
timer.schedule(object : TimerTask() {
override fun run() {
doSync()
}
}, 5_000L)
}
if (failure is Failure.ServerError
&& (failure.error.code == MatrixError.UNKNOWN_TOKEN || failure.error.code == MatrixError.MISSING_TOKEN)) {
// No token or invalid token, stop the thread
stopSelf()
}
}
}
}
.executeBy(taskExecutor)
} }
} }
@ -164,9 +115,8 @@ open class SyncService : Service() {
companion object { companion object {
const val EXTRA_USER_ID = "EXTRA_USER_ID" const val EXTRA_USER_ID = "EXTRA_USER_ID"
private const val TIME_OUT = 0L
const val TIME_OUT = 0L private const val DELAY_NO_NETWORK = 10_000L
const val NEXT_BATCH_DELAY = 60_000L private const val DELAY_FAILURE = 5_000L
const val NO_NETWORK_DELAY = 5_000L
} }
} }

View file

@ -99,11 +99,10 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
isStarted = true isStarted = true
networkConnectivityChecker.register(this) networkConnectivityChecker.register(this)
backgroundDetectionObserver.register(this) backgroundDetectionObserver.register(this)
while (state != SyncState.KILLING) { while (state != SyncState.KILLING) {
Timber.v("Entering loop, state: $state") Timber.v("Entering loop, state: $state")
if (!networkConnectivityChecker.hasInternetAccess) { if (!networkConnectivityChecker.hasInternetAccess()) {
Timber.v("No network. Waiting...") Timber.v("No network. Waiting...")
updateStateTo(SyncState.NO_NETWORK) updateStateTo(SyncState.NO_NETWORK)
synchronized(lock) { lock.wait() } synchronized(lock) { lock.wait() }

View file

@ -18,6 +18,10 @@ package im.vector.matrix.android.internal.session.sync.job
import android.content.Context import android.content.Context
import androidx.work.* import androidx.work.*
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.failure.MatrixError
import im.vector.matrix.android.api.failure.isTokenError
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
import im.vector.matrix.android.internal.session.sync.SyncTask import im.vector.matrix.android.internal.session.sync.SyncTask
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
@ -25,6 +29,7 @@ import im.vector.matrix.android.internal.worker.WorkManagerUtil
import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder
import im.vector.matrix.android.internal.worker.WorkerParamsFactory import im.vector.matrix.android.internal.worker.WorkerParamsFactory
import im.vector.matrix.android.internal.worker.getSessionComponent import im.vector.matrix.android.internal.worker.getSessionComponent
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -46,45 +51,58 @@ internal class SyncWorker(context: Context,
@Inject lateinit var syncTask: SyncTask @Inject lateinit var syncTask: SyncTask
@Inject lateinit var taskExecutor: TaskExecutor @Inject lateinit var taskExecutor: TaskExecutor
@Inject lateinit var coroutineDispatchers: MatrixCoroutineDispatchers @Inject lateinit var coroutineDispatchers: MatrixCoroutineDispatchers
@Inject lateinit var networkConnectivityChecker: NetworkConnectivityChecker
override suspend fun doWork(): Result { override suspend fun doWork(): Result {
Timber.i("Sync work starting") Timber.i("Sync work starting")
val params = WorkerParamsFactory.fromData<Params>(inputData) ?: return Result.success() val params = WorkerParamsFactory.fromData<Params>(inputData) ?: return Result.success()
val sessionComponent = getSessionComponent(params.userId) ?: return Result.success() val sessionComponent = getSessionComponent(params.userId) ?: return Result.success()
sessionComponent.inject(this) sessionComponent.inject(this)
runCatching { return runCatching {
withContext(coroutineDispatchers.sync) { doSync(params.timeout)
val taskParams = SyncTask.Params(0) }.fold(
syncTask.execute(taskParams) { Result.success() },
} { failure ->
} if (failure.isTokenError() || !params.automaticallyRetry) {
return Result.success() Result.failure()
} else {
Result.retry()
}
}
)
}
private suspend fun doSync(timeout: Long) = withContext(coroutineDispatchers.sync) {
val taskParams = SyncTask.Params(timeout)
syncTask.execute(taskParams)
} }
companion object { companion object {
const val BG_SYNC_WORK_NAME = "BG_SYNCP"
fun requireBackgroundSync(context: Context, userId: String, serverTimeout: Long = 0) { fun requireBackgroundSync(context: Context, userId: String, serverTimeout: Long = 0) {
val data = WorkerParamsFactory.toData(Params(userId, serverTimeout, false)) val data = WorkerParamsFactory.toData(Params(userId, serverTimeout, false))
val workRequest = matrixOneTimeWorkRequestBuilder<SyncWorker>() val workRequest = matrixOneTimeWorkRequestBuilder<SyncWorker>()
.setInputData(data)
.setConstraints(WorkManagerUtil.workConstraints) .setConstraints(WorkManagerUtil.workConstraints)
.setBackoffCriteria(BackoffPolicy.LINEAR, 1_000, TimeUnit.MILLISECONDS) .setBackoffCriteria(BackoffPolicy.LINEAR, 1_000, TimeUnit.MILLISECONDS)
.setInputData(data)
.build() .build()
WorkManager.getInstance(context).enqueueUniqueWork("BG_SYNCP", ExistingWorkPolicy.REPLACE, workRequest) WorkManager.getInstance(context).enqueueUniqueWork(BG_SYNC_WORK_NAME, ExistingWorkPolicy.REPLACE, workRequest)
} }
fun automaticallyBackgroundSync(context: Context, userId: String, serverTimeout: Long = 0, delay: Long = 30_000) { fun automaticallyBackgroundSync(context: Context, userId: String, serverTimeout: Long = 0, delay: Long = 30_000) {
val data = WorkerParamsFactory.toData(Params(userId, serverTimeout, true)) val data = WorkerParamsFactory.toData(Params(userId, serverTimeout, true))
val workRequest = matrixOneTimeWorkRequestBuilder<SyncWorker>() val workRequest = matrixOneTimeWorkRequestBuilder<SyncWorker>()
.setInputData(data)
.setConstraints(WorkManagerUtil.workConstraints) .setConstraints(WorkManagerUtil.workConstraints)
.setInputData(data)
.setBackoffCriteria(BackoffPolicy.LINEAR, delay, TimeUnit.MILLISECONDS) .setBackoffCriteria(BackoffPolicy.LINEAR, delay, TimeUnit.MILLISECONDS)
.build() .build()
WorkManager.getInstance(context).enqueueUniqueWork("BG_SYNCP", ExistingWorkPolicy.REPLACE, workRequest) WorkManager.getInstance(context).enqueueUniqueWork(BG_SYNC_WORK_NAME, ExistingWorkPolicy.REPLACE, workRequest)
} }
fun stopAnyBackgroundSync(context: Context) { fun stopAnyBackgroundSync(context: Context) {
WorkManager.getInstance(context).cancelUniqueWork("BG_SYNCP") WorkManager.getInstance(context).cancelUniqueWork(BG_SYNC_WORK_NAME)
} }
} }
} }

View file

@ -20,10 +20,6 @@
android:enabled="true" android:enabled="true"
android:exported="false" /> android:exported="false" />
<service
android:name=".fdroid.service.VectorSyncService"
android:exported="false" />
</application> </application>
</manifest> </manifest>

View file

@ -25,7 +25,7 @@ import android.os.Build
import android.os.PowerManager import android.os.PowerManager
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import im.vector.matrix.android.internal.session.sync.job.SyncService import im.vector.matrix.android.internal.session.sync.job.SyncService
import im.vector.riotx.fdroid.service.VectorSyncService import im.vector.riotx.core.services.VectorSyncService
import timber.log.Timber import timber.log.Timber
class AlarmSyncBroadcastReceiver : BroadcastReceiver() { class AlarmSyncBroadcastReceiver : BroadcastReceiver() {
@ -41,14 +41,9 @@ class AlarmSyncBroadcastReceiver : BroadcastReceiver() {
val userId = intent.getStringExtra(SyncService.EXTRA_USER_ID) val userId = intent.getStringExtra(SyncService.EXTRA_USER_ID)
// This method is called when the BroadcastReceiver is receiving an Intent broadcast. // This method is called when the BroadcastReceiver is receiving an Intent broadcast.
Timber.d("RestartBroadcastReceiver received intent") Timber.d("RestartBroadcastReceiver received intent")
Intent(context, VectorSyncService::class.java).also { VectorSyncService.newIntent(context, userId).also {
it.putExtra(SyncService.EXTRA_USER_ID, userId)
try { try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { ContextCompat.startForegroundService(context, it)
ContextCompat.startForegroundService(context, it)
} else {
context.startService(it)
}
} catch (ex: Throwable) { } catch (ex: Throwable) {
// TODO // TODO
Timber.e(ex) Timber.e(ex)
@ -79,6 +74,7 @@ class AlarmSyncBroadcastReceiver : BroadcastReceiver() {
} }
fun cancelAlarm(context: Context) { fun cancelAlarm(context: Context) {
Timber.v("Cancel alarm")
val intent = Intent(context, AlarmSyncBroadcastReceiver::class.java) val intent = Intent(context, AlarmSyncBroadcastReceiver::class.java)
val pIntent = PendingIntent.getBroadcast(context, REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT) val pIntent = PendingIntent.getBroadcast(context, REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT)
val alarmMgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager val alarmMgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager

View file

@ -104,6 +104,10 @@
android:name=".core.services.CallService" android:name=".core.services.CallService"
android:exported="false" /> android:exported="false" />
<service
android:name=".core.services.VectorSyncService"
android:exported="false" />
<!-- Receivers --> <!-- Receivers -->
<!-- Exported false, should only be accessible from this app!! --> <!-- Exported false, should only be accessible from this app!! -->

View file

@ -116,11 +116,12 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.
if (authenticationService.hasAuthenticatedSessions() && !activeSessionHolder.hasActiveSession()) { if (authenticationService.hasAuthenticatedSessions() && !activeSessionHolder.hasActiveSession()) {
val lastAuthenticatedSession = authenticationService.getLastAuthenticatedSession()!! val lastAuthenticatedSession = authenticationService.getLastAuthenticatedSession()!!
activeSessionHolder.setActiveSession(lastAuthenticatedSession) activeSessionHolder.setActiveSession(lastAuthenticatedSession)
lastAuthenticatedSession.configureAndStart(pushRuleTriggerListener, sessionListener) lastAuthenticatedSession.configureAndStart(applicationContext, pushRuleTriggerListener, sessionListener)
} }
ProcessLifecycleOwner.get().lifecycle.addObserver(object : LifecycleObserver { ProcessLifecycleOwner.get().lifecycle.addObserver(object : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME) @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun entersForeground() { fun entersForeground() {
Timber.i("App entered foreground")
FcmHelper.onEnterForeground(appContext) FcmHelper.onEnterForeground(appContext)
activeSessionHolder.getSafeActiveSession()?.also { activeSessionHolder.getSafeActiveSession()?.also {
it.stopAnyBackgroundSync() it.stopAnyBackgroundSync()

View file

@ -16,23 +16,27 @@
package im.vector.riotx.core.extensions package im.vector.riotx.core.extensions
import android.content.Context
import android.content.Intent
import androidx.core.content.ContextCompat
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ProcessLifecycleOwner import androidx.lifecycle.ProcessLifecycleOwner
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.sync.FilterService import im.vector.matrix.android.api.session.sync.FilterService
import im.vector.matrix.android.internal.session.sync.job.SyncService
import im.vector.riotx.core.services.VectorSyncService
import im.vector.riotx.features.notifications.PushRuleTriggerListener import im.vector.riotx.features.notifications.PushRuleTriggerListener
import im.vector.riotx.features.session.SessionListener import im.vector.riotx.features.session.SessionListener
import timber.log.Timber import timber.log.Timber
fun Session.configureAndStart(pushRuleTriggerListener: PushRuleTriggerListener, fun Session.configureAndStart(context: Context,
pushRuleTriggerListener: PushRuleTriggerListener,
sessionListener: SessionListener) { sessionListener: SessionListener) {
open() open()
addListener(sessionListener) addListener(sessionListener)
setFilter(FilterService.FilterPreset.RiotFilter) setFilter(FilterService.FilterPreset.RiotFilter)
Timber.i("Configure and start session for ${this.myUserId}") Timber.i("Configure and start session for ${this.myUserId}")
val isAtLeastStarted = ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED) startSyncing(context)
Timber.v("--> is at least started? $isAtLeastStarted")
startSync(isAtLeastStarted)
refreshPushers() refreshPushers()
pushRuleTriggerListener.startWithSession(this) pushRuleTriggerListener.startWithSession(this)
@ -40,3 +44,20 @@ fun Session.configureAndStart(pushRuleTriggerListener: PushRuleTriggerListener,
// @Inject lateinit var incomingVerificationRequestHandler: IncomingVerificationRequestHandler // @Inject lateinit var incomingVerificationRequestHandler: IncomingVerificationRequestHandler
// @Inject lateinit var keyRequestHandler: KeyRequestHandler // @Inject lateinit var keyRequestHandler: KeyRequestHandler
} }
fun Session.startSyncing(context: Context) {
val applicationContext = context.applicationContext
if (!hasAlreadySynced()) {
VectorSyncService.newIntent(applicationContext, myUserId).also {
try {
ContextCompat.startForegroundService(applicationContext, it)
} catch (ex: Throwable) {
// TODO
Timber.e(ex)
}
}
}
val isAtLeastStarted = ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)
Timber.v("--> is at least started? $isAtLeastStarted")
startSync(isAtLeastStarted)
}

View file

@ -13,12 +13,13 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package im.vector.riotx.fdroid.service package im.vector.riotx.core.services
import android.app.NotificationManager import android.app.NotificationManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import androidx.core.content.ContextCompat
import im.vector.matrix.android.internal.session.sync.job.SyncService import im.vector.matrix.android.internal.session.sync.job.SyncService
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.extensions.vectorComponent import im.vector.riotx.core.extensions.vectorComponent
@ -27,6 +28,15 @@ import timber.log.Timber
class VectorSyncService : SyncService() { class VectorSyncService : SyncService() {
companion object {
fun newIntent(context: Context, userId: String): Intent {
return Intent(context, VectorSyncService::class.java).also {
it.putExtra(EXTRA_USER_ID, userId)
}
}
}
private lateinit var notificationUtils: NotificationUtils private lateinit var notificationUtils: NotificationUtils
override fun onCreate() { override fun onCreate() {
@ -45,8 +55,7 @@ class VectorSyncService : SyncService() {
} }
/** /**
* Service is started only in fdroid mode when no FCM is available * Service is started in fdroid mode when no FCM is available or is used for initialSync
* Otherwise it is bounded
*/ */
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Timber.v("VectorSyncService - onStartCommand ") Timber.v("VectorSyncService - onStartCommand ")

View file

@ -26,6 +26,8 @@ import im.vector.riotx.R
import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.di.ActiveSessionHolder
import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.core.error.ErrorFormatter import im.vector.riotx.core.error.ErrorFormatter
import im.vector.riotx.core.extensions.configureAndStart
import im.vector.riotx.core.extensions.startSyncing
import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.core.platform.VectorBaseActivity
import im.vector.riotx.core.utils.deleteAllFiles import im.vector.riotx.core.utils.deleteAllFiles
import im.vector.riotx.features.home.HomeActivity import im.vector.riotx.features.home.HomeActivity
@ -75,8 +77,13 @@ class MainActivity : VectorBaseActivity() {
} }
private fun doCleanUp(clearCache: Boolean, clearCredentials: Boolean) { private fun doCleanUp(clearCache: Boolean, clearCredentials: Boolean) {
val session = sessionHolder.getSafeActiveSession()
if (session == null) {
start()
return
}
when { when {
clearCredentials -> sessionHolder.getActiveSession().signOut(object : MatrixCallback<Unit> { clearCredentials -> session.signOut(object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) { override fun onSuccess(data: Unit) {
Timber.w("SIGN_OUT: success, start app") Timber.w("SIGN_OUT: success, start app")
sessionHolder.clearActiveSession() sessionHolder.clearActiveSession()
@ -87,8 +94,9 @@ class MainActivity : VectorBaseActivity() {
displayError(failure, clearCache, clearCredentials) displayError(failure, clearCache, clearCredentials)
} }
}) })
clearCache -> sessionHolder.getActiveSession().clearCache(object : MatrixCallback<Unit> { clearCache -> session.clearCache(object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) { override fun onSuccess(data: Unit) {
session.startSyncing(applicationContext)
doLocalCleanupAndStart() doLocalCleanupAndStart()
} }

View file

@ -16,6 +16,7 @@
package im.vector.riotx.features.login package im.vector.riotx.features.login
import android.content.Context
import com.airbnb.mvrx.* import com.airbnb.mvrx.*
import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
@ -44,6 +45,7 @@ import java.util.concurrent.CancellationException
* *
*/ */
class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginViewState, class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginViewState,
private val applicationContext: Context,
private val authenticationService: AuthenticationService, private val authenticationService: AuthenticationService,
private val activeSessionHolder: ActiveSessionHolder, private val activeSessionHolder: ActiveSessionHolder,
private val pushRuleTriggerListener: PushRuleTriggerListener, private val pushRuleTriggerListener: PushRuleTriggerListener,
@ -469,7 +471,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
private fun onSessionCreated(session: Session) { private fun onSessionCreated(session: Session) {
activeSessionHolder.setActiveSession(session) activeSessionHolder.setActiveSession(session)
session.configureAndStart(pushRuleTriggerListener, sessionListener) session.configureAndStart(applicationContext, pushRuleTriggerListener, sessionListener)
setState { setState {
copy( copy(
asyncLoginAction = Success(Unit) asyncLoginAction = Success(Unit)