Merge pull request #4046 from vector-im/feature/bma/incr_sync_investigation

Incr sync investigation
This commit is contained in:
Benoit Marty 2021-09-23 14:24:01 +02:00 committed by GitHub
commit b52f2b0422
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 393 additions and 118 deletions

1
changelog.d/4046.feature Normal file
View File

@ -0,0 +1 @@
Push and syncs: add debug info on room list and on room detail screen and improves the log format.

1
changelog.d/4046.misc Normal file
View File

@ -0,0 +1 @@
InitialSyncProgressService has been renamed to SyncStatusService and its function getInitialSyncProgressStatus() has been renamed to getSyncStatusLive()

View File

@ -7,8 +7,8 @@ ext.versions = [
'targetCompat' : JavaVersion.VERSION_11,
]
// Ref: https://kotlinlang.org/releases.html
def gradle = "7.0.2"
// Ref: https://kotlinlang.org/releases.html
def kotlin = "1.5.30"
def kotlinCoroutines = "1.5.1"
def dagger = "2.38.1"
@ -55,6 +55,8 @@ ext.libs = [
'lifecycleExtensions' : "androidx.lifecycle:lifecycle-extensions:$lifecycle",
'lifecycleJava8' : "androidx.lifecycle:lifecycle-common-java8:$lifecycle",
'lifecycleLivedata' : "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1",
'datastore' : "androidx.datastore:datastore:1.0.0",
'datastorepreferences' : "androidx.datastore:datastore-preferences:1.0.0",
'pagingRuntimeKtx' : "androidx.paging:paging-runtime-ktx:2.1.2",
'coreTesting' : "androidx.arch.core:core-testing:2.1.0",
'testCore' : "androidx.test:core:$androidxTest",

View File

@ -24,6 +24,7 @@ package org.matrix.android.sdk.api.logger
*/
open class LoggerTag(_value: String, parentTag: LoggerTag? = null) {
object SYNC : LoggerTag("SYNC")
object VOIP : LoggerTag("VOIP")
val value: String = if (parentTag == null) {

View File

@ -36,7 +36,7 @@ import org.matrix.android.sdk.api.session.file.FileService
import org.matrix.android.sdk.api.session.group.GroupService
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
import org.matrix.android.sdk.api.session.identity.IdentityService
import org.matrix.android.sdk.api.session.initsync.InitialSyncProgressService
import org.matrix.android.sdk.api.session.initsync.SyncStatusService
import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService
import org.matrix.android.sdk.api.session.media.MediaService
import org.matrix.android.sdk.api.session.openid.OpenIdService
@ -75,7 +75,7 @@ interface Session :
ProfileService,
PushRuleService,
PushersService,
InitialSyncProgressService,
SyncStatusService,
HomeServerCapabilitiesService,
SecureStorageService,
AccountService {

View File

@ -17,15 +17,33 @@ package org.matrix.android.sdk.api.session.initsync
import androidx.lifecycle.LiveData
interface InitialSyncProgressService {
interface SyncStatusService {
fun getInitialSyncProgressStatus(): LiveData<Status>
fun getSyncStatusLive(): LiveData<Status>
sealed class Status {
object Idle : Status()
/**
* For initial sync
*/
abstract class InitialSyncStatus: Status()
object Idle : InitialSyncStatus()
data class Progressing(
val initSyncStep: InitSyncStep,
val percentProgress: Int = 0
) : Status()
) : InitialSyncStatus()
/**
* For incremental sync
*/
abstract class IncrementalSyncStatus: Status()
object IncrementalSyncIdle : IncrementalSyncStatus()
data class IncrementalSyncParsing(
val rooms: Int,
val toDevice: Int
) : IncrementalSyncStatus()
object IncrementalSyncError : IncrementalSyncStatus()
object IncrementalSyncDone : IncrementalSyncStatus()
}
}

View File

@ -40,7 +40,7 @@ import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker
import org.matrix.android.sdk.api.session.file.FileService
import org.matrix.android.sdk.api.session.group.GroupService
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
import org.matrix.android.sdk.api.session.initsync.InitialSyncProgressService
import org.matrix.android.sdk.api.session.initsync.SyncStatusService
import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService
import org.matrix.android.sdk.api.session.media.MediaService
import org.matrix.android.sdk.api.session.openid.OpenIdService
@ -115,7 +115,7 @@ internal class DefaultSession @Inject constructor(
private val contentUploadProgressTracker: ContentUploadStateTracker,
private val typingUsersTracker: TypingUsersTracker,
private val contentDownloadStateTracker: ContentDownloadStateTracker,
private val initialSyncProgressService: Lazy<InitialSyncProgressService>,
private val syncStatusService: Lazy<SyncStatusService>,
private val homeServerCapabilitiesService: Lazy<HomeServerCapabilitiesService>,
private val accountDataService: Lazy<SessionAccountDataService>,
private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>,
@ -141,7 +141,7 @@ internal class DefaultSession @Inject constructor(
PushersService by pushersService.get(),
EventService by eventService.get(),
TermsService by termsService.get(),
InitialSyncProgressService by initialSyncProgressService.get(),
SyncStatusService by syncStatusService.get(),
SecureStorageService by secureStorageService.get(),
HomeServerCapabilitiesService by homeServerCapabilitiesService.get(),
ProfileService by profileService.get(),

View File

@ -37,7 +37,7 @@ import org.matrix.android.sdk.api.session.SessionLifecycleObserver
import org.matrix.android.sdk.api.session.accountdata.SessionAccountDataService
import org.matrix.android.sdk.api.session.events.EventService
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
import org.matrix.android.sdk.api.session.initsync.InitialSyncProgressService
import org.matrix.android.sdk.api.session.initsync.SyncStatusService
import org.matrix.android.sdk.api.session.openid.OpenIdService
import org.matrix.android.sdk.api.session.permalinks.PermalinkService
import org.matrix.android.sdk.api.session.securestorage.SecureStorageService
@ -81,7 +81,7 @@ import org.matrix.android.sdk.internal.session.download.DownloadProgressIntercep
import org.matrix.android.sdk.internal.session.events.DefaultEventService
import org.matrix.android.sdk.internal.session.homeserver.DefaultHomeServerCapabilitiesService
import org.matrix.android.sdk.internal.session.identity.DefaultIdentityService
import org.matrix.android.sdk.internal.session.initsync.DefaultInitialSyncProgressService
import org.matrix.android.sdk.internal.session.initsync.DefaultSyncStatusService
import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManager
import org.matrix.android.sdk.internal.session.openid.DefaultOpenIdService
import org.matrix.android.sdk.internal.session.permalinks.DefaultPermalinkService
@ -355,7 +355,7 @@ internal abstract class SessionModule {
abstract fun bindEventSenderProcessorAsSessionLifecycleObserver(processor: EventSenderProcessorCoroutine): SessionLifecycleObserver
@Binds
abstract fun bindInitialSyncProgressService(service: DefaultInitialSyncProgressService): InitialSyncProgressService
abstract fun bindSyncStatusService(service: DefaultSyncStatusService): SyncStatusService
@Binds
abstract fun bindSecureStorageService(service: DefaultSecureStorageService): SecureStorageService

View File

@ -18,23 +18,28 @@ package org.matrix.android.sdk.internal.session.initsync
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import org.matrix.android.sdk.api.session.initsync.InitSyncStep
import org.matrix.android.sdk.api.session.initsync.InitialSyncProgressService
import org.matrix.android.sdk.api.session.initsync.SyncStatusService
import org.matrix.android.sdk.internal.session.SessionScope
import javax.inject.Inject
@SessionScope
internal class DefaultInitialSyncProgressService @Inject constructor()
: InitialSyncProgressService,
internal class DefaultSyncStatusService @Inject constructor()
: SyncStatusService,
ProgressReporter {
private val status = MutableLiveData<InitialSyncProgressService.Status>()
private val status = MutableLiveData<SyncStatusService.Status>()
private var rootTask: TaskInfo? = null
override fun getInitialSyncProgressStatus(): LiveData<InitialSyncProgressService.Status> {
override fun getSyncStatusLive(): LiveData<SyncStatusService.Status> {
return status
}
// Only to be used for incremental sync
fun setStatus(newStatus: SyncStatusService.Status.IncrementalSyncStatus) {
status.postValue(newStatus)
}
/**
* Create a rootTask
*/
@ -67,7 +72,7 @@ internal class DefaultInitialSyncProgressService @Inject constructor()
// Update the progress of the leaf and all its parents
leaf.setProgress(progress)
// Then update the live data using leaf wording and root progress
status.postValue(InitialSyncProgressService.Status.Progressing(leaf.initSyncStep, root.currentProgress.toInt()))
status.postValue(SyncStatusService.Status.Progressing(leaf.initSyncStep, root.currentProgress.toInt()))
}
}
}
@ -82,13 +87,13 @@ internal class DefaultInitialSyncProgressService @Inject constructor()
// And close it
endedTask.parent.child = null
} else {
status.postValue(InitialSyncProgressService.Status.Idle)
status.postValue(SyncStatusService.Status.Idle)
}
}
}
fun endAll() {
rootTask = null
status.postValue(InitialSyncProgressService.Status.Idle)
status.postValue(SyncStatusService.Status.Idle)
}
}

View File

@ -17,7 +17,9 @@
package org.matrix.android.sdk.internal.session.sync
import okhttp3.ResponseBody
import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.initsync.InitSyncStep
import org.matrix.android.sdk.api.session.initsync.SyncStatusService
import org.matrix.android.sdk.internal.di.SessionFilesDirectory
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
@ -26,7 +28,7 @@ import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.network.toFailure
import org.matrix.android.sdk.internal.session.filter.FilterRepository
import org.matrix.android.sdk.internal.session.homeserver.GetHomeServerCapabilitiesTask
import org.matrix.android.sdk.internal.session.initsync.DefaultInitialSyncProgressService
import org.matrix.android.sdk.internal.session.initsync.DefaultSyncStatusService
import org.matrix.android.sdk.internal.session.initsync.reportSubtask
import org.matrix.android.sdk.internal.session.sync.model.LazyRoomSyncEphemeral
import org.matrix.android.sdk.internal.session.sync.parsing.InitialSyncResponseParser
@ -40,6 +42,8 @@ import java.io.File
import java.net.SocketTimeoutException
import javax.inject.Inject
private val loggerTag = LoggerTag("SyncTask", LoggerTag.SYNC)
internal interface SyncTask : Task<SyncTask.Params, Unit> {
data class Params(
@ -53,7 +57,7 @@ internal class DefaultSyncTask @Inject constructor(
@UserId private val userId: String,
private val filterRepository: FilterRepository,
private val syncResponseHandler: SyncResponseHandler,
private val initialSyncProgressService: DefaultInitialSyncProgressService,
private val defaultSyncStatusService: DefaultSyncStatusService,
private val syncTokenStore: SyncTokenStore,
private val getHomeServerCapabilitiesTask: GetHomeServerCapabilitiesTask,
private val userStore: UserStore,
@ -75,7 +79,7 @@ internal class DefaultSyncTask @Inject constructor(
}
private suspend fun doSync(params: SyncTask.Params) {
Timber.v("Sync task started on Thread: ${Thread.currentThread().name}")
Timber.tag(loggerTag.value).d("Sync task started on Thread: ${Thread.currentThread().name}")
val requestParams = HashMap<String, String>()
var timeout = 0L
@ -92,7 +96,7 @@ internal class DefaultSyncTask @Inject constructor(
if (isInitialSync) {
// We might want to get the user information in parallel too
userStore.createOrUpdate(userId)
initialSyncProgressService.startRoot(InitSyncStep.ImportingAccount, 100)
defaultSyncStatusService.startRoot(InitSyncStep.ImportingAccount, 100)
}
// Maybe refresh the homeserver capabilities data we know
getHomeServerCapabilitiesTask.execute(GetHomeServerCapabilitiesTask.Params(forceRefresh = false))
@ -100,20 +104,20 @@ internal class DefaultSyncTask @Inject constructor(
val readTimeOut = (params.timeout + TIMEOUT_MARGIN).coerceAtLeast(TimeOutInterceptor.DEFAULT_LONG_TIMEOUT)
if (isInitialSync) {
Timber.d("INIT_SYNC with filter: ${requestParams["filter"]}")
Timber.tag(loggerTag.value).d("INIT_SYNC with filter: ${requestParams["filter"]}")
val initSyncStrategy = initialSyncStrategy
logDuration("INIT_SYNC strategy: $initSyncStrategy") {
logDuration("INIT_SYNC strategy: $initSyncStrategy", loggerTag) {
if (initSyncStrategy is InitialSyncStrategy.Optimized) {
roomSyncEphemeralTemporaryStore.reset()
workingDir.mkdirs()
val file = downloadInitSyncResponse(requestParams)
reportSubtask(initialSyncProgressService, InitSyncStep.ImportingAccount, 1, 0.7F) {
reportSubtask(defaultSyncStatusService, InitSyncStep.ImportingAccount, 1, 0.7F) {
handleSyncFile(file, initSyncStrategy)
}
// Delete all files
workingDir.deleteRecursively()
} else {
val syncResponse = logDuration("INIT_SYNC Request") {
val syncResponse = logDuration("INIT_SYNC Request", loggerTag) {
executeRequest(globalErrorReceiver) {
syncAPI.sync(
params = requestParams,
@ -122,43 +126,60 @@ internal class DefaultSyncTask @Inject constructor(
}
}
logDuration("INIT_SYNC Database insertion") {
syncResponseHandler.handleResponse(syncResponse, token, initialSyncProgressService)
logDuration("INIT_SYNC Database insertion", loggerTag) {
syncResponseHandler.handleResponse(syncResponse, token, defaultSyncStatusService)
}
}
}
initialSyncProgressService.endAll()
defaultSyncStatusService.endAll()
} else {
val syncResponse = executeRequest(globalErrorReceiver) {
syncAPI.sync(
params = requestParams,
readTimeOut = readTimeOut
)
Timber.tag(loggerTag.value).d("Start incremental sync request")
defaultSyncStatusService.setStatus(SyncStatusService.Status.IncrementalSyncIdle)
val syncResponse = try {
executeRequest(globalErrorReceiver) {
syncAPI.sync(
params = requestParams,
readTimeOut = readTimeOut
)
}
} catch (throwable: Throwable) {
Timber.tag(loggerTag.value).e(throwable, "Incremental sync request error")
defaultSyncStatusService.setStatus(SyncStatusService.Status.IncrementalSyncError)
throw throwable
}
val nbRooms = syncResponse.rooms?.invite.orEmpty().size + syncResponse.rooms?.join.orEmpty().size + syncResponse.rooms?.leave.orEmpty().size
val nbToDevice = syncResponse.toDevice?.events.orEmpty().size
Timber.tag(loggerTag.value).d("Incremental sync request parsing, $nbRooms room(s) $nbToDevice toDevice(s)")
defaultSyncStatusService.setStatus(SyncStatusService.Status.IncrementalSyncParsing(
rooms = nbRooms,
toDevice = nbToDevice
))
syncResponseHandler.handleResponse(syncResponse, token, null)
Timber.tag(loggerTag.value).d("Incremental sync done")
defaultSyncStatusService.setStatus(SyncStatusService.Status.IncrementalSyncDone)
}
Timber.v("Sync task finished on Thread: ${Thread.currentThread().name}")
Timber.tag(loggerTag.value).d("Sync task finished on Thread: ${Thread.currentThread().name}")
}
private suspend fun downloadInitSyncResponse(requestParams: Map<String, String>): File {
val workingFile = File(workingDir, "initSync.json")
val status = initialSyncStatusRepository.getStep()
if (workingFile.exists() && status >= InitialSyncStatus.STEP_DOWNLOADED) {
Timber.d("INIT_SYNC file is already here")
reportSubtask(initialSyncProgressService, InitSyncStep.Downloading, 1, 0.3f) {
Timber.tag(loggerTag.value).d("INIT_SYNC file is already here")
reportSubtask(defaultSyncStatusService, InitSyncStep.Downloading, 1, 0.3f) {
// Empty task
}
} else {
initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_DOWNLOADING)
val syncResponse = logDuration("INIT_SYNC Perform server request") {
reportSubtask(initialSyncProgressService, InitSyncStep.ServerComputing, 1, 0.2f) {
val syncResponse = logDuration("INIT_SYNC Perform server request", loggerTag) {
reportSubtask(defaultSyncStatusService, InitSyncStep.ServerComputing, 1, 0.2f) {
getSyncResponse(requestParams, MAX_NUMBER_OF_RETRY_AFTER_TIMEOUT)
}
}
if (syncResponse.isSuccessful) {
logDuration("INIT_SYNC Download and save to file") {
reportSubtask(initialSyncProgressService, InitSyncStep.Downloading, 1, 0.1f) {
logDuration("INIT_SYNC Download and save to file", loggerTag) {
reportSubtask(defaultSyncStatusService, InitSyncStep.Downloading, 1, 0.1f) {
syncResponse.body()?.byteStream()?.use { inputStream ->
workingFile.outputStream().use { outputStream ->
inputStream.copyTo(outputStream)
@ -168,7 +189,7 @@ internal class DefaultSyncTask @Inject constructor(
}
} else {
throw syncResponse.toFailure(globalErrorReceiver)
.also { Timber.w("INIT_SYNC request failure: $this") }
.also { Timber.tag(loggerTag.value).w("INIT_SYNC request failure: $this") }
}
initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_DOWNLOADED)
}
@ -185,9 +206,9 @@ internal class DefaultSyncTask @Inject constructor(
).awaitResponse()
} catch (throwable: Throwable) {
if (throwable is SocketTimeoutException && retry > 0) {
Timber.w("INIT_SYNC timeout retry left: $retry")
Timber.tag(loggerTag.value).w("INIT_SYNC timeout retry left: $retry")
} else {
Timber.e(throwable, "INIT_SYNC timeout, no retry left, or other error")
Timber.tag(loggerTag.value).e(throwable, "INIT_SYNC timeout, no retry left, or other error")
throw throwable
}
}
@ -195,18 +216,18 @@ internal class DefaultSyncTask @Inject constructor(
}
private suspend fun handleSyncFile(workingFile: File, initSyncStrategy: InitialSyncStrategy.Optimized) {
logDuration("INIT_SYNC handleSyncFile()") {
val syncResponse = logDuration("INIT_SYNC Read file and parse") {
logDuration("INIT_SYNC handleSyncFile()", loggerTag) {
val syncResponse = logDuration("INIT_SYNC Read file and parse", loggerTag) {
syncResponseParser.parse(initSyncStrategy, workingFile)
}
initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_PARSED)
// Log some stats
val nbOfJoinedRooms = syncResponse.rooms?.join?.size ?: 0
val nbOfJoinedRoomsInFile = syncResponse.rooms?.join?.values?.count { it.ephemeral is LazyRoomSyncEphemeral.Stored }
Timber.d("INIT_SYNC $nbOfJoinedRooms rooms, $nbOfJoinedRoomsInFile ephemeral stored into files")
Timber.tag(loggerTag.value).d("INIT_SYNC $nbOfJoinedRooms rooms, $nbOfJoinedRoomsInFile ephemeral stored into files")
logDuration("INIT_SYNC Database insertion") {
syncResponseHandler.handleResponse(syncResponse, null, initialSyncProgressService)
logDuration("INIT_SYNC Database insertion", loggerTag) {
syncResponseHandler.handleResponse(syncResponse, null, defaultSyncStatusService)
}
initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_SUCCESS)
}

View File

@ -36,6 +36,7 @@ import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.call.MxCall
import org.matrix.android.sdk.internal.session.call.ActiveCallHandler
import org.matrix.android.sdk.internal.session.sync.SyncPresence
@ -49,6 +50,8 @@ import kotlin.concurrent.schedule
private const val RETRY_WAIT_TIME_MS = 10_000L
private const val DEFAULT_LONG_POOL_TIMEOUT = 30_000L
private val loggerTag = LoggerTag("SyncThread", LoggerTag.SYNC)
internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
private val networkConnectivityChecker: NetworkConnectivityChecker,
private val backgroundDetectionObserver: BackgroundDetectionObserver,
@ -83,7 +86,7 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
fun restart() = synchronized(lock) {
if (!isStarted) {
Timber.v("Resume sync...")
Timber.tag(loggerTag.value).d("Resume sync...")
isStarted = true
// Check again server availability and the token validity
canReachServer = true
@ -94,7 +97,7 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
fun pause() = synchronized(lock) {
if (isStarted) {
Timber.v("Pause sync...")
Timber.tag(loggerTag.value).d("Pause sync...")
isStarted = false
retryNoNetworkTask?.cancel()
syncScope.coroutineContext.cancelChildren()
@ -102,7 +105,7 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
}
fun kill() = synchronized(lock) {
Timber.v("Kill sync...")
Timber.tag(loggerTag.value).d("Kill sync...")
updateStateTo(SyncState.Killing)
retryNoNetworkTask?.cancel()
syncScope.coroutineContext.cancelChildren()
@ -124,21 +127,21 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
}
override fun run() {
Timber.v("Start syncing...")
Timber.tag(loggerTag.value).d("Start syncing...")
isStarted = true
networkConnectivityChecker.register(this)
backgroundDetectionObserver.register(this)
registerActiveCallsObserver()
while (state != SyncState.Killing) {
Timber.v("Entering loop, state: $state")
Timber.tag(loggerTag.value).d("Entering loop, state: $state")
if (!isStarted) {
Timber.v("Sync is Paused. Waiting...")
Timber.tag(loggerTag.value).d("Sync is Paused. Waiting...")
updateStateTo(SyncState.Paused)
synchronized(lock) { lock.wait() }
Timber.v("...unlocked")
Timber.tag(loggerTag.value).d("...unlocked")
} else if (!canReachServer) {
Timber.v("No network. Waiting...")
Timber.tag(loggerTag.value).d("No network. Waiting...")
updateStateTo(SyncState.NoNetwork)
// We force retrying in RETRY_WAIT_TIME_MS maximum. Otherwise it will be unlocked by onConnectivityChanged() or restart()
retryNoNetworkTask = Timer(SyncState.NoNetwork.toString(), false).schedule(RETRY_WAIT_TIME_MS) {
@ -148,19 +151,19 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
}
}
synchronized(lock) { lock.wait() }
Timber.v("...retry")
Timber.tag(loggerTag.value).d("...retry")
} else if (!isTokenValid) {
Timber.v("Token is invalid. Waiting...")
Timber.tag(loggerTag.value).d("Token is invalid. Waiting...")
updateStateTo(SyncState.InvalidToken)
synchronized(lock) { lock.wait() }
Timber.v("...unlocked")
Timber.tag(loggerTag.value).d("...unlocked")
} else {
if (state !is SyncState.Running) {
updateStateTo(SyncState.Running(afterPause = true))
}
// No timeout after a pause
val timeout = state.let { if (it is SyncState.Running && it.afterPause) 0 else DEFAULT_LONG_POOL_TIMEOUT }
Timber.v("Execute sync request with timeout $timeout")
Timber.tag(loggerTag.value).d("Execute sync request with timeout $timeout")
val params = SyncTask.Params(timeout, SyncPresence.Online)
val sync = syncScope.launch {
doSync(params)
@ -168,10 +171,10 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
runBlocking {
sync.join()
}
Timber.v("...Continue")
Timber.tag(loggerTag.value).d("...Continue")
}
}
Timber.v("Sync killed")
Timber.tag(loggerTag.value).d("Sync killed")
updateStateTo(SyncState.Killed)
backgroundDetectionObserver.unregister(this)
networkConnectivityChecker.unregister(this)
@ -199,19 +202,19 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
}
if (failure is Failure.NetworkConnection && failure.cause is SocketTimeoutException) {
// Timeout are not critical
Timber.v("Timeout")
Timber.tag(loggerTag.value).d("Timeout")
} else if (failure is CancellationException) {
Timber.v("Cancelled")
Timber.tag(loggerTag.value).d("Cancelled")
} else if (failure.isTokenError()) {
// No token or invalid token, stop the thread
Timber.w(failure, "Token error")
Timber.tag(loggerTag.value).w(failure, "Token error")
isStarted = false
isTokenValid = false
} else {
Timber.e(failure)
Timber.tag(loggerTag.value).e(failure)
if (failure !is Failure.NetworkConnection || failure.cause is JsonEncodingException) {
// Wait 10s before retrying
Timber.v("Wait 10s")
Timber.tag(loggerTag.value).d("Wait 10s")
delay(RETRY_WAIT_TIME_MS)
}
}
@ -225,7 +228,7 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
}
private fun updateStateTo(newState: SyncState) {
Timber.v("Update state from $state to $newState")
Timber.tag(loggerTag.value).d("Update state from $state to $newState")
if (newState == state) {
return
}

View File

@ -17,6 +17,7 @@
package org.matrix.android.sdk.internal.util
import org.matrix.android.sdk.BuildConfig
import org.matrix.android.sdk.api.logger.LoggerTag
import timber.log.Timber
internal fun <T> Collection<T>.logLimit(maxQuantity: Int = 5): String {
@ -32,14 +33,15 @@ internal fun <T> Collection<T>.logLimit(maxQuantity: Int = 5): String {
}
internal suspend fun <T> logDuration(message: String,
loggerTag: LoggerTag,
block: suspend () -> T): T {
Timber.d("$message -- BEGIN")
Timber.tag(loggerTag.value).d("$message -- BEGIN")
val start = System.currentTimeMillis()
val result = logRamUsage(message) {
block()
}
val duration = System.currentTimeMillis() - start
Timber.d("$message -- END duration: $duration ms")
Timber.tag(loggerTag.value).d("$message -- END duration: $duration ms")
return result
}

View File

@ -345,6 +345,9 @@ dependencies {
implementation libs.androidx.lifecycleExtensions
implementation libs.androidx.lifecycleLivedata
implementation libs.androidx.datastore
implementation libs.androidx.datastorepreferences
// Log
implementation libs.jakewharton.timber
@ -406,7 +409,7 @@ dependencies {
// To convert voice message on old platforms
implementation 'com.arthenica:ffmpeg-kit-audio:4.4.LTS'
//Alerter
// Alerter
implementation 'com.tapadoo.android:alerter:7.0.1'
implementation 'com.otaliastudios:autocomplete:1.1.0'

View File

@ -39,17 +39,22 @@ import im.vector.app.features.notifications.NotifiableMessageEvent
import im.vector.app.features.notifications.NotificationDrawerManager
import im.vector.app.features.notifications.NotificationUtils
import im.vector.app.features.notifications.SimpleNotifiableEvent
import im.vector.app.features.settings.VectorDataStore
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.push.fcm.FcmHelper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.pushrules.Action
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.Event
import timber.log.Timber
private val loggerTag = LoggerTag("Push", LoggerTag.SYNC)
/**
* Class extending FirebaseMessagingService.
*/
@ -60,6 +65,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
private lateinit var pusherManager: PushersManager
private lateinit var activeSessionHolder: ActiveSessionHolder
private lateinit var vectorPreferences: VectorPreferences
private lateinit var vectorDataStore: VectorDataStore
private lateinit var wifiDetector: WifiDetector
private val coroutineScope = CoroutineScope(SupervisorJob())
@ -77,6 +83,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
pusherManager = pusherManager()
activeSessionHolder = activeSessionHolder()
vectorPreferences = vectorPreferences()
vectorDataStore = vectorDataStore()
wifiDetector = wifiDetector()
}
}
@ -88,9 +95,13 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
*/
override fun onMessageReceived(message: RemoteMessage) {
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
Timber.d("## onMessageReceived() %s", message.data.toString())
Timber.tag(loggerTag.value).d("## onMessageReceived() %s", message.data.toString())
}
Timber.tag(loggerTag.value).d("## onMessageReceived() from FCM with priority %s", message.priority)
runBlocking {
vectorDataStore.incrementPushCounter()
}
Timber.d("## onMessageReceived() from FCM with priority %s", message.priority)
// Diagnostic Push
if (message.data["event_id"] == PushersManager.TEST_EVENT_ID) {
@ -100,14 +111,14 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
}
if (!vectorPreferences.areNotificationEnabledForDevice()) {
Timber.i("Notification are disabled for this device")
Timber.tag(loggerTag.value).i("Notification are disabled for this device")
return
}
mUIHandler.post {
if (ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
// we are in foreground, let the sync do the things?
Timber.d("PUSH received in a foreground state, ignore")
Timber.tag(loggerTag.value).d("PUSH received in a foreground state, ignore")
} else {
onMessageReceivedInternal(message.data)
}
@ -121,7 +132,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
* you retrieve the token.
*/
override fun onNewToken(refreshedToken: String) {
Timber.i("onNewToken: FCM Token has been updated")
Timber.tag(loggerTag.value).i("onNewToken: FCM Token has been updated")
FcmHelper.storeFcmToken(this, refreshedToken)
if (vectorPreferences.areNotificationEnabledForDevice() && activeSessionHolder.hasActiveSession()) {
pusherManager.registerPusherWithFcmKey(refreshedToken)
@ -138,7 +149,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
* It is recommended that the app do a full sync with the app server after receiving this call.
*/
override fun onDeletedMessages() {
Timber.v("## onDeletedMessages()")
Timber.tag(loggerTag.value).v("## onDeletedMessages()")
}
/**
@ -150,9 +161,9 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
private fun onMessageReceivedInternal(data: Map<String, String>) {
try {
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
Timber.d("## onMessageReceivedInternal() : $data")
Timber.tag(loggerTag.value).d("## onMessageReceivedInternal() : $data")
} else {
Timber.d("## onMessageReceivedInternal() : $data")
Timber.tag(loggerTag.value).d("## onMessageReceivedInternal()")
}
// update the badge counter
@ -162,24 +173,24 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
val session = activeSessionHolder.getSafeActiveSession()
if (session == null) {
Timber.w("## Can't sync from push, no current session")
Timber.tag(loggerTag.value).w("## Can't sync from push, no current session")
} else {
val eventId = data["event_id"]
val roomId = data["room_id"]
if (isEventAlreadyKnown(eventId, roomId)) {
Timber.d("Ignoring push, event already known")
Timber.tag(loggerTag.value).d("Ignoring push, event already known")
} else {
// Try to get the Event content faster
Timber.d("Requesting event in fast lane")
Timber.tag(loggerTag.value).d("Requesting event in fast lane")
getEventFastLane(session, roomId, eventId)
Timber.d("Requesting background sync")
Timber.tag(loggerTag.value).d("Requesting background sync")
session.requireBackgroundSync()
}
}
} catch (e: Exception) {
Timber.e(e, "## onMessageReceivedInternal() failed")
Timber.tag(loggerTag.value).e(e, "## onMessageReceivedInternal() failed")
}
}
@ -193,18 +204,18 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
}
if (wifiDetector.isConnectedToWifi().not()) {
Timber.d("No WiFi network, do not get Event")
Timber.tag(loggerTag.value).d("No WiFi network, do not get Event")
return
}
coroutineScope.launch {
Timber.d("Fast lane: start request")
Timber.tag(loggerTag.value).d("Fast lane: start request")
val event = tryOrNull { session.getEvent(roomId, eventId) } ?: return@launch
val resolvedEvent = notifiableEventResolver.resolveInMemoryEvent(session, event)
resolvedEvent
?.also { Timber.d("Fast lane: notify drawer") }
?.also { Timber.tag(loggerTag.value).d("Fast lane: notify drawer") }
?.let {
it.isPushGatewayEvent = true
notificationDrawerManager.onNotifiableEventReceived(it)
@ -222,7 +233,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
val room = session.getRoom(roomId) ?: return false
return room.getTimeLineEvent(eventId) != null
} catch (e: Exception) {
Timber.e(e, "## isEventAlreadyKnown() : failed to check if the event was already defined")
Timber.tag(loggerTag.value).e(e, "## isEventAlreadyKnown() : failed to check if the event was already defined")
}
}
return false
@ -230,7 +241,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
private fun handleNotificationWithoutSyncingMode(data: Map<String, String>, session: Session?) {
if (session == null) {
Timber.e("## handleNotificationWithoutSyncingMode cannot find session")
Timber.tag(loggerTag.value).e("## handleNotificationWithoutSyncingMode cannot find session")
return
}
@ -263,9 +274,9 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
val notifiableEvent = notifiableEventResolver.resolveEvent(event, session)
if (notifiableEvent == null) {
Timber.e("Unsupported notifiable event $eventId")
Timber.tag(loggerTag.value).e("Unsupported notifiable event $eventId")
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
Timber.e("--> $event")
Timber.tag(loggerTag.value).e("--> $event")
}
} else {
if (notifiableEvent is NotifiableMessageEvent) {

View File

@ -58,6 +58,7 @@ import im.vector.app.features.rageshake.VectorFileLogger
import im.vector.app.features.rageshake.VectorUncaughtExceptionHandler
import im.vector.app.features.reactions.data.EmojiDataSource
import im.vector.app.features.session.SessionListener
import im.vector.app.features.settings.VectorDataStore
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.ui.UiStateRepository
import org.matrix.android.sdk.api.Matrix
@ -145,6 +146,8 @@ interface VectorComponent {
fun vectorPreferences(): VectorPreferences
fun vectorDataStore(): VectorDataStore
fun wifiDetector(): WifiDetector
fun vectorFileLogger(): VectorFileLogger

View File

@ -70,7 +70,7 @@ import im.vector.app.features.workers.signout.ServerBackupStatusViewState
import im.vector.app.push.fcm.FcmHelper
import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.session.initsync.InitialSyncProgressService
import org.matrix.android.sdk.api.session.initsync.SyncStatusService
import org.matrix.android.sdk.api.session.permalinks.PermalinkService
import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.internal.session.sync.InitialSyncStrategy
@ -308,11 +308,11 @@ class HomeActivity :
}
private fun renderState(state: HomeActivityViewState) {
when (val status = state.initialSyncProgressServiceStatus) {
is InitialSyncProgressService.Status.Idle -> {
when (val status = state.syncStatusServiceStatus) {
is SyncStatusService.Status.Idle -> {
views.waitingView.root.isVisible = false
}
is InitialSyncProgressService.Status.Progressing -> {
is SyncStatusService.Status.Progressing -> {
val initSyncStepStr = initSyncStepFormatter.format(status.initSyncStep)
Timber.v("$initSyncStepStr ${status.percentProgress}")
views.waitingView.root.setOnClickListener {
@ -330,6 +330,7 @@ class HomeActivity :
}
views.waitingView.root.isVisible = true
}
else -> Unit
}.exhaustive
}
@ -474,8 +475,8 @@ class HomeActivity :
override fun getMenuRes() = R.menu.home
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
menu.findItem(R.id.menu_home_init_sync_legacy)?.isVisible = vectorPreferences.developerMode()
menu.findItem(R.id.menu_home_init_sync_optimized)?.isVisible = vectorPreferences.developerMode()
menu.findItem(R.id.menu_home_init_sync_legacy).isVisible = vectorPreferences.developerMode()
menu.findItem(R.id.menu_home_init_sync_optimized).isVisible = vectorPreferences.developerMode()
return super.onPrepareOptionsMenu(menu)
}

View File

@ -40,7 +40,7 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
import org.matrix.android.sdk.api.auth.registration.nextUncompletedStage
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.pushrules.RuleIds
import org.matrix.android.sdk.api.session.initsync.InitialSyncProgressService
import org.matrix.android.sdk.api.session.initsync.SyncStatusService
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
import org.matrix.android.sdk.api.util.toMatrixItem
@ -122,25 +122,26 @@ class HomeActivityViewModel @AssistedInject constructor(
private fun observeInitialSync() {
val session = activeSessionHolder.getSafeActiveSession() ?: return
session.getInitialSyncProgressStatus()
session.getSyncStatusLive()
.asObservable()
.subscribe { status ->
when (status) {
is InitialSyncProgressService.Status.Progressing -> {
is SyncStatusService.Status.Progressing -> {
// Schedule a check of the bootstrap when the init sync will be finished
checkBootstrap = true
}
is InitialSyncProgressService.Status.Idle -> {
is SyncStatusService.Status.Idle -> {
if (checkBootstrap) {
checkBootstrap = false
maybeBootstrapCrossSigningAfterInitialSync()
}
}
else -> Unit
}
setState {
copy(
initialSyncProgressServiceStatus = status
syncStatusServiceStatus = status
)
}
}

View File

@ -17,8 +17,8 @@
package im.vector.app.features.home
import com.airbnb.mvrx.MvRxState
import org.matrix.android.sdk.api.session.initsync.InitialSyncProgressService
import org.matrix.android.sdk.api.session.initsync.SyncStatusService
data class HomeActivityViewState(
val initialSyncProgressServiceStatus: InitialSyncProgressService.Status = InitialSyncProgressService.Status.Idle
val syncStatusServiceStatus: SyncStatusService.Status = SyncStatusService.Status.Idle
) : MvRxState

View File

@ -440,7 +440,11 @@ class HomeDetailFragment @Inject constructor(
views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_people).render(it.notificationCountPeople, it.notificationHighlightPeople)
views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_rooms).render(it.notificationCountRooms, it.notificationHighlightRooms)
views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_notification).render(it.notificationCountCatchup, it.notificationHighlightCatchup)
views.syncStateView.render(it.syncState)
views.syncStateView.render(
it.syncState,
it.incrementalSyncStatus,
it.pushCounter,
vectorPreferences.developerShowDebugInfo())
hasUnreadRooms = it.hasUnreadMessages
}

View File

@ -33,13 +33,16 @@ import im.vector.app.features.call.webrtc.WebRtcCallManager
import im.vector.app.features.createdirect.DirectRoomHelper
import im.vector.app.features.invite.AutoAcceptInvites
import im.vector.app.features.invite.showInvites
import im.vector.app.features.settings.VectorDataStore
import im.vector.app.features.ui.UiStateRepository
import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.query.ActiveSpaceFilter
import org.matrix.android.sdk.api.query.RoomCategoryFilter
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.initsync.SyncStatusService
import org.matrix.android.sdk.api.session.room.RoomSortOrder
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
@ -56,10 +59,11 @@ import java.util.concurrent.TimeUnit
class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: HomeDetailViewState,
private val session: Session,
private val uiStateRepository: UiStateRepository,
private val vectorDataStore: VectorDataStore,
private val callManager: WebRtcCallManager,
private val directRoomHelper: DirectRoomHelper,
private val appStateHandler: AppStateHandler,
private val autoAcceptInvites: AutoAcceptInvites)
private val autoAcceptInvites: AutoAcceptInvites)
: VectorViewModel<HomeDetailViewState, HomeDetailAction, HomeDetailViewEvents>(initialState),
CallProtocolsChecker.Listener {
@ -89,6 +93,7 @@ private val autoAcceptInvites: AutoAcceptInvites)
observeRoomGroupingMethod()
observeRoomSummaries()
updateShowDialPadTab()
observeDataStore()
callManager.addProtocolsCheckerListener(this)
session.rx().liveUser(session.myUserId).execute {
copy(
@ -97,6 +102,18 @@ private val autoAcceptInvites: AutoAcceptInvites)
}
}
private fun observeDataStore() {
viewModelScope.launch {
vectorDataStore.pushCounterFlow.collect { nbOfPush ->
setState {
copy(
pushCounter = nbOfPush
)
}
}
}
}
override fun handle(action: HomeDetailAction) {
when (action) {
is HomeDetailAction.SwitchTab -> handleSwitchTab(action)
@ -173,6 +190,17 @@ private val autoAcceptInvites: AutoAcceptInvites)
}
}
.disposeOnClear()
session.getSyncStatusLive()
.asObservable()
.subscribe {
if (it is SyncStatusService.Status.IncrementalSyncStatus) {
setState {
copy(incrementalSyncStatus = it)
}
}
}
.disposeOnClear()
}
private fun observeRoomGroupingMethod() {

View File

@ -22,6 +22,7 @@ import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized
import im.vector.app.R
import im.vector.app.RoomGroupingMethod
import org.matrix.android.sdk.api.session.initsync.SyncStatusService
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.sync.SyncState
import org.matrix.android.sdk.api.util.MatrixItem
@ -39,6 +40,8 @@ data class HomeDetailViewState(
val notificationHighlightRooms: Boolean = false,
val hasUnreadMessages: Boolean = false,
val syncState: SyncState = SyncState.Idle,
val incrementalSyncStatus: SyncStatusService.Status.IncrementalSyncStatus = SyncStatusService.Status.IncrementalSyncIdle,
val pushCounter: Int = 0,
val showDialPadTab: Boolean = false
) : MvRxState

View File

@ -387,8 +387,17 @@ class RoomDetailFragment @Inject constructor(
}
}
roomDetailViewModel.selectSubscribe(RoomDetailViewState::syncState) { syncState ->
views.syncStateView.render(syncState)
roomDetailViewModel.selectSubscribe(
RoomDetailViewState::syncState,
RoomDetailViewState::incrementalSyncStatus,
RoomDetailViewState::pushCounter
) { syncState, incrementalSyncStatus, pushCounter ->
views.syncStateView.render(
syncState,
incrementalSyncStatus,
pushCounter,
vectorPreferences.developerShowDebugInfo()
)
}
roomDetailViewModel.observeViewEvents {

View File

@ -38,9 +38,9 @@ import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.mvrx.runCatchingToAsync
import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.call.conference.JitsiActiveConferenceHolder
import im.vector.app.features.attachments.toContentAttachmentData
import im.vector.app.features.call.conference.ConferenceEvent
import im.vector.app.features.call.conference.JitsiActiveConferenceHolder
import im.vector.app.features.call.conference.JitsiService
import im.vector.app.features.call.lookup.CallProtocolsChecker
import im.vector.app.features.call.webrtc.WebRtcCallManager
@ -57,12 +57,14 @@ import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever
import im.vector.app.features.home.room.typing.TypingHelper
import im.vector.app.features.powerlevel.PowerLevelsObservableFactory
import im.vector.app.features.session.coroutineScope
import im.vector.app.features.settings.VectorDataStore
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.voice.VoicePlayerHelper
import io.reactivex.Observable
import io.reactivex.rxkotlin.subscribeBy
import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.commonmark.parser.Parser
@ -80,6 +82,7 @@ import org.matrix.android.sdk.api.session.events.model.isTextMessage
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.file.FileService
import org.matrix.android.sdk.api.session.initsync.SyncStatusService
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams
import org.matrix.android.sdk.api.session.room.model.Membership
@ -102,6 +105,7 @@ import org.matrix.android.sdk.api.session.space.CreateSpaceParams
import org.matrix.android.sdk.api.session.widgets.model.WidgetType
import org.matrix.android.sdk.api.util.toOptional
import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode
import org.matrix.android.sdk.rx.asObservable
import org.matrix.android.sdk.rx.rx
import org.matrix.android.sdk.rx.unwrap
import timber.log.Timber
@ -111,6 +115,7 @@ import java.util.concurrent.atomic.AtomicBoolean
class RoomDetailViewModel @AssistedInject constructor(
@Assisted private val initialState: RoomDetailViewState,
private val vectorPreferences: VectorPreferences,
private val vectorDataStore: VectorDataStore,
private val stringProvider: StringProvider,
private val rainbowGenerator: RainbowGenerator,
private val session: Session,
@ -174,6 +179,7 @@ class RoomDetailViewModel @AssistedInject constructor(
observeSummaryState()
getUnreadState()
observeSyncState()
observeDataStore()
observeEventDisplayedActions()
loadDraftIfAny()
observeUnreadState()
@ -198,6 +204,18 @@ class RoomDetailViewModel @AssistedInject constructor(
}
}
private fun observeDataStore() {
viewModelScope.launch {
vectorDataStore.pushCounterFlow.collect { nbOfPush ->
setState {
copy(
pushCounter = nbOfPush
)
}
}
}
}
private fun prepareForEncryption() {
// check if there is not already a call made, or if there has been an error
if (prepareToEncrypt.shouldLoad) {
@ -1493,6 +1511,17 @@ class RoomDetailViewModel @AssistedInject constructor(
}
}
.disposeOnClear()
session.getSyncStatusLive()
.asObservable()
.subscribe { it ->
if (it is SyncStatusService.Status.IncrementalSyncStatus) {
setState {
copy(incrementalSyncStatus = it)
}
}
}
.disposeOnClear()
}
private fun observeRoomSummary() {

View File

@ -21,6 +21,7 @@ import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.initsync.SyncStatusService
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
import org.matrix.android.sdk.api.session.room.model.RoomSummary
@ -77,6 +78,8 @@ data class RoomDetailViewState(
val tombstoneEvent: Event? = null,
val joinUpgradedRoomAsync: Async<String> = Uninitialized,
val syncState: SyncState = SyncState.Idle,
val incrementalSyncStatus: SyncStatusService.Status.IncrementalSyncStatus = SyncStatusService.Status.IncrementalSyncIdle,
val pushCounter: Int = 0,
val highlightedEventId: String? = null,
val unreadState: UnreadState = UnreadState.Unknown,
val canShowJumpToReadMarker: Boolean = true,

View File

@ -0,0 +1,47 @@
/*
* Copyright (c) 2021 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.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "vector_settings")
class VectorDataStore @Inject constructor(
private val context: Context
) {
private val pushCounter = intPreferencesKey("push_counter")
val pushCounterFlow: Flow<Int> = context.dataStore.data.map { preferences ->
preferences[pushCounter] ?: 0
}
suspend fun incrementPushCounter() {
context.dataStore.edit { settings ->
val currentCounterValue = settings[pushCounter] ?: 0
settings[pushCounter] = currentCounterValue + 1
}
}
}

View File

@ -159,6 +159,7 @@ class VectorPreferences @Inject constructor(private val context: Context) {
private const val SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY = "SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY"
private const val SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY = "SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY"
private const val SETTINGS_DEVELOPER_MODE_FAIL_FAST_PREFERENCE_KEY = "SETTINGS_DEVELOPER_MODE_FAIL_FAST_PREFERENCE_KEY"
private const val SETTINGS_DEVELOPER_MODE_SHOW_INFO_ON_SCREEN_KEY = "SETTINGS_DEVELOPER_MODE_SHOW_INFO_ON_SCREEN_KEY"
// SETTINGS_LABS_HIDE_TECHNICAL_E2E_ERRORS
private const val SETTINGS_LABS_SHOW_COMPLETE_HISTORY_IN_ENCRYPTED_ROOM = "SETTINGS_LABS_SHOW_COMPLETE_HISTORY_IN_ENCRYPTED_ROOM"
@ -312,6 +313,10 @@ class VectorPreferences @Inject constructor(private val context: Context) {
return defaultPrefs.getBoolean(SETTINGS_DEVELOPER_MODE_PREFERENCE_KEY, false)
}
fun developerShowDebugInfo(): Boolean {
return developerMode() && defaultPrefs.getBoolean(SETTINGS_DEVELOPER_MODE_SHOW_INFO_ON_SCREEN_KEY, false)
}
fun shouldShowHiddenEvents(): Boolean {
return developerMode() && defaultPrefs.getBoolean(SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY, false)
}

View File

@ -16,27 +16,41 @@
package im.vector.app.features.sync.widget
import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import android.widget.FrameLayout
import android.widget.LinearLayout
import androidx.core.view.isVisible
import im.vector.app.R
import im.vector.app.core.utils.isAirplaneModeOn
import im.vector.app.databinding.ViewSyncStateBinding
import org.matrix.android.sdk.api.session.initsync.SyncStatusService
import org.matrix.android.sdk.api.session.sync.SyncState
class SyncStateView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0)
: FrameLayout(context, attrs, defStyle) {
: LinearLayout(context, attrs, defStyle) {
private val views: ViewSyncStateBinding
init {
inflate(context, R.layout.view_sync_state, this)
views = ViewSyncStateBinding.bind(this)
orientation = VERTICAL
}
fun render(newState: SyncState) {
@SuppressLint("SetTextI18n")
fun render(newState: SyncState,
incrementalSyncStatus: SyncStatusService.Status.IncrementalSyncStatus,
pushCounter: Int,
showDebugInfo: Boolean
) {
views.syncStateDebugInfo.isVisible = showDebugInfo
if (showDebugInfo) {
views.syncStateDebugInfoText.text =
"Sync thread : ${newState.toHumanReadable()}\nSync request: ${incrementalSyncStatus.toHumanReadable()}"
views.syncStateDebugInfoPushCounter.text =
"Push: $pushCounter"
}
views.syncStateProgressBar.isVisible = newState is SyncState.Running && newState.afterPause
if (newState == SyncState.NoNetwork) {
@ -48,4 +62,26 @@ class SyncStateView @JvmOverloads constructor(context: Context, attrs: Attribute
views.syncStateNoNetworkAirplane.isVisible = false
}
}
private fun SyncState.toHumanReadable(): String {
return when (this) {
SyncState.Idle -> "Idle"
SyncState.InvalidToken -> "InvalidToken"
SyncState.Killed -> "Killed"
SyncState.Killing -> "Killing"
SyncState.NoNetwork -> "NoNetwork"
SyncState.Paused -> "Paused"
is SyncState.Running -> "$this"
}
}
private fun SyncStatusService.Status.IncrementalSyncStatus.toHumanReadable(): String {
return when (this) {
SyncStatusService.Status.IncrementalSyncIdle -> "Idle"
is SyncStatusService.Status.IncrementalSyncParsing -> "Parsing ${this.rooms} room(s) ${this.toDevice} toDevice(s)"
SyncStatusService.Status.IncrementalSyncError -> "Error"
SyncStatusService.Status.IncrementalSyncDone -> "Done"
else -> "?"
}
}
}

View File

@ -7,6 +7,34 @@
tools:orientation="vertical"
tools:parentTag="android.widget.LinearLayout">
<FrameLayout
android:id="@+id/syncStateDebugInfo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="2dp"
android:visibility="gone"
tools:visibility="visible">
<TextView
android:id="@+id/syncStateDebugInfoText"
style="@style/Widget.Vector.TextView.Caption"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="monospace"
tools:text="debug info" />
<TextView
android:id="@+id/syncStateDebugInfoPushCounter"
style="@style/Widget.Vector.TextView.Caption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:fontFamily="monospace"
android:textStyle="bold"
tools:text="123" />
</FrameLayout>
<!-- Trick to remove surrounding padding (clip from wrapping frame) -->
<FrameLayout
android:id="@+id/syncStateProgressBar"

View File

@ -2659,6 +2659,9 @@
<!-- Note to translators: the translation MUST contain the string "${app_name}", which will be replaced by the application name -->
<string name="template_settings_developer_mode_fail_fast_summary">${app_name} may crash more often when an unexpected error occurs</string>
<string name="settings_developer_mode_show_info_on_screen_title">Show debug info on screen</string>
<string name="settings_developer_mode_show_info_on_screen_summary">Show some useful info to help debugging the application</string>
<string name="command_description_shrug">Prepends ¯\\_(ツ)_/¯ to a plain-text message</string>
<string name="create_room_encryption_title">"Enable encryption"</string>

View File

@ -6,8 +6,8 @@
<im.vector.app.core.preference.VectorSwitchPreference
android:defaultValue="false"
android:key="SETTINGS_DEVELOPER_MODE_PREFERENCE_KEY"
android:icon="@drawable/ic_verification_glasses"
android:key="SETTINGS_DEVELOPER_MODE_PREFERENCE_KEY"
android:summary="@string/settings_developer_mode_summary"
android:title="@string/settings_developer_mode" />
@ -17,6 +17,13 @@
android:key="SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY"
android:title="@string/settings_labs_show_hidden_events_in_timeline" />
<im.vector.app.core.preference.VectorSwitchPreference
android:defaultValue="false"
android:dependency="SETTINGS_DEVELOPER_MODE_PREFERENCE_KEY"
android:key="SETTINGS_DEVELOPER_MODE_SHOW_INFO_ON_SCREEN_KEY"
android:summary="@string/settings_developer_mode_show_info_on_screen_summary"
android:title="@string/settings_developer_mode_show_info_on_screen_title" />
<im.vector.app.core.preference.VectorSwitchPreference
android:defaultValue="false"
android:dependency="SETTINGS_DEVELOPER_MODE_PREFERENCE_KEY"