From 5338f93852101b2cef3a7a457e781fad86a3ae10 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 10 Dec 2019 19:52:12 +0100 Subject: [PATCH] Sync: use a foreground service for initialSync. --- .../matrix/android/api/failure/Failure.kt | 2 + .../matrix/android/api/session/Session.kt | 5 + .../android/api/session/cache/CacheService.kt | 2 +- .../network/NetworkConnectivityChecker.kt | 29 +++- .../internal/session/DefaultSession.kt | 18 +-- .../internal/session/SessionComponent.kt | 3 + .../android/internal/session/sync/SyncTask.kt | 4 + .../internal/session/sync/job/SyncService.kt | 134 ++++++------------ .../internal/session/sync/job/SyncThread.kt | 3 +- .../internal/session/sync/job/SyncWorker.kt | 42 ++++-- vector/src/fdroid/AndroidManifest.xml | 4 - .../receiver/AlarmSyncBroadcastReceiver.kt | 12 +- vector/src/main/AndroidManifest.xml | 4 + .../java/im/vector/riotx/VectorApplication.kt | 3 +- .../vector/riotx/core/extensions/Session.kt | 29 +++- .../riotx/core/services}/VectorSyncService.kt | 15 +- .../im/vector/riotx/features/MainActivity.kt | 12 +- .../riotx/features/login/LoginViewModel.kt | 4 +- 18 files changed, 177 insertions(+), 148 deletions(-) rename vector/src/{fdroid/java/im/vector/riotx/fdroid/service => main/java/im/vector/riotx/core/services}/VectorSyncService.kt (83%) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/Failure.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/Failure.kt index 4d44e3346b..41468e9e0f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/Failure.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/failure/Failure.kt @@ -44,3 +44,5 @@ sealed class Failure(cause: Throwable? = null) : Throwable(cause = cause) { abstract class FeatureFailure : Failure() } + +fun Throwable.isTokenError() = this is Failure.ServerError && (this.error.code == MatrixError.UNKNOWN_TOKEN || this.error.code == MatrixError.MISSING_TOKEN) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt index 2440713a40..5fd7e3885f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt @@ -104,6 +104,11 @@ interface Session : */ fun syncState(): LiveData + /** + * 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. */ diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/cache/CacheService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/cache/CacheService.kt index a84e5af48c..2f34922280 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/cache/CacheService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/cache/CacheService.kt @@ -24,7 +24,7 @@ import im.vector.matrix.android.api.MatrixCallback 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) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/NetworkConnectivityChecker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/NetworkConnectivityChecker.kt index 3d850c223a..4d4699810f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/NetworkConnectivityChecker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/NetworkConnectivityChecker.kt @@ -17,6 +17,7 @@ package im.vector.matrix.android.internal.network import android.content.Context +import androidx.annotation.WorkerThread import com.novoda.merlin.Merlin import com.novoda.merlin.MerlinsBeard import im.vector.matrix.android.internal.di.MatrixScope @@ -28,8 +29,8 @@ import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine @MatrixScope -internal class NetworkConnectivityChecker @Inject constructor(context: Context, - backgroundDetectionObserver: BackgroundDetectionObserver) +internal class NetworkConnectivityChecker @Inject constructor(private val context: Context, + private val backgroundDetectionObserver: BackgroundDetectionObserver) : BackgroundDetectionObserver.Listener { private val merlin = Merlin.Builder() @@ -37,19 +38,30 @@ internal class NetworkConnectivityChecker @Inject constructor(context: Context, .withDisconnectableCallbacks() .build(context) - private val listeners = Collections.synchronizedSet(LinkedHashSet()) + private val merlinsBeard = MerlinsBeard.Builder().build(context) - // True when internet is available - var hasInternetAccess = MerlinsBeard.Builder().build(context).isConnected - private set + private val listeners = Collections.synchronizedSet(LinkedHashSet()) + private var hasInternetAccess = merlinsBeard.isConnected init { 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() { merlin.bind() - merlin.registerDisconnectable { if (hasInternetAccess) { Timber.v("On Disconnect") @@ -76,14 +88,17 @@ internal class NetworkConnectivityChecker @Inject constructor(context: Context, merlin.unbind() } + // In background you won't get notification as merlin is unbound suspend fun waitUntilConnected() { if (hasInternetAccess) { return } else { + Timber.v("Waiting for network...") suspendCoroutine { continuation -> register(object : Listener { override fun onConnect() { unregister(this) + Timber.v("Connected to network...") continuation.resume(Unit) } }) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt index 4b33e28000..32126fb78d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt @@ -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.internal.crypto.DefaultCryptoService 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.SyncWorker import org.greenrobot.eventbus.EventBus @@ -72,6 +73,7 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se private val secureStorageService: Lazy, private val syncThreadProvider: Provider, private val contentUrlResolver: ContentUrlResolver, + private val syncTokenStore: SyncTokenStore, private val contentUploadProgressTracker: ContentUploadStateTracker, private val initialSyncProgressService: Lazy, private val homeServerCapabilitiesService: Lazy) @@ -147,6 +149,10 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se return getSyncThread().liveState() } + override fun hasAlreadySynced(): Boolean { + return syncTokenStore.getLastToken() != null + } + private fun getSyncThread(): SyncThread { return syncThread ?: syncThreadProvider.get().also { syncThread = it @@ -156,17 +162,7 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se override fun clearCache(callback: MatrixCallback) { stopSync() stopAnyBackgroundSync() - cacheService.get().clearCache(object : MatrixCallback { - override fun onSuccess(data: Unit) { - startSync(true) - callback.onSuccess(data) - } - - override fun onFailure(failure: Throwable) { - startSync(true) - callback.onFailure(failure) - } - }) + cacheService.get().clearCache(callback) } @Subscribe(threadMode = ThreadMode.MAIN) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt index 8c16050442..a208a7a720 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt @@ -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.accountdata.AccountDataModule import im.vector.matrix.android.internal.task.TaskExecutor +import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers @Component(dependencies = [MatrixComponent::class], modules = [ @@ -69,6 +70,8 @@ import im.vector.matrix.android.internal.task.TaskExecutor @SessionScope internal interface SessionComponent { + fun coroutineDispatchers(): MatrixCoroutineDispatchers + fun session(): Session fun syncTask(): SyncTask diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt index 51c02456d7..1082fa835b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt @@ -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.internal.auth.SessionParamsStore 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.session.DefaultInitialSyncProgressService 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.user.UserStore import im.vector.matrix.android.internal.task.Task +import timber.log.Timber import javax.inject.Inject internal interface SyncTask : Task { @@ -47,6 +49,7 @@ internal class DefaultSyncTask @Inject constructor(private val syncAPI: SyncAPI, ) : SyncTask { 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 getHomeServerCapabilitiesTask.execute(Unit) @@ -84,5 +87,6 @@ internal class DefaultSyncTask @Inject constructor(private val syncAPI: SyncAPI, if (isInitialSync) { initialSyncProgressService.endAll() } + Timber.v("Sync task finished on Thread: ${Thread.currentThread().name}") } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt index 4e57aa5be1..3aeb408db1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncService.kt @@ -18,21 +18,15 @@ package im.vector.matrix.android.internal.session.sync.job import android.app.Service import android.content.Intent import android.os.IBinder -import com.squareup.moshi.JsonEncodingException import im.vector.matrix.android.api.Matrix -import im.vector.matrix.android.api.MatrixCallback -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.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.task.TaskExecutor -import im.vector.matrix.android.internal.task.TaskThread -import im.vector.matrix.android.internal.task.configureWith +import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers +import kotlinx.coroutines.* import timber.log.Timber -import java.net.SocketTimeoutException -import java.util.Timer -import java.util.TimerTask +import java.util.concurrent.atomic.AtomicBoolean /** * Can execute periodic sync task. @@ -43,13 +37,15 @@ import java.util.TimerTask open class SyncService : Service() { private var mIsSelfDestroyed: Boolean = false - private var cancelableTask: Cancelable? = null private lateinit var syncTask: SyncTask private lateinit var networkConnectivityChecker: NetworkConnectivityChecker 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 { Timber.i("onStartCommand $intent") @@ -60,13 +56,14 @@ open class SyncService : Service() { syncTask = sessionComponent.syncTask() networkConnectivityChecker = sessionComponent.networkConnectivityChecker() taskExecutor = sessionComponent.taskExecutor() - if (cancelableTask == null) { - timer.cancel() - timer = Timer() - doSync(true) + coroutineDispatchers = sessionComponent.coroutineDispatchers() + if (isRunning.get()) { + Timber.i("Received a start while was already syncing... ignore") } else { - // Already syncing ignore - Timber.i("Received a start while was already syncking... ignore") + isRunning.set(true) + serviceScope.launch(coroutineDispatchers.sync) { + doSync() + } } } // No intent just start the service, an alarm will should call with intent @@ -75,86 +72,40 @@ open class SyncService : Service() { override fun onDestroy() { Timber.i("## onDestroy() : $this") - if (!mIsSelfDestroyed) { Timber.w("## Destroy by the system : $this") } - - cancelableTask?.cancel() + serviceScope.coroutineContext.cancelChildren() + isRunning.set(false) super.onDestroy() } - fun stopMe() { - timer.cancel() - timer = Timer() - cancelableTask?.cancel() + private fun stopMe() { mIsSelfDestroyed = true stopSelf() } - fun doSync(once: Boolean = false) { - if (!networkConnectivityChecker.hasInternetAccess) { - Timber.v("No internet access. Waiting...") - // TODO Retry in ? - timer.schedule(object : TimerTask() { - override fun run() { - doSync() - } - }, NO_NETWORK_DELAY) - } else { - Timber.v("Execute sync request with timeout 0") - val params = SyncTask.Params(TIME_OUT) - cancelableTask = syncTask - .configureWith(params) { - callbackThread = TaskThread.SYNC - executionThread = TaskThread.SYNC - callback = object : MatrixCallback { - override fun onSuccess(data: Unit) { - cancelableTask = null - if (!once) { - 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) + private suspend fun doSync() { + if (!networkConnectivityChecker.hasInternetAccess()) { + Timber.v("No network, try to sync again in 10s") + delay(DELAY_NO_NETWORK) + doSync() + return + } + Timber.v("Execute sync request with timeout 0") + val params = SyncTask.Params(TIME_OUT) + try { + syncTask.execute(params) + stopMe() + } catch (throwable: Throwable) { + Timber.e(throwable) + if (throwable.isTokenError()) { + stopMe() + } else { + Timber.v("Retry to sync in 5s") + delay(DELAY_FAILURE) + doSync() + } } } @@ -164,9 +115,8 @@ open class SyncService : Service() { companion object { const val EXTRA_USER_ID = "EXTRA_USER_ID" - - const val TIME_OUT = 0L - const val NEXT_BATCH_DELAY = 60_000L - const val NO_NETWORK_DELAY = 5_000L + private const val TIME_OUT = 0L + private const val DELAY_NO_NETWORK = 10_000L + private const val DELAY_FAILURE = 5_000L } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt index d8de292d70..67169cdc3c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt @@ -99,11 +99,10 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask, isStarted = true networkConnectivityChecker.register(this) backgroundDetectionObserver.register(this) - while (state != SyncState.KILLING) { Timber.v("Entering loop, state: $state") - if (!networkConnectivityChecker.hasInternetAccess) { + if (!networkConnectivityChecker.hasInternetAccess()) { Timber.v("No network. Waiting...") updateStateTo(SyncState.NO_NETWORK) synchronized(lock) { lock.wait() } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncWorker.kt index b5177172d0..804102913d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncWorker.kt @@ -18,6 +18,10 @@ package im.vector.matrix.android.internal.session.sync.job import android.content.Context import androidx.work.* 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.task.TaskExecutor 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.WorkerParamsFactory import im.vector.matrix.android.internal.worker.getSessionComponent +import kotlinx.coroutines.delay import kotlinx.coroutines.withContext import timber.log.Timber import java.util.concurrent.TimeUnit @@ -46,45 +51,58 @@ internal class SyncWorker(context: Context, @Inject lateinit var syncTask: SyncTask @Inject lateinit var taskExecutor: TaskExecutor @Inject lateinit var coroutineDispatchers: MatrixCoroutineDispatchers + @Inject lateinit var networkConnectivityChecker: NetworkConnectivityChecker override suspend fun doWork(): Result { Timber.i("Sync work starting") val params = WorkerParamsFactory.fromData(inputData) ?: return Result.success() val sessionComponent = getSessionComponent(params.userId) ?: return Result.success() sessionComponent.inject(this) - runCatching { - withContext(coroutineDispatchers.sync) { - val taskParams = SyncTask.Params(0) - syncTask.execute(taskParams) - } - } - return Result.success() + return runCatching { + doSync(params.timeout) + }.fold( + { Result.success() }, + { failure -> + if (failure.isTokenError() || !params.automaticallyRetry) { + Result.failure() + } else { + Result.retry() + } + } + ) + } + + private suspend fun doSync(timeout: Long) = withContext(coroutineDispatchers.sync) { + val taskParams = SyncTask.Params(timeout) + syncTask.execute(taskParams) } companion object { + const val BG_SYNC_WORK_NAME = "BG_SYNCP" + fun requireBackgroundSync(context: Context, userId: String, serverTimeout: Long = 0) { val data = WorkerParamsFactory.toData(Params(userId, serverTimeout, false)) val workRequest = matrixOneTimeWorkRequestBuilder() - .setInputData(data) .setConstraints(WorkManagerUtil.workConstraints) .setBackoffCriteria(BackoffPolicy.LINEAR, 1_000, TimeUnit.MILLISECONDS) + .setInputData(data) .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) { val data = WorkerParamsFactory.toData(Params(userId, serverTimeout, true)) val workRequest = matrixOneTimeWorkRequestBuilder() - .setInputData(data) .setConstraints(WorkManagerUtil.workConstraints) + .setInputData(data) .setBackoffCriteria(BackoffPolicy.LINEAR, delay, TimeUnit.MILLISECONDS) .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) { - WorkManager.getInstance(context).cancelUniqueWork("BG_SYNCP") + WorkManager.getInstance(context).cancelUniqueWork(BG_SYNC_WORK_NAME) } } } diff --git a/vector/src/fdroid/AndroidManifest.xml b/vector/src/fdroid/AndroidManifest.xml index 1a35caff41..51234e0bba 100644 --- a/vector/src/fdroid/AndroidManifest.xml +++ b/vector/src/fdroid/AndroidManifest.xml @@ -20,10 +20,6 @@ android:enabled="true" android:exported="false" /> - - \ No newline at end of file diff --git a/vector/src/fdroid/java/im/vector/riotx/fdroid/receiver/AlarmSyncBroadcastReceiver.kt b/vector/src/fdroid/java/im/vector/riotx/fdroid/receiver/AlarmSyncBroadcastReceiver.kt index 6648d79490..951fcaa14d 100644 --- a/vector/src/fdroid/java/im/vector/riotx/fdroid/receiver/AlarmSyncBroadcastReceiver.kt +++ b/vector/src/fdroid/java/im/vector/riotx/fdroid/receiver/AlarmSyncBroadcastReceiver.kt @@ -25,7 +25,7 @@ import android.os.Build import android.os.PowerManager import androidx.core.content.ContextCompat 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 class AlarmSyncBroadcastReceiver : BroadcastReceiver() { @@ -41,14 +41,9 @@ class AlarmSyncBroadcastReceiver : BroadcastReceiver() { val userId = intent.getStringExtra(SyncService.EXTRA_USER_ID) // This method is called when the BroadcastReceiver is receiving an Intent broadcast. Timber.d("RestartBroadcastReceiver received intent") - Intent(context, VectorSyncService::class.java).also { - it.putExtra(SyncService.EXTRA_USER_ID, userId) + VectorSyncService.newIntent(context, userId).also { try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - ContextCompat.startForegroundService(context, it) - } else { - context.startService(it) - } + ContextCompat.startForegroundService(context, it) } catch (ex: Throwable) { // TODO Timber.e(ex) @@ -79,6 +74,7 @@ class AlarmSyncBroadcastReceiver : BroadcastReceiver() { } fun cancelAlarm(context: Context) { + Timber.v("Cancel alarm") val intent = Intent(context, AlarmSyncBroadcastReceiver::class.java) val pIntent = PendingIntent.getBroadcast(context, REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT) val alarmMgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index 5f1687c9c9..49af440501 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -104,6 +104,10 @@ android:name=".core.services.CallService" android:exported="false" /> + + diff --git a/vector/src/main/java/im/vector/riotx/VectorApplication.kt b/vector/src/main/java/im/vector/riotx/VectorApplication.kt index b6b0d16360..3f55e8f5e6 100644 --- a/vector/src/main/java/im/vector/riotx/VectorApplication.kt +++ b/vector/src/main/java/im/vector/riotx/VectorApplication.kt @@ -116,11 +116,12 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration. if (authenticationService.hasAuthenticatedSessions() && !activeSessionHolder.hasActiveSession()) { val lastAuthenticatedSession = authenticationService.getLastAuthenticatedSession()!! activeSessionHolder.setActiveSession(lastAuthenticatedSession) - lastAuthenticatedSession.configureAndStart(pushRuleTriggerListener, sessionListener) + lastAuthenticatedSession.configureAndStart(applicationContext, pushRuleTriggerListener, sessionListener) } ProcessLifecycleOwner.get().lifecycle.addObserver(object : LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun entersForeground() { + Timber.i("App entered foreground") FcmHelper.onEnterForeground(appContext) activeSessionHolder.getSafeActiveSession()?.also { it.stopAnyBackgroundSync() diff --git a/vector/src/main/java/im/vector/riotx/core/extensions/Session.kt b/vector/src/main/java/im/vector/riotx/core/extensions/Session.kt index 0ce4d04497..9a6905e4c7 100644 --- a/vector/src/main/java/im/vector/riotx/core/extensions/Session.kt +++ b/vector/src/main/java/im/vector/riotx/core/extensions/Session.kt @@ -16,23 +16,27 @@ 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.ProcessLifecycleOwner import im.vector.matrix.android.api.session.Session 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.session.SessionListener import timber.log.Timber -fun Session.configureAndStart(pushRuleTriggerListener: PushRuleTriggerListener, +fun Session.configureAndStart(context: Context, + pushRuleTriggerListener: PushRuleTriggerListener, sessionListener: SessionListener) { open() addListener(sessionListener) setFilter(FilterService.FilterPreset.RiotFilter) Timber.i("Configure and start session for ${this.myUserId}") - val isAtLeastStarted = ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED) - Timber.v("--> is at least started? $isAtLeastStarted") - startSync(isAtLeastStarted) + startSyncing(context) refreshPushers() pushRuleTriggerListener.startWithSession(this) @@ -40,3 +44,20 @@ fun Session.configureAndStart(pushRuleTriggerListener: PushRuleTriggerListener, // @Inject lateinit var incomingVerificationRequestHandler: IncomingVerificationRequestHandler // @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) +} diff --git a/vector/src/fdroid/java/im/vector/riotx/fdroid/service/VectorSyncService.kt b/vector/src/main/java/im/vector/riotx/core/services/VectorSyncService.kt similarity index 83% rename from vector/src/fdroid/java/im/vector/riotx/fdroid/service/VectorSyncService.kt rename to vector/src/main/java/im/vector/riotx/core/services/VectorSyncService.kt index fbf9c1a031..175f921e07 100644 --- a/vector/src/fdroid/java/im/vector/riotx/fdroid/service/VectorSyncService.kt +++ b/vector/src/main/java/im/vector/riotx/core/services/VectorSyncService.kt @@ -13,12 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package im.vector.riotx.fdroid.service +package im.vector.riotx.core.services import android.app.NotificationManager import android.content.Context import android.content.Intent import android.os.Build +import androidx.core.content.ContextCompat import im.vector.matrix.android.internal.session.sync.job.SyncService import im.vector.riotx.R import im.vector.riotx.core.extensions.vectorComponent @@ -27,6 +28,15 @@ import timber.log.Timber 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 override fun onCreate() { @@ -45,8 +55,7 @@ class VectorSyncService : SyncService() { } /** - * Service is started only in fdroid mode when no FCM is available - * Otherwise it is bounded + * Service is started in fdroid mode when no FCM is available or is used for initialSync */ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { Timber.v("VectorSyncService - onStartCommand ") diff --git a/vector/src/main/java/im/vector/riotx/features/MainActivity.kt b/vector/src/main/java/im/vector/riotx/features/MainActivity.kt index 7064ad0d49..b95eddd011 100644 --- a/vector/src/main/java/im/vector/riotx/features/MainActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/MainActivity.kt @@ -26,6 +26,8 @@ import im.vector.riotx.R import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.di.ScreenComponent 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.utils.deleteAllFiles import im.vector.riotx.features.home.HomeActivity @@ -75,8 +77,13 @@ class MainActivity : VectorBaseActivity() { } private fun doCleanUp(clearCache: Boolean, clearCredentials: Boolean) { + val session = sessionHolder.getSafeActiveSession() + if (session == null) { + start() + return + } when { - clearCredentials -> sessionHolder.getActiveSession().signOut(object : MatrixCallback { + clearCredentials -> session.signOut(object : MatrixCallback { override fun onSuccess(data: Unit) { Timber.w("SIGN_OUT: success, start app") sessionHolder.clearActiveSession() @@ -87,8 +94,9 @@ class MainActivity : VectorBaseActivity() { displayError(failure, clearCache, clearCredentials) } }) - clearCache -> sessionHolder.getActiveSession().clearCache(object : MatrixCallback { + clearCache -> session.clearCache(object : MatrixCallback { override fun onSuccess(data: Unit) { + session.startSyncing(applicationContext) doLocalCleanupAndStart() } diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt index 00207cbfbf..11f2f149a8 100644 --- a/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginViewModel.kt @@ -16,6 +16,7 @@ package im.vector.riotx.features.login +import android.content.Context import com.airbnb.mvrx.* import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject @@ -44,6 +45,7 @@ import java.util.concurrent.CancellationException * */ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginViewState, + private val applicationContext: Context, private val authenticationService: AuthenticationService, private val activeSessionHolder: ActiveSessionHolder, private val pushRuleTriggerListener: PushRuleTriggerListener, @@ -469,7 +471,7 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi private fun onSessionCreated(session: Session) { activeSessionHolder.setActiveSession(session) - session.configureAndStart(pushRuleTriggerListener, sessionListener) + session.configureAndStart(applicationContext, pushRuleTriggerListener, sessionListener) setState { copy( asyncLoginAction = Success(Unit)