Basic FCM vs fdroid mode

This commit is contained in:
Valere
2019-06-20 15:22:40 +02:00
committed by Benoit Marty
parent 0e46fc4c0a
commit 2e417a9143
30 changed files with 663 additions and 1245 deletions

View File

@ -61,9 +61,7 @@ class Matrix private constructor(context: Context) : MatrixKoinComponent {
currentSession = it
it.open()
it.setFilter(FilterService.FilterPreset.RiotFilter)
//TODO check if using push or not (should pause if we use push)
// it.shoudPauseOnBackground(false)
// it.startSync()
it.startSync()
}
}

View File

@ -60,35 +60,31 @@ interface Session :
@MainThread
fun open()
// /**
// * This method start the sync thread.
// */
// @MainThread
// fun startSync()
//
//
// fun isSyncThreadAlice() : Boolean
// fun syncThreadState() : String
//
//// fun pauseSync()
//// fun resumeSync()
//
// fun shoudPauseOnBackground(shouldPause: Boolean)
/**
* Requires a one time background sync
*/
fun requireBackgroundSync()
/**
* Configures the sync long pooling options
* @param timoutMS The maximum time to wait, in milliseconds, before returning the sync request.
* If no events (or other data) become available before this time elapses, the server will return a response with empty fields.
* If set to 0 the server will return immediately even if the response is empty.
* @param delayMs When the server responds to a sync request, the client waits for `longPoolDelay` before calling a new sync.
* Launches infinite periodic background syncs
* THis does not work in doze mode :/
* If battery optimization is on it can work in app standby but that's all :/
*/
// fun configureSyncLongPooling(timoutMS : Long, delayMs : Long )
fun startAutomaticBackgroundSync(repeatDelay: Long = 30_000L)
// /**
// * This method stop the sync thread.
// */
// @MainThread
// fun stopSync()
fun stopAnyBackgroundSync()
/**
* This method start the sync thread.
*/
@MainThread
fun startSync()
/**
* This method stop the sync thread.
*/
@MainThread
fun stopSync()
/**
* This method allows to listen the sync state.

View File

@ -38,4 +38,5 @@ interface ReadService {
*/
fun setReadMarker(fullyReadEventId: String, callback: MatrixCallback<Unit>)
fun isEventRead(eventId: String): Boolean
}

View File

