From 80b155e042356ae6675a6bf7aad5ca49dbd312c8 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 22 Apr 2021 19:28:17 +0200 Subject: [PATCH] Refact coroutine session scope --- .../matrix/android/sdk/api/session/Session.kt | 13 +------ .../session/SessionLifecycleObserver.kt | 11 +++--- .../sdk/internal/database/DatabaseCleaner.kt | 5 ++- .../database/RealmLiveEntityObserver.kt | 9 +++-- .../internal/database/RealmSessionProvider.kt | 7 ++-- .../sdk/internal/session/DefaultSession.kt | 38 ++++++++++--------- .../session/SessionCoroutineScopeHolder.kt | 21 ++++++---- .../sdk/internal/session/SessionListeners.kt | 5 +-- .../sdk/internal/session/SessionModule.kt | 5 +++ .../identity/DefaultIdentityService.kt | 7 ++-- .../integrationmanager/IntegrationManager.kt | 7 ++-- .../room/send/queue/EventSenderProcessor.kt | 2 +- .../queue/EventSenderProcessorCoroutine.kt | 3 +- .../send/queue/EventSenderProcessorThread.kt | 5 ++- .../widgets/DefaultWidgetURLFormatter.kt | 7 ++-- .../internal/session/widgets/WidgetManager.kt | 7 ++-- .../app/core/glide/VectorGlideModelLoader.kt | 3 +- .../home/room/detail/RoomDetailFragment.kt | 4 +- .../home/room/detail/RoomDetailViewModel.kt | 5 ++- .../NotificationBroadcastReceiver.kt | 8 ++-- .../session/SessionCoroutineScopes.kt | 33 ++++++++++++++++ .../app/features/session/SessionListener.kt | 12 +++++- .../troubleshoot/TestAccountSettings.kt | 4 +- 23 files changed, 142 insertions(+), 79 deletions(-) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/{internal => api}/session/SessionLifecycleObserver.kt (80%) create mode 100644 vector/src/main/java/im/vector/app/features/session/SessionCoroutineScopes.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt index 17f5e1fc1e..86252665a6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt @@ -18,7 +18,6 @@ package org.matrix.android.sdk.api.session import androidx.annotation.MainThread import androidx.lifecycle.LiveData -import kotlinx.coroutines.Job import okhttp3.OkHttpClient import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.failure.GlobalError @@ -57,8 +56,6 @@ import org.matrix.android.sdk.api.session.thirdparty.ThirdPartyService import org.matrix.android.sdk.api.session.typing.TypingUsersTracker import org.matrix.android.sdk.api.session.user.UserService import org.matrix.android.sdk.api.session.widgets.WidgetService -import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.EmptyCoroutineContext /** * This interface defines interactions with a session. @@ -258,13 +255,13 @@ interface Session : /** * A global session listener to get notified for some events. */ - interface Listener { + interface Listener : SessionLifecycleObserver { /** * Possible cases: * - The access token is not valid anymore, * - a M_CONSENT_NOT_GIVEN error has been received from the homeserver */ - fun onGlobalError(globalError: GlobalError) + fun onGlobalError(session: Session, globalError: GlobalError) } val sharedSecretStorageService: SharedSecretStorageService @@ -275,10 +272,4 @@ interface Session : * Maintenance API, allows to print outs info on DB size to logcat */ fun logDbUsageInfo() - - /** - * Launch a coroutine using the session scope - */ - fun launch(context: CoroutineContext = EmptyCoroutineContext, - block: suspend () -> Unit): Job } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionLifecycleObserver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/SessionLifecycleObserver.kt similarity index 80% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionLifecycleObserver.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/SessionLifecycleObserver.kt index cb37fbec75..b76e454e4b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionLifecycleObserver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/SessionLifecycleObserver.kt @@ -14,20 +14,19 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session +package org.matrix.android.sdk.api.session import androidx.annotation.MainThread /** * This defines methods associated with some lifecycle events of a session. - * A list of SessionLifecycle will be injected into [DefaultSession] */ -internal interface SessionLifecycleObserver { +interface SessionLifecycleObserver { /* Called when the session is opened */ @MainThread - fun onSessionStarted() { + fun onSessionStarted(session: Session) { // noop } @@ -35,7 +34,7 @@ internal interface SessionLifecycleObserver { Called when the session is cleared */ @MainThread - fun onClearCache() { + fun onClearCache(session: Session) { // noop } @@ -43,7 +42,7 @@ internal interface SessionLifecycleObserver { Called when the session is closed */ @MainThread - fun onSessionStopped() { + fun onSessionStopped(session: Session) { // noop } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/DatabaseCleaner.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/DatabaseCleaner.kt index f11ecc5d75..ee58880eb8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/DatabaseCleaner.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/DatabaseCleaner.kt @@ -20,6 +20,7 @@ import io.realm.Realm import io.realm.RealmConfiguration import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.internal.database.helper.nextDisplayIndex import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.ChunkEntityFields @@ -29,7 +30,7 @@ import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields import org.matrix.android.sdk.internal.database.model.deleteOnCascade import org.matrix.android.sdk.internal.di.SessionDatabase -import org.matrix.android.sdk.internal.session.SessionLifecycleObserver +import org.matrix.android.sdk.api.session.SessionLifecycleObserver import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection import org.matrix.android.sdk.internal.task.TaskExecutor import timber.log.Timber @@ -47,7 +48,7 @@ private const val MIN_NUMBER_OF_EVENTS_BY_CHUNK = 300 internal class DatabaseCleaner @Inject constructor(@SessionDatabase private val realmConfiguration: RealmConfiguration, private val taskExecutor: TaskExecutor) : SessionLifecycleObserver { - override fun onSessionStarted() { + override fun onSessionStarted(session: Session) { taskExecutor.executorScope.launch(Dispatchers.Default) { awaitTransaction(realmConfiguration) { realm -> val allRooms = realm.where(RoomEntity::class.java).findAll() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmLiveEntityObserver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmLiveEntityObserver.kt index 2a0cd963b2..c602ed7075 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmLiveEntityObserver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmLiveEntityObserver.kt @@ -17,7 +17,7 @@ package org.matrix.android.sdk.internal.database import com.zhuinden.monarchy.Monarchy -import org.matrix.android.sdk.internal.session.SessionLifecycleObserver +import org.matrix.android.sdk.api.session.SessionLifecycleObserver import org.matrix.android.sdk.internal.util.createBackgroundHandler import io.realm.Realm import io.realm.RealmChangeListener @@ -28,6 +28,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.android.asCoroutineDispatcher import kotlinx.coroutines.cancelChildren +import org.matrix.android.sdk.api.session.Session import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicReference @@ -46,7 +47,7 @@ internal abstract class RealmLiveEntityObserver(protected val r private val backgroundRealm = AtomicReference() private lateinit var results: AtomicReference> - override fun onSessionStarted() { + override fun onSessionStarted(session: Session) { if (isStarted.compareAndSet(false, true)) { BACKGROUND_HANDLER.post { val realm = Realm.getInstance(realmConfiguration) @@ -58,7 +59,7 @@ internal abstract class RealmLiveEntityObserver(protected val r } } - override fun onSessionStopped() { + override fun onSessionStopped(session: Session) { if (isStarted.compareAndSet(true, false)) { BACKGROUND_HANDLER.post { results.getAndSet(null).removeAllChangeListeners() @@ -70,7 +71,7 @@ internal abstract class RealmLiveEntityObserver(protected val r } } - override fun onClearCache() { + override fun onClearCache(session: Session) { observerScope.coroutineContext.cancelChildren() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionProvider.kt index f8d5d323a5..52fbabb49f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionProvider.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionProvider.kt @@ -20,8 +20,9 @@ import android.os.Looper import androidx.annotation.MainThread import com.zhuinden.monarchy.Monarchy import io.realm.Realm +import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.internal.di.SessionDatabase -import org.matrix.android.sdk.internal.session.SessionLifecycleObserver +import org.matrix.android.sdk.api.session.SessionLifecycleObserver import org.matrix.android.sdk.internal.session.SessionScope import javax.inject.Inject import kotlin.concurrent.getOrSet @@ -44,14 +45,14 @@ internal class RealmSessionProvider @Inject constructor(@SessionDatabase private } @MainThread - override fun onSessionStarted() { + override fun onSessionStarted(session: Session) { realmThreadLocal.getOrSet { Realm.getInstance(monarchy.realmConfiguration) } } @MainThread - override fun onSessionStopped() { + override fun onSessionStopped(session: Session) { realmThreadLocal.get()?.close() realmThreadLocal.remove() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt index 842ff55f06..7199fc0bfe 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt @@ -19,14 +19,15 @@ package org.matrix.android.sdk.internal.session import androidx.annotation.MainThread import dagger.Lazy import io.realm.RealmConfiguration -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch +import kotlinx.coroutines.NonCancellable +import kotlinx.coroutines.withContext import okhttp3.OkHttpClient import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.failure.GlobalError import org.matrix.android.sdk.api.federation.FederationService import org.matrix.android.sdk.api.pushrules.PushRuleService import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.SessionLifecycleObserver import org.matrix.android.sdk.api.session.account.AccountService import org.matrix.android.sdk.api.session.accountdata.AccountDataService import org.matrix.android.sdk.api.session.cache.CacheService @@ -76,7 +77,6 @@ import org.matrix.android.sdk.internal.util.createUIHandler import timber.log.Timber import javax.inject.Inject import javax.inject.Provider -import kotlin.coroutines.CoroutineContext @SessionScope internal class DefaultSession @Inject constructor( @@ -163,10 +163,12 @@ internal class DefaultSession @Inject constructor( override fun open() { assert(!isOpen) isOpen = true - sessionCoroutineScopeHolder.start() cryptoService.get().ensureDevice() uiHandler.post { - lifecycleObservers.forEach { it.onSessionStarted() } + lifecycleObservers.forEach { it.onSessionStarted(this) } + sessionListeners.dispatch { + it.onSessionStarted(this) + } } globalErrorHandler.listener = this } @@ -204,11 +206,13 @@ internal class DefaultSession @Inject constructor( override fun close() { assert(isOpen) - sessionCoroutineScopeHolder.stop() stopSync() // timelineEventDecryptor.destroy() uiHandler.post { - lifecycleObservers.forEach { it.onSessionStopped() } + lifecycleObservers.forEach { it.onSessionStopped(this) } + sessionListeners.dispatch { + it.onSessionStopped(this) + } } cryptoService.get().close() isOpen = false @@ -233,14 +237,21 @@ internal class DefaultSession @Inject constructor( stopSync() stopAnyBackgroundSync() uiHandler.post { - lifecycleObservers.forEach { it.onClearCache() } + lifecycleObservers.forEach { it.onClearCache(this) } + sessionListeners.dispatch { + it.onClearCache(this) + } + } + withContext(NonCancellable) { + cacheService.get().clearCache() } - cacheService.get().clearCache() workManagerProvider.cancelAllWorks() } override fun onGlobalError(globalError: GlobalError) { - sessionListeners.dispatchGlobalError(globalError) + sessionListeners.dispatch { + it.onGlobalError(this, globalError) + } } override fun contentUrlResolver() = contentUrlResolver @@ -307,11 +318,4 @@ internal class DefaultSession @Inject constructor( override fun logDbUsageInfo() { RealmDebugTools(realmConfiguration).logInfo("Session") } - - override fun launch(context: CoroutineContext, - block: suspend () -> Unit): Job { - return sessionCoroutineScopeHolder.scope?.launch(context) { - block() - } ?: throw IllegalStateException("Session is closed") - } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionCoroutineScopeHolder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionCoroutineScopeHolder.kt index 2a099ba2ff..29368debc3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionCoroutineScopeHolder.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionCoroutineScopeHolder.kt @@ -21,19 +21,24 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancelChildren import javax.inject.Inject +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.SessionLifecycleObserver @SessionScope -internal class SessionCoroutineScopeHolder @Inject constructor() { +internal class SessionCoroutineScopeHolder @Inject constructor(): SessionLifecycleObserver { - var scope: CoroutineScope? = null - private set + val scope: CoroutineScope = CoroutineScope(SupervisorJob()) - fun start() { - scope = CoroutineScope(SupervisorJob()) + override fun onSessionStopped(session: Session) { + scope.cancelChildren() } - fun stop() { - scope?.coroutineContext?.cancelChildren(CancellationException("Closing session")) - scope = null + override fun onClearCache(session: Session) { + scope.cancelChildren() } + + private fun CoroutineScope.cancelChildren(){ + coroutineContext.cancelChildren(CancellationException("Closing session")) + } + } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionListeners.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionListeners.kt index 64f2d249f3..563ff4ada3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionListeners.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionListeners.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.internal.session -import org.matrix.android.sdk.api.failure.GlobalError import org.matrix.android.sdk.api.session.Session import javax.inject.Inject @@ -36,10 +35,10 @@ internal class SessionListeners @Inject constructor() { } } - fun dispatchGlobalError(globalError: GlobalError) { + fun dispatch(block: (Session.Listener) -> Unit) { synchronized(listeners) { listeners.forEach { - it.onGlobalError(globalError) + block(it) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt index e61e4ecd89..63423b72c6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt @@ -33,6 +33,7 @@ import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.auth.data.sessionId import org.matrix.android.sdk.api.crypto.MXCryptoConfig import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.SessionLifecycleObserver import org.matrix.android.sdk.api.session.accountdata.AccountDataService import org.matrix.android.sdk.api.session.events.EventService import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService @@ -343,6 +344,10 @@ internal abstract class SessionModule { @IntoSet abstract fun bindRealmSessionProvider(provider: RealmSessionProvider): SessionLifecycleObserver + @Binds + @IntoSet + abstract fun bindSessionCoroutineScopeHolder(holder: SessionCoroutineScopeHolder): SessionLifecycleObserver + @Binds @IntoSet abstract fun bindEventSenderProcessorAsSessionLifecycleObserver(processor: EventSenderProcessorCoroutine): SessionLifecycleObserver diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt index f5391d6cdb..475781ef01 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt @@ -36,7 +36,7 @@ import org.matrix.android.sdk.internal.di.AuthenticatedIdentity import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate import org.matrix.android.sdk.internal.extensions.observeNotNull import org.matrix.android.sdk.internal.network.RetrofitFactory -import org.matrix.android.sdk.internal.session.SessionLifecycleObserver +import org.matrix.android.sdk.api.session.SessionLifecycleObserver import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.identity.data.IdentityStore import org.matrix.android.sdk.internal.session.openid.GetOpenIdTokenTask @@ -51,6 +51,7 @@ import org.matrix.android.sdk.internal.util.ensureProtocol import kotlinx.coroutines.withContext import okhttp3.OkHttpClient import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.session.Session import timber.log.Timber import javax.inject.Inject import javax.net.ssl.HttpsURLConnection @@ -86,7 +87,7 @@ internal class DefaultIdentityService @Inject constructor( private val listeners = mutableSetOf() - override fun onSessionStarted() { + override fun onSessionStarted(session: Session) { lifecycleRegistry.currentState = Lifecycle.State.STARTED // Observe the account data change accountDataDataSource @@ -111,7 +112,7 @@ internal class DefaultIdentityService @Inject constructor( } } - override fun onSessionStopped() { + override fun onSessionStopped(session: Session) { lifecycleRegistry.currentState = Lifecycle.State.DESTROYED } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt index e34615d269..3df9a00cc1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt @@ -21,6 +21,7 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry import com.zhuinden.monarchy.Monarchy import org.matrix.android.sdk.api.MatrixConfiguration +import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerConfig import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService @@ -29,7 +30,7 @@ import org.matrix.android.sdk.api.session.widgets.model.WidgetType import org.matrix.android.sdk.internal.database.model.WellknownIntegrationManagerConfigEntity import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.extensions.observeNotNull -import org.matrix.android.sdk.internal.session.SessionLifecycleObserver +import org.matrix.android.sdk.api.session.SessionLifecycleObserver import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent @@ -77,7 +78,7 @@ internal class IntegrationManager @Inject constructor(matrixConfiguration: Matri currentConfigs.add(defaultConfig) } - override fun onSessionStarted() { + override fun onSessionStarted(session: Session) { lifecycleRegistry.currentState = Lifecycle.State.STARTED observeWellknownConfig() accountDataDataSource @@ -105,7 +106,7 @@ internal class IntegrationManager @Inject constructor(matrixConfiguration: Matri } } - override fun onSessionStopped() { + override fun onSessionStopped(session: Session) { lifecycleRegistry.currentState = Lifecycle.State.DESTROYED } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessor.kt index 8bafa5f882..cd5bf575db 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessor.kt @@ -18,7 +18,7 @@ package org.matrix.android.sdk.internal.session.room.send.queue import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.util.Cancelable -import org.matrix.android.sdk.internal.session.SessionLifecycleObserver +import org.matrix.android.sdk.api.session.SessionLifecycleObserver internal interface EventSenderProcessor: SessionLifecycleObserver { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt index a5c09f5ff6..80bfd02b0e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt @@ -25,6 +25,7 @@ import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.failure.getRetryDelay +import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.util.Cancelable @@ -72,7 +73,7 @@ internal class EventSenderProcessorCoroutine @Inject constructor( */ private val cancelableBag = ConcurrentHashMap() - override fun onSessionStarted() { + override fun onSessionStarted(session: Session) { // We should check for sending events not handled because app was killed // But we should be careful of only took those that was submitted to us, because if it's // for example it's a media event it is handled by some worker and he will handle it diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt index b79a86dd7e..9db7cc9039 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt @@ -25,6 +25,7 @@ import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.failure.isTokenError +import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.sync.SyncState @@ -64,11 +65,11 @@ internal class EventSenderProcessorThread @Inject constructor( memento.unTrack(task) } - override fun onSessionStarted() { + override fun onSessionStarted(session: Session) { start() } - override fun onSessionStopped() { + override fun onSessionStopped(session: Session) { interrupt() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetURLFormatter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetURLFormatter.kt index 0937f6d18b..f7664bf3c2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetURLFormatter.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetURLFormatter.kt @@ -17,12 +17,13 @@ package org.matrix.android.sdk.internal.session.widgets import org.matrix.android.sdk.api.MatrixConfiguration +import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerConfig import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService import org.matrix.android.sdk.api.session.widgets.WidgetURLFormatter import org.matrix.android.sdk.api.util.appendParamToUrl import org.matrix.android.sdk.api.util.appendParamsToUrl -import org.matrix.android.sdk.internal.session.SessionLifecycleObserver +import org.matrix.android.sdk.api.session.SessionLifecycleObserver import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManager import org.matrix.android.sdk.internal.session.widgets.token.GetScalarTokenTask @@ -37,12 +38,12 @@ internal class DefaultWidgetURLFormatter @Inject constructor(private val integra private lateinit var currentConfig: IntegrationManagerConfig private var whiteListedUrls: List = emptyList() - override fun onSessionStarted() { + override fun onSessionStarted(session: Session) { setupWithConfiguration() integrationManager.addListener(this) } - override fun onSessionStopped() { + override fun onSessionStopped(session: Session) { integrationManager.removeListener(this) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt index 3244212487..d741dbc966 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt @@ -22,6 +22,7 @@ import androidx.lifecycle.LifecycleRegistry import androidx.lifecycle.LiveData import androidx.lifecycle.Transformations import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes import org.matrix.android.sdk.api.session.events.model.Content @@ -34,7 +35,7 @@ import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.widgets.WidgetManagementFailure import org.matrix.android.sdk.api.session.widgets.model.Widget import org.matrix.android.sdk.internal.di.UserId -import org.matrix.android.sdk.internal.session.SessionLifecycleObserver +import org.matrix.android.sdk.api.session.SessionLifecycleObserver import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManager import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource @@ -57,12 +58,12 @@ internal class WidgetManager @Inject constructor(private val integrationManager: private val lifecycleOwner: LifecycleOwner = LifecycleOwner { lifecycleRegistry } private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(lifecycleOwner) - override fun onSessionStarted() { + override fun onSessionStarted(session: Session) { lifecycleRegistry.currentState = Lifecycle.State.STARTED integrationManager.addListener(this) } - override fun onSessionStopped() { + override fun onSessionStopped(session: Session) { integrationManager.removeListener(this) lifecycleRegistry.currentState = Lifecycle.State.DESTROYED } diff --git a/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt b/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt index 5a053b446b..5fae815dfb 100644 --- a/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt +++ b/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt @@ -28,6 +28,7 @@ import com.bumptech.glide.signature.ObjectKey import im.vector.app.core.extensions.vectorComponent import im.vector.app.core.files.LocalFilesHelper import im.vector.app.features.media.ImageContentRenderer +import im.vector.app.features.session.coroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -114,7 +115,7 @@ class VectorGlideDataFetcher(context: Context, callback.onLoadFailed(IllegalArgumentException("No File service")) } // Use the file vector service, will avoid flickering and redownload after upload - activeSessionHolder.getSafeActiveSession()?.launch { + activeSessionHolder.getSafeActiveSession()?.coroutineScope?.launch { val result = runCatching { fileService.downloadFile( fileName = data.filename, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 5eaca48e68..82187ecdd3 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -161,6 +161,7 @@ import im.vector.app.features.permalink.NavigationInterceptor import im.vector.app.features.permalink.PermalinkHandler import im.vector.app.features.reactions.EmojiReactionPickerActivity import im.vector.app.features.roomprofile.RoomProfileActivity +import im.vector.app.features.session.coroutineScope import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorSettingsActivity import im.vector.app.features.share.SharedData @@ -1744,12 +1745,13 @@ class RoomDetailFragment @Inject constructor( } private fun onSaveActionClicked(action: EventSharedAction.Save) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q && !checkPermissions(PERMISSIONS_FOR_WRITING_FILES, requireActivity(), saveActionActivityResultLauncher)) { sharedActionViewModel.pendingAction = action return } - session.launch { + session.coroutineScope.launch { val result = runCatching { session.fileService().downloadFile(messageContent = action.messageContent) } if (!isAdded) return@launch result.fold( diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index a5a140cadc..d64acb9afb 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -53,6 +53,7 @@ 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.raw.wellknown.getElementWellknown +import im.vector.app.features.session.coroutineScope import im.vector.app.features.settings.VectorLocale import im.vector.app.features.settings.VectorPreferences import io.reactivex.Observable @@ -570,7 +571,7 @@ class RoomDetailViewModel @AssistedInject constructor( * Convert a send mode to a draft and save the draft */ private fun handleSaveDraft(action: RoomDetailAction.SaveDraft) = withState { - session.launch { + session.coroutineScope.launch { when { it.sendMode is SendMode.REGULAR && !it.sendMode.fromSharing -> { setState { copy(sendMode = it.sendMode.copy(action.draft)) } @@ -1317,7 +1318,7 @@ class RoomDetailViewModel @AssistedInject constructor( } } bufferedMostRecentDisplayedEvent.root.eventId?.let { eventId -> - session.launch { + session.coroutineScope.launch { tryOrNull { room.setReadReceipt(eventId) } } } diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt index 5368491d96..2c4cdab25e 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt @@ -23,6 +23,8 @@ import androidx.core.app.RemoteInput import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.extensions.vectorComponent +import im.vector.app.features.session.coroutineScope +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.Room @@ -76,7 +78,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { activeSessionHolder.getSafeActiveSession()?.let { session -> val room = session.getRoom(roomId) if (room != null) { - session.launch { + session.coroutineScope.launch { tryOrNull { room.join() } } } @@ -87,7 +89,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { activeSessionHolder.getSafeActiveSession()?.let { session -> val room = session.getRoom(roomId) if (room != null) { - session.launch { + session.coroutineScope.launch { tryOrNull { room.leave() } } } @@ -98,7 +100,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { activeSessionHolder.getActiveSession().let { session -> val room = session.getRoom(roomId) if (room != null) { - session.launch { + session.coroutineScope.launch { tryOrNull { room.markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT) } } } diff --git a/vector/src/main/java/im/vector/app/features/session/SessionCoroutineScopes.kt b/vector/src/main/java/im/vector/app/features/session/SessionCoroutineScopes.kt new file mode 100644 index 0000000000..28fcab6fb9 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/session/SessionCoroutineScopes.kt @@ -0,0 +1,33 @@ +/* + * 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.session + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import org.matrix.android.sdk.api.session.Session + +private val sessionCoroutineScopes = HashMap(1) + +val Session.coroutineScope: CoroutineScope + get() { + return synchronized(sessionCoroutineScopes) { + sessionCoroutineScopes.getOrPut(sessionId) { + CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) + } + } + } diff --git a/vector/src/main/java/im/vector/app/features/session/SessionListener.kt b/vector/src/main/java/im/vector/app/features/session/SessionListener.kt index 66b1bae70d..aaed15f12f 100644 --- a/vector/src/main/java/im/vector/app/features/session/SessionListener.kt +++ b/vector/src/main/java/im/vector/app/features/session/SessionListener.kt @@ -20,6 +20,7 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import im.vector.app.core.extensions.postLiveEvent import im.vector.app.core.utils.LiveEvent +import kotlinx.coroutines.cancelChildren import org.matrix.android.sdk.api.failure.GlobalError import org.matrix.android.sdk.api.session.Session import javax.inject.Inject @@ -32,7 +33,16 @@ class SessionListener @Inject constructor() : Session.Listener { val globalErrorLiveData: LiveData> get() = _globalErrorLiveData - override fun onGlobalError(globalError: GlobalError) { + override fun onGlobalError(session: Session, globalError: GlobalError) { _globalErrorLiveData.postLiveEvent(globalError) } + + override fun onSessionStopped(session: Session) { + session.coroutineScope.coroutineContext.cancelChildren() + } + + override fun onClearCache(session: Session) { + session.coroutineScope.coroutineContext.cancelChildren() + } + } diff --git a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestAccountSettings.kt b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestAccountSettings.kt index 723d2d797d..dad35fd13f 100644 --- a/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestAccountSettings.kt +++ b/vector/src/main/java/im/vector/app/features/settings/troubleshoot/TestAccountSettings.kt @@ -20,7 +20,9 @@ import androidx.activity.result.ActivityResultLauncher import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.resources.StringProvider +import im.vector.app.features.session.coroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.pushrules.RuleIds @@ -50,7 +52,7 @@ class TestAccountSettings @Inject constructor(private val stringProvider: String override fun doFix() { if (manager?.diagStatus == TestStatus.RUNNING) return // wait before all is finished - session.launch { + session.coroutineScope.launch { tryOrNull { session.updatePushRuleEnableStatus(RuleKind.OVERRIDE, defaultRule, !defaultRule.enabled) }