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, 'targetCompat' : JavaVersion.VERSION_11,
] ]
// Ref: https://kotlinlang.org/releases.html
def gradle = "7.0.2" def gradle = "7.0.2"
// Ref: https://kotlinlang.org/releases.html
def kotlin = "1.5.30" def kotlin = "1.5.30"
def kotlinCoroutines = "1.5.1" def kotlinCoroutines = "1.5.1"
def dagger = "2.38.1" def dagger = "2.38.1"
@ -55,6 +55,8 @@ ext.libs = [
'lifecycleExtensions' : "androidx.lifecycle:lifecycle-extensions:$lifecycle", 'lifecycleExtensions' : "androidx.lifecycle:lifecycle-extensions:$lifecycle",
'lifecycleJava8' : "androidx.lifecycle:lifecycle-common-java8:$lifecycle", 'lifecycleJava8' : "androidx.lifecycle:lifecycle-common-java8:$lifecycle",
'lifecycleLivedata' : "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1", '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", 'pagingRuntimeKtx' : "androidx.paging:paging-runtime-ktx:2.1.2",
'coreTesting' : "androidx.arch.core:core-testing:2.1.0", 'coreTesting' : "androidx.arch.core:core-testing:2.1.0",
'testCore' : "androidx.test:core:$androidxTest", '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) { open class LoggerTag(_value: String, parentTag: LoggerTag? = null) {
object SYNC : LoggerTag("SYNC")
object VOIP : LoggerTag("VOIP") object VOIP : LoggerTag("VOIP")
val value: String = if (parentTag == null) { 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.group.GroupService
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService 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.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.integrationmanager.IntegrationManagerService
import org.matrix.android.sdk.api.session.media.MediaService import org.matrix.android.sdk.api.session.media.MediaService
import org.matrix.android.sdk.api.session.openid.OpenIdService import org.matrix.android.sdk.api.session.openid.OpenIdService
@ -75,7 +75,7 @@ interface Session :
ProfileService, ProfileService,
PushRuleService, PushRuleService,
PushersService, PushersService,
InitialSyncProgressService, SyncStatusService,
HomeServerCapabilitiesService, HomeServerCapabilitiesService,
SecureStorageService, SecureStorageService,
AccountService { AccountService {

View File

@ -17,15 +17,33 @@ package org.matrix.android.sdk.api.session.initsync
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
interface InitialSyncProgressService { interface SyncStatusService {
fun getInitialSyncProgressStatus(): LiveData<Status> fun getSyncStatusLive(): LiveData<Status>
sealed class Status { sealed class Status {
object Idle : Status() /**
* For initial sync
*/
abstract class InitialSyncStatus: Status()
object Idle : InitialSyncStatus()
data class Progressing( data class Progressing(
val initSyncStep: InitSyncStep, val initSyncStep: InitSyncStep,
val percentProgress: Int = 0 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.file.FileService
import org.matrix.android.sdk.api.session.group.GroupService 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.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.integrationmanager.IntegrationManagerService
import org.matrix.android.sdk.api.session.media.MediaService import org.matrix.android.sdk.api.session.media.MediaService
import org.matrix.android.sdk.api.session.openid.OpenIdService import org.matrix.android.sdk.api.session.openid.OpenIdService
@ -115,7 +115,7 @@ internal class DefaultSession @Inject constructor(
private val contentUploadProgressTracker: ContentUploadStateTracker, private val contentUploadProgressTracker: ContentUploadStateTracker,
private val typingUsersTracker: TypingUsersTracker, private val typingUsersTracker: TypingUsersTracker,
private val contentDownloadStateTracker: ContentDownloadStateTracker, private val contentDownloadStateTracker: ContentDownloadStateTracker,
private val initialSyncProgressService: Lazy<InitialSyncProgressService>, private val syncStatusService: Lazy<SyncStatusService>,
private val homeServerCapabilitiesService: Lazy<HomeServerCapabilitiesService>, private val homeServerCapabilitiesService: Lazy<HomeServerCapabilitiesService>,
private val accountDataService: Lazy<SessionAccountDataService>, private val accountDataService: Lazy<SessionAccountDataService>,
private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>, private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>,
@ -141,7 +141,7 @@ internal class DefaultSession @Inject constructor(
PushersService by pushersService.get(), PushersService by pushersService.get(),
EventService by eventService.get(), EventService by eventService.get(),
TermsService by termsService.get(), TermsService by termsService.get(),
InitialSyncProgressService by initialSyncProgressService.get(), SyncStatusService by syncStatusService.get(),
SecureStorageService by secureStorageService.get(), SecureStorageService by secureStorageService.get(),
HomeServerCapabilitiesService by homeServerCapabilitiesService.get(), HomeServerCapabilitiesService by homeServerCapabilitiesService.get(),
ProfileService by profileService.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.accountdata.SessionAccountDataService
import org.matrix.android.sdk.api.session.events.EventService 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.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.openid.OpenIdService
import org.matrix.android.sdk.api.session.permalinks.PermalinkService import org.matrix.android.sdk.api.session.permalinks.PermalinkService
import org.matrix.android.sdk.api.session.securestorage.SecureStorageService 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.events.DefaultEventService
import org.matrix.android.sdk.internal.session.homeserver.DefaultHomeServerCapabilitiesService 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.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.integrationmanager.IntegrationManager
import org.matrix.android.sdk.internal.session.openid.DefaultOpenIdService import org.matrix.android.sdk.internal.session.openid.DefaultOpenIdService
import org.matrix.android.sdk.internal.session.permalinks.DefaultPermalinkService import org.matrix.android.sdk.internal.session.permalinks.DefaultPermalinkService
@ -355,7 +355,7 @@ internal abstract class SessionModule {
abstract fun bindEventSenderProcessorAsSessionLifecycleObserver(processor: EventSenderProcessorCoroutine): SessionLifecycleObserver abstract fun bindEventSenderProcessorAsSessionLifecycleObserver(processor: EventSenderProcessorCoroutine): SessionLifecycleObserver
@Binds @Binds
abstract fun bindInitialSyncProgressService(service: DefaultInitialSyncProgressService): InitialSyncProgressService abstract fun bindSyncStatusService(service: DefaultSyncStatusService): SyncStatusService
@Binds @Binds
abstract fun bindSecureStorageService(service: DefaultSecureStorageService): SecureStorageService 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.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import org.matrix.android.sdk.api.session.initsync.InitSyncStep 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 org.matrix.android.sdk.internal.session.SessionScope
import javax.inject.Inject import javax.inject.Inject
@SessionScope @SessionScope
internal class DefaultInitialSyncProgressService @Inject constructor() internal class DefaultSyncStatusService @Inject constructor()
: InitialSyncProgressService, : SyncStatusService,
ProgressReporter { ProgressReporter {
private val status = MutableLiveData<InitialSyncProgressService.Status>() private val status = MutableLiveData<SyncStatusService.Status>()
private var rootTask: TaskInfo? = null private var rootTask: TaskInfo? = null
override fun getInitialSyncProgressStatus(): LiveData<InitialSyncProgressService.Status> { override fun getSyncStatusLive(): LiveData<SyncStatusService.Status> {
return status return status
} }
// Only to be used for incremental sync
fun setStatus(newStatus: SyncStatusService.Status.IncrementalSyncStatus) {
status.postValue(newStatus)
}
/** /**
* Create a rootTask * Create a rootTask
*/ */
@ -67,7 +72,7 @@ internal class DefaultInitialSyncProgressService @Inject constructor()
// Update the progress of the leaf and all its parents // Update the progress of the leaf and all its parents
leaf.setProgress(progress) leaf.setProgress(progress)
// Then update the live data using leaf wording and root 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 // And close it
endedTask.parent.child = null endedTask.parent.child = null
} else { } else {
status.postValue(InitialSyncProgressService.Status.Idle) status.postValue(SyncStatusService.Status.Idle)
} }
} }
} }
fun endAll() { fun endAll() {
rootTask = null 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 package org.matrix.android.sdk.internal.session.sync
import okhttp3.ResponseBody 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.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.SessionFilesDirectory
import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver 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.network.toFailure
import org.matrix.android.sdk.internal.session.filter.FilterRepository 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.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.initsync.reportSubtask
import org.matrix.android.sdk.internal.session.sync.model.LazyRoomSyncEphemeral import org.matrix.android.sdk.internal.session.sync.model.LazyRoomSyncEphemeral
import org.matrix.android.sdk.internal.session.sync.parsing.InitialSyncResponseParser import org.matrix.android.sdk.internal.session.sync.parsing.InitialSyncResponseParser
@ -40,6 +42,8 @@ import java.io.File
import java.net.SocketTimeoutException import java.net.SocketTimeoutException
import javax.inject.Inject import javax.inject.Inject
private val loggerTag = LoggerTag("SyncTask", LoggerTag.SYNC)
internal interface SyncTask : Task<SyncTask.Params, Unit> { internal interface SyncTask : Task<SyncTask.Params, Unit> {
data class Params( data class Params(
@ -53,7 +57,7 @@ internal class DefaultSyncTask @Inject constructor(
@UserId private val userId: String, @UserId private val userId: String,
private val filterRepository: FilterRepository, private val filterRepository: FilterRepository,
private val syncResponseHandler: SyncResponseHandler, private val syncResponseHandler: SyncResponseHandler,
private val initialSyncProgressService: DefaultInitialSyncProgressService, private val defaultSyncStatusService: DefaultSyncStatusService,
private val syncTokenStore: SyncTokenStore, private val syncTokenStore: SyncTokenStore,
private val getHomeServerCapabilitiesTask: GetHomeServerCapabilitiesTask, private val getHomeServerCapabilitiesTask: GetHomeServerCapabilitiesTask,
private val userStore: UserStore, private val userStore: UserStore,
@ -75,7 +79,7 @@ internal class DefaultSyncTask @Inject constructor(
} }
private suspend fun doSync(params: SyncTask.Params) { 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>() val requestParams = HashMap<String, String>()
var timeout = 0L var timeout = 0L
@ -92,7 +96,7 @@ internal class DefaultSyncTask @Inject constructor(
if (isInitialSync) { if (isInitialSync) {
// We might want to get the user information in parallel too // We might want to get the user information in parallel too
userStore.createOrUpdate(userId) userStore.createOrUpdate(userId)
initialSyncProgressService.startRoot(InitSyncStep.ImportingAccount, 100) defaultSyncStatusService.startRoot(InitSyncStep.ImportingAccount, 100)
} }
// Maybe refresh the homeserver capabilities data we know // Maybe refresh the homeserver capabilities data we know
getHomeServerCapabilitiesTask.execute(GetHomeServerCapabilitiesTask.Params(forceRefresh = false)) 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) val readTimeOut = (params.timeout + TIMEOUT_MARGIN).coerceAtLeast(TimeOutInterceptor.DEFAULT_LONG_TIMEOUT)
if (isInitialSync) { if (isInitialSync) {
Timber.d("INIT_SYNC with filter: ${requestParams["filter"]}") Timber.tag(loggerTag.value).d("INIT_SYNC with filter: ${requestParams["filter"]}")
val initSyncStrategy = initialSyncStrategy val initSyncStrategy = initialSyncStrategy
logDuration("INIT_SYNC strategy: $initSyncStrategy") { logDuration("INIT_SYNC strategy: $initSyncStrategy", loggerTag) {
if (initSyncStrategy is InitialSyncStrategy.Optimized) { if (initSyncStrategy is InitialSyncStrategy.Optimized) {
roomSyncEphemeralTemporaryStore.reset() roomSyncEphemeralTemporaryStore.reset()
workingDir.mkdirs() workingDir.mkdirs()
val file = downloadInitSyncResponse(requestParams) val file = downloadInitSyncResponse(requestParams)
reportSubtask(initialSyncProgressService, InitSyncStep.ImportingAccount, 1, 0.7F) { reportSubtask(defaultSyncStatusService, InitSyncStep.ImportingAccount, 1, 0.7F) {
handleSyncFile(file, initSyncStrategy) handleSyncFile(file, initSyncStrategy)
} }
// Delete all files // Delete all files
workingDir.deleteRecursively() workingDir.deleteRecursively()
} else { } else {
val syncResponse = logDuration("INIT_SYNC Request") { val syncResponse = logDuration("INIT_SYNC Request", loggerTag) {
executeRequest(globalErrorReceiver) { executeRequest(globalErrorReceiver) {
syncAPI.sync( syncAPI.sync(
params = requestParams, params = requestParams,
@ -122,43 +126,60 @@ internal class DefaultSyncTask @Inject constructor(
} }
} }
logDuration("INIT_SYNC Database insertion") { logDuration("INIT_SYNC Database insertion", loggerTag) {
syncResponseHandler.handleResponse(syncResponse, token, initialSyncProgressService) syncResponseHandler.handleResponse(syncResponse, token, defaultSyncStatusService)
} }
} }
} }
initialSyncProgressService.endAll() defaultSyncStatusService.endAll()
} else { } else {
val syncResponse = executeRequest(globalErrorReceiver) { Timber.tag(loggerTag.value).d("Start incremental sync request")
syncAPI.sync( defaultSyncStatusService.setStatus(SyncStatusService.Status.IncrementalSyncIdle)
params = requestParams, val syncResponse = try {
readTimeOut = readTimeOut 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) 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 { private suspend fun downloadInitSyncResponse(requestParams: Map<String, String>): File {
val workingFile = File(workingDir, "initSync.json") val workingFile = File(workingDir, "initSync.json")
val status = initialSyncStatusRepository.getStep() val status = initialSyncStatusRepository.getStep()
if (workingFile.exists() && status >= InitialSyncStatus.STEP_DOWNLOADED) { if (workingFile.exists() && status >= InitialSyncStatus.STEP_DOWNLOADED) {
Timber.d("INIT_SYNC file is already here") Timber.tag(loggerTag.value).d("INIT_SYNC file is already here")
reportSubtask(initialSyncProgressService, InitSyncStep.Downloading, 1, 0.3f) { reportSubtask(defaultSyncStatusService, InitSyncStep.Downloading, 1, 0.3f) {
// Empty task // Empty task
} }
} else { } else {
initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_DOWNLOADING) initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_DOWNLOADING)
val syncResponse = logDuration("INIT_SYNC Perform server request") { val syncResponse = logDuration("INIT_SYNC Perform server request", loggerTag) {
reportSubtask(initialSyncProgressService, InitSyncStep.ServerComputing, 1, 0.2f) { reportSubtask(defaultSyncStatusService, InitSyncStep.ServerComputing, 1, 0.2f) {
getSyncResponse(requestParams, MAX_NUMBER_OF_RETRY_AFTER_TIMEOUT) getSyncResponse(requestParams, MAX_NUMBER_OF_RETRY_AFTER_TIMEOUT)
} }
} }
if (syncResponse.isSuccessful) { if (syncResponse.isSuccessful) {
logDuration("INIT_SYNC Download and save to file") { logDuration("INIT_SYNC Download and save to file", loggerTag) {
reportSubtask(initialSyncProgressService, InitSyncStep.Downloading, 1, 0.1f) { reportSubtask(defaultSyncStatusService, InitSyncStep.Downloading, 1, 0.1f) {
syncResponse.body()?.byteStream()?.use { inputStream -> syncResponse.body()?.byteStream()?.use { inputStream ->
workingFile.outputStream().use { outputStream -> workingFile.outputStream().use { outputStream ->
inputStream.copyTo(outputStream) inputStream.copyTo(outputStream)
@ -168,7 +189,7 @@ internal class DefaultSyncTask @Inject constructor(
} }
} else { } else {
throw syncResponse.toFailure(globalErrorReceiver) 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) initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_DOWNLOADED)
} }
@ -185,9 +206,9 @@ internal class DefaultSyncTask @Inject constructor(
).awaitResponse() ).awaitResponse()
} catch (throwable: Throwable) { } catch (throwable: Throwable) {
if (throwable is SocketTimeoutException && retry > 0) { 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 { } 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 throw throwable
} }
} }
@ -195,18 +216,18 @@ internal class DefaultSyncTask @Inject constructor(
} }
private suspend fun handleSyncFile(workingFile: File, initSyncStrategy: InitialSyncStrategy.Optimized) { private suspend fun handleSyncFile(workingFile: File, initSyncStrategy: InitialSyncStrategy.Optimized) {
logDuration("INIT_SYNC handleSyncFile()") { logDuration("INIT_SYNC handleSyncFile()", loggerTag) {
val syncResponse = logDuration("INIT_SYNC Read file and parse") { val syncResponse = logDuration("INIT_SYNC Read file and parse", loggerTag) {
syncResponseParser.parse(initSyncStrategy, workingFile) syncResponseParser.parse(initSyncStrategy, workingFile)
} }
initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_PARSED) initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_PARSED)
// Log some stats // Log some stats
val nbOfJoinedRooms = syncResponse.rooms?.join?.size ?: 0 val nbOfJoinedRooms = syncResponse.rooms?.join?.size ?: 0
val nbOfJoinedRoomsInFile = syncResponse.rooms?.join?.values?.count { it.ephemeral is LazyRoomSyncEphemeral.Stored } 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") { logDuration("INIT_SYNC Database insertion", loggerTag) {
syncResponseHandler.handleResponse(syncResponse, null, initialSyncProgressService) syncResponseHandler.handleResponse(syncResponse, null, defaultSyncStatusService)
} }
initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_SUCCESS) initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_SUCCESS)
} }

View File

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

View File

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

View File

@ -345,6 +345,9 @@ dependencies {
implementation libs.androidx.lifecycleExtensions implementation libs.androidx.lifecycleExtensions
implementation libs.androidx.lifecycleLivedata implementation libs.androidx.lifecycleLivedata
implementation libs.androidx.datastore
implementation libs.androidx.datastorepreferences
// Log // Log
implementation libs.jakewharton.timber implementation libs.jakewharton.timber
@ -406,7 +409,7 @@ dependencies {
// To convert voice message on old platforms // To convert voice message on old platforms
implementation 'com.arthenica:ffmpeg-kit-audio:4.4.LTS' implementation 'com.arthenica:ffmpeg-kit-audio:4.4.LTS'
//Alerter // Alerter
implementation 'com.tapadoo.android:alerter:7.0.1' implementation 'com.tapadoo.android:alerter:7.0.1'
implementation 'com.otaliastudios:autocomplete:1.1.0' 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.NotificationDrawerManager
import im.vector.app.features.notifications.NotificationUtils import im.vector.app.features.notifications.NotificationUtils
import im.vector.app.features.notifications.SimpleNotifiableEvent 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.features.settings.VectorPreferences
import im.vector.app.push.fcm.FcmHelper import im.vector.app.push.fcm.FcmHelper
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.matrix.android.sdk.api.extensions.tryOrNull 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.pushrules.Action
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import timber.log.Timber import timber.log.Timber
private val loggerTag = LoggerTag("Push", LoggerTag.SYNC)
/** /**
* Class extending FirebaseMessagingService. * Class extending FirebaseMessagingService.
*/ */
@ -60,6 +65,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
private lateinit var pusherManager: PushersManager private lateinit var pusherManager: PushersManager
private lateinit var activeSessionHolder: ActiveSessionHolder private lateinit var activeSessionHolder: ActiveSessionHolder
private lateinit var vectorPreferences: VectorPreferences private lateinit var vectorPreferences: VectorPreferences
private lateinit var vectorDataStore: VectorDataStore
private lateinit var wifiDetector: WifiDetector private lateinit var wifiDetector: WifiDetector
private val coroutineScope = CoroutineScope(SupervisorJob()) private val coroutineScope = CoroutineScope(SupervisorJob())
@ -77,6 +83,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
pusherManager = pusherManager() pusherManager = pusherManager()
activeSessionHolder = activeSessionHolder() activeSessionHolder = activeSessionHolder()
vectorPreferences = vectorPreferences() vectorPreferences = vectorPreferences()
vectorDataStore = vectorDataStore()
wifiDetector = wifiDetector() wifiDetector = wifiDetector()
} }
} }
@ -88,9 +95,13 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
*/ */
override fun onMessageReceived(message: RemoteMessage) { override fun onMessageReceived(message: RemoteMessage) {
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) { 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 // Diagnostic Push
if (message.data["event_id"] == PushersManager.TEST_EVENT_ID) { if (message.data["event_id"] == PushersManager.TEST_EVENT_ID) {
@ -100,14 +111,14 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
} }
if (!vectorPreferences.areNotificationEnabledForDevice()) { if (!vectorPreferences.areNotificationEnabledForDevice()) {
Timber.i("Notification are disabled for this device") Timber.tag(loggerTag.value).i("Notification are disabled for this device")
return return
} }
mUIHandler.post { mUIHandler.post {
if (ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) { if (ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
// we are in foreground, let the sync do the things? // 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 { } else {
onMessageReceivedInternal(message.data) onMessageReceivedInternal(message.data)
} }
@ -121,7 +132,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
* you retrieve the token. * you retrieve the token.
*/ */
override fun onNewToken(refreshedToken: String) { 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) FcmHelper.storeFcmToken(this, refreshedToken)
if (vectorPreferences.areNotificationEnabledForDevice() && activeSessionHolder.hasActiveSession()) { if (vectorPreferences.areNotificationEnabledForDevice() && activeSessionHolder.hasActiveSession()) {
pusherManager.registerPusherWithFcmKey(refreshedToken) 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. * It is recommended that the app do a full sync with the app server after receiving this call.
*/ */
override fun onDeletedMessages() { 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>) { private fun onMessageReceivedInternal(data: Map<String, String>) {
try { try {
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) { if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
Timber.d("## onMessageReceivedInternal() : $data") Timber.tag(loggerTag.value).d("## onMessageReceivedInternal() : $data")
} else { } else {
Timber.d("## onMessageReceivedInternal() : $data") Timber.tag(loggerTag.value).d("## onMessageReceivedInternal()")
} }
// update the badge counter // update the badge counter
@ -162,24 +173,24 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
val session = activeSessionHolder.getSafeActiveSession() val session = activeSessionHolder.getSafeActiveSession()
if (session == null) { 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 { } else {
val eventId = data["event_id"] val eventId = data["event_id"]
val roomId = data["room_id"] val roomId = data["room_id"]
if (isEventAlreadyKnown(eventId, roomId)) { if (isEventAlreadyKnown(eventId, roomId)) {
Timber.d("Ignoring push, event already known") Timber.tag(loggerTag.value).d("Ignoring push, event already known")
} else { } else {
// Try to get the Event content faster // 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) getEventFastLane(session, roomId, eventId)
Timber.d("Requesting background sync") Timber.tag(loggerTag.value).d("Requesting background sync")
session.requireBackgroundSync() session.requireBackgroundSync()
} }
} }
} catch (e: Exception) { } 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()) { 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 return
} }
coroutineScope.launch { 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 event = tryOrNull { session.getEvent(roomId, eventId) } ?: return@launch
val resolvedEvent = notifiableEventResolver.resolveInMemoryEvent(session, event) val resolvedEvent = notifiableEventResolver.resolveInMemoryEvent(session, event)
resolvedEvent resolvedEvent
?.also { Timber.d("Fast lane: notify drawer") } ?.also { Timber.tag(loggerTag.value).d("Fast lane: notify drawer") }
?.let { ?.let {
it.isPushGatewayEvent = true it.isPushGatewayEvent = true
notificationDrawerManager.onNotifiableEventReceived(it) notificationDrawerManager.onNotifiableEventReceived(it)
@ -222,7 +233,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
val room = session.getRoom(roomId) ?: return false val room = session.getRoom(roomId) ?: return false
return room.getTimeLineEvent(eventId) != null return room.getTimeLineEvent(eventId) != null
} catch (e: Exception) { } 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 return false
@ -230,7 +241,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
private fun handleNotificationWithoutSyncingMode(data: Map<String, String>, session: Session?) { private fun handleNotificationWithoutSyncingMode(data: Map<String, String>, session: Session?) {
if (session == null) { if (session == null) {
Timber.e("## handleNotificationWithoutSyncingMode cannot find session") Timber.tag(loggerTag.value).e("## handleNotificationWithoutSyncingMode cannot find session")
return return
} }
@ -263,9 +274,9 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
val notifiableEvent = notifiableEventResolver.resolveEvent(event, session) val notifiableEvent = notifiableEventResolver.resolveEvent(event, session)
if (notifiableEvent == null) { if (notifiableEvent == null) {
Timber.e("Unsupported notifiable event $eventId") Timber.tag(loggerTag.value).e("Unsupported notifiable event $eventId")
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) { if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
Timber.e("--> $event") Timber.tag(loggerTag.value).e("--> $event")
} }
} else { } else {
if (notifiableEvent is NotifiableMessageEvent) { 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.rageshake.VectorUncaughtExceptionHandler
import im.vector.app.features.reactions.data.EmojiDataSource import im.vector.app.features.reactions.data.EmojiDataSource
import im.vector.app.features.session.SessionListener 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.settings.VectorPreferences
import im.vector.app.features.ui.UiStateRepository import im.vector.app.features.ui.UiStateRepository
import org.matrix.android.sdk.api.Matrix import org.matrix.android.sdk.api.Matrix
@ -145,6 +146,8 @@ interface VectorComponent {
fun vectorPreferences(): VectorPreferences fun vectorPreferences(): VectorPreferences
fun vectorDataStore(): VectorDataStore
fun wifiDetector(): WifiDetector fun wifiDetector(): WifiDetector
fun vectorFileLogger(): VectorFileLogger 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 im.vector.app.push.fcm.FcmHelper
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.parcelize.Parcelize 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.session.permalinks.PermalinkService
import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.internal.session.sync.InitialSyncStrategy import org.matrix.android.sdk.internal.session.sync.InitialSyncStrategy
@ -308,11 +308,11 @@ class HomeActivity :
} }
private fun renderState(state: HomeActivityViewState) { private fun renderState(state: HomeActivityViewState) {
when (val status = state.initialSyncProgressServiceStatus) { when (val status = state.syncStatusServiceStatus) {
is InitialSyncProgressService.Status.Idle -> { is SyncStatusService.Status.Idle -> {
views.waitingView.root.isVisible = false views.waitingView.root.isVisible = false
} }
is InitialSyncProgressService.Status.Progressing -> { is SyncStatusService.Status.Progressing -> {
val initSyncStepStr = initSyncStepFormatter.format(status.initSyncStep) val initSyncStepStr = initSyncStepFormatter.format(status.initSyncStep)
Timber.v("$initSyncStepStr ${status.percentProgress}") Timber.v("$initSyncStepStr ${status.percentProgress}")
views.waitingView.root.setOnClickListener { views.waitingView.root.setOnClickListener {
@ -330,6 +330,7 @@ class HomeActivity :
} }
views.waitingView.root.isVisible = true views.waitingView.root.isVisible = true
} }
else -> Unit
}.exhaustive }.exhaustive
} }
@ -474,8 +475,8 @@ class HomeActivity :
override fun getMenuRes() = R.menu.home override fun getMenuRes() = R.menu.home
override fun onPrepareOptionsMenu(menu: Menu): Boolean { 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_legacy).isVisible = vectorPreferences.developerMode()
menu.findItem(R.id.menu_home_init_sync_optimized)?.isVisible = vectorPreferences.developerMode() menu.findItem(R.id.menu_home_init_sync_optimized).isVisible = vectorPreferences.developerMode()
return super.onPrepareOptionsMenu(menu) 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.auth.registration.nextUncompletedStage
import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.pushrules.RuleIds 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.model.Membership
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toMatrixItem
@ -122,25 +122,26 @@ class HomeActivityViewModel @AssistedInject constructor(
private fun observeInitialSync() { private fun observeInitialSync() {
val session = activeSessionHolder.getSafeActiveSession() ?: return val session = activeSessionHolder.getSafeActiveSession() ?: return
session.getInitialSyncProgressStatus() session.getSyncStatusLive()
.asObservable() .asObservable()
.subscribe { status -> .subscribe { status ->
when (status) { when (status) {
is InitialSyncProgressService.Status.Progressing -> { is SyncStatusService.Status.Progressing -> {
// Schedule a check of the bootstrap when the init sync will be finished // Schedule a check of the bootstrap when the init sync will be finished
checkBootstrap = true checkBootstrap = true
} }
is InitialSyncProgressService.Status.Idle -> { is SyncStatusService.Status.Idle -> {
if (checkBootstrap) { if (checkBootstrap) {
checkBootstrap = false checkBootstrap = false
maybeBootstrapCrossSigningAfterInitialSync() maybeBootstrapCrossSigningAfterInitialSync()
} }
} }
else -> Unit
} }
setState { setState {
copy( copy(
initialSyncProgressServiceStatus = status syncStatusServiceStatus = status
) )
} }
} }

View File

@ -17,8 +17,8 @@
package im.vector.app.features.home package im.vector.app.features.home
import com.airbnb.mvrx.MvRxState 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( data class HomeActivityViewState(
val initialSyncProgressServiceStatus: InitialSyncProgressService.Status = InitialSyncProgressService.Status.Idle val syncStatusServiceStatus: SyncStatusService.Status = SyncStatusService.Status.Idle
) : MvRxState ) : 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_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_rooms).render(it.notificationCountRooms, it.notificationHighlightRooms)
views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_notification).render(it.notificationCountCatchup, it.notificationHighlightCatchup) 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 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.createdirect.DirectRoomHelper
import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.invite.AutoAcceptInvites
import im.vector.app.features.invite.showInvites import im.vector.app.features.invite.showInvites
import im.vector.app.features.settings.VectorDataStore
import im.vector.app.features.ui.UiStateRepository import im.vector.app.features.ui.UiStateRepository
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.query.ActiveSpaceFilter import org.matrix.android.sdk.api.query.ActiveSpaceFilter
import org.matrix.android.sdk.api.query.RoomCategoryFilter import org.matrix.android.sdk.api.query.RoomCategoryFilter
import org.matrix.android.sdk.api.session.Session 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.RoomSortOrder
import org.matrix.android.sdk.api.session.room.model.Membership 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.session.room.roomSummaryQueryParams
@ -56,10 +59,11 @@ import java.util.concurrent.TimeUnit
class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: HomeDetailViewState, class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: HomeDetailViewState,
private val session: Session, private val session: Session,
private val uiStateRepository: UiStateRepository, private val uiStateRepository: UiStateRepository,
private val vectorDataStore: VectorDataStore,
private val callManager: WebRtcCallManager, private val callManager: WebRtcCallManager,
private val directRoomHelper: DirectRoomHelper, private val directRoomHelper: DirectRoomHelper,
private val appStateHandler: AppStateHandler, private val appStateHandler: AppStateHandler,
private val autoAcceptInvites: AutoAcceptInvites) private val autoAcceptInvites: AutoAcceptInvites)
: VectorViewModel<HomeDetailViewState, HomeDetailAction, HomeDetailViewEvents>(initialState), : VectorViewModel<HomeDetailViewState, HomeDetailAction, HomeDetailViewEvents>(initialState),
CallProtocolsChecker.Listener { CallProtocolsChecker.Listener {
@ -89,6 +93,7 @@ private val autoAcceptInvites: AutoAcceptInvites)
observeRoomGroupingMethod() observeRoomGroupingMethod()
observeRoomSummaries() observeRoomSummaries()
updateShowDialPadTab() updateShowDialPadTab()
observeDataStore()
callManager.addProtocolsCheckerListener(this) callManager.addProtocolsCheckerListener(this)
session.rx().liveUser(session.myUserId).execute { session.rx().liveUser(session.myUserId).execute {
copy( 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) { override fun handle(action: HomeDetailAction) {
when (action) { when (action) {
is HomeDetailAction.SwitchTab -> handleSwitchTab(action) is HomeDetailAction.SwitchTab -> handleSwitchTab(action)
@ -173,6 +190,17 @@ private val autoAcceptInvites: AutoAcceptInvites)
} }
} }
.disposeOnClear() .disposeOnClear()
session.getSyncStatusLive()
.asObservable()
.subscribe {
if (it is SyncStatusService.Status.IncrementalSyncStatus) {
setState {
copy(incrementalSyncStatus = it)
}
}
}
.disposeOnClear()
} }
private fun observeRoomGroupingMethod() { private fun observeRoomGroupingMethod() {

View File

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

View File

@ -387,8 +387,17 @@ class RoomDetailFragment @Inject constructor(
} }
} }
roomDetailViewModel.selectSubscribe(RoomDetailViewState::syncState) { syncState -> roomDetailViewModel.selectSubscribe(
views.syncStateView.render(syncState) RoomDetailViewState::syncState,
RoomDetailViewState::incrementalSyncStatus,
RoomDetailViewState::pushCounter
) { syncState, incrementalSyncStatus, pushCounter ->
views.syncStateView.render(
syncState,
incrementalSyncStatus,
pushCounter,
vectorPreferences.developerShowDebugInfo()
)
} }
roomDetailViewModel.observeViewEvents { 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.mvrx.runCatchingToAsync
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider 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.attachments.toContentAttachmentData
import im.vector.app.features.call.conference.ConferenceEvent 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.conference.JitsiService
import im.vector.app.features.call.lookup.CallProtocolsChecker import im.vector.app.features.call.lookup.CallProtocolsChecker
import im.vector.app.features.call.webrtc.WebRtcCallManager 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.home.room.typing.TypingHelper
import im.vector.app.features.powerlevel.PowerLevelsObservableFactory import im.vector.app.features.powerlevel.PowerLevelsObservableFactory
import im.vector.app.features.session.coroutineScope 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.settings.VectorPreferences
import im.vector.app.features.voice.VoicePlayerHelper import im.vector.app.features.voice.VoicePlayerHelper
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.rxkotlin.subscribeBy import io.reactivex.rxkotlin.subscribeBy
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.commonmark.parser.Parser 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.toContent
import org.matrix.android.sdk.api.session.events.model.toModel 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.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.ChangeMembershipState
import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams
import org.matrix.android.sdk.api.session.room.model.Membership 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.session.widgets.model.WidgetType
import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.api.util.toOptional
import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode 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.rx
import org.matrix.android.sdk.rx.unwrap import org.matrix.android.sdk.rx.unwrap
import timber.log.Timber import timber.log.Timber
@ -111,6 +115,7 @@ import java.util.concurrent.atomic.AtomicBoolean
class RoomDetailViewModel @AssistedInject constructor( class RoomDetailViewModel @AssistedInject constructor(
@Assisted private val initialState: RoomDetailViewState, @Assisted private val initialState: RoomDetailViewState,
private val vectorPreferences: VectorPreferences, private val vectorPreferences: VectorPreferences,
private val vectorDataStore: VectorDataStore,
private val stringProvider: StringProvider, private val stringProvider: StringProvider,
private val rainbowGenerator: RainbowGenerator, private val rainbowGenerator: RainbowGenerator,
private val session: Session, private val session: Session,
@ -174,6 +179,7 @@ class RoomDetailViewModel @AssistedInject constructor(
observeSummaryState() observeSummaryState()
getUnreadState() getUnreadState()
observeSyncState() observeSyncState()
observeDataStore()
observeEventDisplayedActions() observeEventDisplayedActions()
loadDraftIfAny() loadDraftIfAny()
observeUnreadState() 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() { private fun prepareForEncryption() {
// check if there is not already a call made, or if there has been an error // check if there is not already a call made, or if there has been an error
if (prepareToEncrypt.shouldLoad) { if (prepareToEncrypt.shouldLoad) {
@ -1493,6 +1511,17 @@ class RoomDetailViewModel @AssistedInject constructor(
} }
} }
.disposeOnClear() .disposeOnClear()
session.getSyncStatusLive()
.asObservable()
.subscribe { it ->
if (it is SyncStatusService.Status.IncrementalSyncStatus) {
setState {
copy(incrementalSyncStatus = it)
}
}
}
.disposeOnClear()
} }
private fun observeRoomSummary() { private fun observeRoomSummary() {

View File

@ -21,6 +21,7 @@ import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.Uninitialized
import org.matrix.android.sdk.api.extensions.orFalse 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.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.members.ChangeMembershipState
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary
@ -77,6 +78,8 @@ data class RoomDetailViewState(
val tombstoneEvent: Event? = null, val tombstoneEvent: Event? = null,
val joinUpgradedRoomAsync: Async<String> = Uninitialized, val joinUpgradedRoomAsync: Async<String> = Uninitialized,
val syncState: SyncState = SyncState.Idle, val syncState: SyncState = SyncState.Idle,
val incrementalSyncStatus: SyncStatusService.Status.IncrementalSyncStatus = SyncStatusService.Status.IncrementalSyncIdle,
val pushCounter: Int = 0,
val highlightedEventId: String? = null, val highlightedEventId: String? = null,
val unreadState: UnreadState = UnreadState.Unknown, val unreadState: UnreadState = UnreadState.Unknown,
val canShowJumpToReadMarker: Boolean = true, 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_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_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_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 // 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" 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) 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 { fun shouldShowHiddenEvents(): Boolean {
return developerMode() && defaultPrefs.getBoolean(SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY, false) 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 package im.vector.app.features.sync.widget
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.widget.FrameLayout import android.widget.LinearLayout
import androidx.core.view.isVisible import androidx.core.view.isVisible
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.utils.isAirplaneModeOn import im.vector.app.core.utils.isAirplaneModeOn
import im.vector.app.databinding.ViewSyncStateBinding import im.vector.app.databinding.ViewSyncStateBinding
import org.matrix.android.sdk.api.session.initsync.SyncStatusService
import org.matrix.android.sdk.api.session.sync.SyncState import org.matrix.android.sdk.api.session.sync.SyncState
class SyncStateView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) class SyncStateView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0)
: FrameLayout(context, attrs, defStyle) { : LinearLayout(context, attrs, defStyle) {
private val views: ViewSyncStateBinding private val views: ViewSyncStateBinding
init { init {
inflate(context, R.layout.view_sync_state, this) inflate(context, R.layout.view_sync_state, this)
views = ViewSyncStateBinding.bind(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 views.syncStateProgressBar.isVisible = newState is SyncState.Running && newState.afterPause
if (newState == SyncState.NoNetwork) { if (newState == SyncState.NoNetwork) {
@ -48,4 +62,26 @@ class SyncStateView @JvmOverloads constructor(context: Context, attrs: Attribute
views.syncStateNoNetworkAirplane.isVisible = false 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:orientation="vertical"
tools:parentTag="android.widget.LinearLayout"> 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) --> <!-- Trick to remove surrounding padding (clip from wrapping frame) -->
<FrameLayout <FrameLayout
android:id="@+id/syncStateProgressBar" 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 --> <!-- 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="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="command_description_shrug">Prepends ¯\\_(ツ)_/¯ to a plain-text message</string>
<string name="create_room_encryption_title">"Enable encryption"</string> <string name="create_room_encryption_title">"Enable encryption"</string>

View File

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