@ -73,6 +73,7 @@ import im.vector.matrix.android.internal.session.room.RoomModule
import im.vector.matrix.android.internal.session.signout.SignOutModule
import im.vector.matrix.android.internal.session.sync.SyncModule
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.user.UserModule
import org.koin.core.scope.Scope
import org.koin.standalone.inject
@ -136,45 +137,34 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi
bingRuleWatcher.start()
}
// @MainThread
// override fun startSync() {
// assert(isOpen)
// if (!syncThread.isAlive) {
// syncThread.start()
// } else {
// syncThread.restart()
// Timber.w("Attempt to start an already started thread")
// }
// }
//
// override fun isSyncThreadAlice(): Boolean = syncThread.isAlive
//
// override fun syncThreadState(): String = syncThread.getSyncState()
//
// override fun shoudPauseOnBackground(shouldPause: Boolean) {
// //TODO check if using push or not (should pause if we use push)
// syncThread.shouldPauseOnBackground = shouldPause
// }
override fun requireBackgroundSync() {
SyncWorker.requireBackgroundSync()
}
// override fun resumeSync() {
// assert(isOpen)
// syncThread.restart()
// }
//
// override fun pauseSync() {
// assert(isOpen)
// syncThread.pause()
// }
override fun startAutomaticBackgroundSync(repeatDelay: Long) {
SyncWorker.automaticallyBackgroundSync(0, repeatDelay)
}
// override fun configureSyncLongPooling(timoutMS: Long, delayMs: Long) {
// syncThread.configureLongPoolingSettings(timoutMS, delayMs)
// }
//
// @MainThread
// override fun stopSync() {
// assert(isOpen)
// syncThread.kill()
// }
override fun stopAnyBackgroundSync() {
SyncWorker.stopAnyBackgroundSync()
}
@MainThread
override fun startSync() {
assert(isOpen)
if (!syncThread.isAlive) {
syncThread.start()
} else {
syncThread.restart()
Timber.w("Attempt to start an already started thread")
}
}
@MainThread
override fun stopSync() {
assert(isOpen)
syncThread.kill()
}
@MainThread
override fun close() {

View File

@ -17,6 +17,7 @@
package im.vector.matrix.android.internal.session.room
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.internal.session.room.membership.DefaultMembershipService
@ -51,7 +52,8 @@ internal class RoomFactory(private val monarchy: Monarchy,
private val cryptoService: CryptoService,
private val findReactionEventForUndoTask: FindReactionEventForUndoTask,
private val joinRoomTask: JoinRoomTask,
private val leaveRoomTask: LeaveRoomTask) {
private val leaveRoomTask: LeaveRoomTask,
private val sessionParams: SessionParams) {
fun instantiate(roomId: String): Room {
val roomMemberExtractor = SenderRoomMemberExtractor(roomId)
@ -61,7 +63,7 @@ internal class RoomFactory(private val monarchy: Monarchy,
val sendService = DefaultSendService(roomId, eventFactory, cryptoService, monarchy)
val stateService = DefaultStateService(roomId, taskExecutor, sendStateTask)
val roomMembersService = DefaultMembershipService(roomId, monarchy, taskExecutor, loadRoomMembersTask, inviteTask, joinRoomTask, leaveRoomTask)
val readService = DefaultReadService(roomId, monarchy, taskExecutor, setReadMarkersTask)
val readService = DefaultReadService(roomId, monarchy, taskExecutor, setReadMarkersTask, sessionParams)
val reactionService = DefaultRelationService(roomId, eventFactory, findReactionEventForUndoTask, monarchy, taskExecutor)
return DefaultRoom(

View File

@ -80,7 +80,7 @@ class RoomModule {
}
scope(DefaultSession.SCOPE) {
RoomFactory(get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get())
RoomFactory(get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get())
}
scope(DefaultSession.SCOPE) {

View File

@ -18,9 +18,15 @@ package im.vector.matrix.android.internal.session.room.read
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.session.room.read.ReadService
import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
import im.vector.matrix.android.internal.database.query.find
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
import im.vector.matrix.android.internal.database.query.latestEvent
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.util.fetchCopied
@ -28,7 +34,8 @@ import im.vector.matrix.android.internal.util.fetchCopied
internal class DefaultReadService(private val roomId: String,
private val monarchy: Monarchy,
private val taskExecutor: TaskExecutor,
private val setReadMarkersTask: SetReadMarkersTask) : ReadService {
private val setReadMarkersTask: SetReadMarkersTask,
private val sessionParams: SessionParams) : ReadService {
override fun markAllAsRead(callback: MatrixCallback<Unit>) {
val latestEvent = getLatestEvent()
@ -50,5 +57,20 @@ internal class DefaultReadService(private val roomId: String,
return monarchy.fetchCopied { EventEntity.latestEvent(it, roomId) }
}
override fun isEventRead(eventId: String): Boolean {
var isEventRead = false
monarchy.doWithRealm {
val readReceipt = ReadReceiptEntity.where(it, roomId, sessionParams.credentials.userId).findFirst()
?: return@doWithRealm
val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(it, roomId)
?: return@doWithRealm
val readReceiptIndex = liveChunk.events.find(readReceipt.eventId)?.displayIndex
?: Int.MIN_VALUE
val eventToCheckIndex = liveChunk.events.find(eventId)?.displayIndex
?: Int.MAX_VALUE
isEventRead = eventToCheckIndex <= readReceiptIndex
}
return isEventRead
}
}

View File

@ -20,7 +20,11 @@ import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.room.timeline.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.session.room.timeline.TimelineService
import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.ReadReceiptEntity
import im.vector.matrix.android.internal.database.query.find
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.util.fetchCopyMap
@ -45,4 +49,5 @@ internal class DefaultTimelineService(private val roomId: String,
})
}
}

View File

@ -72,7 +72,7 @@ open class SyncService : Service(), MatrixKoinComponent {
if (cancelableTask == null) {
timer.cancel()
timer = Timer()
doSync()
doSync(true)
} else {
//Already syncing ignore
Timber.i("Received a start while was already syncking... ignore")
@ -101,7 +101,7 @@ open class SyncService : Service(), MatrixKoinComponent {
stopSelf()
}
fun doSync() {
fun doSync(once: Boolean = false) {
var nextBatch = syncTokenStore.getLastToken()
if (!networkConnectivityChecker.isConnected()) {
Timber.v("Sync is Paused. Waiting...")
@ -110,7 +110,7 @@ open class SyncService : Service(), MatrixKoinComponent {
override fun run() {
doSync()
}
}, 10_000L)
}, 5_000L)
} else {
Timber.v("Execute sync request with token $nextBatch and timeout $timeout")
val params = SyncTask.Params(nextBatch, timeout)
@ -123,11 +123,16 @@ open class SyncService : Service(), MatrixKoinComponent {
nextBatch = data.nextBatch
syncTokenStore.saveToken(nextBatch)
localBinder.notifySyncFinish()
timer.schedule(object : TimerTask() {
override fun run() {
doSync()
}
}, nextBatchDelay)
if (!once) {
timer.schedule(object : TimerTask() {
override fun run() {
doSync()
}
}, nextBatchDelay)
} else {
//stop
stopMe()
}
}
override fun onFailure(failure: Throwable) {
@ -141,7 +146,7 @@ open class SyncService : Service(), MatrixKoinComponent {
override fun run() {
doSync()
}
}, 10_000L)
}, 5_000L)
}
if (failure !is Failure.NetworkConnection
@ -151,7 +156,7 @@ open class SyncService : Service(), MatrixKoinComponent {
override fun run() {
doSync()
}
}, 10_000L)
}, 5_000L)
}
if (failure is Failure.ServerError

View File

@ -40,11 +40,6 @@ private const val RETRY_WAIT_TIME_MS = 10_000L
private const val DEFAULT_LONG_POOL_TIMEOUT = 10_000L
private const val DEFAULT_LONG_POOL_DELAY = 0L
private const val DEFAULT_BACKGROUND_LONG_POOL_TIMEOUT = 0L
private const val DEFAULT_BACKGROUND_LONG_POOL_DELAY = 30_000L
internal class SyncThread(private val syncTask: SyncTask,
private val networkConnectivityChecker: NetworkConnectivityChecker,
private val syncTokenStore: SyncTokenStore,
@ -62,27 +57,6 @@ internal class SyncThread(private val syncTask: SyncTask,
updateStateTo(SyncState.IDLE)
}
/**
* The maximum time to wait, in milliseconds, before returning this request.
* If no events (or other data) become available before this time elapses, the server will return a response with empty fields.
* If set to 0 the server will return immediately even if the response is empty.
*/
private var longPoolTimeoutMs = DEFAULT_LONG_POOL_TIMEOUT
/**
* When the server responds to a sync request, the client waits for `longPoolDelay` before calling a new sync.
*/
private var longPoolDelayMs = DEFAULT_LONG_POOL_DELAY
var shouldPauseOnBackground: Boolean = true
private var backgroundedLongPoolTimeoutMs = DEFAULT_BACKGROUND_LONG_POOL_TIMEOUT
private var backgroundedLongPoolDelayMs = DEFAULT_BACKGROUND_LONG_POOL_DELAY
private var currentLongPoolTimeoutMs = longPoolTimeoutMs
private var currentLongPoolDelayMs = longPoolDelayMs
fun restart() = synchronized(lock) {
if (state is SyncState.PAUSED) {
Timber.v("Resume sync...")
@ -93,30 +67,6 @@ internal class SyncThread(private val syncTask: SyncTask,
}
}
/**
* Configures the long pooling settings
*/
fun configureLongPoolingSettings(timoutMS: Long, delayMs: Long) {
longPoolTimeoutMs = Math.max(0, timoutMS)
longPoolDelayMs = Math.max(0, delayMs)
}
/**
* Configures the long pooling settings in background mode (used only if should not pause on BG)
*/
fun configureBackgroundeLongPoolingSettings(timoutMS: Long, delayMs: Long) {
backgroundedLongPoolTimeoutMs = Math.max(0, timoutMS)
backgroundedLongPoolDelayMs = Math.max(0, delayMs)
}
fun resetLongPoolingSettings() {
longPoolTimeoutMs = DEFAULT_LONG_POOL_TIMEOUT
longPoolDelayMs = DEFAULT_LONG_POOL_DELAY
backgroundedLongPoolTimeoutMs = DEFAULT_BACKGROUND_LONG_POOL_TIMEOUT
backgroundedLongPoolDelayMs = DEFAULT_BACKGROUND_LONG_POOL_DELAY
}
fun pause() = synchronized(lock) {
if (state is SyncState.RUNNING) {
Timber.v("Pause sync...")
@ -148,9 +98,9 @@ internal class SyncThread(private val syncTask: SyncTask,
lock.wait()
}
} else {
Timber.v("Execute sync request with token $nextBatch and timeout $currentLongPoolTimeoutMs")
Timber.v("Execute sync request with token $nextBatch and timeout $DEFAULT_LONG_POOL_TIMEOUT")
val latch = CountDownLatch(1)
val params = SyncTask.Params(nextBatch, currentLongPoolTimeoutMs)
val params = SyncTask.Params(nextBatch, DEFAULT_LONG_POOL_TIMEOUT)
cancelableTask = syncTask.configureWith(params)
.callbackOn(TaskThread.CALLER)
.executeOn(TaskThread.CALLER)
@ -193,8 +143,8 @@ internal class SyncThread(private val syncTask: SyncTask,
updateStateTo(SyncState.RUNNING(catchingUp = false))
}
Timber.v("Waiting for $currentLongPoolDelayMs delay before new pool...")
if (currentLongPoolDelayMs > 0) sleep(currentLongPoolDelayMs)
Timber.v("Waiting for $DEFAULT_LONG_POOL_DELAY delay before new pool...")
if (DEFAULT_LONG_POOL_DELAY > 0) sleep(DEFAULT_LONG_POOL_DELAY)
Timber.v("...Continue")
}
}
@ -216,20 +166,11 @@ internal class SyncThread(private val syncTask: SyncTask,
}
override fun onMoveToForeground() {
currentLongPoolTimeoutMs = longPoolTimeoutMs
currentLongPoolDelayMs = longPoolDelayMs
restart()
}
override fun onMoveToBackground() {
if (shouldPauseOnBackground) {
pause()
} else {
Timber.v("Slower sync in background mode")
//we continue but with a slower pace
currentLongPoolTimeoutMs = backgroundedLongPoolTimeoutMs
currentLongPoolDelayMs = backgroundedLongPoolDelayMs
}
pause()
}
}

View File

@ -17,8 +17,6 @@ package im.vector.matrix.android.internal.session.sync.job
import android.content.Context
import androidx.work.*
import arrow.core.failure
import arrow.core.recoverWith
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.failure.MatrixError
@ -33,19 +31,19 @@ import im.vector.matrix.android.internal.session.sync.model.SyncResponse
import im.vector.matrix.android.internal.util.WorkerParamsFactory
import org.koin.standalone.inject
import timber.log.Timber
import java.net.SocketTimeoutException
import java.util.concurrent.TimeUnit
private const val DEFAULT_LONG_POOL_TIMEOUT = 0L
class SyncWorker(context: Context,
internal class SyncWorker(context: Context,
workerParameters: WorkerParameters
) : CoroutineWorker(context, workerParameters), MatrixKoinComponent {
@JsonClass(generateAdapter = true)
internal data class Params(
val timeout: Long = DEFAULT_LONG_POOL_TIMEOUT
val timeout: Long = DEFAULT_LONG_POOL_TIMEOUT,
val automaticallyRetry: Boolean = false
)
private val syncAPI by inject<SyncAPI>()
@ -54,7 +52,6 @@ class SyncWorker(context: Context,
private val sessionParamsStore by inject<SessionParamsStore>()
private val syncTokenStore by inject<SyncTokenStore>()
val autoMode = false
override suspend fun doWork(): Result {
Timber.i("Sync work starting")
@ -69,51 +66,56 @@ class SyncWorker(context: Context,
return executeRequest<SyncResponse> {
apiCall = syncAPI.sync(requestParams)
}.recoverWith { throwable ->
// Intercept 401
if (throwable is Failure.ServerError
&& throwable.error.code == MatrixError.UNKNOWN_TOKEN) {
sessionParamsStore.delete()
}
Timber.i("Sync work failed $throwable")
// Transmit the throwable
throwable.failure()
}.fold(
{
Timber.i("Sync work failed $it")
again()
if (it is Failure.NetworkConnection && it.cause is SocketTimeoutException) {
// Timeout are not critical
Result.Success()
if (it is Failure.ServerError
&& it.error.code == MatrixError.UNKNOWN_TOKEN) {
sessionParamsStore.delete()
Result.failure()
} else {
Result.Success()
Timber.i("Sync work failed $it")
Result.retry()
}
},
{
Timber.i("Sync work success next batch ${it.nextBatch}")
syncResponseHandler.handleResponse(it, token, false)
syncTokenStore.saveToken(it.nextBatch)
again()
Result.success()
if (!isStopped) {
syncResponseHandler.handleResponse(it, token, false)
syncTokenStore.saveToken(it.nextBatch)
}
if (params.automaticallyRetry) Result.retry() else Result.success()
}
)
}
fun again() {
if (autoMode) {
Timber.i("Sync work Again!!")
companion object {
fun requireBackgroundSync(serverTimeout: Long = 0) {
val data = WorkerParamsFactory.toData(Params(serverTimeout, false))
val workRequest = OneTimeWorkRequestBuilder<SyncWorker>()
.setInitialDelay(30_000, TimeUnit.MILLISECONDS)
.setInputData(data)
.setConstraints(Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build())
.setBackoffCriteria(BackoffPolicy.LINEAR, 10_000, TimeUnit.MILLISECONDS)
.setBackoffCriteria(BackoffPolicy.LINEAR, 1_000, TimeUnit.MILLISECONDS)
.build()
WorkManager.getInstance().enqueueUniqueWork("BG_SYNCP", ExistingWorkPolicy.APPEND, workRequest)
WorkManager.getInstance().enqueueUniqueWork("BG_SYNCP", ExistingWorkPolicy.REPLACE, workRequest)
}
fun automaticallyBackgroundSync(serverTimeout: Long = 0, delay: Long = 30_000) {
val data = WorkerParamsFactory.toData(Params(serverTimeout, true))
val workRequest = OneTimeWorkRequestBuilder<SyncWorker>()
.setInputData(data)
.setConstraints(Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build())
.setBackoffCriteria(BackoffPolicy.LINEAR, delay, TimeUnit.MILLISECONDS)
.build()
WorkManager.getInstance().enqueueUniqueWork("BG_SYNCP", ExistingWorkPolicy.REPLACE, workRequest)
}
fun stopAnyBackgroundSync() {
WorkManager.getInstance().cancelUniqueWork("BG_SYNCP")
}
}
}