From 676fd1ed5c53d0976fa7ffde3341a1571eda6240 Mon Sep 17 00:00:00 2001
From: ganfra <francoisg@element.io>
Date: Tue, 10 May 2022 18:46:46 +0200
Subject: [PATCH] Import from Element 1.4.14

---
 .idea/compiler.xml                            |   2 +-
 .idea/misc.xml                                |   2 +-
 matrix-sdk-android/build.gradle               |  23 ++-
 matrix-sdk-android/docs/modules.md            |  18 ++
 matrix-sdk-android/docs/packages.md           |   3 +
 .../android/sdk/account/ChangePasswordTest.kt |   2 +-
 .../sdk/account/DeactivateAccountTest.kt      |   2 +-
 .../android/sdk/common/CommonTestHelper.kt    |  18 +-
 .../android/sdk/common/CryptoTestHelper.kt    |  35 ++--
 .../crypto/AttachmentEncryptionTest.kt        |  31 +++-
 .../sdk/internal/crypto/CryptoStoreHelper.kt  |   4 +-
 .../sdk/internal/crypto/CryptoStoreTest.kt    |   6 +-
 .../sdk/internal/crypto/E2eeSanityTests.kt    |  15 +-
 .../sdk/internal/crypto/PreShareKeysTest.kt   |   2 +
 .../sdk/internal/crypto/UnwedgingTest.kt      |   9 +-
 .../crypto/crosssigning/XSigningTest.kt       |  36 ++--
 .../crypto/encryption/EncryptionTest.kt       |  11 +-
 .../crypto/gossiping/KeyShareTests.kt         |  14 +-
 .../crypto/gossiping/WithHeldTests.kt         |   2 +
 .../crypto/keysbackup/KeysBackupTest.kt       |   1 +
 .../sdk/internal/crypto/ssss/QuadSTests.kt    |  24 +--
 .../verification/qrcode/VerificationTest.kt   |   4 +-
 .../room/threads/ThreadMessagingTest.kt       |   9 +-
 .../session/room/timeline/ChunkEntityTest.kt  |   8 +-
 .../timeline/FakeGetContextOfEventTask.kt     |   4 +-
 .../room/timeline/FakePaginationTask.kt       |   2 +-
 .../room/timeline/PollAggregationTest.kt      |  17 +-
 .../timeline/TimelineForwardPaginationTest.kt |   3 +-
 .../TimelinePreviousLastForwardTest.kt        |   3 +-
 .../TimelineSimpleBackPaginationTest.kt       |   3 +-
 .../timeline/TimelineWithManyMembersTest.kt   |   3 +-
 .../sdk/session/search/SearchMessagesTest.kt  |   4 +-
 .../sdk/session/space/SpaceCreationTest.kt    |   5 +-
 .../sdk/session/space/SpaceHierarchyTest.kt   |  44 ++---
 .../interceptors/CurlLoggingInterceptor.kt    |   2 +-
 .../java/org/matrix/android/sdk/api/Matrix.kt |  14 +-
 .../matrix/android/sdk/api/auth/UrlAndName.kt |  22 +++
 .../matrix/android/sdk/api/auth/converter.kt  | 127 ++++++++++++++
 .../auth/registration/RegistrationWizard.kt   |   9 +
 .../android/sdk/api/failure/GlobalError.kt    |   5 +
 .../api/failure/InitialSyncRequestReason.kt   |  27 +++
 .../matrix/android/sdk/api/session/Session.kt | 127 +++++++++++---
 .../sdk/api/session/SessionExtensions.kt      |  36 ++++
 .../model/IncomingRequestCancellation.kt      |   5 +-
 .../crypto/model/IncomingRoomKeyRequest.kt    |   5 +-
 .../model/IncomingSecretShareRequest.kt       |   5 +-
 .../verification/VerificationService.kt       |   7 +-
 .../sdk/api/session/file/FileService.kt       |   6 +-
 .../api/session/initsync/SyncStatusService.kt |   1 +
 .../api/session/permalinks/MatrixLinkify.kt   |   2 +-
 .../api/session/permalinks/PermalinkData.kt   |  24 +--
 .../sdk/api/{ => session}/pushrules/Action.kt |   4 +-
 .../api/{ => session}/pushrules/Condition.kt  |   2 +-
 .../pushrules/ConditionResolver.kt            |   2 +-
 .../pushrules/ContainsDisplayNameCondition.kt |   2 +-
 .../pushrules/EventMatchCondition.kt          |   2 +-
 .../sdk/api/{ => session}/pushrules/Kind.kt   |   2 +-
 .../api/{ => session}/pushrules/PushEvents.kt |   4 +-
 .../pushrules/PushRuleService.kt              |   6 +-
 .../pushrules/RoomMemberCountCondition.kt     |   4 +-
 .../api/{ => session}/pushrules/RuleIds.kt    |   2 +-
 .../api/{ => session}/pushrules/RuleScope.kt  |   2 +-
 .../api/{ => session}/pushrules/RuleSetKey.kt |   2 +-
 .../SenderNotificationPermissionCondition.kt  |   2 +-
 .../pushrules/rest/PushCondition.kt           |  16 +-
 .../{ => session}/pushrules/rest/PushRule.kt  |   8 +-
 .../{ => session}/pushrules/rest/RuleSet.kt   |   6 +-
 .../android/sdk/api/session/room/Room.kt      | 135 ++++++++++-----
 .../sdk/api/session/room/RoomExtensions.kt    |  33 ++++
 .../room/model/EventAnnotationsSummary.kt     |   6 +-
 .../room/model/ReferencesAggregatedSummary.kt |   1 -
 .../room/model/RoomJoinRulesContent.kt        |   8 +-
 .../room/model/call/CallAnswerContent.kt      |   2 +-
 .../room/model/call/CallInviteContent.kt      |   2 +-
 .../room/model/call/CallNegotiateContent.kt   |   2 +-
 .../room/model/call/CallReplacesContent.kt    |   2 +-
 .../LiveLocationShareAggregatedSummary.kt     |  28 +++
 .../session/room/model/message/FileInfo.kt    |   2 +-
 .../session/room/model/message/ImageInfo.kt   |   2 +-
 .../MessageBeaconInfoContent.kt}              |  49 +++---
 ...kt => MessageBeaconLocationDataContent.kt} |  18 +-
 .../model/message/MessageLocationContent.kt   |   6 +-
 .../session/room/model/message/MessageType.kt |   4 +-
 .../MessageVerificationRequestContent.kt      |   2 +-
 .../room/model/message/MessageVideoContent.kt |   2 +-
 .../session/room/model/message/VideoInfo.kt   |   2 +-
 .../api/session/room/state/StateService.kt    |   3 +-
 .../session/room/timeline/TimelineEvent.kt    |   4 +-
 .../room/timeline/TimelineEventFilters.kt     |   1 +
 .../session/room/timeline/TimelineSettings.kt |   3 +-
 .../securestorage/SharedSecretStorageError.kt |   2 +-
 .../sdk/api/session/space/SpaceService.kt     |   2 +-
 .../android/sdk/api/session/uia/UiaResult.kt  |  23 +++
 .../uia/exceptions/UiaCancelledException.kt   |  19 +++
 .../android/sdk/internal/auth/AuthAPI.kt      |   9 +
 .../registration/DefaultRegistrationWizard.kt |  44 ++++-
 .../auth/registration/RegisterCustomTask.kt   |  47 +++++
 .../registration/RegistrationCustomParams.kt} |  22 ++-
 .../sdk/internal/auth/registration/UIAExt.kt  |  20 ++-
 .../crypto/CancelGossipRequestWorker.kt       |   6 +-
 .../crypto/CryptoSessionInfoProvider.kt       |   2 +-
 .../internal/crypto/DefaultCryptoService.kt   |  14 +-
 .../sdk/internal/crypto/DeviceListManager.kt  |  38 +++--
 .../sdk/internal/crypto/EventDecryptor.kt     |   4 +-
 .../internal/crypto/GossipingWorkManager.kt   |   5 +-
 .../crypto/InboundGroupSessionStore.kt        |  18 +-
 .../crypto/IncomingGossipingRequestManager.kt |  21 ++-
 .../crypto/MXMegolmExportEncryption.kt        |  57 ++++---
 .../sdk/internal/crypto/MXOlmDevice.kt        |  11 +-
 .../sdk/internal/crypto/OlmSessionStore.kt    |   3 +-
 .../internal/crypto/OneTimeKeysUploader.kt    |  10 +-
 .../crypto/SendGossipRequestWorker.kt         |   8 +-
 .../sdk/internal/crypto/SendGossipWorker.kt   |   4 +-
 .../actions/MegolmSessionDataImporter.kt      |  16 +-
 .../algorithms/megolm/MXMegolmEncryption.kt   |  33 ++--
 .../megolm/MXMegolmEncryptionFactory.kt       |   8 +-
 .../megolm/MXOutboundSessionInfo.kt           |   7 +-
 .../crypto/algorithms/olm/MXOlmEncryption.kt  |   2 +-
 .../attachments/MXEncryptedAttachments.kt     |  21 ++-
 .../crypto/keysbackup/KeysBackupPassword.kt   |  62 +++----
 .../crypto/model/OlmSessionWrapper.kt         |   4 +-
 .../DefaultSharedSecretStorageService.kt      |  12 +-
 .../crypto/store/db/RealmCryptoStore.kt       |  49 ++++--
 .../store/db/RealmCryptoStoreMigration.kt     |   7 +-
 .../store/db/migration/MigrateCryptoTo008.kt  |   8 +-
 .../crypto/store/db/model/CryptoRoomEntity.kt |   4 +-
 .../db/model/OlmInboundGroupSessionEntity.kt  |   2 +-
 .../crypto/store/db/model/OlmSessionEntity.kt |   2 +-
 .../internal/crypto/tasks/DeleteDeviceTask.kt |   5 +-
 .../tasks/InitializeCrossSigningTask.kt       |   5 +-
 ...tgoingSASDefaultVerificationTransaction.kt |   2 +-
 .../DefaultVerificationService.kt             | 108 +++++++-----
 .../SendVerificationMessageWorker.kt          |   2 +-
 .../VerificationMessageProcessor.kt           |   5 +-
 .../VerificationTransportRoomMessage.kt       |  62 ++++---
 ...VerificationTransportRoomMessageFactory.kt |  11 +-
 .../VerificationTransportToDevice.kt          |  16 +-
 .../VerificationTransportToDeviceFactory.kt   |   7 +-
 .../sdk/internal/database/AsyncTransaction.kt |  15 +-
 .../database/EventInsertLiveObserver.kt       |  58 ++++---
 .../database/RealmLiveEntityObserver.kt       |   2 +-
 .../internal/database/RealmSessionProvider.kt |   2 +-
 .../database/RealmSessionStoreMigration.kt    |   6 +-
 .../database/helper/ThreadSummaryHelper.kt    |  37 ++--
 .../helper/TimelineEventEntityHelper.kt       |   3 +-
 .../mapper/EventAnnotationsSummaryMapper.kt   |   6 +-
 .../internal/database/mapper/EventMapper.kt   |   8 +-
 ...iveLocationShareAggregatedSummaryMapper.kt |  33 ++++
 .../database/mapper/PushConditionMapper.kt    |   2 +-
 .../database/mapper/PushRulesMapper.kt        |   6 +-
 .../database/migration/MigrateSessionTo019.kt |   2 +-
 .../database/migration/MigrateSessionTo027.kt |  46 +++++
 .../database/migration/MigrateSessionTo028.kt |  34 ++++
 .../internal/database/model/ChunkEntity.kt    |  21 +--
 .../model/EventAnnotationsSummaryEntity.kt    |   4 +-
 .../internal/database/model/GroupEntity.kt    |   2 +-
 .../database/model/PushRulesEntity.kt         |   2 +-
 .../sdk/internal/database/model/RoomEntity.kt |   2 +
 .../database/model/SessionRealmModule.kt      |   2 +
 .../database/model/TimelineEventEntity.kt     |   4 +-
 ...iveLocationShareAggregatedSummaryEntity.kt |  45 +++++
 .../database/query/ChunkEntityQueries.kt      |   3 +
 ...cationShareAggregatedSummaryEntityQuery.kt |  57 +++++++
 .../internal/database/query/PushersQueries.kt |   2 +-
 .../query/ThreadSummaryEntityQueries.kt       |   2 +
 .../query/TimelineEventEntityQueries.kt       |  38 ++++-
 .../network/NetworkConnectivityChecker.kt     |   2 +-
 .../sdk/internal/network/RequestExecutor.kt   |   1 +
 .../internal/session/DefaultFileService.kt    |   9 +-
 .../sdk/internal/session/DefaultSession.kt    |  71 +++-----
 .../session/account/DeactivateAccountTask.kt  |  32 ++--
 .../session/call/CallEventProcessor.kt        |   2 +-
 .../session/call/CallSignalingHandler.kt      |  12 +-
 .../internal/session/call/MxCallFactory.kt    |  10 +-
 .../internal/session/call/model/MxCallImpl.kt |   6 +-
 .../internal/session/content/FileUploader.kt  |   4 +-
 .../session/content/ImageCompressor.kt        |  12 +-
 .../session/content/UploadContentWorker.kt    |   6 +-
 .../DefaultContentScannerService.kt           |  18 +-
 .../db/ContentScannerEntityQueries.kt         |   7 +-
 .../db/RealmContentScannerStore.kt            |  10 +-
 .../initsync/DefaultSyncStatusService.kt      |   2 +-
 .../integrationmanager/IntegrationManager.kt  |   2 +-
 .../session/permalinks/ViaParameterFinder.kt  |   2 +
 .../profile/FinalizeAddingThreePidTask.kt     |   5 +-
 .../session/pushers/AddPushRuleTask.kt        |   4 +-
 .../session/pushers/AddPusherWorker.kt        |   2 +-
 .../pushers/DefaultConditionResolver.kt       |  13 +-
 .../session/pushers}/GetPushRulesResponse.kt  |   3 +-
 .../internal/session/pushers/PushRulesApi.kt  |   3 +-
 .../internal/session/pushers/PushersModule.kt |  10 +-
 .../session/pushers/RemovePushRuleTask.kt     |   2 +-
 .../session/pushers/SavePushRulesTask.kt      |   5 +-
 .../pushers/UpdatePushRuleActionsTask.kt      |  28 +--
 .../pushers/UpdatePushRuleEnableStatusTask.kt |   4 +-
 .../DefaultPushRuleService.kt                 |  24 +--
 .../ProcessEventForPushTask.kt                |   6 +-
 .../PushRuleFinder.kt                         |   6 +-
 .../sdk/internal/session/room/DefaultRoom.kt  | 160 +++++-------------
 .../EventRelationsAggregationProcessor.kt     |  61 ++++---
 .../sdk/internal/session/room/RoomAPI.kt      |   6 +-
 .../sdk/internal/session/room/RoomFactory.kt  |  57 +++----
 ...DefaultLiveLocationAggregationProcessor.kt |  89 +++++-----
 .../LiveLocationAggregationProcessor.kt       |  23 ++-
 .../room/alias/GetRoomIdByAliasTask.kt        |   2 +-
 .../session/room/create/CreateRoomTask.kt     |   6 +-
 .../room/crypto/DefaultRoomCryptoService.kt   |  81 +++++++++
 .../room/membership/LoadRoomMembersTask.kt    |   6 +-
 .../room/membership/joining/JoinRoomTask.kt   |   6 +-
 .../DefaultRoomPushRuleService.kt             |   4 +-
 .../session/room/notification/RoomPushRule.kt |   4 +-
 .../room/notification/RoomPushRuleMapper.kt   |  14 +-
 .../SetRoomNotificationStateTask.kt           |   4 +-
 .../session/room/read/SetReadMarkersTask.kt   |   6 +-
 .../session/room/relation/EventEditor.kt      |  12 +-
 .../threads/FetchThreadSummariesTask.kt       |   9 +-
 .../threads/FetchThreadTimelineTask.kt        |  16 +-
 .../room/send/LocalEchoEventFactory.kt        |  61 ++++---
 .../session/room/send/LocalEchoRepository.kt  |  20 ++-
 .../MultipleEventSendingDispatcherWorker.kt   |  10 +-
 .../session/room/send/RedactEventWorker.kt    |   2 +-
 .../session/room/send/SendEventWorker.kt      |   8 +-
 .../queue/EventSenderProcessorCoroutine.kt    |   2 +-
 .../send/queue/EventSenderProcessorThread.kt  |   8 +-
 .../session/room/send/queue/QueueMemento.kt   |   4 +-
 .../session/room/state/DefaultStateService.kt |  22 +--
 .../room/state/SafePowerLevelContent.kt       |   2 +-
 .../session/room/state/SendStateTask.kt       |  10 +-
 .../session/room/summary/GraphUtils.kt        |   4 +-
 .../room/summary/RoomSummaryDataSource.kt     |   4 -
 .../session/room/timeline/DefaultTimeline.kt  |  46 +++--
 .../room/timeline/DefaultTimelineService.kt   |   9 +-
 .../session/room/timeline/GetEventTask.kt     |   6 +-
 .../room/timeline/LiveTimelineEvent.kt        |   2 +-
 .../room/timeline/LoadTimelineStrategy.kt     |  13 +-
 .../session/room/timeline/TimelineChunk.kt    |  41 +++--
 .../room/timeline/TimelineEventDataSource.kt  |   5 +-
 .../room/timeline/TimelineEventDecryptor.kt   |   9 +-
 .../room/timeline/TokenChunkEventPersistor.kt |   7 +-
 .../session/room/timeline/UIEchoManager.kt    |  14 +-
 .../sdk/internal/session/search/SearchTask.kt |   2 +-
 .../internal/session/space/DefaultSpace.kt    |  14 +-
 .../session/space/DefaultSpaceService.kt      |   5 +-
 .../sync/InitialSyncStatusRepository.kt       |  10 +-
 .../sdk/internal/session/sync/SyncPresence.kt |   5 +-
 .../session/sync/SyncResponseHandler.kt       |   6 +-
 .../sdk/internal/session/sync/SyncTask.kt     |  39 +++--
 .../handler/UserAccountDataSyncHandler.kt     |  51 +++++-
 .../sync/handler/room/ReadReceiptHandler.kt   |   6 +-
 .../sync/handler/room/RoomSyncHandler.kt      | 111 ++++++------
 .../internal/session/sync/job/SyncWorker.kt   |   2 +-
 .../thirdparty/DefaultThirdPartyService.kt    |   2 +-
 .../sdk/internal/session/user/UserModule.kt   |   5 -
 .../user/accountdata/SaveIgnoredUsersTask.kt  |  47 -----
 .../accountdata/UpdateIgnoredUserIdsTask.kt   |   4 -
 .../session/widgets/CreateWidgetTask.kt       |  10 +-
 .../widgets/DefaultWidgetPostAPIMediator.kt   |   2 +-
 .../session/widgets/DefaultWidgetService.kt   |   4 +-
 .../internal/session/widgets/WidgetManager.kt |   2 +-
 .../android/sdk/internal/util/LogUtil.kt      |   6 +-
 .../sdk/internal/util/TemporaryFileCreator.kt |   1 +
 .../internal/util/database/RealmMigrator.kt   |   2 +-
 .../DefaultBuildVersionSdkIntProvider.kt      |   2 +-
 .../sdk/internal/util/system/SystemModule.kt  |   5 +
 .../android/sdk/internal/util/time/Clock.kt   |  36 ++++
 .../internal/worker/AlwaysSuccessfulWorker.kt |   2 +-
 .../internal/worker/MatrixWorkerFactory.kt    |   2 +-
 .../interceptors/CurlLoggingInterceptor.kt    |   2 +-
 .../pushrules/PushRuleActionsTest.kt          |   4 +-
 .../pushrules/PushRulesConditionTest.kt       |  15 +-
 270 files changed, 2664 insertions(+), 1407 deletions(-)
 create mode 100644 matrix-sdk-android/docs/modules.md
 create mode 100644 matrix-sdk-android/docs/packages.md
 create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/UrlAndName.kt
 create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/converter.kt
 create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/InitialSyncRequestReason.kt
 create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/SessionExtensions.kt
 rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/{ => session}/pushrules/Action.kt (97%)
 rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/{ => session}/pushrules/Condition.kt (93%)
 rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/{ => session}/pushrules/ConditionResolver.kt (96%)
 rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/{ => session}/pushrules/ContainsDisplayNameCondition.kt (97%)
 rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/{ => session}/pushrules/EventMatchCondition.kt (98%)
 rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/{ => session}/pushrules/Kind.kt (96%)
 rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/{ => session}/pushrules/PushEvents.kt (88%)
 rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/{ => session}/pushrules/PushRuleService.kt (91%)
 rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/{ => session}/pushrules/RoomMemberCountCondition.kt (94%)
 rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/{ => session}/pushrules/RuleIds.kt (97%)
 rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/{ => session}/pushrules/RuleScope.kt (92%)
 rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/{ => session}/pushrules/RuleSetKey.kt (95%)
 rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/{ => session}/pushrules/SenderNotificationPermissionCondition.kt (97%)
 rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/{ => session}/pushrules/rest/PushCondition.kt (84%)
 rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/{ => session}/pushrules/rest/PushRule.kt (94%)
 rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/{ => session}/pushrules/rest/RuleSet.kt (93%)
 create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomExtensions.kt
 create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/LiveLocationShareAggregatedSummary.kt
 rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/{livelocation/LiveLocationBeaconContent.kt => message/MessageBeaconInfoContent.kt} (57%)
 rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/{MessageLiveLocationContent.kt => MessageBeaconLocationDataContent.kt} (72%)
 create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/uia/UiaResult.kt
 create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/uia/exceptions/UiaCancelledException.kt
 create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterCustomTask.kt
 rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/{api/session/room/model/livelocation/BeaconInfo.kt => internal/auth/registration/RegistrationCustomParams.kt} (56%)
 create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/LiveLocationShareAggregatedSummaryMapper.kt
 create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo027.kt
 create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo028.kt
 create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/livelocation/LiveLocationShareAggregatedSummaryEntity.kt
 create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LiveLocationShareAggregatedSummaryEntityQuery.kt
 rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/{api/pushrules/rest => internal/session/pushers}/GetPushRulesResponse.kt (90%)
 rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/{notification => pushrules}/DefaultPushRuleService.kt (90%)
 rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/{notification => pushrules}/ProcessEventForPushTask.kt (95%)
 rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/{notification => pushrules}/PushRuleFinder.kt (86%)
 create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/crypto/DefaultRoomCryptoService.kt
 delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/SaveIgnoredUsersTask.kt
 create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/time/Clock.kt
 rename matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/{ => session}/pushrules/PushRuleActionsTest.kt (95%)
 rename matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/{ => session}/pushrules/PushRulesConditionTest.kt (95%)

diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index fb7f4a8a..61a9130c 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
   <component name="CompilerConfiguration">
-    <bytecodeTargetLevel target="11" />
+    <bytecodeTargetLevel target="1.8" />
   </component>
 </project>
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index ef61796f..d5d35ec4 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
-  <component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="1.8" project-jdk-type="JavaSDK">
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
     <output url="file://$PROJECT_DIR$/build/classes" />
   </component>
   <component name="ProjectType">
diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle
index 18f43250..8362d359 100644
--- a/matrix-sdk-android/build.gradle
+++ b/matrix-sdk-android/build.gradle
@@ -16,6 +16,27 @@ buildscript {
     }
 }
 
+dokkaHtml {
+    dokkaSourceSets {
+        configureEach {
+            // Emit warnings about not documented members.
+            reportUndocumented.set(true)
+            // Suppress legacy Riot's packages.
+            perPackageOption {
+                matchingRegex.set("org.matrix.android.sdk.internal.legacy.riot")
+                suppress.set(true)
+            }
+            perPackageOption {
+                matchingRegex.set("org.matrix.androidsdk.crypto.data")
+                suppress.set(true)
+            }
+            // List of files with module and package documentation
+            // https://kotlinlang.org/docs/reference/kotlin-doc.html#module-and-package-documentation
+            includes.from("./docs/modules.md", "./docs/packages.md")
+        }
+    }
+}
+
 android {
     testOptions.unitTests.includeAndroidResources = true
 
@@ -174,7 +195,7 @@ dependencies {
     implementation libs.apache.commonsImaging
 
     // Phone number https://github.com/google/libphonenumber
-    implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.46'
+    implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.47'
 
     testImplementation libs.tests.junit
     testImplementation 'org.robolectric:robolectric:4.7.3'
diff --git a/matrix-sdk-android/docs/modules.md b/matrix-sdk-android/docs/modules.md
new file mode 100644
index 00000000..edf5af64
--- /dev/null
+++ b/matrix-sdk-android/docs/modules.md
@@ -0,0 +1,18 @@
+# Module matrix-sdk-android
+
+## Welcome to the matrix-sdk-android documentation!
+
+This pages list the complete API that this SDK is exposing to a client application.
+
+*We are still building the documentation, so everything is not documented yet.*
+
+A few entry points:
+
+- **Matrix**: The app will have to create and manage a Matrix object.
+- From this **Matrix** object, you will be able to get various services, including the **AuthenticationService**.
+- With this **AuthenticationService** you will be able to get an existing **Session**, or create one using a **LoginWizard** or a **RegistrationWizard**, which will finally give you a **Session**.
+- From the **Session**, you will be able to retrieve many Services, including the **RoomService**.
+- From the **RoomService**, you will be able to list the rooms, create a **Room**, and get a specific **Room**.
+- And from a **Room**, you will be able to do many things, including get a **Timeline**, send messages, etc.
+
+Please read the whole documentation to learn more!
diff --git a/matrix-sdk-android/docs/packages.md b/matrix-sdk-android/docs/packages.md
new file mode 100644
index 00000000..ae7bee1b
--- /dev/null
+++ b/matrix-sdk-android/docs/packages.md
@@ -0,0 +1,3 @@
+# Package org.matrix.android.sdk.api
+
+This is the root package of the API exposed by this SDK.
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/ChangePasswordTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/ChangePasswordTest.kt
index 933074cd..6d740c5a 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/ChangePasswordTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/ChangePasswordTest.kt
@@ -46,7 +46,7 @@ class ChangePasswordTest : InstrumentedTest {
 
         // Change password
         commonTestHelper.runBlockingTest {
-            session.changePassword(TestConstants.PASSWORD, NEW_PASSWORD)
+            session.accountService().changePassword(TestConstants.PASSWORD, NEW_PASSWORD)
         }
 
         // Try to login with the previous password, it will fail
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/DeactivateAccountTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/DeactivateAccountTest.kt
index f8d108fb..52dbfc71 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/DeactivateAccountTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/DeactivateAccountTest.kt
@@ -47,7 +47,7 @@ class DeactivateAccountTest : InstrumentedTest {
 
         // Deactivate the account
         commonTestHelper.runBlockingTest {
-            session.deactivateAccount(
+            session.accountService().deactivateAccount(
                     eraseAllData = false,
                     userInteractiveAuthInterceptor = object : UserInteractiveAuthInterceptor {
                         override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt
index ac4ccf56..4b9e605c 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt
@@ -57,7 +57,8 @@ import java.util.concurrent.TimeUnit
 class CommonTestHelper(context: Context) {
 
     internal val matrix: TestMatrix
-    val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
+    private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
+    private var accountNumber = 0
 
     fun getTestInterceptor(session: Session): MockOkHttpInterceptor? = TestModule.interceptorForSession(session.sessionId) as? MockOkHttpInterceptor
 
@@ -145,7 +146,7 @@ class CommonTestHelper(context: Context) {
      * @param nbOfMessages the number of time the message will be sent
      */
     fun sendTextMessage(room: Room, message: String, nbOfMessages: Int, timeout: Long = TestConstants.timeOutMillis): List<TimelineEvent> {
-        val timeline = room.createTimeline(null, TimelineSettings(10))
+        val timeline = room.timelineService().createTimeline(null, TimelineSettings(10))
         timeline.start()
         val sentEvents = sendTextMessagesBatched(timeline, room, message, nbOfMessages, timeout)
         timeline.dispose()
@@ -165,11 +166,12 @@ class CommonTestHelper(context: Context) {
                 .forEach { batchedMessages ->
                     batchedMessages.forEach { formattedMessage ->
                         if (rootThreadEventId != null) {
-                            room.replyInThread(
+                            room.relationService().replyInThread(
                                     rootThreadEventId = rootThreadEventId,
-                                    replyInThreadText = formattedMessage)
+                                    replyInThreadText = formattedMessage
+                            )
                         } else {
-                            room.sendTextMessage(formattedMessage)
+                            room.sendService().sendTextMessage(formattedMessage)
                         }
                     }
                     waitWithLatch(timeout) { latch ->
@@ -214,7 +216,7 @@ class CommonTestHelper(context: Context) {
             numberOfMessages: Int,
             rootThreadEventId: String,
             timeout: Long = TestConstants.timeOutMillis): List<TimelineEvent> {
-        val timeline = room.createTimeline(null, TimelineSettings(10))
+        val timeline = room.timelineService().createTimeline(null, TimelineSettings(10))
         timeline.start()
         val sentEvents = sendTextMessagesBatched(timeline, room, message, numberOfMessages, timeout, rootThreadEventId)
         timeline.dispose()
@@ -237,7 +239,7 @@ class CommonTestHelper(context: Context) {
                               password: String,
                               testParams: SessionTestParams): Session {
         val session = createAccountAndSync(
-                userNamePrefix + "_" + System.currentTimeMillis() + UUID.randomUUID(),
+                userNamePrefix + "_" + accountNumber++ + "_" + UUID.randomUUID(),
                 password,
                 testParams
         )
@@ -431,7 +433,7 @@ class CommonTestHelper(context: Context) {
 
     fun signOutAndClose(session: Session) {
         runBlockingTest(timeout = 60_000) {
-            session.signOut(true)
+            session.signOutService().signOut(true)
         }
         // no need signout will close
         // session.close()
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt
index e3ab1a49..058b1f79 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt
@@ -40,6 +40,7 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxStat
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.toContent
+import org.matrix.android.sdk.api.session.getRoom
 import org.matrix.android.sdk.api.session.room.Room
 import org.matrix.android.sdk.api.session.room.model.Membership
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
@@ -64,12 +65,12 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
         val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
 
         val roomId = testHelper.runBlockingTest {
-            aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" })
+            aliceSession.roomService().createRoom(CreateRoomParams().apply { name = "MyRoom" })
         }
         if (encryptedRoom) {
             testHelper.waitWithLatch { latch ->
                 val room = aliceSession.getRoom(roomId)!!
-                room.enableEncryption()
+                room.roomCryptoService().enableEncryption()
                 val roomSummaryLive = room.getRoomSummaryLive()
                 val roomSummaryObserver = object : Observer<Optional<RoomSummary>> {
                     override fun onChanged(roomSummary: Optional<RoomSummary>) {
@@ -98,7 +99,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
         val bobSession = testHelper.createAccount(TestConstants.USER_BOB, defaultSessionParams)
 
         testHelper.waitWithLatch { latch ->
-            val bobRoomSummariesLive = bobSession.getRoomSummariesLive(roomSummaryQueryParams { })
+            val bobRoomSummariesLive = bobSession.roomService().getRoomSummariesLive(roomSummaryQueryParams { })
             val newRoomObserver = object : Observer<List<RoomSummary>> {
                 override fun onChanged(t: List<RoomSummary>?) {
                     if (t?.isNotEmpty() == true) {
@@ -108,14 +109,15 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
                 }
             }
             bobRoomSummariesLive.observeForever(newRoomObserver)
-            aliceRoom.invite(bobSession.myUserId)
+            aliceRoom.membershipService().invite(bobSession.myUserId)
         }
 
         testHelper.waitWithLatch { latch ->
-            val bobRoomSummariesLive = bobSession.getRoomSummariesLive(roomSummaryQueryParams { })
+            val bobRoomSummariesLive = bobSession.roomService().getRoomSummariesLive(roomSummaryQueryParams { })
             val roomJoinedObserver = object : Observer<List<RoomSummary>> {
                 override fun onChanged(t: List<RoomSummary>?) {
                     if (bobSession.getRoom(aliceRoomId)
+                                    ?.membershipService()
                                     ?.getRoomMember(bobSession.myUserId)
                                     ?.membership == Membership.JOIN) {
                         bobRoomSummariesLive.removeObserver(this)
@@ -124,7 +126,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
                 }
             }
             bobRoomSummariesLive.observeForever(roomJoinedObserver)
-            bobSession.joinRoom(aliceRoomId)
+            bobSession.roomService().joinRoom(aliceRoomId)
         }
         // Ensure bob can send messages to the room
 //        val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
@@ -160,11 +162,11 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
         val samSession = testHelper.createAccount(TestConstants.USER_SAM, defaultSessionParams)
 
         testHelper.runBlockingTest {
-            room.invite(samSession.myUserId, null)
+            room.membershipService().invite(samSession.myUserId, null)
         }
 
         testHelper.runBlockingTest {
-            samSession.joinRoom(room.roomId, null, emptyList())
+            samSession.roomService().joinRoom(room.roomId, null, emptyList())
         }
 
         return samSession
@@ -242,8 +244,8 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
     fun createDM(alice: Session, bob: Session): String {
         var roomId: String = ""
         testHelper.waitWithLatch { latch ->
-            roomId = alice.createDirectRoom(bob.myUserId)
-            val bobRoomSummariesLive = bob.getRoomSummariesLive(roomSummaryQueryParams { })
+            roomId = alice.roomService().createDirectRoom(bob.myUserId)
+            val bobRoomSummariesLive = bob.roomService().getRoomSummariesLive(roomSummaryQueryParams { })
             val newRoomObserver = object : Observer<List<RoomSummary>> {
                 override fun onChanged(t: List<RoomSummary>?) {
                     if (t?.any { it.roomId == roomId }.orFalse()) {
@@ -256,10 +258,11 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
         }
 
         testHelper.waitWithLatch { latch ->
-            val bobRoomSummariesLive = bob.getRoomSummariesLive(roomSummaryQueryParams { })
+            val bobRoomSummariesLive = bob.roomService().getRoomSummariesLive(roomSummaryQueryParams { })
             val newRoomObserver = object : Observer<List<RoomSummary>> {
                 override fun onChanged(t: List<RoomSummary>?) {
                     if (bob.getRoom(roomId)
+                                    ?.membershipService()
                                     ?.getRoomMember(bob.myUserId)
                                     ?.membership == Membership.JOIN) {
                         bobRoomSummariesLive.removeObserver(this)
@@ -268,7 +271,7 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
                 }
             }
             bobRoomSummariesLive.observeForever(newRoomObserver)
-            bob.joinRoom(roomId)
+            bob.roomService().joinRoom(roomId)
         }
 
         return roomId
@@ -367,20 +370,20 @@ class CryptoTestHelper(private val testHelper: CommonTestHelper) {
         aliceSession.cryptoService().setWarnOnUnknownDevices(false)
 
         val roomId = testHelper.runBlockingTest {
-            aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" })
+            aliceSession.roomService().createRoom(CreateRoomParams().apply { name = "MyRoom" })
         }
         val room = aliceSession.getRoom(roomId)!!
 
         testHelper.runBlockingTest {
-            room.enableEncryption()
+            room.roomCryptoService().enableEncryption()
         }
 
         val sessions = mutableListOf(aliceSession)
         for (index in 1 until numberOfMembers) {
             val session = testHelper.createAccount("User_$index", defaultSessionParams)
-            testHelper.runBlockingTest(timeout = 600_000) { room.invite(session.myUserId, null) }
+            testHelper.runBlockingTest(timeout = 600_000) { room.membershipService().invite(session.myUserId, null) }
             println("TEST -> " + session.myUserId + " invited")
-            testHelper.runBlockingTest { session.joinRoom(room.roomId, null, emptyList()) }
+            testHelper.runBlockingTest { session.roomService().joinRoom(room.roomId, null, emptyList()) }
             println("TEST -> " + session.myUserId + " joined")
             sessions.add(session)
         }
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/AttachmentEncryptionTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/AttachmentEncryptionTest.kt
index 732f4f7d..f5f585a1 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/AttachmentEncryptionTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/AttachmentEncryptionTest.kt
@@ -29,8 +29,10 @@ import org.matrix.android.sdk.api.session.crypto.attachments.toElementToDecrypt
 import org.matrix.android.sdk.api.session.crypto.model.EncryptedFileInfo
 import org.matrix.android.sdk.api.session.crypto.model.EncryptedFileKey
 import org.matrix.android.sdk.internal.crypto.attachments.MXEncryptedAttachments
+import org.matrix.android.sdk.internal.util.time.DefaultClock
 import java.io.ByteArrayOutputStream
 import java.io.InputStream
+import java.util.UUID
 
 /**
  * Unit tests AttachmentEncryptionTest.
@@ -48,13 +50,18 @@ class AttachmentEncryptionTest {
         inputStream = if (inputAsByteArray.isEmpty()) {
             inputAsByteArray.inputStream()
         } else {
-            val memoryFile = MemoryFile("file" + System.currentTimeMillis(), inputAsByteArray.size)
+            val memoryFile = MemoryFile("file_" + UUID.randomUUID(), inputAsByteArray.size)
             memoryFile.outputStream.write(inputAsByteArray)
             memoryFile.inputStream
         }
 
         val decryptedStream = ByteArrayOutputStream()
-        val result = MXEncryptedAttachments.decryptAttachment(inputStream, encryptedFileInfo.toElementToDecrypt()!!, decryptedStream)
+        val result = MXEncryptedAttachments.decryptAttachment(
+                attachmentStream = inputStream,
+                elementToDecrypt = encryptedFileInfo.toElementToDecrypt()!!,
+                outputStream = decryptedStream,
+                clock = DefaultClock()
+        )
 
         assert(result)
 
@@ -117,9 +124,13 @@ class AttachmentEncryptionTest {
                 url = "dummyUrl"
         )
 
-        assertEquals("YWxwaGFudW1lcmljYWxseWFscGhhbnVtZXJpY2FsbHlhbHBoYW51bWVyaWNhbGx5YWxwaGFudW1lcmljYWxseQ",
-                checkDecryption("zhtFStAeFx0s+9L/sSQO+WQMtldqYEHqTxMduJrCIpnkyer09kxJJuA4K+adQE4w+7jZe/vR9kIcqj9rOhDR8Q",
-                        encryptedFileInfo))
+        assertEquals(
+                "YWxwaGFudW1lcmljYWxseWFscGhhbnVtZXJpY2FsbHlhbHBoYW51bWVyaWNhbGx5YWxwaGFudW1lcmljYWxseQ",
+                checkDecryption(
+                        "zhtFStAeFx0s+9L/sSQO+WQMtldqYEHqTxMduJrCIpnkyer09kxJJuA4K+adQE4w+7jZe/vR9kIcqj9rOhDR8Q",
+                        encryptedFileInfo
+                )
+        )
     }
 
     @Test
@@ -138,8 +149,12 @@ class AttachmentEncryptionTest {
                 url = "dummyUrl"
         )
 
-        assertNotEquals("YWxwaGFudW1lcmljYWxseWFscGhhbnVtZXJpY2FsbHlhbHBoYW51bWVyaWNhbGx5YWxwaGFudW1lcmljYWxseQ",
-                checkDecryption("tJVNBVJ/vl36UQt4Y5e5m84bRUrQHhcdLPvS/7EkDvlkDLZXamBB6k8THbiawiKZ5Mnq9PZMSSbgOCvmnUBOMA",
-                        encryptedFileInfo))
+        assertNotEquals(
+                "YWxwaGFudW1lcmljYWxseWFscGhhbnVtZXJpY2FsbHlhbHBoYW51bWVyaWNhbGx5YWxwaGFudW1lcmljYWxseQ",
+                checkDecryption(
+                        "tJVNBVJ/vl36UQt4Y5e5m84bRUrQHhcdLPvS/7EkDvlkDLZXamBB6k8THbiawiKZ5Mnq9PZMSSbgOCvmnUBOMA",
+                        encryptedFileInfo
+                )
+        )
     }
 }
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreHelper.kt
index c717c8e3..ba1afd47 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreHelper.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreHelper.kt
@@ -22,6 +22,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStore
 import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule
 import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper
 import org.matrix.android.sdk.internal.di.MoshiProvider
+import org.matrix.android.sdk.internal.util.time.DefaultClock
 import kotlin.random.Random
 
 internal class CryptoStoreHelper {
@@ -34,7 +35,8 @@ internal class CryptoStoreHelper {
                         .build(),
                 crossSigningKeysMapper = CrossSigningKeysMapper(MoshiProvider.providesMoshi()),
                 userId = "userId_" + Random.nextInt(),
-                deviceId = "deviceId_sample"
+                deviceId = "deviceId_sample",
+                clock = DefaultClock(),
         )
     }
 }
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreTest.kt
index f43c425c..3f75aa09 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/CryptoStoreTest.kt
@@ -27,6 +27,7 @@ import org.junit.runner.RunWith
 import org.matrix.android.sdk.InstrumentedTest
 import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper
 import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
+import org.matrix.android.sdk.internal.util.time.DefaultClock
 import org.matrix.olm.OlmAccount
 import org.matrix.olm.OlmManager
 import org.matrix.olm.OlmSession
@@ -37,6 +38,7 @@ private const val DUMMY_DEVICE_KEY = "DeviceKey"
 class CryptoStoreTest : InstrumentedTest {
 
     private val cryptoStoreHelper = CryptoStoreHelper()
+    private val clock = DefaultClock()
 
     @Before
     fun setup() {
@@ -106,7 +108,7 @@ class CryptoStoreTest : InstrumentedTest {
 
         // Note: we cannot be sure what will be the result of getLastUsedSessionId() here
 
-        olmSessionWrapper2.onMessageReceived()
+        olmSessionWrapper2.onMessageReceived(clock.epochMillis())
         cryptoStore.storeSession(olmSessionWrapper2, DUMMY_DEVICE_KEY)
 
         // sessionId2 is returned now
@@ -114,7 +116,7 @@ class CryptoStoreTest : InstrumentedTest {
 
         Thread.sleep(2)
 
-        olmSessionWrapper1.onMessageReceived()
+        olmSessionWrapper1.onMessageReceived(clock.epochMillis())
         cryptoStore.storeSession(olmSessionWrapper1, DUMMY_DEVICE_KEY)
 
         // sessionId1 is returned now
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt
index 38846831..88d99f12 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt
@@ -38,8 +38,11 @@ import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
 import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.getRoom
+import org.matrix.android.sdk.api.session.getRoomSummary
 import org.matrix.android.sdk.api.session.room.Room
 import org.matrix.android.sdk.api.session.room.failure.JoinRoomFailure
+import org.matrix.android.sdk.api.session.room.getTimelineEvent
 import org.matrix.android.sdk.api.session.room.model.Membership
 import org.matrix.android.sdk.api.session.room.model.message.MessageContent
 import org.matrix.android.sdk.api.session.room.send.SendState
@@ -86,7 +89,7 @@ class E2eeSanityTests : InstrumentedTest {
         otherAccounts.forEach {
             testHelper.runBlockingTest {
                 Log.v("#E2E TEST", "Alice invites ${it.myUserId}")
-                aliceRoomPOV.invite(it.myUserId)
+                aliceRoomPOV.membershipService().invite(it.myUserId)
             }
         }
 
@@ -128,7 +131,7 @@ class E2eeSanityTests : InstrumentedTest {
         newAccount.forEach {
             testHelper.runBlockingTest {
                 Log.v("#E2E TEST", "Alice invites ${it.myUserId}")
-                aliceRoomPOV.invite(it.myUserId)
+                aliceRoomPOV.membershipService().invite(it.myUserId)
             }
         }
 
@@ -523,10 +526,10 @@ class E2eeSanityTests : InstrumentedTest {
     }
 
     private fun sendMessageInRoom(aliceRoomPOV: Room, text: String): String? {
-        aliceRoomPOV.sendTextMessage(text)
+        aliceRoomPOV.sendService().sendTextMessage(text)
         var sentEventId: String? = null
         testHelper.waitWithLatch(4 * TestConstants.timeOutMillis) { latch ->
-            val timeline = aliceRoomPOV.createTimeline(null, TimelineSettings(60))
+            val timeline = aliceRoomPOV.timelineService().createTimeline(null, TimelineSettings(60))
             timeline.start()
 
             testHelper.retryPeriodicallyWithLatch(latch) {
@@ -551,7 +554,7 @@ class E2eeSanityTests : InstrumentedTest {
         testHelper.waitWithLatch { latch ->
             testHelper.retryPeriodicallyWithLatch(latch) {
                 otherAccounts.map {
-                    aliceSession.getRoomMember(it.myUserId, e2eRoomID)?.membership
+                    aliceSession.roomService().getRoomMember(it.myUserId, e2eRoomID)?.membership
                 }.all {
                     it == Membership.JOIN
                 }
@@ -574,7 +577,7 @@ class E2eeSanityTests : InstrumentedTest {
         testHelper.runBlockingTest(60_000) {
             Log.v("#E2E TEST", "${otherSession.myUserId} tries to join room $e2eRoomID")
             try {
-                otherSession.joinRoom(e2eRoomID)
+                otherSession.roomService().joinRoom(e2eRoomID)
             } catch (ex: JoinRoomFailure.JoinedWithTimeout) {
                 // it's ok we will wait after
             }
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/PreShareKeysTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/PreShareKeysTest.kt
index aa9f0931..8a1edec5 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/PreShareKeysTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/PreShareKeysTest.kt
@@ -29,6 +29,8 @@ import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
 import org.matrix.android.sdk.api.session.events.model.content.RoomKeyContent
 import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.getRoom
+import org.matrix.android.sdk.api.session.room.getTimelineEvent
 import org.matrix.android.sdk.common.CommonTestHelper
 import org.matrix.android.sdk.common.CryptoTestHelper
 
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt
index 83464305..de4a928d 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt
@@ -34,6 +34,7 @@ import org.matrix.android.sdk.api.session.crypto.MXCryptoError
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
 import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.getRoom
 import org.matrix.android.sdk.api.session.room.timeline.Timeline
 import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
 import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
@@ -97,7 +98,7 @@ class UnwedgingTest : InstrumentedTest {
         val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
         val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
 
-        val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(20))
+        val bobTimeline = roomFromBobPOV.timelineService().createTimeline(null, TimelineSettings(20))
         bobTimeline.start()
 
         val bobFinalLatch = CountDownLatch(1)
@@ -128,7 +129,7 @@ class UnwedgingTest : InstrumentedTest {
         messagesReceivedByBob = emptyList()
 
         // - Alice sends a 1st message with a 1st megolm session
-        roomFromAlicePOV.sendTextMessage("First message")
+        roomFromAlicePOV.sendService().sendTextMessage("First message")
 
         // Wait for the message to be received by Bob
         testHelper.await(latch)
@@ -156,7 +157,7 @@ class UnwedgingTest : InstrumentedTest {
 
         Timber.i("## CRYPTO | testUnwedging:  Alice sends a 2nd message with a 2nd megolm session")
         // - Alice sends a 2nd message with a 2nd megolm session
-        roomFromAlicePOV.sendTextMessage("Second message")
+        roomFromAlicePOV.sendService().sendTextMessage("Second message")
 
         // Wait for the message to be received by Bob
         testHelper.await(latch)
@@ -185,7 +186,7 @@ class UnwedgingTest : InstrumentedTest {
 
             Timber.i("## CRYPTO | testUnwedging: Alice sends a 3rd message with a 3rd megolm session but a wedged olm session")
             // - Alice sends a 3rd message with a 3rd megolm session but a wedged olm session
-            roomFromAlicePOV.sendTextMessage("Third message")
+            roomFromAlicePOV.sendService().sendTextMessage("Third message")
             // Bob should not be able to decrypt, because the session key could not be sent
         }
         bobTimeline.removeListener(bobEventsListener)
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt
index dc65cec1..0f3ff789 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt
@@ -110,11 +110,13 @@ class XSigningTest : InstrumentedTest {
                 }
             }, it)
         }
-        testHelper.doSync<Unit> { bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
-            override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
-                promise.resume(bobAuthParams)
-            }
-        }, it) }
+        testHelper.doSync<Unit> {
+            bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
+                override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
+                    promise.resume(bobAuthParams)
+                }
+            }, it)
+        }
 
         // Check that alice can see bob keys
         testHelper.doSync<MXUsersDevicesMap<CryptoDeviceInfo>> { aliceSession.cryptoService().downloadKeys(listOf(bobSession.myUserId), true, it) }
@@ -149,16 +151,20 @@ class XSigningTest : InstrumentedTest {
                 password = TestConstants.PASSWORD
         )
 
-        testHelper.doSync<Unit> { aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
-            override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
-                promise.resume(aliceAuthParams)
-            }
-        }, it) }
-        testHelper.doSync<Unit> { bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
-            override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
-                promise.resume(bobAuthParams)
-            }
-        }, it) }
+        testHelper.doSync<Unit> {
+            aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
+                override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
+                    promise.resume(aliceAuthParams)
+                }
+            }, it)
+        }
+        testHelper.doSync<Unit> {
+            bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
+                override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
+                    promise.resume(bobAuthParams)
+                }
+            }, it)
+        }
 
         // Check that alice can see bob keys
         val bobUserId = bobSession.myUserId
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt
index 84c9487e..85b6c21d 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt
@@ -28,6 +28,7 @@ import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.content.EncryptionEventContent
 import org.matrix.android.sdk.api.session.events.model.toContent
+import org.matrix.android.sdk.api.session.getRoom
 import org.matrix.android.sdk.api.session.room.Room
 import org.matrix.android.sdk.api.session.room.send.SendState
 import org.matrix.android.sdk.api.session.room.timeline.Timeline
@@ -48,7 +49,7 @@ class EncryptionTest : InstrumentedTest {
     fun test_EncryptionEvent() {
         performTest(roomShouldBeEncrypted = false) { room ->
             // Send an encryption Event as an Event (and not as a state event)
-            room.sendEvent(
+            room.sendService().sendEvent(
                     eventType = EventType.STATE_ROOM_ENCRYPTION,
                     content = EncryptionEventContent(algorithm = MXCRYPTO_ALGORITHM_MEGOLM).toContent()
             )
@@ -60,7 +61,7 @@ class EncryptionTest : InstrumentedTest {
         performTest(roomShouldBeEncrypted = true) { room ->
             runBlocking {
                 // Send an encryption Event as a State Event
-                room.sendStateEvent(
+                room.stateService().sendStateEvent(
                         eventType = EventType.STATE_ROOM_ENCRYPTION,
                         stateKey = "",
                         body = EncryptionEventContent(algorithm = MXCRYPTO_ALGORITHM_MEGOLM).toContent()
@@ -75,9 +76,9 @@ class EncryptionTest : InstrumentedTest {
         val aliceSession = cryptoTestData.firstSession
         val room = aliceSession.getRoom(cryptoTestData.roomId)!!
 
-        room.isEncrypted() shouldBe false
+        room.roomCryptoService().isEncrypted() shouldBe false
 
-        val timeline = room.createTimeline(null, TimelineSettings(10))
+        val timeline = room.timelineService().createTimeline(null, TimelineSettings(10))
         val latch = CountDownLatch(1)
         val timelineListener = object : Timeline.Listener {
             override fun onTimelineFailure(throwable: Throwable) {
@@ -105,7 +106,7 @@ class EncryptionTest : InstrumentedTest {
         testHelper.await(latch)
         timeline.dispose()
         testHelper.waitWithLatch {
-            room.isEncrypted() shouldBe roomShouldBeEncrypted
+            room.roomCryptoService().isEncrypted() shouldBe roomShouldBeEncrypted
             it.countDown()
         }
         cryptoTestData.cleanUp(testHelper)
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt
index 5e271c69..592d24fb 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt
@@ -50,6 +50,8 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransa
 import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
 import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
 import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.getRoom
+import org.matrix.android.sdk.api.session.room.getTimelineEvent
 import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
 import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
 import org.matrix.android.sdk.api.session.room.model.message.MessageContent
@@ -73,7 +75,7 @@ class KeyShareTests : InstrumentedTest {
 
         // Create an encrypted room and add a message
         val roomId = commonTestHelper.runBlockingTest {
-            aliceSession.createRoom(
+            aliceSession.roomService().createRoom(
                     CreateRoomParams().apply {
                         visibility = RoomDirectoryVisibility.PRIVATE
                         enableEncryption()
@@ -83,7 +85,7 @@ class KeyShareTests : InstrumentedTest {
         val room = aliceSession.getRoom(roomId)
         assertNotNull(room)
         Thread.sleep(4_000)
-        assertTrue(room?.isEncrypted() == true)
+        assertTrue(room?.roomCryptoService()?.isEncrypted() == true)
         val sentEventId = commonTestHelper.sendTextMessage(room!!, "My Message", 1).first().eventId
 
         // Open a new sessionx
@@ -340,7 +342,7 @@ class KeyShareTests : InstrumentedTest {
 
         // Create an encrypted room and send a couple of messages
         val roomId = commonTestHelper.runBlockingTest {
-            aliceSession.createRoom(
+            aliceSession.roomService().createRoom(
                     CreateRoomParams().apply {
                         visibility = RoomDirectoryVisibility.PRIVATE
                         enableEncryption()
@@ -350,7 +352,7 @@ class KeyShareTests : InstrumentedTest {
         val roomAlicePov = aliceSession.getRoom(roomId)
         assertNotNull(roomAlicePov)
         Thread.sleep(1_000)
-        assertTrue(roomAlicePov?.isEncrypted() == true)
+        assertTrue(roomAlicePov?.roomCryptoService()?.isEncrypted() == true)
         val secondEventId = commonTestHelper.sendTextMessage(roomAlicePov!!, "Message", 3)[1].eventId
 
         // Create bob session
@@ -374,11 +376,11 @@ class KeyShareTests : InstrumentedTest {
 
         // Let alice invite bob
         commonTestHelper.runBlockingTest {
-            roomAlicePov.invite(bobSession.myUserId, null)
+            roomAlicePov.membershipService().invite(bobSession.myUserId, null)
         }
 
         commonTestHelper.runBlockingTest {
-            bobSession.joinRoom(roomAlicePov.roomId, null, emptyList())
+            bobSession.roomService().joinRoom(roomAlicePov.roomId, null, emptyList())
         }
 
         // we want to discard alice outbound session
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt
index 55bb0327..bad9fd0f 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt
@@ -33,6 +33,8 @@ import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
 import org.matrix.android.sdk.api.session.events.model.content.WithHeldCode
 import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.getRoom
+import org.matrix.android.sdk.api.session.room.getTimelineEvent
 import org.matrix.android.sdk.common.CommonTestHelper
 import org.matrix.android.sdk.common.CryptoTestHelper
 import org.matrix.android.sdk.common.MockOkHttpInterceptor
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt
index 063c0c0c..3220f161 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt
@@ -41,6 +41,7 @@ import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion
 import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
 import org.matrix.android.sdk.api.session.crypto.keysbackup.toKeysVersionResult
 import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult
+import org.matrix.android.sdk.api.session.getRoom
 import org.matrix.android.sdk.common.CommonTestHelper
 import org.matrix.android.sdk.common.CryptoTestHelper
 import org.matrix.android.sdk.common.TestConstants
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt
index d6baa4b1..a882f690 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt
@@ -60,7 +60,7 @@ class QuadSTests : InstrumentedTest {
 
         val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
 
-        val quadS = aliceSession.sharedSecretStorageService
+        val quadS = aliceSession.sharedSecretStorageService()
 
         val TEST_KEY_ID = "my.test.Key"
 
@@ -120,7 +120,7 @@ class QuadSTests : InstrumentedTest {
         // Store a secret
         val clearSecret = "42".toByteArray().toBase64NoPadding()
         testHelper.runBlockingTest {
-            aliceSession.sharedSecretStorageService.storeSecret(
+            aliceSession.sharedSecretStorageService().storeSecret(
                     "secret.of.life",
                     clearSecret,
                     listOf(SharedSecretStorageService.KeyRef(null, keySpec)) // default key
@@ -141,7 +141,7 @@ class QuadSTests : InstrumentedTest {
         // Try to decrypt??
 
         val decryptedSecret = testHelper.runBlockingTest {
-            aliceSession.sharedSecretStorageService.getSecret(
+            aliceSession.sharedSecretStorageService().getSecret(
                     "secret.of.life",
                     null, // default key
                     keySpec!!
@@ -158,7 +158,7 @@ class QuadSTests : InstrumentedTest {
 
         val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
 
-        val quadS = aliceSession.sharedSecretStorageService
+        val quadS = aliceSession.sharedSecretStorageService()
 
         val TEST_KEY_ID = "my.test.Key"
 
@@ -187,7 +187,7 @@ class QuadSTests : InstrumentedTest {
         val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit"
 
         testHelper.runBlockingTest {
-            aliceSession.sharedSecretStorageService.storeSecret(
+            aliceSession.sharedSecretStorageService().storeSecret(
                     "my.secret",
                     mySecretText.toByteArray().toBase64NoPadding(),
                     listOf(
@@ -207,14 +207,14 @@ class QuadSTests : InstrumentedTest {
 
         // Assert that can decrypt with both keys
         testHelper.runBlockingTest {
-            aliceSession.sharedSecretStorageService.getSecret("my.secret",
+            aliceSession.sharedSecretStorageService().getSecret("my.secret",
                     keyId1,
                     RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)!!
             )
         }
 
         testHelper.runBlockingTest {
-            aliceSession.sharedSecretStorageService.getSecret("my.secret",
+            aliceSession.sharedSecretStorageService().getSecret("my.secret",
                     keyId2,
                     RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey)!!
             )
@@ -236,7 +236,7 @@ class QuadSTests : InstrumentedTest {
         val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit"
 
         testHelper.runBlockingTest {
-            aliceSession.sharedSecretStorageService.storeSecret(
+            aliceSession.sharedSecretStorageService().storeSecret(
                     "my.secret",
                     mySecretText.toByteArray().toBase64NoPadding(),
                     listOf(SharedSecretStorageService.KeyRef(keyId1, RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)))
@@ -245,7 +245,7 @@ class QuadSTests : InstrumentedTest {
 
         testHelper.runBlockingTest {
             try {
-                aliceSession.sharedSecretStorageService.getSecret("my.secret",
+                aliceSession.sharedSecretStorageService().getSecret("my.secret",
                         keyId1,
                         RawBytesKeySpec.fromPassphrase(
                                 "A bad passphrase",
@@ -260,7 +260,7 @@ class QuadSTests : InstrumentedTest {
 
         // Now try with correct key
         testHelper.runBlockingTest {
-            aliceSession.sharedSecretStorageService.getSecret("my.secret",
+            aliceSession.sharedSecretStorageService().getSecret("my.secret",
                     keyId1,
                     RawBytesKeySpec.fromPassphrase(
                             passphrase,
@@ -292,7 +292,7 @@ class QuadSTests : InstrumentedTest {
     }
 
     private fun generatedSecret(session: Session, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo {
-        val quadS = session.sharedSecretStorageService
+        val quadS = session.sharedSecretStorageService()
         val testHelper = CommonTestHelper(context())
 
         val creationInfo = testHelper.runBlockingTest {
@@ -312,7 +312,7 @@ class QuadSTests : InstrumentedTest {
     }
 
     private fun generatedSecretFromPassphrase(session: Session, passphrase: String, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo {
-        val quadS = session.sharedSecretStorageService
+        val quadS = session.sharedSecretStorageService()
         val testHelper = CommonTestHelper(context())
 
         val creationInfo = testHelper.runBlockingTest {
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt
index 2c965681..374d7095 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt
@@ -155,8 +155,8 @@ class VerificationTest : InstrumentedTest {
                        bobSupportedMethods: List<VerificationMethod>,
                        expectedResultForAlice: ExpectedResult,
                        expectedResultForBob: ExpectedResult) {
-         val testHelper = CommonTestHelper(context())
-         val cryptoTestHelper = CryptoTestHelper(testHelper)
+        val testHelper = CommonTestHelper(context())
+        val cryptoTestHelper = CryptoTestHelper(testHelper)
         val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
 
         val aliceSession = cryptoTestData.firstSession
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/ThreadMessagingTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/ThreadMessagingTest.kt
index dcb181f0..f6e08a57 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/ThreadMessagingTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/threads/ThreadMessagingTest.kt
@@ -31,6 +31,7 @@ import org.matrix.android.sdk.InstrumentedTest
 import org.matrix.android.sdk.api.session.events.model.getRootThreadEventId
 import org.matrix.android.sdk.api.session.events.model.isTextMessage
 import org.matrix.android.sdk.api.session.events.model.isThread
+import org.matrix.android.sdk.api.session.getRoom
 import org.matrix.android.sdk.api.session.room.timeline.Timeline
 import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
 import org.matrix.android.sdk.common.CommonTestHelper
@@ -80,7 +81,7 @@ class ThreadMessagingTest : InstrumentedTest {
         replyInThread.root.getRootThreadEventId().shouldBeEqualTo(initMessage.root.eventId)
 
         // The init normal message should now be a root thread event
-        val timeline = aliceRoom.createTimeline(null, TimelineSettings(30))
+        val timeline = aliceRoom.timelineService().createTimeline(null, TimelineSettings(30))
         timeline.start()
 
         aliceSession.startSync(true)
@@ -141,7 +142,7 @@ class ThreadMessagingTest : InstrumentedTest {
         replyInThread.root.getRootThreadEventId().shouldBeEqualTo(initMessage.root.eventId)
 
         // The init normal message should now be a root thread event
-        val timeline = aliceRoom.createTimeline(null, TimelineSettings(30))
+        val timeline = aliceRoom.timelineService().createTimeline(null, TimelineSettings(30))
         timeline.start()
 
         aliceSession.startSync(true)
@@ -214,7 +215,7 @@ class ThreadMessagingTest : InstrumentedTest {
         }
 
         // The init normal message should now be a root thread event
-        val timeline = aliceRoom.createTimeline(null, TimelineSettings(30))
+        val timeline = aliceRoom.timelineService().createTimeline(null, TimelineSettings(30))
         timeline.start()
 
         aliceSession.startSync(true)
@@ -309,7 +310,7 @@ class ThreadMessagingTest : InstrumentedTest {
         }
 
         // The init normal message should now be a root thread event
-        val timeline = aliceRoom.createTimeline(null, TimelineSettings(30))
+        val timeline = aliceRoom.timelineService().createTimeline(null, TimelineSettings(30))
         timeline.start()
 
         aliceSession.startSync(true)
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/ChunkEntityTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/ChunkEntityTest.kt
index 5c011c8b..27d3fdc8 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/ChunkEntityTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/ChunkEntityTest.kt
@@ -35,6 +35,7 @@ import org.matrix.android.sdk.internal.database.mapper.toEntity
 import org.matrix.android.sdk.internal.database.model.ChunkEntity
 import org.matrix.android.sdk.internal.database.model.SessionRealmModule
 import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection
+import org.matrix.android.sdk.internal.util.time.DefaultClock
 import org.matrix.android.sdk.session.room.timeline.RoomDataHelper.createFakeListOfEvents
 import org.matrix.android.sdk.session.room.timeline.RoomDataHelper.createFakeMessageEvent
 
@@ -42,6 +43,7 @@ import org.matrix.android.sdk.session.room.timeline.RoomDataHelper.createFakeMes
 internal class ChunkEntityTest : InstrumentedTest {
 
     private lateinit var monarchy: Monarchy
+    private val clock = DefaultClock()
 
     @Before
     fun setup() {
@@ -59,7 +61,7 @@ internal class ChunkEntityTest : InstrumentedTest {
         monarchy.runTransactionSync { realm ->
             val chunk: ChunkEntity = realm.createObject()
 
-            val fakeEvent = createFakeMessageEvent().toEntity(ROOM_ID, SendState.SYNCED, System.currentTimeMillis()).let {
+            val fakeEvent = createFakeMessageEvent().toEntity(ROOM_ID, SendState.SYNCED, clock.epochMillis()).let {
                 realm.copyToRealm(it)
             }
             chunk.addTimelineEvent(
@@ -75,7 +77,7 @@ internal class ChunkEntityTest : InstrumentedTest {
     fun add_shouldNotAdd_whenAlreadyIncluded() {
         monarchy.runTransactionSync { realm ->
             val chunk: ChunkEntity = realm.createObject()
-            val fakeEvent = createFakeMessageEvent().toEntity(ROOM_ID, SendState.SYNCED, System.currentTimeMillis()).let {
+            val fakeEvent = createFakeMessageEvent().toEntity(ROOM_ID, SendState.SYNCED, clock.epochMillis()).let {
                 realm.copyToRealm(it)
             }
             chunk.addTimelineEvent(
@@ -153,7 +155,7 @@ internal class ChunkEntityTest : InstrumentedTest {
                                    events: List<Event>,
                                    direction: PaginationDirection) {
         events.forEach { event ->
-            val fakeEvent = event.toEntity(roomId, SendState.SYNCED, System.currentTimeMillis()).let {
+            val fakeEvent = event.toEntity(roomId, SendState.SYNCED, clock.epochMillis()).let {
                 realm.copyToRealm(it)
             }
             addTimelineEvent(
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakeGetContextOfEventTask.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakeGetContextOfEventTask.kt
index b86c86c0..ccf1c7c2 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakeGetContextOfEventTask.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakeGetContextOfEventTask.kt
@@ -26,8 +26,8 @@ internal class FakeGetContextOfEventTask constructor(private val tokenChunkEvent
     override suspend fun execute(params: GetContextOfEventTask.Params): TokenChunkEventPersistor.Result {
         val fakeEvents = RoomDataHelper.createFakeListOfEvents(30)
         val tokenChunkEvent = FakeTokenChunkEvent(
-                Random.nextLong(System.currentTimeMillis()).toString(),
-                Random.nextLong(System.currentTimeMillis()).toString(),
+                Random.nextLong().toString(),
+                Random.nextLong().toString(),
                 fakeEvents
         )
         return tokenChunkEventPersistor.insertInDb(tokenChunkEvent, params.roomId, PaginationDirection.BACKWARDS)
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakePaginationTask.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakePaginationTask.kt
index d09bfb18..f241be0c 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakePaginationTask.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/FakePaginationTask.kt
@@ -25,7 +25,7 @@ internal class FakePaginationTask @Inject constructor(private val tokenChunkEven
 
     override suspend fun execute(params: PaginationTask.Params): TokenChunkEventPersistor.Result {
         val fakeEvents = RoomDataHelper.createFakeListOfEvents(30)
-        val tokenChunkEvent = FakeTokenChunkEvent(params.from, Random.nextLong(System.currentTimeMillis()).toString(), fakeEvents)
+        val tokenChunkEvent = FakeTokenChunkEvent(params.from, Random.nextLong().toString(), fakeEvents)
         return tokenChunkEventPersistor.insertInDb(tokenChunkEvent, params.roomId, params.direction)
     }
 }
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/PollAggregationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/PollAggregationTest.kt
index 6792d6dd..61ab6d4b 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/PollAggregationTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/PollAggregationTest.kt
@@ -30,6 +30,7 @@ import org.junit.runners.MethodSorters
 import org.matrix.android.sdk.InstrumentedTest
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.getRoom
 import org.matrix.android.sdk.api.session.room.model.PollResponseAggregatedSummary
 import org.matrix.android.sdk.api.session.room.model.PollSummaryContent
 import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
@@ -57,10 +58,10 @@ class PollAggregationTest : InstrumentedTest {
 
         val roomFromBobPOV = cryptoTestData.secondSession!!.getRoom(cryptoTestData.roomId)!!
         // Bob creates a poll
-        roomFromBobPOV.sendPoll(PollType.DISCLOSED, pollQuestion, pollOptions)
+        roomFromBobPOV.sendService().sendPoll(PollType.DISCLOSED, pollQuestion, pollOptions)
 
         aliceSession.startSync(true)
-        val aliceTimeline = roomFromAlicePOV.createTimeline(null, TimelineSettings(30))
+        val aliceTimeline = roomFromAlicePOV.timelineService().createTimeline(null, TimelineSettings(30))
         aliceTimeline.start()
 
         val TOTAL_TEST_COUNT = 7
@@ -83,37 +84,37 @@ class PollAggregationTest : InstrumentedTest {
                             // Poll has just been created.
                             testInitialPollConditions(pollContent, pollSummary)
                             lock.countDown()
-                            roomFromBobPOV.voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.firstOrNull()?.id ?: "")
+                            roomFromBobPOV.sendService().voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.firstOrNull()?.id ?: "")
                         }
                         TOTAL_TEST_COUNT - 1 -> {
                             // Bob: Option 1
                             testBobVotesOption1(pollContent, pollSummary)
                             lock.countDown()
-                            roomFromBobPOV.voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.get(1)?.id ?: "")
+                            roomFromBobPOV.sendService().voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.get(1)?.id ?: "")
                         }
                         TOTAL_TEST_COUNT - 2 -> {
                             // Bob: Option 2
                             testBobChangesVoteToOption2(pollContent, pollSummary)
                             lock.countDown()
-                            roomFromAlicePOV.voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.get(1)?.id ?: "")
+                            roomFromAlicePOV.sendService().voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.get(1)?.id ?: "")
                         }
                         TOTAL_TEST_COUNT - 3 -> {
                             // Alice: Option 2, Bob: Option 2
                             testAliceAndBobVoteToOption2(pollContent, pollSummary)
                             lock.countDown()
-                            roomFromAlicePOV.voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.firstOrNull()?.id ?: "")
+                            roomFromAlicePOV.sendService().voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.firstOrNull()?.id ?: "")
                         }
                         TOTAL_TEST_COUNT - 4 -> {
                             // Alice: Option 1, Bob: Option 2
                             testAliceVotesOption1AndBobVotesOption2(pollContent, pollSummary)
                             lock.countDown()
-                            roomFromBobPOV.endPoll(pollEventId)
+                            roomFromBobPOV.sendService().endPoll(pollEventId)
                         }
                         TOTAL_TEST_COUNT - 5 -> {
                             // Alice: Option 1, Bob: Option 2 [poll is ended]
                             testEndedPoll(pollSummary)
                             lock.countDown()
-                            roomFromAlicePOV.voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.get(1)?.id ?: "")
+                            roomFromAlicePOV.sendService().voteToPoll(pollEventId, pollContent.getBestPollCreationInfo()?.answers?.get(1)?.id ?: "")
                         }
                         TOTAL_TEST_COUNT - 6 -> {
                             // Alice: Option 1 (ignore change), Bob: Option 2 [poll is ended]
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt
index ee44af58..3864ea1c 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt
@@ -30,6 +30,7 @@ import org.matrix.android.sdk.InstrumentedTest
 import org.matrix.android.sdk.api.extensions.orFalse
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.getRoom
 import org.matrix.android.sdk.api.session.room.model.message.MessageContent
 import org.matrix.android.sdk.api.session.room.timeline.Timeline
 import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
@@ -74,7 +75,7 @@ class TimelineForwardPaginationTest : InstrumentedTest {
 
         // Alice clear the cache and restart the sync
         commonTestHelper.clearCacheAndSync(aliceSession)
-        val aliceTimeline = roomFromAlicePOV.createTimeline(null, TimelineSettings(30))
+        val aliceTimeline = roomFromAlicePOV.timelineService().createTimeline(null, TimelineSettings(30))
         aliceTimeline.start()
 
         // Alice sees the 10 last message of the room, and can only navigate BACKWARD
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt
index c6d40bca..5d09b74e 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt
@@ -28,6 +28,7 @@ import org.matrix.android.sdk.InstrumentedTest
 import org.matrix.android.sdk.api.extensions.orFalse
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.getRoom
 import org.matrix.android.sdk.api.session.room.model.message.MessageContent
 import org.matrix.android.sdk.api.session.room.timeline.Timeline
 import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
@@ -62,7 +63,7 @@ class TimelinePreviousLastForwardTest : InstrumentedTest {
         val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
         val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
 
-        val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(30))
+        val bobTimeline = roomFromBobPOV.timelineService().createTimeline(null, TimelineSettings(30))
         bobTimeline.start()
 
         run {
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt
index 53f76f1c..251b2c61 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt
@@ -28,6 +28,7 @@ import org.matrix.android.sdk.InstrumentedTest
 import org.matrix.android.sdk.api.extensions.orFalse
 import org.matrix.android.sdk.api.session.events.model.isTextMessage
 import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.getRoom
 import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
 import org.matrix.android.sdk.api.session.room.timeline.Timeline
 import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
@@ -65,7 +66,7 @@ class TimelineSimpleBackPaginationTest : InstrumentedTest {
                 message,
                 numberOfMessagesToSent)
 
-        val bobTimeline = roomFromBobPOV.createTimeline(null, TimelineSettings(30))
+        val bobTimeline = roomFromBobPOV.timelineService().createTimeline(null, TimelineSettings(30))
         bobTimeline.start()
 
         commonTestHelper.waitWithLatch(timeout = TestConstants.timeOutMillis * 10) {
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt
index ce02b2b5..02430dda 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt
@@ -27,6 +27,7 @@ import org.junit.runners.MethodSorters
 import org.matrix.android.sdk.InstrumentedTest
 import org.matrix.android.sdk.api.extensions.orFalse
 import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.getRoom
 import org.matrix.android.sdk.api.session.room.model.message.MessageContent
 import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
 import org.matrix.android.sdk.common.CommonTestHelper
@@ -71,7 +72,7 @@ class TimelineWithManyMembersTest : InstrumentedTest {
         for (index in 1 until cryptoTestData.sessions.size) {
             val session = cryptoTestData.sessions[index]
             val roomForCurrentMember = session.getRoom(cryptoTestData.roomId)!!
-            val timelineForCurrentMember = roomForCurrentMember.createTimeline(null, TimelineSettings(30))
+            val timelineForCurrentMember = roomForCurrentMember.timelineService().createTimeline(null, TimelineSettings(30))
             timelineForCurrentMember.start()
 
             session.startSync(true)
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt
index fa07cf5a..ab0bbe7f 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt
@@ -24,6 +24,7 @@ import org.junit.runners.JUnit4
 import org.junit.runners.MethodSorters
 import org.matrix.android.sdk.InstrumentedTest
 import org.matrix.android.sdk.api.extensions.orFalse
+import org.matrix.android.sdk.api.session.getRoom
 import org.matrix.android.sdk.api.session.search.SearchResult
 import org.matrix.android.sdk.common.CommonTestHelper
 import org.matrix.android.sdk.common.CryptoTestData
@@ -59,9 +60,10 @@ class SearchMessagesTest : InstrumentedTest {
     fun sendTextMessageAndSearchPartOfItUsingRoom() {
         doTest { cryptoTestData ->
             cryptoTestData.firstSession
-                    .getRoom(cryptoTestData.roomId)!!
+                    .searchService()
                     .search(
                             searchTerm = "lore",
+                            roomId = cryptoTestData.roomId,
                             limit = 10,
                             includeProfile = true,
                             afterLimit = 0,
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt
index 3b0f7586..b9760c1b 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt
@@ -29,6 +29,7 @@ import org.junit.runners.MethodSorters
 import org.matrix.android.sdk.InstrumentedTest
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.room.getStateEvent
 import org.matrix.android.sdk.api.session.room.model.GuestAccess
 import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
 import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent
@@ -147,7 +148,7 @@ class SpaceCreationTest : InstrumentedTest {
         // create a room
         var firstChild: String? = null
         commonTestHelper.waitWithLatch {
-            firstChild = aliceSession.createRoom(CreateRoomParams().apply {
+            firstChild = aliceSession.roomService().createRoom(CreateRoomParams().apply {
                 this.name = "FirstRoom"
                 this.topic = "Description of first room"
                 this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
@@ -162,7 +163,7 @@ class SpaceCreationTest : InstrumentedTest {
 
         var secondChild: String? = null
         commonTestHelper.waitWithLatch {
-            secondChild = aliceSession.createRoom(CreateRoomParams().apply {
+            secondChild = aliceSession.roomService().createRoom(CreateRoomParams().apply {
                 this.name = "SecondRoom"
                 this.topic = "Description of second room"
                 this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt
index 50e4a6fe..d2c8b52f 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt
@@ -35,6 +35,9 @@ import org.matrix.android.sdk.api.session.Session
 import org.matrix.android.sdk.api.session.events.model.EventType
 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.getRoom
+import org.matrix.android.sdk.api.session.getRoomSummary
+import org.matrix.android.sdk.api.session.room.getStateEvent
 import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
 import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
@@ -68,7 +71,7 @@ class SpaceHierarchyTest : InstrumentedTest {
 
         var roomId = ""
         commonTestHelper.waitWithLatch {
-            roomId = session.createRoom(CreateRoomParams().apply { name = "General" })
+            roomId = session.roomService().createRoom(CreateRoomParams().apply { name = "General" })
             it.countDown()
         }
 
@@ -203,27 +206,27 @@ class SpaceHierarchyTest : InstrumentedTest {
 
         var orphan1 = ""
         commonTestHelper.waitWithLatch {
-            orphan1 = session.createRoom(CreateRoomParams().apply { name = "O1" })
+            orphan1 = session.roomService().createRoom(CreateRoomParams().apply { name = "O1" })
             it.countDown()
         }
 
         var orphan2 = ""
         commonTestHelper.waitWithLatch {
-            orphan2 = session.createRoom(CreateRoomParams().apply { name = "O2" })
+            orphan2 = session.roomService().createRoom(CreateRoomParams().apply { name = "O2" })
             it.countDown()
         }
 
-        val allRooms = session.getRoomSummaries(roomSummaryQueryParams { excludeType = listOf(RoomType.SPACE) })
+        val allRooms = session.roomService().getRoomSummaries(roomSummaryQueryParams { excludeType = listOf(RoomType.SPACE) })
 
         assertEquals("Unexpected number of rooms", 9, allRooms.size)
 
-        val orphans = session.getFlattenRoomSummaryChildrenOf(null)
+        val orphans = session.roomService().getFlattenRoomSummaryChildrenOf(null)
 
         assertEquals("Unexpected number of orphan rooms", 2, orphans.size)
         assertTrue("O1 should be an orphan", orphans.any { it.roomId == orphan1 })
         assertTrue("O2 should be an orphan ${orphans.map { it.name }}", orphans.any { it.roomId == orphan2 })
 
-        val aChildren = session.getFlattenRoomSummaryChildrenOf(spaceAInfo.spaceId)
+        val aChildren = session.roomService().getFlattenRoomSummaryChildrenOf(spaceAInfo.spaceId)
 
         assertEquals("Unexpected number of flatten child rooms", 4, aChildren.size)
         assertTrue("A1 should be a child of A", aChildren.any { it.name == "A1" })
@@ -233,13 +236,13 @@ class SpaceHierarchyTest : InstrumentedTest {
 
         // Add a non canonical child and check that it does not appear as orphan
         commonTestHelper.waitWithLatch {
-            val a3 = session.createRoom(CreateRoomParams().apply { name = "A3" })
+            val a3 = session.roomService().createRoom(CreateRoomParams().apply { name = "A3" })
             spaceA!!.addChildren(a3, viaServers, null, false)
             it.countDown()
         }
 
         Thread.sleep(6_000)
-        val orphansUpdate = session.getRoomSummaries(roomSummaryQueryParams {
+        val orphansUpdate = session.roomService().getRoomSummaries(roomSummaryQueryParams {
             activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null)
         })
         assertEquals("Unexpected number of orphan rooms ${orphansUpdate.map { it.name }}", 2, orphansUpdate.size)
@@ -279,7 +282,7 @@ class SpaceHierarchyTest : InstrumentedTest {
 
         // A -> C -> A
 
-        val aChildren = session.getFlattenRoomSummaryChildrenOf(spaceAInfo.spaceId)
+        val aChildren = session.roomService().getFlattenRoomSummaryChildrenOf(spaceAInfo.spaceId)
 
         assertEquals("Unexpected number of flatten child rooms ${aChildren.map { it.name }}", 4, aChildren.size)
         assertTrue("A1 should be a child of A", aChildren.any { it.name == "A1" })
@@ -319,7 +322,7 @@ class SpaceHierarchyTest : InstrumentedTest {
 
         commonTestHelper.waitWithLatch { latch ->
 
-            val flatAChildren = session.getFlattenRoomSummaryChildrenOfLive(spaceAInfo.spaceId)
+            val flatAChildren = session.roomService().getFlattenRoomSummaryChildrenOfLive(spaceAInfo.spaceId)
             val childObserver = object : Observer<List<RoomSummary>> {
                 override fun onChanged(children: List<RoomSummary>?) {
 //                    Log.d("## TEST", "Space A flat children update : ${children?.map { it.name }}")
@@ -346,7 +349,7 @@ class SpaceHierarchyTest : InstrumentedTest {
         val bRoomId = spaceBInfo.roomIds.first()
 
         commonTestHelper.waitWithLatch { latch ->
-            val flatAChildren = session.getFlattenRoomSummaryChildrenOfLive(spaceAInfo.spaceId)
+            val flatAChildren = session.roomService().getFlattenRoomSummaryChildrenOfLive(spaceAInfo.spaceId)
             val childObserver = object : Observer<List<RoomSummary>> {
                 override fun onChanged(children: List<RoomSummary>?) {
                     System.out.println("## TEST | Space A flat children update : ${children?.map { it.name }}")
@@ -359,7 +362,7 @@ class SpaceHierarchyTest : InstrumentedTest {
             }
 
             // part from b room
-            session.leaveRoom(bRoomId)
+            session.roomService().leaveRoom(bRoomId)
             // The room should have disapear from flat children
             flatAChildren.observeForever(childObserver)
         }
@@ -385,7 +388,7 @@ class SpaceHierarchyTest : InstrumentedTest {
             val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
 
             roomIds = childInfo.map { entry ->
-                session.createRoom(CreateRoomParams().apply { name = entry.first })
+                session.roomService().createRoom(CreateRoomParams().apply { name = entry.first })
             }
             roomIds.forEachIndexed { index, roomId ->
                 syncedSpace!!.addChildren(roomId, viaServers, null, childInfo[index].second)
@@ -414,8 +417,9 @@ class SpaceHierarchyTest : InstrumentedTest {
             roomIds =
                     childInfo.map { entry ->
                         val homeServerCapabilities = session
+                                .homeServerCapabilitiesService()
                                 .getHomeServerCapabilities()
-                        session.createRoom(CreateRoomParams().apply {
+                        session.roomService().createRoom(CreateRoomParams().apply {
                             name = entry.first
                             this.featurePreset = RestrictedRoomPreset(
                                     homeServerCapabilities,
@@ -496,22 +500,22 @@ class SpaceHierarchyTest : InstrumentedTest {
         ))
 
         commonTestHelper.runBlockingTest {
-            aliceSession.getRoom(spaceAInfo.spaceId)!!.invite(bobSession.myUserId, null)
+            aliceSession.getRoom(spaceAInfo.spaceId)!!.membershipService().invite(bobSession.myUserId, null)
         }
 
         commonTestHelper.runBlockingTest {
-            bobSession.joinRoom(spaceAInfo.spaceId, null, emptyList())
+            bobSession.roomService().joinRoom(spaceAInfo.spaceId, null, emptyList())
         }
 
         var bobRoomId = ""
         commonTestHelper.waitWithLatch {
-            bobRoomId = bobSession.createRoom(CreateRoomParams().apply { name = "A Bob Room" })
-            bobSession.getRoom(bobRoomId)!!.invite(aliceSession.myUserId)
+            bobRoomId = bobSession.roomService().createRoom(CreateRoomParams().apply { name = "A Bob Room" })
+            bobSession.getRoom(bobRoomId)!!.membershipService().invite(aliceSession.myUserId)
             it.countDown()
         }
 
         commonTestHelper.runBlockingTest {
-            aliceSession.joinRoom(bobRoomId)
+            aliceSession.roomService().joinRoom(bobRoomId)
         }
 
         commonTestHelper.waitWithLatch { latch ->
@@ -551,7 +555,7 @@ class SpaceHierarchyTest : InstrumentedTest {
                     ?.setUserPowerLevel(aliceSession.myUserId, Role.Admin.value)
                     ?.toContent()
 
-            room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, stateKey = "", newPowerLevelsContent!!)
+            room.stateService().sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, stateKey = "", newPowerLevelsContent!!)
             it.countDown()
         }
 
diff --git a/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/CurlLoggingInterceptor.kt b/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/CurlLoggingInterceptor.kt
index 3add757e..6dd3553d 100644
--- a/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/CurlLoggingInterceptor.kt
+++ b/matrix-sdk-android/src/debug/java/org/matrix/android/sdk/internal/network/interceptors/CurlLoggingInterceptor.kt
@@ -37,7 +37,7 @@ import javax.inject.Inject
  */
 @MatrixScope
 internal class CurlLoggingInterceptor @Inject constructor() :
-    Interceptor {
+        Interceptor {
 
     /**
      * Set any additional curl command options (see 'curl --help').
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt
index e7b4b766..217f7e3d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt
@@ -43,7 +43,8 @@ import javax.inject.Inject
 
 /**
  * This is the main entry point to the matrix sdk.
- * To get the singleton instance, use getInstance static method.
+ * <br/>
+ * See [Companion.createInstance] to create an instance. The app should create and manage the instance itself.
  */
 class Matrix private constructor(context: Context, matrixConfiguration: MatrixConfiguration) {
 
@@ -74,9 +75,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
 
     fun getUserAgent() = userAgentHolder.userAgent
 
-    fun authenticationService(): AuthenticationService {
-        return authenticationService
-    }
+    fun authenticationService() = authenticationService
 
     fun rawService() = rawService
 
@@ -84,9 +83,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
 
     fun homeServerHistoryService() = homeServerHistoryService
 
-    fun legacySessionImporter(): LegacySessionImporter {
-        return legacySessionImporter
-    }
+    fun legacySessionImporter() = legacySessionImporter
 
     fun workerFactory(): WorkerFactory = matrixWorkerFactory
 
@@ -143,6 +140,9 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
             return instance
         }
 
+        /**
+         * @return a String with details about the Matrix SDK version
+         */
         fun getSdkVersion(): String {
             return BuildConfig.SDK_VERSION + " (" + BuildConfig.GIT_SDK_REVISION + ")"
         }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/UrlAndName.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/UrlAndName.kt
new file mode 100644
index 00000000..ca0123b8
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/UrlAndName.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * 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 org.matrix.android.sdk.api.auth
+
+data class UrlAndName(
+        val url: String,
+        val name: String
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/converter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/converter.kt
new file mode 100644
index 00000000..1a4c1ee5
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/converter.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * 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 org.matrix.android.sdk.api.auth
+
+import org.matrix.android.sdk.api.auth.data.LocalizedFlowDataLoginTerms
+import org.matrix.android.sdk.api.auth.registration.TermPolicies
+
+/**
+ * This method extract the policies from the login terms parameter, regarding the user language.
+ * For each policy, if user language is not found, the default language is used and if not found, the first url and name are used (not predictable)
+ *
+ * Example of Data:
+ * <pre>
+ * "m.login.terms": {
+ *       "policies": {
+ *         "privacy_policy": {
+ *           "version": "1.0",
+ *           "en": {
+ *             "url": "http:\/\/matrix.org\/_matrix\/consent?v=1.0",
+ *             "name": "Terms and Conditions"
+ *           }
+ *         }
+ *       }
+ *     }
+ *</pre>
+ *
+ * @param userLanguage the user language
+ * @param defaultLanguage the default language to use if the user language is not found for a policy in registrationFlowResponse
+ */
+fun TermPolicies.toLocalizedLoginTerms(userLanguage: String,
+                                       defaultLanguage: String = "en"): List<LocalizedFlowDataLoginTerms> {
+    val result = ArrayList<LocalizedFlowDataLoginTerms>()
+
+    val policies = get("policies")
+    if (policies is Map<*, *>) {
+        policies.keys.forEach { policyName ->
+            val localizedFlowDataLoginTermsPolicyName = policyName as String
+            var localizedFlowDataLoginTermsVersion: String? = null
+            var localizedFlowDataLoginTermsLocalizedUrl: String? = null
+            var localizedFlowDataLoginTermsLocalizedName: String? = null
+
+            val policy = policies[policyName]
+
+            // Enter this policy
+            if (policy is Map<*, *>) {
+                // Version
+                localizedFlowDataLoginTermsVersion = policy["version"] as String?
+
+                var userLanguageUrlAndName: UrlAndName? = null
+                var defaultLanguageUrlAndName: UrlAndName? = null
+                var firstUrlAndName: UrlAndName? = null
+
+                // Search for language
+                policy.keys.forEach { policyKey ->
+                    when (policyKey) {
+                        "version"       -> Unit // Ignore
+                        userLanguage    -> {
+                            // We found the data for the user language
+                            userLanguageUrlAndName = extractUrlAndName(policy[policyKey])
+                        }
+                        defaultLanguage -> {
+                            // We found default language
+                            defaultLanguageUrlAndName = extractUrlAndName(policy[policyKey])
+                        }
+                        else            -> {
+                            if (firstUrlAndName == null) {
+                                // Get at least some data
+                                firstUrlAndName = extractUrlAndName(policy[policyKey])
+                            }
+                        }
+                    }
+                }
+
+                // Copy found language data by priority
+                when {
+                    userLanguageUrlAndName != null    -> {
+                        localizedFlowDataLoginTermsLocalizedUrl = userLanguageUrlAndName!!.url
+                        localizedFlowDataLoginTermsLocalizedName = userLanguageUrlAndName!!.name
+                    }
+                    defaultLanguageUrlAndName != null -> {
+                        localizedFlowDataLoginTermsLocalizedUrl = defaultLanguageUrlAndName!!.url
+                        localizedFlowDataLoginTermsLocalizedName = defaultLanguageUrlAndName!!.name
+                    }
+                    firstUrlAndName != null           -> {
+                        localizedFlowDataLoginTermsLocalizedUrl = firstUrlAndName!!.url
+                        localizedFlowDataLoginTermsLocalizedName = firstUrlAndName!!.name
+                    }
+                }
+            }
+
+            result.add(LocalizedFlowDataLoginTerms(
+                    policyName = localizedFlowDataLoginTermsPolicyName,
+                    version = localizedFlowDataLoginTermsVersion,
+                    localizedUrl = localizedFlowDataLoginTermsLocalizedUrl,
+                    localizedName = localizedFlowDataLoginTermsLocalizedName
+            ))
+        }
+    }
+
+    return result
+}
+
+private fun extractUrlAndName(policyData: Any?): UrlAndName? {
+    if (policyData is Map<*, *>) {
+        val url = policyData["url"] as String?
+        val name = policyData["name"] as String?
+
+        if (url != null && name != null) {
+            return UrlAndName(url, name)
+        }
+    }
+    return null
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt
index 621253fa..0cda6449 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt
@@ -16,6 +16,8 @@
 
 package org.matrix.android.sdk.api.auth.registration
 
+import org.matrix.android.sdk.api.util.JsonDict
+
 /**
  * Set of methods to be able to create an account on a homeserver.
  *
@@ -73,6 +75,13 @@ interface RegistrationWizard {
      */
     suspend fun dummy(): RegistrationResult
 
+    /**
+     * Perform custom registration stage by sending a custom JsonDict.
+     * Current registration "session" param will be included into authParams by default.
+     * The authParams should contain at least one entry "type" with a String value.
+     */
+    suspend fun registrationCustom(authParams: JsonDict): RegistrationResult
+
     /**
      * Perform the "m.login.email.identity" or "m.login.msisdn" stage.
      *
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/GlobalError.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/GlobalError.kt
index b5165b66..5b4896f9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/GlobalError.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/GlobalError.kt
@@ -23,5 +23,10 @@ sealed class GlobalError {
     data class InvalidToken(val softLogout: Boolean) : GlobalError()
     data class ConsentNotGivenError(val consentUri: String) : GlobalError()
     data class CertificateError(val fingerprint: Fingerprint) : GlobalError()
+
+    /**
+     * The SDK requires the app (which should request the user) to perform an initial sync.
+     */
+    data class InitialSyncRequest(val reason: InitialSyncRequestReason) : GlobalError()
     object ExpiredAccount : GlobalError()
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/InitialSyncRequestReason.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/InitialSyncRequestReason.kt
new file mode 100644
index 00000000..ebe07823
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/InitialSyncRequestReason.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * 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 org.matrix.android.sdk.api.failure
+
+/**
+ * This enum provide the reason why the SDK request an initial sync to the application
+ */
+enum class InitialSyncRequestReason {
+    /**
+     * The list of ignored users has changed, and at least one user who was ignored is not ignored anymore
+     */
+    IGNORED_USERS_LIST_CHANGE,
+}
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 be924e20..19502f0b 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
@@ -24,10 +24,8 @@ import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
 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.account.AccountService
 import org.matrix.android.sdk.api.session.accountdata.SessionAccountDataService
-import org.matrix.android.sdk.api.session.cache.CacheService
 import org.matrix.android.sdk.api.session.call.CallSignalingService
 import org.matrix.android.sdk.api.session.content.ContentUploadStateTracker
 import org.matrix.android.sdk.api.session.content.ContentUrlResolver
@@ -47,6 +45,7 @@ import org.matrix.android.sdk.api.session.permalinks.PermalinkService
 import org.matrix.android.sdk.api.session.presence.PresenceService
 import org.matrix.android.sdk.api.session.profile.ProfileService
 import org.matrix.android.sdk.api.session.pushers.PushersService
+import org.matrix.android.sdk.api.session.pushrules.PushRuleService
 import org.matrix.android.sdk.api.session.room.RoomDirectoryService
 import org.matrix.android.sdk.api.session.room.RoomService
 import org.matrix.android.sdk.api.session.search.SearchService
@@ -68,26 +67,7 @@ import org.matrix.android.sdk.api.session.widgets.WidgetService
  * This interface defines interactions with a session.
  * An instance of a session will be provided by the SDK.
  */
-interface Session :
-        RoomService,
-        RoomDirectoryService,
-        GroupService,
-        UserService,
-        CacheService,
-        SignOutService,
-        FilterService,
-        TermsService,
-        EventService,
-        ProfileService,
-        PresenceService,
-        PushRuleService,
-        PushersService,
-        SyncStatusService,
-        HomeServerCapabilitiesService,
-        SecureStorageService,
-        AccountService,
-        ToDeviceService,
-        EventStreamService {
+interface Session {
 
     val coroutineDispatchers: MatrixCoroutineDispatchers
 
@@ -144,6 +124,11 @@ interface Session :
      */
     fun stopSync()
 
+    /**
+     * Clear cache of the session
+     */
+    suspend fun clearCache()
+
     /**
      * This method allows to listen the sync state.
      * @return a [LiveData] of [SyncState].
@@ -206,6 +191,96 @@ interface Session :
      */
     fun identityService(): IdentityService
 
+    /**
+     * Returns the HomeServerCapabilities service associated with the session
+     */
+    fun homeServerCapabilitiesService(): HomeServerCapabilitiesService
+
+    /**
+     * Returns the RoomService associated with the session
+     */
+    fun roomService(): RoomService
+
+    /**
+     * Returns the RoomDirectoryService associated with the session
+     */
+    fun roomDirectoryService(): RoomDirectoryService
+
+    /**
+     * Returns the GroupService associated with the session
+     */
+    fun groupService(): GroupService
+
+    /**
+     * Returns the UserService associated with the session
+     */
+    fun userService(): UserService
+
+    /**
+     * Returns the SignOutService associated with the session
+     */
+    fun signOutService(): SignOutService
+
+    /**
+     * Returns the FilterService associated with the session
+     */
+    fun filterService(): FilterService
+
+    /**
+     * Returns the PushRuleService associated with the session
+     */
+    fun pushRuleService(): PushRuleService
+
+    /**
+     * Returns the PushersService associated with the session
+     */
+    fun pushersService(): PushersService
+
+    /**
+     * Returns the EventService associated with the session
+     */
+    fun eventService(): EventService
+
+    /**
+     * Returns the TermsService associated with the session
+     */
+    fun termsService(): TermsService
+
+    /**
+     * Returns the SyncStatusService associated with the session
+     */
+    fun syncStatusService(): SyncStatusService
+
+    /**
+     * Returns the SecureStorageService associated with the session
+     */
+    fun secureStorageService(): SecureStorageService
+
+    /**
+     * Returns the ProfileService associated with the session
+     */
+    fun profileService(): ProfileService
+
+    /**
+     * Returns the PresenceService associated with the session
+     */
+    fun presenceService(): PresenceService
+
+    /**
+     * Returns the AccountService associated with the session
+     */
+    fun accountService(): AccountService
+
+    /**
+     * Returns the ToDeviceService associated with the session
+     */
+    fun toDeviceService(): ToDeviceService
+
+    /**
+     * Returns the EventStreamService associated with the session
+     */
+    fun eventStreamService(): EventStreamService
+
     /**
      * Returns the widget service associated with the session
      */
@@ -266,6 +341,11 @@ interface Session :
      */
     fun accountDataService(): SessionAccountDataService
 
+    /**
+     * Returns the SharedSecretStorageService associated with the session
+     */
+    fun sharedSecretStorageService(): SharedSecretStorageService
+
     /**
      * Add a listener to the session.
      * @param listener the listener to add.
@@ -298,12 +378,11 @@ interface Session :
          * Possible cases:
          * - The access token is not valid anymore,
          * - a M_CONSENT_NOT_GIVEN error has been received from the homeserver
+         * See [GlobalError] for all the possible cases
          */
         fun onGlobalError(session: Session, globalError: GlobalError) = Unit
     }
 
-    val sharedSecretStorageService: SharedSecretStorageService
-
     fun getUiaSsoFallbackUrl(authenticationSessionId: String): String
 
     /**
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/SessionExtensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/SessionExtensions.kt
new file mode 100644
index 00000000..aeb0e7e4
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/SessionExtensions.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * 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 org.matrix.android.sdk.api.session
+
+import org.matrix.android.sdk.api.session.room.Room
+import org.matrix.android.sdk.api.session.room.model.RoomSummary
+import org.matrix.android.sdk.api.session.user.model.User
+
+/**
+ * Get a room using the RoomService of a Session
+ */
+fun Session.getRoom(roomId: String): Room? = roomService().getRoom(roomId)
+
+/**
+ * Get a room summary using the RoomService of a Session
+ */
+fun Session.getRoomSummary(roomIdOrAlias: String): RoomSummary? = roomService().getRoomSummary(roomIdOrAlias)
+
+/**
+ * Get a user using the UserService of a Session
+ */
+fun Session.getUser(userId: String): User? = userService().getUser(userId)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRequestCancellation.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRequestCancellation.kt
index 74ca7304..ad11ef9a 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRequestCancellation.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRequestCancellation.kt
@@ -46,8 +46,9 @@ data class IncomingRequestCancellation(
          * Factory
          *
          * @param event the event
+         * @param currentTimeMillis the current time in milliseconds
          */
-        fun fromEvent(event: Event): IncomingRequestCancellation? {
+        fun fromEvent(event: Event, currentTimeMillis: Long): IncomingRequestCancellation? {
             return event.getClearContent()
                     .toModel<ShareRequestCancellation>()
                     ?.let {
@@ -55,7 +56,7 @@ data class IncomingRequestCancellation(
                                 userId = event.senderId,
                                 deviceId = it.requestingDeviceId,
                                 requestId = it.requestId,
-                                localCreationTimestamp = event.ageLocalTs ?: System.currentTimeMillis()
+                                localCreationTimestamp = event.ageLocalTs ?: currentTimeMillis
                         )
                     }
         }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRoomKeyRequest.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRoomKeyRequest.kt
index 45b0926d..0b2c3228 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRoomKeyRequest.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRoomKeyRequest.kt
@@ -64,8 +64,9 @@ data class IncomingRoomKeyRequest(
          * Factory
          *
          * @param event the event
+         * @param currentTimeMillis the current time in milliseconds
          */
-        fun fromEvent(event: Event): IncomingRoomKeyRequest? {
+        fun fromEvent(event: Event, currentTimeMillis: Long): IncomingRoomKeyRequest? {
             return event.getClearContent()
                     .toModel<RoomKeyShareRequest>()
                     ?.let {
@@ -74,7 +75,7 @@ data class IncomingRoomKeyRequest(
                                 deviceId = it.requestingDeviceId,
                                 requestId = it.requestId,
                                 requestBody = it.body ?: RoomKeyRequestBody(),
-                                localCreationTimestamp = event.ageLocalTs ?: System.currentTimeMillis()
+                                localCreationTimestamp = event.ageLocalTs ?: currentTimeMillis
                         )
                     }
         }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingSecretShareRequest.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingSecretShareRequest.kt
index 5afffef1..80f70c83 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingSecretShareRequest.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingSecretShareRequest.kt
@@ -64,8 +64,9 @@ data class IncomingSecretShareRequest(
          * Factory
          *
          * @param event the event
+         * @param currentTimeMillis the current time in milliseconds
          */
-        fun fromEvent(event: Event): IncomingSecretShareRequest? {
+        fun fromEvent(event: Event, currentTimeMillis: Long): IncomingSecretShareRequest? {
             return event.getClearContent()
                     .toModel<SecretShareRequest>()
                     ?.let {
@@ -74,7 +75,7 @@ data class IncomingSecretShareRequest(
                                 deviceId = it.requestingDeviceId,
                                 requestId = it.requestId,
                                 secretName = it.secretName,
-                                localCreationTimestamp = event.ageLocalTs ?: System.currentTimeMillis()
+                                localCreationTimestamp = event.ageLocalTs ?: currentTimeMillis
                         )
                     }
         }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt
index b9d0c0ad..ec67e4b3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt
@@ -129,11 +129,10 @@ interface VerificationService {
         private const val TEN_MINUTES_IN_MILLIS = 10 * 60 * 1000
         private const val FIVE_MINUTES_IN_MILLIS = 5 * 60 * 1000
 
-        fun isValidRequest(age: Long?): Boolean {
+        fun isValidRequest(age: Long?, currentTimeMillis: Long): Boolean {
             if (age == null) return false
-            val now = System.currentTimeMillis()
-            val tooInThePast = now - TEN_MINUTES_IN_MILLIS
-            val tooInTheFuture = now + FIVE_MINUTES_IN_MILLIS
+            val tooInThePast = currentTimeMillis - TEN_MINUTES_IN_MILLIS
+            val tooInTheFuture = currentTimeMillis + FIVE_MINUTES_IN_MILLIS
             return age in tooInThePast..tooInTheFuture
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt
index 72f8019a..e3ccbad2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt
@@ -45,9 +45,9 @@ interface FileService {
      * Result will be a decrypted file, stored in the cache folder. url parameter will be used to create unique filename to avoid name collision.
      */
     suspend fun downloadFile(fileName: String,
-                     mimeType: String?,
-                     url: String?,
-                     elementToDecrypt: ElementToDecrypt?): File
+                             mimeType: String?,
+                             url: String?,
+                             elementToDecrypt: ElementToDecrypt?): File
 
     suspend fun downloadFile(messageContent: MessageWithAttachmentContent): File =
             downloadFile(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/initsync/SyncStatusService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/initsync/SyncStatusService.kt
index 26743691..75981393 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/initsync/SyncStatusService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/initsync/SyncStatusService.kt
@@ -43,6 +43,7 @@ interface SyncStatusService {
                 val rooms: Int,
                 val toDevice: Int
         ) : IncrementalSyncStatus()
+
         object IncrementalSyncError : IncrementalSyncStatus()
         object IncrementalSyncDone : IncrementalSyncStatus()
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixLinkify.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixLinkify.kt
index 3e27da0c..c5d91940 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixLinkify.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixLinkify.kt
@@ -55,7 +55,7 @@ object MatrixLinkify {
                             MatrixPatterns.isRoomId(url) ||
                             MatrixPatterns.isGroupId(url) ||
                             MatrixPatterns.isEventId(url)) {
-                        url = PermalinkService.MATRIX_TO_URL_BASE  + url
+                        url = PermalinkService.MATRIX_TO_URL_BASE + url
                     }
                     val span = MatrixPermalinkSpan(url, callback)
                     spannable.setSpan(span, startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkData.kt
index 85291cf0..57aacc98 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkData.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkData.kt
@@ -35,21 +35,21 @@ sealed class PermalinkData {
 
     /**
      * &room_name=Team2
-        &room_avatar_url=mxc:
-         &inviter_name=bob
+    &room_avatar_url=mxc:
+    &inviter_name=bob
      */
     @Parcelize
     data class RoomEmailInviteLink(
-        val roomId: String,
-        val email: String,
-        val signUrl: String,
-        val roomName: String?,
-        val roomAvatarUrl: String?,
-        val inviterName: String?,
-        val identityServer: String,
-        val token: String,
-        val privateKey: String,
-        val roomType: String?
+            val roomId: String,
+            val email: String,
+            val signUrl: String,
+            val roomName: String?,
+            val roomAvatarUrl: String?,
+            val inviterName: String?,
+            val identityServer: String,
+            val token: String,
+            val privateKey: String,
+            val roomType: String?
     ) : PermalinkData(), Parcelable
 
     data class UserLink(val userId: String) : PermalinkData()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/Action.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Action.kt
similarity index 97%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/Action.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Action.kt
index 30289531..7790942d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/Action.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Action.kt
@@ -13,9 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
+import org.matrix.android.sdk.api.session.pushrules.rest.PushRule
 import timber.log.Timber
 
 sealed class Action {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/Condition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Condition.kt
similarity index 93%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/Condition.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Condition.kt
index 04cccf73..df5b056c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/Condition.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Condition.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
 import org.matrix.android.sdk.api.session.events.model.Event
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/ConditionResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/ConditionResolver.kt
similarity index 96%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/ConditionResolver.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/ConditionResolver.kt
index 0a7366e5..f8a930f9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/ConditionResolver.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/ConditionResolver.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
 import org.matrix.android.sdk.api.session.events.model.Event
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/ContainsDisplayNameCondition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/ContainsDisplayNameCondition.kt
similarity index 97%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/ContainsDisplayNameCondition.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/ContainsDisplayNameCondition.kt
index 7f430238..69dd14dd 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/ContainsDisplayNameCondition.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/ContainsDisplayNameCondition.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.EventType
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/EventMatchCondition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/EventMatchCondition.kt
similarity index 98%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/EventMatchCondition.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/EventMatchCondition.kt
index 65a13b4f..8875807b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/EventMatchCondition.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/EventMatchCondition.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.internal.di.MoshiProvider
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/Kind.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Kind.kt
similarity index 96%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/Kind.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Kind.kt
index 293a06af..463f3c2a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/Kind.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/Kind.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
 enum class Kind(val value: String) {
     EventMatch("event_match"),
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushEvents.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/PushEvents.kt
similarity index 88%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushEvents.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/PushEvents.kt
index 466e345c..ee460d70 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushEvents.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/PushEvents.kt
@@ -13,10 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
 import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.api.session.pushrules.rest.PushRule
 
 data class PushEvents(
         val matchedEvents: List<Pair<Event, PushRule>>,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/PushRuleService.kt
similarity index 91%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/PushRuleService.kt
index 76885d85..abbdbf81 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/PushRuleService.kt
@@ -13,12 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
 import androidx.lifecycle.LiveData
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
-import org.matrix.android.sdk.api.pushrules.rest.RuleSet
 import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.api.session.pushrules.rest.PushRule
+import org.matrix.android.sdk.api.session.pushrules.rest.RuleSet
 
 interface PushRuleService {
     /**
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RoomMemberCountCondition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RoomMemberCountCondition.kt
similarity index 94%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RoomMemberCountCondition.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RoomMemberCountCondition.kt
index 328e6dae..6973ff13 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RoomMemberCountCondition.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RoomMemberCountCondition.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.internal.session.room.RoomGetter
@@ -44,7 +44,7 @@ class RoomMemberCountCondition(
         // Parse the is field into prefix and number the first time
         val (prefix, count) = parseIsField() ?: return false
 
-        val numMembers = room.getNumberOfJoinedMembers()
+        val numMembers = room.membershipService().getNumberOfJoinedMembers()
 
         return when (prefix) {
             "<"  -> numMembers < count
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleIds.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleIds.kt
similarity index 97%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleIds.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleIds.kt
index 5b14e97d..4f35fb79 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleIds.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleIds.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
 /**
  * Known rule ids
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleScope.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleScope.kt
similarity index 92%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleScope.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleScope.kt
index 7c1edc1a..307b9db0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleScope.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleScope.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
 object RuleScope {
     const val GLOBAL = "global"
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleSetKey.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleSetKey.kt
similarity index 95%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleSetKey.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleSetKey.kt
index 5b6f6713..7b8f4c9f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleSetKey.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/RuleSetKey.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
 /**
  * Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-pushrules
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/SenderNotificationPermissionCondition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/SenderNotificationPermissionCondition.kt
similarity index 97%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/SenderNotificationPermissionCondition.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/SenderNotificationPermissionCondition.kt
index 6675fb0f..82f5023c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/SenderNotificationPermissionCondition.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/SenderNotificationPermissionCondition.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/PushCondition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushCondition.kt
similarity index 84%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/PushCondition.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushCondition.kt
index b31a1e63..1fc83295 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/PushCondition.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushCondition.kt
@@ -13,17 +13,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.api.pushrules.rest
+package org.matrix.android.sdk.api.session.pushrules.rest
 
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
-import org.matrix.android.sdk.api.pushrules.Condition
-import org.matrix.android.sdk.api.pushrules.ContainsDisplayNameCondition
-import org.matrix.android.sdk.api.pushrules.EventMatchCondition
-import org.matrix.android.sdk.api.pushrules.Kind
-import org.matrix.android.sdk.api.pushrules.RoomMemberCountCondition
-import org.matrix.android.sdk.api.pushrules.RuleIds
-import org.matrix.android.sdk.api.pushrules.SenderNotificationPermissionCondition
+import org.matrix.android.sdk.api.session.pushrules.Condition
+import org.matrix.android.sdk.api.session.pushrules.ContainsDisplayNameCondition
+import org.matrix.android.sdk.api.session.pushrules.EventMatchCondition
+import org.matrix.android.sdk.api.session.pushrules.Kind
+import org.matrix.android.sdk.api.session.pushrules.RoomMemberCountCondition
+import org.matrix.android.sdk.api.session.pushrules.RuleIds
+import org.matrix.android.sdk.api.session.pushrules.SenderNotificationPermissionCondition
 import timber.log.Timber
 
 /**
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/PushRule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushRule.kt
similarity index 94%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/PushRule.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushRule.kt
index 31d7770a..270ffb29 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/PushRule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/PushRule.kt
@@ -14,14 +14,14 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.api.pushrules.rest
+package org.matrix.android.sdk.api.session.pushrules.rest
 
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 import org.matrix.android.sdk.api.extensions.orFalse
-import org.matrix.android.sdk.api.pushrules.Action
-import org.matrix.android.sdk.api.pushrules.getActions
-import org.matrix.android.sdk.api.pushrules.toJson
+import org.matrix.android.sdk.api.session.pushrules.Action
+import org.matrix.android.sdk.api.session.pushrules.getActions
+import org.matrix.android.sdk.api.session.pushrules.toJson
 
 /**
  * Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-pushrules
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/RuleSet.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/RuleSet.kt
similarity index 93%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/RuleSet.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/RuleSet.kt
index 46f51487..5bf42b82 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/RuleSet.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/RuleSet.kt
@@ -13,12 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.api.pushrules.rest
+package org.matrix.android.sdk.api.session.pushrules.rest
 
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
-import org.matrix.android.sdk.api.pushrules.RuleIds
-import org.matrix.android.sdk.api.pushrules.RuleSetKey
+import org.matrix.android.sdk.api.session.pushrules.RuleIds
+import org.matrix.android.sdk.api.session.pushrules.RuleSetKey
 
 /**
  * Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-pushrules
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt
index be65b883..1f990f4c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt
@@ -38,33 +38,13 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineService
 import org.matrix.android.sdk.api.session.room.typing.TypingService
 import org.matrix.android.sdk.api.session.room.uploads.UploadsService
 import org.matrix.android.sdk.api.session.room.version.RoomVersionService
-import org.matrix.android.sdk.api.session.search.SearchResult
 import org.matrix.android.sdk.api.session.space.Space
 import org.matrix.android.sdk.api.util.Optional
 
 /**
  * This interface defines methods to interact within a room.
  */
-interface Room :
-        TimelineService,
-        ThreadsService,
-        ThreadsLocalService,
-        SendService,
-        DraftService,
-        ReadService,
-        TypingService,
-        AliasService,
-        TagsService,
-        MembershipService,
-        StateService,
-        UploadsService,
-        ReportingService,
-        RoomCallService,
-        RelationService,
-        RoomCryptoService,
-        RoomPushRuleService,
-        RoomAccountDataService,
-        RoomVersionService {
+interface Room {
 
     val coroutineDispatchers: MatrixCoroutineDispatchers
 
@@ -85,27 +65,102 @@ interface Room :
     fun roomSummary(): RoomSummary?
 
     /**
-     * Generic function to search a term in a room.
-     * Ref: https://matrix.org/docs/spec/client_server/latest#module-search
-     * @param searchTerm the term to search
-     * @param nextBatch the token that retrieved from the previous response. Should be provided to get the next batch of results
-     * @param orderByRecent if true, the most recent message events will return in the first places of the list
-     * @param limit the maximum number of events to return.
-     * @param beforeLimit how many events before the result are returned.
-     * @param afterLimit how many events after the result are returned.
-     * @param includeProfile requests that the server returns the historic profile information for the users that sent the events that were returned.
-     * @return The search result
+     * Use this room as a Space, if the type is correct.
      */
-    suspend fun search(searchTerm: String,
-                       nextBatch: String?,
-                       orderByRecent: Boolean,
-                       limit: Int,
-                       beforeLimit: Int,
-                       afterLimit: Int,
-                       includeProfile: Boolean): SearchResult
+    fun asSpace(): Space?
 
     /**
-     * Use this room as a Space, if the type is correct.
+     * Get the TimelineService associated to this Room
      */
-    fun asSpace(): Space?
+    fun timelineService(): TimelineService
+
+    /**
+     * Get the ThreadsService associated to this Room
+     */
+    fun threadsService(): ThreadsService
+
+    /**
+     * Get the ThreadsLocalService associated to this Room
+     */
+    fun threadsLocalService(): ThreadsLocalService
+
+    /**
+     * Get the SendService associated to this Room
+     */
+    fun sendService(): SendService
+
+    /**
+     * Get the DraftService associated to this Room
+     */
+    fun draftService(): DraftService
+
+    /**
+     * Get the ReadService associated to this Room
+     */
+    fun readService(): ReadService
+
+    /**
+     * Get the TypingService associated to this Room
+     */
+    fun typingService(): TypingService
+
+    /**
+     * Get the AliasService associated to this Room
+     */
+    fun aliasService(): AliasService
+
+    /**
+     * Get the TagsService associated to this Room
+     */
+    fun tagsService(): TagsService
+
+    /**
+     * Get the MembershipService associated to this Room
+     */
+    fun membershipService(): MembershipService
+
+    /**
+     * Get the StateService associated to this Room
+     */
+    fun stateService(): StateService
+
+    /**
+     * Get the UploadsService associated to this Room
+     */
+    fun uploadsService(): UploadsService
+
+    /**
+     * Get the ReportingService associated to this Room
+     */
+    fun reportingService(): ReportingService
+
+    /**
+     * Get the RoomCallService associated to this Room
+     */
+    fun roomCallService(): RoomCallService
+
+    /**
+     * Get the RelationService associated to this Room
+     */
+    fun relationService(): RelationService
+
+    /**
+     * Get the RoomCryptoService associated to this Room
+     */
+    fun roomCryptoService(): RoomCryptoService
+
+    /**
+     * Get the RoomPushRuleService associated to this Room
+     */
+    fun roomPushRuleService(): RoomPushRuleService
+
+    /**
+     * Get the RoomAccountDataService associated to this Room
+     */
+    fun roomAccountDataService(): RoomAccountDataService
+
+    /**
+     * Get the RoomVersionService associated to this Room
+     */
+    fun roomVersionService(): RoomVersionService
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomExtensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomExtensions.kt
new file mode 100644
index 00000000..ece9cfbf
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomExtensions.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * 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 org.matrix.android.sdk.api.session.room
+
+import org.matrix.android.sdk.api.query.QueryStringValue
+import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
+
+/**
+ * Get a TimelineEvent using the TimelineService of a Room
+ */
+fun Room.getTimelineEvent(eventId: String): TimelineEvent? =
+        timelineService().getTimelineEvent(eventId)
+
+/**
+ * Get a StateEvent using the StateService of a Room
+ */
+fun Room.getStateEvent(eventType: String, stateKey: QueryStringValue = QueryStringValue.NoCondition): Event? =
+        stateService().getStateEvent(eventType, stateKey)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/EventAnnotationsSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/EventAnnotationsSummary.kt
index 0238eb6c..0aded203 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/EventAnnotationsSummary.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/EventAnnotationsSummary.kt
@@ -15,10 +15,12 @@
  */
 package org.matrix.android.sdk.api.session.room.model
 
+import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary
+
 data class EventAnnotationsSummary(
-        val eventId: String,
         val reactionsSummary: List<ReactionAggregatedSummary> = emptyList(),
         val editSummary: EditAggregatedSummary? = null,
         val pollResponseSummary: PollResponseAggregatedSummary? = null,
-        val referencesAggregatedSummary: ReferencesAggregatedSummary? = null
+        val referencesAggregatedSummary: ReferencesAggregatedSummary? = null,
+        val liveLocationShareAggregatedSummary: LiveLocationShareAggregatedSummary? = null,
 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/ReferencesAggregatedSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/ReferencesAggregatedSummary.kt
index 31ac09ef..49ba2d5a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/ReferencesAggregatedSummary.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/ReferencesAggregatedSummary.kt
@@ -22,7 +22,6 @@ import org.matrix.android.sdk.api.session.events.model.Content
  * of all events that are referencing the 'eventId' event via the RelationType.REFERENCE
  */
 data class ReferencesAggregatedSummary(
-        val eventId: String,
         val content: Content?,
         val sourceEvents: List<String>,
         val localEchos: List<String>
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt
index 871b299f..5237b10d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt
@@ -36,10 +36,10 @@ data class RoomJoinRulesContent(
         @Json(name = "allow") val allowList: List<RoomJoinRulesAllowEntry>? = null
 ) {
     val joinRules: RoomJoinRules? = when (_joinRules) {
-        "public" -> RoomJoinRules.PUBLIC
-        "invite" -> RoomJoinRules.INVITE
-        "knock" -> RoomJoinRules.KNOCK
-        "private" -> RoomJoinRules.PRIVATE
+        "public"     -> RoomJoinRules.PUBLIC
+        "invite"     -> RoomJoinRules.INVITE
+        "knock"      -> RoomJoinRules.KNOCK
+        "private"    -> RoomJoinRules.PRIVATE
         "restricted" -> RoomJoinRules.RESTRICTED
         else         -> {
             Timber.w("Invalid value for RoomJoinRules: `$_joinRules`")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallAnswerContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallAnswerContent.kt
index 6b4d7828..67ef8578 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallAnswerContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallAnswerContent.kt
@@ -44,7 +44,7 @@ data class CallAnswerContent(
          * Capability advertisement.
          */
         @Json(name = "capabilities") val capabilities: CallCapabilities? = null
-) : CallSignalingContent  {
+) : CallSignalingContent {
 
     @JsonClass(generateAdapter = true)
     data class Answer(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallInviteContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallInviteContent.kt
index d70e63d1..24c8152f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallInviteContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallInviteContent.kt
@@ -55,7 +55,7 @@ data class CallInviteContent(
          */
         @Json(name = "capabilities") val capabilities: CallCapabilities? = null
 
-) : CallSignalingContent  {
+) : CallSignalingContent {
     @JsonClass(generateAdapter = true)
     data class Offer(
             /**
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallNegotiateContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallNegotiateContent.kt
index bbbfbe68..5c6c6cda 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallNegotiateContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallNegotiateContent.kt
@@ -47,7 +47,7 @@ data class CallNegotiateContent(
          */
         @Json(name = "version") override val version: String?
 
-        ) : CallSignalingContent  {
+) : CallSignalingContent {
     @JsonClass(generateAdapter = true)
     data class Description(
             /**
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallReplacesContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallReplacesContent.kt
index 7947b7d0..e480e013 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallReplacesContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallReplacesContent.kt
@@ -61,7 +61,7 @@ data class CallReplacesContent(
          * Required. The version of the VoIP specification this messages adheres to.
          */
         @Json(name = "version") override val version: String?
-) : CallSignalingContent  {
+) : CallSignalingContent {
 
     @JsonClass(generateAdapter = true)
     data class TargetUser(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/LiveLocationShareAggregatedSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/LiveLocationShareAggregatedSummary.kt
new file mode 100644
index 00000000..0b28d62f
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/LiveLocationShareAggregatedSummary.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * 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 org.matrix.android.sdk.api.session.room.model.livelocation
+
+import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
+
+/**
+ * Aggregation info concerning a live location share.
+ */
+data class LiveLocationShareAggregatedSummary(
+        val isActive: Boolean?,
+        val endOfLiveTimestampMillis: Long?,
+        val lastLocationDataContent: MessageBeaconLocationDataContent?,
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/FileInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/FileInfo.kt
index fa18bfd2..132b7290 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/FileInfo.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/FileInfo.kt
@@ -52,5 +52,5 @@ data class FileInfo(
  * Get the url of the encrypted thumbnail or of the thumbnail
  */
 fun FileInfo.getThumbnailUrl(): String? {
-        return thumbnailFile?.url ?: thumbnailUrl
+    return thumbnailFile?.url ?: thumbnailUrl
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/ImageInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/ImageInfo.kt
index 00992083..bd99ea69 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/ImageInfo.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/ImageInfo.kt
@@ -62,5 +62,5 @@ data class ImageInfo(
  * Get the url of the encrypted thumbnail or of the thumbnail
  */
 fun ImageInfo.getThumbnailUrl(): String? {
-        return thumbnailFile?.url ?: thumbnailUrl
+    return thumbnailFile?.url ?: thumbnailUrl
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/LiveLocationBeaconContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageBeaconInfoContent.kt
similarity index 57%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/LiveLocationBeaconContent.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageBeaconInfoContent.kt
index 106e76ea..f75704a8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/LiveLocationBeaconContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageBeaconInfoContent.kt
@@ -14,60 +14,59 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.api.session.room.model.livelocation
+package org.matrix.android.sdk.api.session.room.model.message
 
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
 import org.matrix.android.sdk.api.session.events.model.Content
-import org.matrix.android.sdk.api.session.room.model.message.LocationAsset
-import org.matrix.android.sdk.api.session.room.model.message.LocationAssetType
-import org.matrix.android.sdk.api.session.room.model.message.MessageContent
-import org.matrix.android.sdk.api.session.room.model.message.MessageLiveLocationContent
-import org.matrix.android.sdk.api.session.room.model.message.MessageType
 import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
 
+/**
+ * Content of the state event of type
+ * [EventType.STATE_ROOM_BEACON_INFO][org.matrix.android.sdk.api.session.events.model.EventType.STATE_ROOM_BEACON_INFO]
+ *
+ * It contains general info related to a live location share.
+ * Locations are sent in a different message related to the state event.
+ * See [MessageBeaconLocationDataContent][org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent]
+ */
 @JsonClass(generateAdapter = true)
-data class LiveLocationBeaconContent(
+data class MessageBeaconInfoContent(
         /**
          * Local message type, not from server
          */
         @Transient
-        override val msgType: String = MessageType.MSGTYPE_LIVE_LOCATION_STATE,
+        override val msgType: String = MessageType.MSGTYPE_BEACON_INFO,
 
         @Json(name = "body") override val body: String = "",
         @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
         @Json(name = "m.new_content") override val newContent: Content? = null,
 
         /**
-         * Indicates user's intent to share ephemeral location.
+         * Optional description of the beacon.
          */
-        @Json(name = "org.matrix.msc3672.beacon_info") val unstableBeaconInfo: BeaconInfo? = null,
-        @Json(name = "m.beacon_info") val beaconInfo: BeaconInfo? = null,
+        @Json(name = "description") val description: String? = null,
         /**
-         * Beacon creation timestamp.
+         * Beacon should be considered as inactive after this timeout as milliseconds.
          */
-        @Json(name = "org.matrix.msc3488.ts") val unstableTimestampAsMilliseconds: Long? = null,
-        @Json(name = "m.ts") val timestampAsMilliseconds: Long? = null,
+        @Json(name = "timeout") val timeout: Long? = null,
         /**
-         * Live location asset type.
+         * Should be set true to start sharing beacon.
          */
-        @Json(name = "org.matrix.msc3488.asset") val unstableLocationAsset: LocationAsset = LocationAsset(LocationAssetType.SELF),
-        @Json(name = "m.asset") val locationAsset: LocationAsset? = null,
+        @Json(name = "live") val isLive: Boolean? = null,
 
         /**
-         * Client side tracking of the last location
+         * Beacon creation timestamp.
          */
-        var lastLocationContent: MessageLiveLocationContent? = null,
-
+        @Json(name = "org.matrix.msc3488.ts") val unstableTimestampMillis: Long? = null,
+        @Json(name = "m.ts") val timestampMillis: Long? = null,
         /**
-         * Client side tracking of whether the beacon has timed out.
+         * Live location asset type.
          */
-        var hasTimedOut: Boolean = false
+        @Json(name = "org.matrix.msc3488.asset") val unstableLocationAsset: LocationAsset = LocationAsset(LocationAssetType.SELF),
+        @Json(name = "m.asset") val locationAsset: LocationAsset? = null,
 ) : MessageContent {
 
-    fun getBestBeaconInfo() = beaconInfo ?: unstableBeaconInfo
-
-    fun getBestTimestampAsMilliseconds() = timestampAsMilliseconds ?: unstableTimestampAsMilliseconds
+    fun getBestTimestampMillis() = timestampMillis ?: unstableTimestampMillis
 
     fun getBestLocationAsset() = locationAsset ?: unstableLocationAsset
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLiveLocationContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageBeaconLocationDataContent.kt
similarity index 72%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLiveLocationContent.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageBeaconLocationDataContent.kt
index 548dc853..4a4ef46b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLiveLocationContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageBeaconLocationDataContent.kt
@@ -21,13 +21,21 @@ import com.squareup.moshi.JsonClass
 import org.matrix.android.sdk.api.session.events.model.Content
 import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
 
+/**
+ * Content of the state event of type
+ * [EventType.BEACON_LOCATION_DATA][org.matrix.android.sdk.api.session.events.model.EventType.BEACON_LOCATION_DATA]
+ *
+ * It contains location data related to a live location share.
+ * It is related to the state event that originally started the live.
+ * See [MessageBeaconInfoContent][org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent]
+ */
 @JsonClass(generateAdapter = true)
-data class MessageLiveLocationContent(
+data class MessageBeaconLocationDataContent(
         /**
          * Local message type, not from server
          */
         @Transient
-        override val msgType: String = MessageType.MSGTYPE_LIVE_LOCATION,
+        override val msgType: String = MessageType.MSGTYPE_BEACON_LOCATION_DATA,
 
         @Json(name = "body") override val body: String = "",
         @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@@ -42,11 +50,11 @@ data class MessageLiveLocationContent(
         /**
          * Exact time that the data in the event refers to (milliseconds since the UNIX epoch)
          */
-        @Json(name = "org.matrix.msc3488.ts") val unstableTimestampAsMilliseconds: Long? = null,
-        @Json(name = "m.ts") val timestampAsMilliseconds: Long? = null
+        @Json(name = "org.matrix.msc3488.ts") val unstableTimestampMillis: Long? = null,
+        @Json(name = "m.ts") val timestampMillis: Long? = null
 ) : MessageContent {
 
     fun getBestLocationInfo() = locationInfo ?: unstableLocationInfo
 
-    fun getBestTimestampAsMilliseconds() = timestampAsMilliseconds ?: unstableTimestampAsMilliseconds
+    fun getBestTimestampMillis() = timestampMillis ?: unstableTimestampMillis
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt
index 2052133b..19cb2043 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt
@@ -49,8 +49,8 @@ data class MessageLocationContent(
         /**
          * Exact time that the data in the event refers to (milliseconds since the UNIX epoch)
          */
-        @Json(name = "org.matrix.msc3488.ts") val unstableTs: Long? = null,
-        @Json(name = "m.ts") val ts: Long? = null,
+        @Json(name = "org.matrix.msc3488.ts") val unstableTimestampMillis: Long? = null,
+        @Json(name = "m.ts") val timestampMillis: Long? = null,
         @Json(name = "org.matrix.msc1767.text") val unstableText: String? = null,
         @Json(name = "m.text") val text: String? = null,
         /**
@@ -66,7 +66,7 @@ data class MessageLocationContent(
 
     fun getBestLocationInfo() = locationInfo ?: unstableLocationInfo
 
-    fun getBestTs() = ts ?: unstableTs
+    fun getBestTimestampMillis() = timestampMillis ?: unstableTimestampMillis
 
     fun getBestText() = text ?: unstableText
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt
index 106bf2e0..b12d9ed6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt
@@ -41,6 +41,6 @@ object MessageType {
     const val MSGTYPE_SNOWFALL = "io.element.effect.snowfall"
 
     // Fake message types for live location events to be able to inherit them from MessageContent
-    const val MSGTYPE_LIVE_LOCATION_STATE = "org.matrix.android.sdk.livelocation.state"
-    const val MSGTYPE_LIVE_LOCATION = "org.matrix.android.sdk.livelocation"
+    const val MSGTYPE_BEACON_INFO = "org.matrix.android.sdk.beacon.info"
+    const val MSGTYPE_BEACON_LOCATION_DATA = "org.matrix.android.sdk.beacon.location.data"
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVerificationRequestContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVerificationRequestContent.kt
index b2b3cdac..a0699831 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVerificationRequestContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVerificationRequestContent.kt
@@ -24,7 +24,7 @@ import org.matrix.android.sdk.internal.crypto.verification.VerificationInfoReque
 
 @JsonClass(generateAdapter = true)
 data class MessageVerificationRequestContent(
-        @Json(name = MessageContent.MSG_TYPE_JSON_KEY)override val msgType: String = MessageType.MSGTYPE_VERIFICATION_REQUEST,
+        @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String = MessageType.MSGTYPE_VERIFICATION_REQUEST,
         @Json(name = "body") override val body: String,
         @Json(name = "from_device") override val fromDevice: String?,
         @Json(name = "methods") override val methods: List<String>,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVideoContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVideoContent.kt
index 9266a0fb..9b657971 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVideoContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVideoContent.kt
@@ -27,7 +27,7 @@ data class MessageVideoContent(
         /**
          * Required. Must be 'm.video'.
          */
-        @Json(name = MessageContent.MSG_TYPE_JSON_KEY)override val msgType: String,
+        @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String,
 
         /**
          * Required. A description of the video e.g. 'Gangnam style', or some kind of content description for accessibility e.g. 'video attachment'.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/VideoInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/VideoInfo.kt
index 28f3a47d..b02b4d96 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/VideoInfo.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/VideoInfo.kt
@@ -67,5 +67,5 @@ data class VideoInfo(
  * Get the url of the encrypted thumbnail or of the thumbnail
  */
 fun VideoInfo.getThumbnailUrl(): String? {
-        return thumbnailFile?.url ?: thumbnailUrl
+    return thumbnailFile?.url ?: thumbnailUrl
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt
index f645f3eb..98171795 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt
@@ -84,8 +84,9 @@ interface StateService {
      * @param eventType The type of event to send.
      * @param stateKey The state_key for the state to send. Can be an empty string.
      * @param body The content object of the event; the fields in this object will vary depending on the type of event
+     * @return the id of the created state event
      */
-    suspend fun sendStateEvent(eventType: String, stateKey: String, body: JsonDict)
+    suspend fun sendStateEvent(eventType: String, stateKey: String, body: JsonDict): String
 
     /**
      * Get a state event of the room
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt
index a2ae8bfe..adbc8ab1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt
@@ -29,7 +29,7 @@ import org.matrix.android.sdk.api.session.events.model.isSticker
 import org.matrix.android.sdk.api.session.events.model.toModel
 import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
 import org.matrix.android.sdk.api.session.room.model.ReadReceipt
-import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationBeaconContent
+import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageContent
 import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent
@@ -139,7 +139,7 @@ fun TimelineEvent.getLastMessageContent(): MessageContent? {
     return when (root.getClearType()) {
         EventType.STICKER                   -> root.getClearContent().toModel<MessageStickerContent>()
         in EventType.POLL_START             -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel<MessagePollContent>()
-        in EventType.STATE_ROOM_BEACON_INFO -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel<LiveLocationBeaconContent>()
+        in EventType.STATE_ROOM_BEACON_INFO -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel<MessageBeaconInfoContent>()
         else                                -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel()
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEventFilters.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEventFilters.kt
index 4415c8e4..a35a291d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEventFilters.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEventFilters.kt
@@ -16,6 +16,7 @@
 
 package org.matrix.android.sdk.api.session.room.timeline
 
+// TODO Move to internal, strange?
 data class TimelineEventFilters(
         /**
          * A flag to filter edit events
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt
index 6548453c..b45f3ecb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt
@@ -31,7 +31,8 @@ data class TimelineSettings(
         /**
          * The root thread eventId if this is a thread timeline, or null if this is NOT a thread timeline
          */
-        val rootThreadEventId: String? = null) {
+        val rootThreadEventId: String? = null,
+) {
 
     /**
      * Returns true if this is a thread timeline or false otherwise
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageError.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageError.kt
index a91b97b8..038533c1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageError.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageError.kt
@@ -23,7 +23,7 @@ sealed class SharedSecretStorageError(message: String?) : Throwable(message) {
     data class UnsupportedAlgorithm(val algorithm: String) : SharedSecretStorageError("Unknown algorithm $algorithm")
     data class SecretNotEncrypted(val secretName: String) : SharedSecretStorageError("Missing content for secret $secretName")
     data class SecretNotEncryptedWithKey(val secretName: String, val keyId: String) :
-        SharedSecretStorageError("Missing content for secret $secretName with key $keyId")
+            SharedSecretStorageError("Missing content for secret $secretName with key $keyId")
 
     object BadKeyFormat : SharedSecretStorageError("Bad Key Format")
     object ParsingError : SharedSecretStorageError("parsing Error")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt
index 78267640..afd26f7b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt
@@ -68,7 +68,7 @@ interface SpaceService {
                                    suggestedOnly: Boolean? = null,
                                    limit: Int? = null,
                                    from: String? = null,
-                                   // when paginating, pass back the m.space.child state events
+            // when paginating, pass back the m.space.child state events
                                    knownStateList: List<Event>? = null): SpaceHierarchyData
 
     /**
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/uia/UiaResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/uia/UiaResult.kt
new file mode 100644
index 00000000..ee4a180d
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/uia/UiaResult.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * 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 org.matrix.android.sdk.api.session.uia
+
+enum class UiaResult {
+    SUCCESS,
+    FAILURE,
+    CANCELLED
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/uia/exceptions/UiaCancelledException.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/uia/exceptions/UiaCancelledException.kt
new file mode 100644
index 00000000..d5f9d72e
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/uia/exceptions/UiaCancelledException.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * 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 org.matrix.android.sdk.api.session.uia.exceptions
+
+class UiaCancelledException(message: String? = null) : Exception(message)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt
index 554e21ce..ebad859b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt
@@ -26,6 +26,7 @@ import org.matrix.android.sdk.internal.auth.data.WebClientConfig
 import org.matrix.android.sdk.internal.auth.login.ResetPasswordMailConfirmed
 import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationParams
 import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationResponse
+import org.matrix.android.sdk.internal.auth.registration.RegistrationCustomParams
 import org.matrix.android.sdk.internal.auth.registration.RegistrationParams
 import org.matrix.android.sdk.internal.auth.registration.SuccessResult
 import org.matrix.android.sdk.internal.auth.registration.ValidationCodeBody
@@ -68,6 +69,14 @@ internal interface AuthAPI {
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register")
     suspend fun register(@Body registrationParams: RegistrationParams): Credentials
 
+    /**
+     * Register to the homeserver, or get error 401 with a RegistrationFlowResponse object if registration is incomplete
+     * method to perform other custom stages
+     * Ref: https://matrix.org/docs/spec/client_server/latest#account-registration-and-management
+     */
+    @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register")
+    suspend fun registerCustom(@Body registrationCustomParams: RegistrationCustomParams): Credentials
+
     /**
      * Checks to see if a username is available, and valid, for the server.
      */
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt
index 4a156e74..590b333e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt
@@ -17,6 +17,7 @@
 package org.matrix.android.sdk.internal.auth.registration
 
 import kotlinx.coroutines.delay
+import org.matrix.android.sdk.api.auth.data.Credentials
 import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
 import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
 import org.matrix.android.sdk.api.auth.registration.RegistrationAvailability
@@ -25,6 +26,7 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
 import org.matrix.android.sdk.api.auth.registration.toFlowResult
 import org.matrix.android.sdk.api.failure.Failure
 import org.matrix.android.sdk.api.failure.Failure.RegistrationFlowError
+import org.matrix.android.sdk.api.util.JsonDict
 import org.matrix.android.sdk.internal.auth.AuthAPI
 import org.matrix.android.sdk.internal.auth.PendingSessionStore
 import org.matrix.android.sdk.internal.auth.SessionCreator
@@ -45,6 +47,7 @@ internal class DefaultRegistrationWizard(
     private val registerAvailableTask: RegisterAvailableTask = DefaultRegisterAvailableTask(authAPI)
     private val registerAddThreePidTask: RegisterAddThreePidTask = DefaultRegisterAddThreePidTask(authAPI)
     private val validateCodeTask: ValidateCodeTask = DefaultValidateCodeTask(authAPI)
+    private val registerCustomTask: RegisterCustomTask = DefaultRegisterCustomTask(authAPI)
 
     override val currentThreePid: String?
         get() {
@@ -187,22 +190,51 @@ internal class DefaultRegistrationWizard(
         return performRegistrationRequest(params)
     }
 
-    private suspend fun performRegistrationRequest(registrationParams: RegistrationParams,
-                                                   delayMillis: Long = 0): RegistrationResult {
+    override suspend fun registrationCustom(
+            authParams: JsonDict
+    ): RegistrationResult {
+        val safeSession = pendingSessionData.currentSession
+                ?: throw IllegalStateException("developer error, call createAccount() method first")
+
+        val mutableParams = authParams.toMutableMap()
+        mutableParams["session"] = safeSession
+
+        val params = RegistrationCustomParams(auth = mutableParams)
+        return performRegistrationOtherRequest(params)
+    }
+
+    private suspend fun performRegistrationRequest(
+            registrationParams: RegistrationParams,
+            delayMillis: Long = 0
+    ): RegistrationResult {
         delay(delayMillis)
+        return register { registerTask.execute(RegisterTask.Params(registrationParams)) }
+    }
+
+    private suspend fun performRegistrationOtherRequest(
+            registrationCustomParams: RegistrationCustomParams
+    ): RegistrationResult {
+        return register { registerCustomTask.execute(RegisterCustomTask.Params(registrationCustomParams)) }
+    }
+
+    private suspend fun register(
+            execute: suspend () -> Credentials
+    ): RegistrationResult {
         val credentials = try {
-            registerTask.execute(RegisterTask.Params(registrationParams))
+            execute.invoke()
         } catch (exception: Throwable) {
             if (exception is RegistrationFlowError) {
-                pendingSessionData = pendingSessionData.copy(currentSession = exception.registrationFlowResponse.session)
-                        .also { pendingSessionStore.savePendingSessionData(it) }
+                pendingSessionData =
+                        pendingSessionData.copy(currentSession = exception.registrationFlowResponse.session)
+                                .also { pendingSessionStore.savePendingSessionData(it) }
                 return RegistrationResult.FlowResponse(exception.registrationFlowResponse.toFlowResult())
             } else {
                 throw exception
             }
         }
 
-        val session = sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
+        val session =
+                sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
         return RegistrationResult.Success(session)
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterCustomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterCustomTask.kt
new file mode 100644
index 00000000..60af708c
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterCustomTask.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * 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 org.matrix.android.sdk.internal.auth.registration
+
+import org.matrix.android.sdk.api.auth.data.Credentials
+import org.matrix.android.sdk.api.failure.Failure
+import org.matrix.android.sdk.api.failure.toRegistrationFlowResponse
+import org.matrix.android.sdk.internal.auth.AuthAPI
+import org.matrix.android.sdk.internal.network.executeRequest
+import org.matrix.android.sdk.internal.task.Task
+
+internal interface RegisterCustomTask : Task<RegisterCustomTask.Params, Credentials> {
+    data class Params(
+            val registrationCustomParams: RegistrationCustomParams
+    )
+}
+
+internal class DefaultRegisterCustomTask(
+        private val authAPI: AuthAPI
+) : RegisterCustomTask {
+
+    override suspend fun execute(params: RegisterCustomTask.Params): Credentials {
+        try {
+            return executeRequest(null) {
+                authAPI.registerCustom(params.registrationCustomParams)
+            }
+        } catch (throwable: Throwable) {
+            throw throwable.toRegistrationFlowResponse()
+                    ?.let { Failure.RegistrationFlowError(it) }
+                    ?: throwable
+        }
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/BeaconInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegistrationCustomParams.kt
similarity index 56%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/BeaconInfo.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegistrationCustomParams.kt
index 873edc0f..45adac6c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/BeaconInfo.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegistrationCustomParams.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Matrix.org Foundation C.I.C.
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,20 +14,18 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.api.session.room.model.livelocation
+package org.matrix.android.sdk.internal.auth.registration
 
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
+import org.matrix.android.sdk.api.util.JsonDict
 
+/**
+ * Class to pass parameters to the custom registration types for /register.
+ */
 @JsonClass(generateAdapter = true)
-data class BeaconInfo(
-        @Json(name = "description") val description: String? = null,
-        /**
-         * Beacon should be considered as inactive after this timeout as milliseconds.
-         */
-        @Json(name = "timeout") val timeout: Long? = null,
-        /**
-         * Should be set true to start sharing beacon.
-         */
-        @Json(name = "live") val isLive: Boolean? = null
+internal data class RegistrationCustomParams(
+        // authentication parameters
+        @Json(name = "auth")
+        val auth: JsonDict? = null,
 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/UIAExt.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/UIAExt.kt
index da0866a5..7dafacb3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/UIAExt.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/UIAExt.kt
@@ -20,6 +20,8 @@ import org.matrix.android.sdk.api.auth.UIABaseAuth
 import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
 import org.matrix.android.sdk.api.failure.Failure
 import org.matrix.android.sdk.api.failure.toRegistrationFlowResponse
+import org.matrix.android.sdk.api.session.uia.UiaResult
+import org.matrix.android.sdk.api.session.uia.exceptions.UiaCancelledException
 import timber.log.Timber
 import kotlin.coroutines.suspendCoroutine
 
@@ -30,14 +32,15 @@ import kotlin.coroutines.suspendCoroutine
  * @param interceptor see doc in [UserInteractiveAuthInterceptor]
  * @param retryBlock called at the end of the process, in this block generally retry executing the task, with
  * provided authUpdate
- * @return true if UIA is handled without error
+ * @return UiaResult if UIA handled, failed or cancelled
+ *
  */
 internal suspend fun handleUIA(failure: Throwable,
                                interceptor: UserInteractiveAuthInterceptor,
-                               retryBlock: suspend (UIABaseAuth) -> Unit): Boolean {
+                               retryBlock: suspend (UIABaseAuth) -> Unit): UiaResult {
     Timber.d("## UIA: check error ${failure.message}")
     val flowResponse = failure.toRegistrationFlowResponse()
-            ?: return false.also {
+            ?: return UiaResult.FAILURE.also {
                 Timber.d("## UIA: not a UIA error")
             }
 
@@ -50,14 +53,19 @@ internal suspend fun handleUIA(failure: Throwable,
             interceptor.performStage(flowResponse, (failure as? Failure.ServerError)?.error?.code, continuation)
         }
     } catch (failure2: Throwable) {
-        Timber.w(failure2, "## UIA: failed to participate")
-        return false
+        return if (failure2 is UiaCancelledException) {
+            Timber.w(failure2, "## UIA: cancelled")
+            UiaResult.CANCELLED
+        } else {
+            Timber.w(failure2, "## UIA: failed to participate")
+            UiaResult.FAILURE
+        }
     }
 
     Timber.d("## UIA: updated auth")
     return try {
         retryBlock(authUpdate)
-        true
+        UiaResult.SUCCESS
     } catch (failure3: Throwable) {
         handleUIA(failure3, interceptor, retryBlock)
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt
index 98950374..aaf23d17 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt
@@ -32,12 +32,13 @@ import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
 import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
 import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId
 import org.matrix.android.sdk.internal.session.SessionComponent
+import org.matrix.android.sdk.internal.util.time.Clock
 import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
 import org.matrix.android.sdk.internal.worker.SessionWorkerParams
 import javax.inject.Inject
 
 internal class CancelGossipRequestWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
-    SessionSafeCoroutineWorker<CancelGossipRequestWorker.Params>(context, params, sessionManager, Params::class.java) {
+        SessionSafeCoroutineWorker<CancelGossipRequestWorker.Params>(context, params, sessionManager, Params::class.java) {
 
     @JsonClass(generateAdapter = true)
     internal data class Params(
@@ -65,6 +66,7 @@ internal class CancelGossipRequestWorker(context: Context, params: WorkerParamet
     @Inject lateinit var sendToDeviceTask: SendToDeviceTask
     @Inject lateinit var cryptoStore: IMXCryptoStore
     @Inject lateinit var credentials: Credentials
+    @Inject lateinit var clock: Clock
 
     override fun injectWith(injector: SessionComponent) {
         injector.inject(this)
@@ -85,7 +87,7 @@ internal class CancelGossipRequestWorker(context: Context, params: WorkerParamet
                 content = toDeviceContent.toContent(),
                 senderId = credentials.userId
         ).also {
-            it.ageLocalTs = System.currentTimeMillis()
+            it.ageLocalTs = clock.epochMillis()
         })
 
         params.recipients.forEach { userToDeviceMap ->
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt
index 2a58d731..73dfc468 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt
@@ -48,7 +48,7 @@ internal class CryptoSessionInfoProvider @Inject constructor(
     /**
      * @param allActive if true return joined as well as invited, if false, only joined
      */
-     fun getRoomUserIds(roomId: String, allActive: Boolean): List<String> {
+    fun getRoomUserIds(roomId: String, allActive: Boolean): List<String> {
         var userIds: List<String> = emptyList()
         monarchy.doWithRealm { realm ->
             userIds = if (allActive) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt
index 6a57d946..54c9990b 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt
@@ -103,6 +103,7 @@ import org.matrix.android.sdk.internal.task.TaskThread
 import org.matrix.android.sdk.internal.task.configureWith
 import org.matrix.android.sdk.internal.task.launchToCallback
 import org.matrix.android.sdk.internal.util.JsonCanonicalizer
+import org.matrix.android.sdk.internal.util.time.Clock
 import org.matrix.olm.OlmManager
 import timber.log.Timber
 import java.util.concurrent.atomic.AtomicBoolean
@@ -130,6 +131,7 @@ internal class DefaultCryptoService @Inject constructor(
         private val userId: String,
         @DeviceId
         private val deviceId: String?,
+        private val clock: Clock,
         private val myDeviceInfoHolder: Lazy<MyDeviceInfoHolder>,
         // the crypto store
         private val cryptoStore: IMXCryptoStore,
@@ -701,11 +703,11 @@ internal class DefaultCryptoService @Inject constructor(
             }
             val safeAlgorithm = alg
             if (safeAlgorithm != null) {
-                val t0 = System.currentTimeMillis()
+                val t0 = clock.epochMillis()
                 Timber.tag(loggerTag.value).v("encryptEventContent() starts")
                 runCatching {
                     val content = safeAlgorithm.encryptEventContent(eventContent, eventType, userIds)
-                    Timber.tag(loggerTag.value).v("## CRYPTO | encryptEventContent() : succeeds after ${System.currentTimeMillis() - t0} ms")
+                    Timber.tag(loggerTag.value).v("## CRYPTO | encryptEventContent() : succeeds after ${clock.epochMillis() - t0} ms")
                     MXEncryptEventContentResult(content, EventType.ENCRYPTED)
                 }.foldToCallback(callback)
             } else {
@@ -1022,9 +1024,9 @@ internal class DefaultCryptoService @Inject constructor(
         return withContext(coroutineDispatchers.crypto) {
             Timber.tag(loggerTag.value).v("importRoomKeys starts")
 
-            val t0 = System.currentTimeMillis()
+            val t0 = clock.epochMillis()
             val roomKeys = MXMegolmExportEncryption.decryptMegolmKeyFile(roomKeysAsArray, password)
-            val t1 = System.currentTimeMillis()
+            val t1 = clock.epochMillis()
 
             Timber.tag(loggerTag.value).v("importRoomKeys : decryptMegolmKeyFile done in ${t1 - t0} ms")
 
@@ -1032,7 +1034,7 @@ internal class DefaultCryptoService @Inject constructor(
                     .adapter<List<MegolmSessionData>>(Types.newParameterizedType(List::class.java, MegolmSessionData::class.java))
                     .fromJson(roomKeys)
 
-            val t2 = System.currentTimeMillis()
+            val t2 = clock.epochMillis()
 
             Timber.tag(loggerTag.value).v("importRoomKeys : JSON parsing ${t2 - t1} ms")
 
@@ -1224,7 +1226,7 @@ internal class DefaultCryptoService @Inject constructor(
 //        val deviceKey = deviceInfo.identityKey()
 //
 //        val lastForcedDate = lastNewSessionForcedDates.getObject(senderId, deviceKey) ?: 0
-//        val now = System.currentTimeMillis()
+//        val now = clock.epochMillis()
 //        if (now - lastForcedDate < CRYPTO_MIN_FORCE_SESSION_PERIOD_MILLIS) {
 //            Timber.d("## CRYPTO | markOlmSessionForUnwedging: New session already forced with device at $lastForcedDate. Not forcing another")
 //            return
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt
index 6cae2d09..53599937 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt
@@ -31,19 +31,23 @@ import org.matrix.android.sdk.internal.session.SessionScope
 import org.matrix.android.sdk.internal.session.sync.SyncTokenStore
 import org.matrix.android.sdk.internal.task.TaskExecutor
 import org.matrix.android.sdk.internal.util.logLimit
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import javax.inject.Inject
 
 // Legacy name: MXDeviceList
 @SessionScope
-internal class DeviceListManager @Inject constructor(private val cryptoStore: IMXCryptoStore,
-                                                     private val olmDevice: MXOlmDevice,
-                                                     private val syncTokenStore: SyncTokenStore,
-                                                     private val credentials: Credentials,
-                                                     private val downloadKeysForUsersTask: DownloadKeysForUsersTask,
-                                                     private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
-                                                     coroutineDispatchers: MatrixCoroutineDispatchers,
-                                                     private val taskExecutor: TaskExecutor) {
+internal class DeviceListManager @Inject constructor(
+        private val cryptoStore: IMXCryptoStore,
+        private val olmDevice: MXOlmDevice,
+        private val syncTokenStore: SyncTokenStore,
+        private val credentials: Credentials,
+        private val downloadKeysForUsersTask: DownloadKeysForUsersTask,
+        private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
+        coroutineDispatchers: MatrixCoroutineDispatchers,
+        private val taskExecutor: TaskExecutor,
+        private val clock: Clock,
+) {
 
     interface UserDevicesUpdateListener {
         fun onUsersDeviceUpdate(userIds: List<String>)
@@ -310,9 +314,9 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
             stored
         } else {
             Timber.v("## CRYPTO | downloadKeys() : starts")
-            val t0 = System.currentTimeMillis()
+            val t0 = clock.epochMillis()
             val result = doKeyDownloadForUsers(downloadUsers)
-            Timber.v("## CRYPTO | downloadKeys() : doKeyDownloadForUsers succeeds after ${System.currentTimeMillis() - t0} ms")
+            Timber.v("## CRYPTO | downloadKeys() : doKeyDownloadForUsers succeeds after ${clock.epochMillis() - t0} ms")
             result.also {
                 it.addEntriesFromMap(stored)
             }
@@ -475,8 +479,10 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
         }
 
         if (!isVerified) {
-            Timber.e("## CRYPTO | validateDeviceKeys() : Unable to verify signature on device " + userId + ":" +
-                    deviceKeys.deviceId + " with error " + errorMessage)
+            Timber.e(
+                    "## CRYPTO | validateDeviceKeys() : Unable to verify signature on device " + userId + ":" +
+                            deviceKeys.deviceId + " with error " + errorMessage
+            )
             return false
         }
 
@@ -486,9 +492,11 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
                 // best off sticking with the original keys.
                 //
                 // Should we warn the user about it somehow?
-                Timber.e("## CRYPTO | validateDeviceKeys() : WARNING:Ed25519 key for device " + userId + ":" +
-                        deviceKeys.deviceId + " has changed : " +
-                        previouslyStoredDeviceKeys.fingerprint() + " -> " + signKey)
+                Timber.e(
+                        "## CRYPTO | validateDeviceKeys() : WARNING:Ed25519 key for device " + userId + ":" +
+                                deviceKeys.deviceId + " has changed : " +
+                                previouslyStoredDeviceKeys.fingerprint() + " -> " + signKey
+                )
 
                 Timber.e("## CRYPTO | validateDeviceKeys() : $previouslyStoredDeviceKeys -> $deviceKeys")
                 Timber.e("## CRYPTO | validateDeviceKeys() : ${previouslyStoredDeviceKeys.keys} -> ${deviceKeys.keys}")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt
index 1c8bce73..a0941896 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt
@@ -36,6 +36,7 @@ import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
 import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
 import org.matrix.android.sdk.internal.extensions.foldToCallback
 import org.matrix.android.sdk.internal.session.SessionScope
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import javax.inject.Inject
 
@@ -47,6 +48,7 @@ private val loggerTag = LoggerTag("CryptoSyncHandler", LoggerTag.CRYPTO)
 internal class EventDecryptor @Inject constructor(
         private val cryptoCoroutineScope: CoroutineScope,
         private val coroutineDispatchers: MatrixCoroutineDispatchers,
+        private val clock: Clock,
         private val roomDecryptorProvider: RoomDecryptorProvider,
         private val messageEncrypter: MessageEncrypter,
         private val sendToDeviceTask: SendToDeviceTask,
@@ -153,7 +155,7 @@ internal class EventDecryptor @Inject constructor(
         // we should force start a new session for those
         Timber.tag(loggerTag.value).v("Unwedging:  ${wedgedDevices.size} are wedged")
         // get the one that should be retried according to rate limit
-        val now = System.currentTimeMillis()
+        val now = clock.epochMillis()
         val toUnwedge = wedgedDevices.filter {
             val lastForcedDate = lastNewSessionForcedDates[it] ?: 0
             if (now - lastForcedDate < DefaultCryptoService.CRYPTO_MIN_FORCE_SESSION_PERIOD_MILLIS) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/GossipingWorkManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/GossipingWorkManager.kt
index 0013c31e..a2c85e5c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/GossipingWorkManager.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/GossipingWorkManager.kt
@@ -26,6 +26,7 @@ import org.matrix.android.sdk.internal.di.WorkManagerProvider
 import org.matrix.android.sdk.internal.session.SessionScope
 import org.matrix.android.sdk.internal.util.CancelableWork
 import org.matrix.android.sdk.internal.worker.startChain
+import java.util.UUID
 import java.util.concurrent.TimeUnit
 import javax.inject.Inject
 
@@ -44,8 +45,8 @@ internal class GossipingWorkManager @Inject constructor(
     }
 
     // Prevent sending queue to stay broken after app restart
-    // The unique queue id will stay the same as long as this object is instanciated
-    val queueSuffixApp = System.currentTimeMillis()
+    // The unique queue id will stay the same as long as this object is instantiated
+    private val queueSuffixApp = UUID.randomUUID()
 
     fun postWork(workRequest: OneTimeWorkRequest, policy: ExistingWorkPolicy = ExistingWorkPolicy.APPEND): Cancelable {
         workManagerProvider.workManager
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt
index a78444df..28ddf291 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt
@@ -75,15 +75,15 @@ internal class InboundGroupSessionStore @Inject constructor(
 
     @Synchronized
     fun getInboundGroupSession(sessionId: String, senderKey: String): InboundGroupSessionHolder? {
-            val known = sessionCache[CacheKey(sessionId, senderKey)]
-            Timber.tag(loggerTag.value).v("## Inbound: getInboundGroupSession  $sessionId in cache ${known != null}")
-            return known
-                    ?: store.getInboundGroupSession(sessionId, senderKey)?.also {
-                Timber.tag(loggerTag.value).v("## Inbound: getInboundGroupSession cache populate ${it.roomId}")
-                sessionCache.put(CacheKey(sessionId, senderKey), InboundGroupSessionHolder(it))
-            }?.let {
-                InboundGroupSessionHolder(it)
-            }
+        val known = sessionCache[CacheKey(sessionId, senderKey)]
+        Timber.tag(loggerTag.value).v("## Inbound: getInboundGroupSession  $sessionId in cache ${known != null}")
+        return known
+                ?: store.getInboundGroupSession(sessionId, senderKey)?.also {
+                    Timber.tag(loggerTag.value).v("## Inbound: getInboundGroupSession cache populate ${it.roomId}")
+                    sessionCache.put(CacheKey(sessionId, senderKey), InboundGroupSessionHolder(it))
+                }?.let {
+                    InboundGroupSessionHolder(it)
+                }
     }
 
     @Synchronized
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt
index 3a409cf3..1612caba 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt
@@ -44,6 +44,7 @@ import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
 import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId
 import org.matrix.android.sdk.internal.di.SessionId
 import org.matrix.android.sdk.internal.session.SessionScope
+import org.matrix.android.sdk.internal.util.time.Clock
 import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
 import timber.log.Timber
 import java.util.concurrent.Executors
@@ -59,7 +60,9 @@ internal class IncomingGossipingRequestManager @Inject constructor(
         private val roomEncryptorsStore: RoomEncryptorsStore,
         private val roomDecryptorProvider: RoomDecryptorProvider,
         private val coroutineDispatchers: MatrixCoroutineDispatchers,
-        private val cryptoCoroutineScope: CoroutineScope) {
+        private val cryptoCoroutineScope: CoroutineScope,
+        private val clock: Clock,
+) {
 
     private val executor = Executors.newSingleThreadExecutor()
 
@@ -89,7 +92,7 @@ internal class IncomingGossipingRequestManager @Inject constructor(
     fun onVerificationCompleteForDevice(deviceId: String) {
         // For now we just keep an in memory cache
         synchronized(recentlyVerifiedDevices) {
-            recentlyVerifiedDevices[deviceId] = System.currentTimeMillis()
+            recentlyVerifiedDevices[deviceId] = clock.epochMillis()
         }
     }
 
@@ -100,7 +103,7 @@ internal class IncomingGossipingRequestManager @Inject constructor(
         }
         if (verifTimestamp == null) return false
 
-        val age = System.currentTimeMillis() - verifTimestamp
+        val age = clock.epochMillis() - verifTimestamp
 
         return age < FIVE_MINUTES_IN_MILLIS
     }
@@ -114,11 +117,11 @@ internal class IncomingGossipingRequestManager @Inject constructor(
     fun onGossipingRequestEvent(event: Event) {
         val roomKeyShare = event.getClearContent().toModel<GossipingDefaultContent>()
         Timber.i("## CRYPTO | GOSSIP onGossipingRequestEvent received type ${event.type} from user:${event.senderId}, content:$roomKeyShare")
-        // val ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it }
+        // val ageLocalTs = event.unsignedData?.age?.let { clock.epochMillis() - it }
         when (roomKeyShare?.action) {
-            GossipingToDeviceObject.ACTION_SHARE_REQUEST -> {
+            GossipingToDeviceObject.ACTION_SHARE_REQUEST      -> {
                 if (event.getClearType() == EventType.REQUEST_SECRET) {
-                    IncomingSecretShareRequest.fromEvent(event)?.let {
+                    IncomingSecretShareRequest.fromEvent(event, clock.epochMillis())?.let {
                         if (event.senderId == credentials.userId && it.deviceId == credentials.deviceId) {
                             // ignore, it was sent by me as *
                             Timber.v("## GOSSIP onGossipingRequestEvent type ${event.type} ignore remote echo")
@@ -129,7 +132,7 @@ internal class IncomingGossipingRequestManager @Inject constructor(
                         }
                     }
                 } else if (event.getClearType() == EventType.ROOM_KEY_REQUEST) {
-                    IncomingRoomKeyRequest.fromEvent(event)?.let {
+                    IncomingRoomKeyRequest.fromEvent(event, clock.epochMillis())?.let {
                         if (event.senderId == credentials.userId && it.deviceId == credentials.deviceId) {
                             // ignore, it was sent by me as *
                             Timber.v("## GOSSIP onGossipingRequestEvent type ${event.type} ignore remote echo")
@@ -141,7 +144,7 @@ internal class IncomingGossipingRequestManager @Inject constructor(
                 }
             }
             GossipingToDeviceObject.ACTION_SHARE_CANCELLATION -> {
-                IncomingRequestCancellation.fromEvent(event)?.let {
+                IncomingRequestCancellation.fromEvent(event, clock.epochMillis())?.let {
                     receivedRequestCancellations.add(it)
                 }
             }
@@ -346,7 +349,7 @@ internal class IncomingGossipingRequestManager @Inject constructor(
         val isDeviceLocallyVerified = cryptoStore.getUserDevice(userId, deviceId)?.trustLevel?.isLocallyVerified()
 
         when (secretName) {
-            MASTER_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.master
+            MASTER_KEY_SSSS_NAME       -> cryptoStore.getCrossSigningPrivateKeys()?.master
             SELF_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.selfSigned
             USER_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.user
             KEYBACKUP_SECRET_SSSS_NAME -> cryptoStore.getKeyBackupRecoveryKeyInfo()?.recoveryKey
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXMegolmExportEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXMegolmExportEncryption.kt
index f8235bf3..89e38cb7 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXMegolmExportEncryption.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXMegolmExportEncryption.kt
@@ -29,6 +29,7 @@ import javax.crypto.spec.SecretKeySpec
 import kotlin.experimental.and
 import kotlin.experimental.xor
 import kotlin.math.min
+import kotlin.system.measureTimeMillis
 
 /**
  * Utility class to import/export the crypto data
@@ -310,41 +311,41 @@ internal object MXMegolmExportEncryption {
      */
     @Throws(Exception::class)
     private fun deriveKeys(salt: ByteArray, iterations: Int, password: String): ByteArray {
-        val t0 = System.currentTimeMillis()
-
-        // based on https://en.wikipedia.org/wiki/PBKDF2 algorithm
-        // it is simpler than the generic algorithm because the expected key length is equal to the mac key length.
-        // noticed as dklen/hlen
-        val prf = Mac.getInstance("HmacSHA512")
-        prf.init(SecretKeySpec(password.toByteArray(Charsets.UTF_8), "HmacSHA512"))
-
-        // 512 bits key length
         val key = ByteArray(64)
-        val uc = ByteArray(64)
-
-        // U1 = PRF(Password, Salt || INT_32_BE(i))
-        prf.update(salt)
-        val int32BE = ByteArray(4) { 0.toByte() }
-        int32BE[3] = 1.toByte()
-        prf.update(int32BE)
-        prf.doFinal(uc, 0)
+        measureTimeMillis {
+            // based on https://en.wikipedia.org/wiki/PBKDF2 algorithm
+            // it is simpler than the generic algorithm because the expected key length is equal to the mac key length.
+            // noticed as dklen/hlen
+            val prf = Mac.getInstance("HmacSHA512")
+            prf.init(SecretKeySpec(password.toByteArray(Charsets.UTF_8), "HmacSHA512"))
+
+            // 512 bits key length
+            val uc = ByteArray(64)
+
+            // U1 = PRF(Password, Salt || INT_32_BE(i))
+            prf.update(salt)
+            val int32BE = ByteArray(4) { 0.toByte() }
+            int32BE[3] = 1.toByte()
+            prf.update(int32BE)
+            prf.doFinal(uc, 0)
 
-        // copy to the key
-        System.arraycopy(uc, 0, key, 0, uc.size)
+            // copy to the key
+            System.arraycopy(uc, 0, key, 0, uc.size)
 
-        for (index in 2..iterations) {
-            // Uc = PRF(Password, Uc-1)
-            prf.update(uc)
-            prf.doFinal(uc, 0)
+            for (index in 2..iterations) {
+                // Uc = PRF(Password, Uc-1)
+                prf.update(uc)
+                prf.doFinal(uc, 0)
 
-            // F(Password, Salt, c, i) = U1 ^ U2 ^ ... ^ Uc
-            for (byteIndex in uc.indices) {
-                key[byteIndex] = key[byteIndex] xor uc[byteIndex]
+                // F(Password, Salt, c, i) = U1 ^ U2 ^ ... ^ Uc
+                for (byteIndex in uc.indices) {
+                    key[byteIndex] = key[byteIndex] xor uc[byteIndex]
+                }
             }
+        }.also {
+            Timber.v("## deriveKeys() : $iterations in $it ms")
         }
 
-        Timber.v("## deriveKeys() : $iterations in ${System.currentTimeMillis() - t0} ms")
-
         return key
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt
index 4947761f..7eec83ab 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt
@@ -35,6 +35,7 @@ import org.matrix.android.sdk.internal.session.SessionScope
 import org.matrix.android.sdk.internal.util.JsonCanonicalizer
 import org.matrix.android.sdk.internal.util.convertFromUTF8
 import org.matrix.android.sdk.internal.util.convertToUTF8
+import org.matrix.android.sdk.internal.util.time.Clock
 import org.matrix.olm.OlmAccount
 import org.matrix.olm.OlmException
 import org.matrix.olm.OlmMessage
@@ -55,7 +56,8 @@ internal class MXOlmDevice @Inject constructor(
          */
         private val store: IMXCryptoStore,
         private val olmSessionStore: OlmSessionStore,
-        private val inboundGroupSessionStore: InboundGroupSessionStore
+        private val inboundGroupSessionStore: InboundGroupSessionStore,
+        private val clock: Clock,
 ) {
 
     val mutex = Mutex()
@@ -277,7 +279,7 @@ internal class MXOlmDevice @Inject constructor(
             // Pretend we've received a message at this point, otherwise
             // if we try to send a message to the device, it won't use
             // this session
-            olmSessionWrapper.onMessageReceived()
+            olmSessionWrapper.onMessageReceived(clock.epochMillis())
 
             olmSessionStore.storeSession(olmSessionWrapper, theirIdentityKey)
 
@@ -348,7 +350,7 @@ internal class MXOlmDevice @Inject constructor(
 
                 val olmSessionWrapper = OlmSessionWrapper(olmSession, 0)
                 // This counts as a received message: set last received message time to now
-                olmSessionWrapper.onMessageReceived()
+                olmSessionWrapper.onMessageReceived(clock.epochMillis())
 
                 olmSessionStore.storeSession(olmSessionWrapper, theirDeviceIdentityKey)
             } catch (e: Exception) {
@@ -454,7 +456,7 @@ internal class MXOlmDevice @Inject constructor(
             payloadString =
                     olmSessionWrapper.mutex.withLock {
                         olmSessionWrapper.olmSession.decryptMessage(olmMessage).also {
-                            olmSessionWrapper.onMessageReceived()
+                            olmSessionWrapper.onMessageReceived(clock.epochMillis())
                         }
                     }
             olmSessionStore.storeSession(olmSessionWrapper, theirDeviceIdentityKey)
@@ -520,6 +522,7 @@ internal class MXOlmDevice @Inject constructor(
             return MXOutboundSessionInfo(
                     sessionId = sessionId,
                     sharedWithHelper = SharedWithHelper(roomId, sessionId, store),
+                    clock,
                     restoredOutboundGroupSession.creationTime
             )
         }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmSessionStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmSessionStore.kt
index f4fbca6a..aac6f67a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmSessionStore.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmSessionStore.kt
@@ -66,7 +66,8 @@ internal class OlmSessionStore @Inject constructor(private val store: IMXCryptoS
         olmSessions.getOrPut(deviceKey) { mutableListOf() }.forEach { cached ->
             getSafeSessionIdentifier(cached.olmSession)?.let { cachedSessionId ->
                 if (!persistedKnownSessions.contains(cachedSessionId)) {
-                    persistedKnownSessions.add(cachedSessionId)
+                    // as it's in cache put in on top
+                    persistedKnownSessions.add(0, cachedSessionId)
                 }
             }
         }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt
index 792c9a25..8143e368 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt
@@ -23,6 +23,7 @@ import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse
 import org.matrix.android.sdk.internal.crypto.tasks.UploadKeysTask
 import org.matrix.android.sdk.internal.session.SessionScope
 import org.matrix.android.sdk.internal.util.JsonCanonicalizer
+import org.matrix.android.sdk.internal.util.time.Clock
 import org.matrix.olm.OlmAccount
 import timber.log.Timber
 import javax.inject.Inject
@@ -38,6 +39,7 @@ internal class OneTimeKeysUploader @Inject constructor(
         private val olmDevice: MXOlmDevice,
         private val objectSigner: ObjectSigner,
         private val uploadKeysTask: UploadKeysTask,
+        private val clock: Clock,
         context: Context
 ) {
     // tell if there is a OTK check in progress
@@ -77,7 +79,7 @@ internal class OneTimeKeysUploader @Inject constructor(
             Timber.v("maybeUploadOneTimeKeys: already in progress")
             return
         }
-        if (System.currentTimeMillis() - lastOneTimeKeyCheck < ONE_TIME_KEY_UPLOAD_PERIOD) {
+        if (clock.epochMillis() - lastOneTimeKeyCheck < ONE_TIME_KEY_UPLOAD_PERIOD) {
             // we've done a key upload recently.
             Timber.v("maybeUploadOneTimeKeys: executed too recently")
             return
@@ -94,7 +96,7 @@ internal class OneTimeKeysUploader @Inject constructor(
 
         Timber.d("maybeUploadOneTimeKeys: otk count $oneTimeKeyCountFromSync , unpublished fallback key ${olmDevice.hasUnpublishedFallbackKey()}")
 
-        lastOneTimeKeyCheck = System.currentTimeMillis()
+        lastOneTimeKeyCheck = clock.epochMillis()
 
         // We then check how many keys we can store in the Account object.
         val maxOneTimeKeys = olmDevice.getMaxNumberOfOneTimeKeys()
@@ -126,7 +128,7 @@ internal class OneTimeKeysUploader @Inject constructor(
 
         // Check if we need to forget a fallback key
         val latestPublishedTime = getLastFallbackKeyPublishTime()
-        if (latestPublishedTime != 0L && System.currentTimeMillis() - latestPublishedTime > FALLBACK_KEY_FORGET_DELAY) {
+        if (latestPublishedTime != 0L && clock.epochMillis() - latestPublishedTime > FALLBACK_KEY_FORGET_DELAY) {
             // This should be called once you are reasonably certain that you will not receive any more messages
             // that use the old fallback key
             Timber.d("## forgetFallbackKey()")
@@ -168,7 +170,7 @@ internal class OneTimeKeysUploader @Inject constructor(
         olmDevice.markKeysAsPublished()
         if (hadUnpublishedFallbackKey) {
             // It had an unpublished fallback key that was published just now
-            saveLastFallbackKeyPublishTime(System.currentTimeMillis())
+            saveLastFallbackKeyPublishTime(clock.epochMillis())
         }
 
         if (response.hasOneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt
index dbdea974..3b43ad67 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt
@@ -35,13 +35,14 @@ import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
 import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
 import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId
 import org.matrix.android.sdk.internal.session.SessionComponent
+import org.matrix.android.sdk.internal.util.time.Clock
 import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
 import org.matrix.android.sdk.internal.worker.SessionWorkerParams
 import timber.log.Timber
 import javax.inject.Inject
 
 internal class SendGossipRequestWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
-    SessionSafeCoroutineWorker<SendGossipRequestWorker.Params>(context, params, sessionManager, Params::class.java) {
+        SessionSafeCoroutineWorker<SendGossipRequestWorker.Params>(context, params, sessionManager, Params::class.java) {
 
     @JsonClass(generateAdapter = true)
     internal data class Params(
@@ -57,6 +58,7 @@ internal class SendGossipRequestWorker(context: Context, params: WorkerParameter
     @Inject lateinit var sendToDeviceTask: SendToDeviceTask
     @Inject lateinit var cryptoStore: IMXCryptoStore
     @Inject lateinit var credentials: Credentials
+    @Inject lateinit var clock: Clock
 
     override fun injectWith(injector: SessionComponent) {
         injector.inject(this)
@@ -85,7 +87,7 @@ internal class SendGossipRequestWorker(context: Context, params: WorkerParameter
                         content = toDeviceContent.toContent(),
                         senderId = credentials.userId
                 ).also {
-                    it.ageLocalTs = System.currentTimeMillis()
+                    it.ageLocalTs = clock.epochMillis()
                 })
 
                 params.keyShareRequest.recipients.forEach { userToDeviceMap ->
@@ -109,7 +111,7 @@ internal class SendGossipRequestWorker(context: Context, params: WorkerParameter
                         content = toDeviceContent.toContent(),
                         senderId = credentials.userId
                 ).also {
-                    it.ageLocalTs = System.currentTimeMillis()
+                    it.ageLocalTs = clock.epochMillis()
                 })
 
                 params.secretShareRequest.recipients.forEach { userToDeviceMap ->
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt
index fd472fe7..113d71d3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt
@@ -34,6 +34,7 @@ import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
 import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
 import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId
 import org.matrix.android.sdk.internal.session.SessionComponent
+import org.matrix.android.sdk.internal.util.time.Clock
 import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
 import org.matrix.android.sdk.internal.worker.SessionWorkerParams
 import timber.log.Timber
@@ -63,6 +64,7 @@ internal class SendGossipWorker(
     @Inject lateinit var credentials: Credentials
     @Inject lateinit var messageEncrypter: MessageEncrypter
     @Inject lateinit var ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction
+    @Inject lateinit var clock: Clock
 
     override fun injectWith(injector: SessionComponent) {
         injector.inject(this)
@@ -129,7 +131,7 @@ internal class SendGossipWorker(
                 content = toDeviceContent.toContent(),
                 senderId = credentials.userId
         ).also {
-            it.ageLocalTs = System.currentTimeMillis()
+            it.ageLocalTs = clock.epochMillis()
         })
 
         try {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MegolmSessionDataImporter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MegolmSessionDataImporter.kt
index f9bcdf2c..86674b4a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MegolmSessionDataImporter.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MegolmSessionDataImporter.kt
@@ -26,13 +26,17 @@ import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestManager
 import org.matrix.android.sdk.internal.crypto.RoomDecryptorProvider
 import org.matrix.android.sdk.internal.crypto.algorithms.megolm.MXMegolmDecryption
 import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import javax.inject.Inject
 
-internal class MegolmSessionDataImporter @Inject constructor(private val olmDevice: MXOlmDevice,
-                                                             private val roomDecryptorProvider: RoomDecryptorProvider,
-                                                             private val outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
-                                                             private val cryptoStore: IMXCryptoStore) {
+internal class MegolmSessionDataImporter @Inject constructor(
+        private val olmDevice: MXOlmDevice,
+        private val roomDecryptorProvider: RoomDecryptorProvider,
+        private val outgoingGossipingRequestManager: OutgoingGossipingRequestManager,
+        private val cryptoStore: IMXCryptoStore,
+        private val clock: Clock,
+) {
 
     /**
      * Import a list of megolm session keys.
@@ -47,7 +51,7 @@ internal class MegolmSessionDataImporter @Inject constructor(private val olmDevi
     fun handle(megolmSessionsData: List<MegolmSessionData>,
                fromBackup: Boolean,
                progressListener: ProgressListener?): ImportRoomKeysResult {
-        val t0 = System.currentTimeMillis()
+        val t0 = clock.epochMillis()
 
         val totalNumbersOfKeys = megolmSessionsData.size
         var lastProgress = 0
@@ -103,7 +107,7 @@ internal class MegolmSessionDataImporter @Inject constructor(private val olmDevi
             cryptoStore.markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers)
         }
 
-        val t1 = System.currentTimeMillis()
+        val t1 = clock.epochMillis()
 
         Timber.v("## importMegolmSessionsData : sessions import " + (t1 - t0) + " ms (" + megolmSessionsData.size + " sessions)")
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt
index f0521942..b31b5e8a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt
@@ -46,6 +46,7 @@ import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
 import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
 import org.matrix.android.sdk.internal.util.JsonCanonicalizer
 import org.matrix.android.sdk.internal.util.convertToUTF8
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 
 private val loggerTag = LoggerTag("MXMegolmEncryption", LoggerTag.CRYPTO)
@@ -64,7 +65,8 @@ internal class MXMegolmEncryption(
         private val messageEncrypter: MessageEncrypter,
         private val warnOnUnknownDevicesRepository: WarnOnUnknownDeviceRepository,
         private val coroutineDispatchers: MatrixCoroutineDispatchers,
-        private val cryptoCoroutineScope: CoroutineScope
+        private val cryptoCoroutineScope: CoroutineScope,
+        private val clock: Clock,
 ) : IMXEncrypting, IMXGroupEncryption {
 
     // OutboundSessionInfo. Null if we haven't yet started setting one up. Note
@@ -86,11 +88,11 @@ internal class MXMegolmEncryption(
     override suspend fun encryptEventContent(eventContent: Content,
                                              eventType: String,
                                              userIds: List<String>): Content {
-        val ts = System.currentTimeMillis()
+        val ts = clock.epochMillis()
         Timber.tag(loggerTag.value).v("encryptEventContent : getDevicesInRoom")
         val devices = getDevicesInRoom(userIds)
         Timber.tag(loggerTag.value).d("encrypt event in room=$roomId - devices count in room ${devices.allowedDevices.toDebugCount()}")
-        Timber.tag(loggerTag.value).v("encryptEventContent ${System.currentTimeMillis() - ts}: getDevicesInRoom ${devices.allowedDevices.toDebugString()}")
+        Timber.tag(loggerTag.value).v("encryptEventContent ${clock.epochMillis() - ts}: getDevicesInRoom ${devices.allowedDevices.toDebugString()}")
         val outboundSession = ensureOutboundSession(devices.allowedDevices)
 
         return encryptContent(outboundSession, eventType, eventContent)
@@ -99,7 +101,7 @@ internal class MXMegolmEncryption(
                     // annoyingly we have to serialize again the saved outbound session to store message index :/
                     // if not we would see duplicate message index errors
                     olmDevice.storeOutboundGroupSessionForRoom(roomId, outboundSession.sessionId)
-                    Timber.tag(loggerTag.value).d("encrypt event in room=$roomId Finished in ${System.currentTimeMillis() - ts} millis")
+                    Timber.tag(loggerTag.value).d("encrypt event in room=$roomId Finished in ${clock.epochMillis() - ts} millis")
                 }
     }
 
@@ -125,14 +127,14 @@ internal class MXMegolmEncryption(
     }
 
     override suspend fun preshareKey(userIds: List<String>) {
-        val ts = System.currentTimeMillis()
+        val ts = clock.epochMillis()
         Timber.tag(loggerTag.value).d("preshareKey started in $roomId ...")
         val devices = getDevicesInRoom(userIds)
         val outboundSession = ensureOutboundSession(devices.allowedDevices)
 
         notifyWithheldForSession(devices.withHeldDevices, outboundSession)
 
-        Timber.tag(loggerTag.value).d("preshareKey in $roomId done in  ${System.currentTimeMillis() - ts} millis")
+        Timber.tag(loggerTag.value).d("preshareKey in $roomId done in  ${clock.epochMillis() - ts} millis")
     }
 
     /**
@@ -148,12 +150,14 @@ internal class MXMegolmEncryption(
                 "ed25519" to olmDevice.deviceEd25519Key!!
         )
 
-        olmDevice.addInboundGroupSession(sessionId!!, olmDevice.getSessionKey(sessionId)!!, roomId, olmDevice.deviceCurve25519Key!!,
-                emptyList(), keysClaimedMap, false)
+        olmDevice.addInboundGroupSession(
+                sessionId!!, olmDevice.getSessionKey(sessionId)!!, roomId, olmDevice.deviceCurve25519Key!!,
+                emptyList(), keysClaimedMap, false
+        )
 
         defaultKeysBackupService.maybeBackupKeys()
 
-        return MXOutboundSessionInfo(sessionId, SharedWithHelper(roomId, sessionId, cryptoStore))
+        return MXOutboundSessionInfo(sessionId, SharedWithHelper(roomId, sessionId, cryptoStore), clock)
     }
 
     /**
@@ -243,12 +247,12 @@ internal class MXMegolmEncryption(
         payload["type"] = EventType.ROOM_KEY
         payload["content"] = submap
 
-        var t0 = System.currentTimeMillis()
+        var t0 = clock.epochMillis()
         Timber.tag(loggerTag.value).v("shareUserDevicesKey() : starts")
 
         val results = ensureOlmSessionsForDevicesAction.handle(devicesByUser)
         Timber.tag(loggerTag.value).v(
-                """shareUserDevicesKey(): ensureOlmSessionsForDevices succeeds after ${System.currentTimeMillis() - t0} ms"""
+                """shareUserDevicesKey(): ensureOlmSessionsForDevices succeeds after ${clock.epochMillis() - t0} ms"""
                         .trimMargin()
         )
         val contentMap = MXUsersDevicesMap<Any>()
@@ -301,7 +305,7 @@ internal class MXMegolmEncryption(
         cryptoStore.saveGossipingEvents(gossipingEventBuffer)
 
         if (haveTargets) {
-            t0 = System.currentTimeMillis()
+            t0 = clock.epochMillis()
             Timber.tag(loggerTag.value).i("shareUserDevicesKey() ${session.sessionId} : has target")
             Timber.tag(loggerTag.value).d("sending to device room key for ${session.sessionId} to ${contentMap.toDebugString()}")
             val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, contentMap)
@@ -309,7 +313,7 @@ internal class MXMegolmEncryption(
                 withContext(coroutineDispatchers.io) {
                     sendToDeviceTask.execute(sendToDeviceParams)
                 }
-                Timber.tag(loggerTag.value).i("shareUserDevicesKey() : sendToDevice succeeds after ${System.currentTimeMillis() - t0} ms")
+                Timber.tag(loggerTag.value).i("shareUserDevicesKey() : sendToDevice succeeds after ${clock.epochMillis() - t0} ms")
             } catch (failure: Throwable) {
                 // What to do here...
                 Timber.tag(loggerTag.value).e("shareUserDevicesKey() : Failed to share <${session.sessionId}>")
@@ -334,7 +338,8 @@ internal class MXMegolmEncryption(
                                           senderKey: String?,
                                           code: WithHeldCode) {
         Timber.tag(loggerTag.value).d("notifyKeyWithHeld() :sending withheld for session:$sessionId and code $code to" +
-                " ${targets.joinToString { "${it.userId}|${it.deviceId}" }}")
+                " ${targets.joinToString { "${it.userId}|${it.deviceId}" }}"
+        )
         val withHeldContent = RoomKeyWithHeldContent(
                 roomId = roomId,
                 senderKey = senderKey,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt
index 136fdc05..4225d604 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt
@@ -28,6 +28,7 @@ import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
 import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
 import org.matrix.android.sdk.internal.di.DeviceId
 import org.matrix.android.sdk.internal.di.UserId
+import org.matrix.android.sdk.internal.util.time.Clock
 import javax.inject.Inject
 
 internal class MXMegolmEncryptionFactory @Inject constructor(
@@ -42,7 +43,9 @@ internal class MXMegolmEncryptionFactory @Inject constructor(
         private val messageEncrypter: MessageEncrypter,
         private val warnOnUnknownDevicesRepository: WarnOnUnknownDeviceRepository,
         private val coroutineDispatchers: MatrixCoroutineDispatchers,
-        private val cryptoCoroutineScope: CoroutineScope) {
+        private val cryptoCoroutineScope: CoroutineScope,
+        private val clock: Clock,
+) {
 
     fun create(roomId: String): MXMegolmEncryption {
         return MXMegolmEncryption(
@@ -58,7 +61,8 @@ internal class MXMegolmEncryptionFactory @Inject constructor(
                 messageEncrypter = messageEncrypter,
                 warnOnUnknownDevicesRepository = warnOnUnknownDevicesRepository,
                 coroutineDispatchers = coroutineDispatchers,
-                cryptoCoroutineScope = cryptoCoroutineScope
+                cryptoCoroutineScope = cryptoCoroutineScope,
+                clock = clock,
         )
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXOutboundSessionInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXOutboundSessionInfo.kt
index 091abd49..28d925d8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXOutboundSessionInfo.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXOutboundSessionInfo.kt
@@ -18,21 +18,24 @@ package org.matrix.android.sdk.internal.crypto.algorithms.megolm
 
 import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
 import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 
 internal class MXOutboundSessionInfo(
         // The id of the session
         val sessionId: String,
         val sharedWithHelper: SharedWithHelper,
+        private val clock: Clock,
         // When the session was created
-        private val creationTime: Long = System.currentTimeMillis()) {
+        private val creationTime: Long = clock.epochMillis(),
+) {
 
     // Number of times this session has been used
     var useCount: Int = 0
 
     fun needsRotation(rotationPeriodMsgs: Int, rotationPeriodMs: Int): Boolean {
         var needsRotation = false
-        val sessionLifetime = System.currentTimeMillis() - creationTime
+        val sessionLifetime = clock.epochMillis() - creationTime
 
         if (useCount >= rotationPeriodMsgs || sessionLifetime >= rotationPeriodMs) {
             Timber.v("## needsRotation() : Rotating megolm session after $useCount, ${sessionLifetime}ms")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryption.kt
index 7fdfd5a2..c842c540 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryption.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryption.kt
@@ -33,7 +33,7 @@ internal class MXOlmEncryption(
         private val messageEncrypter: MessageEncrypter,
         private val deviceListManager: DeviceListManager,
         private val ensureOlmSessionsForUsersAction: EnsureOlmSessionsForUsersAction) :
-    IMXEncrypting {
+        IMXEncrypting {
 
     override suspend fun encryptEventContent(eventContent: Content, eventType: String, userIds: List<String>): Content {
         // pick the list of recipients based on the membership list.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt
index 91b6af6f..65bbb0c4 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt
@@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.session.crypto.model.EncryptedFileKey
 import org.matrix.android.sdk.internal.util.base64ToBase64Url
 import org.matrix.android.sdk.internal.util.base64ToUnpaddedBase64
 import org.matrix.android.sdk.internal.util.base64UrlToBase64
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import java.io.ByteArrayOutputStream
 import java.io.File
@@ -42,8 +43,9 @@ internal object MXEncryptedAttachments {
 
     fun encrypt(clearStream: InputStream,
                 outputFile: File,
+                clock: Clock,
                 progress: ((current: Int, total: Int) -> Unit)): EncryptedFileInfo {
-        val t0 = System.currentTimeMillis()
+        val t0 = clock.epochMillis()
         val secureRandom = SecureRandom()
         val initVectorBytes = ByteArray(16) { 0.toByte() }
 
@@ -100,7 +102,7 @@ internal object MXEncryptedAttachments {
                 hashes = mapOf("sha256" to base64ToUnpaddedBase64(Base64.encodeToString(messageDigest.digest(), Base64.DEFAULT))),
                 v = "v2"
         )
-                .also { Timber.v("Encrypt in ${System.currentTimeMillis() - t0}ms") }
+                .also { Timber.v("Encrypt in ${clock.epochMillis() - t0}ms") }
     }
 
 //    fun cipherInputStream(attachmentStream: InputStream, mimetype: String?): Pair<DigestInputStream, EncryptedFileInfo> {
@@ -159,8 +161,8 @@ internal object MXEncryptedAttachments {
      * @param attachmentStream the attachment stream. Will be closed after this method call.
      * @return the encryption file info
      */
-    fun encryptAttachment(attachmentStream: InputStream): EncryptionResult {
-        val t0 = System.currentTimeMillis()
+    fun encryptAttachment(attachmentStream: InputStream, clock: Clock): EncryptionResult {
+        val t0 = clock.epochMillis()
         val secureRandom = SecureRandom()
 
         // generate a random iv key
@@ -221,7 +223,7 @@ internal object MXEncryptedAttachments {
                 ),
                 encryptedByteArray = byteArrayOutputStream.toByteArray()
         )
-                .also { Timber.v("Encrypt in ${System.currentTimeMillis() - t0}ms") }
+                .also { Timber.v("Encrypt in ${clock.epochMillis() - t0}ms") }
     }
 
     /**
@@ -234,14 +236,16 @@ internal object MXEncryptedAttachments {
      */
     fun decryptAttachment(attachmentStream: InputStream?,
                           elementToDecrypt: ElementToDecrypt?,
-                          outputStream: OutputStream): Boolean {
+                          outputStream: OutputStream,
+                          clock: Clock
+    ): Boolean {
         // sanity checks
         if (null == attachmentStream || elementToDecrypt == null) {
             Timber.e("## decryptAttachment() : null stream")
             return false
         }
 
-        val t0 = System.currentTimeMillis()
+        val t0 = clock.epochMillis()
 
         try {
             val key = Base64.decode(base64UrlToBase64(elementToDecrypt.k), Base64.DEFAULT)
@@ -279,7 +283,8 @@ internal object MXEncryptedAttachments {
                 return false
             }
 
-            return true.also { Timber.v("Decrypt in ${System.currentTimeMillis() - t0}ms") }
+            Timber.v("Decrypt in ${clock.epochMillis() - t0} ms")
+            return true
         } catch (oom: OutOfMemoryError) {
             Timber.e(oom, "## decryptAttachment() failed: OOM")
         } catch (e: Exception) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupPassword.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupPassword.kt
index c12879db..4d5b38ac 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupPassword.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupPassword.kt
@@ -26,6 +26,7 @@ import java.util.UUID
 import javax.crypto.Mac
 import javax.crypto.spec.SecretKeySpec
 import kotlin.experimental.xor
+import kotlin.system.measureTimeMillis
 
 private const val SALT_LENGTH = 32
 private const val DEFAULT_ITERATION = 500_000
@@ -91,52 +92,53 @@ internal fun deriveKey(password: String,
                        iterations: Int,
                        progressListener: ProgressListener?): ByteArray {
     // Note: copied and adapted from MXMegolmExportEncryption
-    val t0 = System.currentTimeMillis()
-
     // based on https://en.wikipedia.org/wiki/PBKDF2 algorithm
     // it is simpler than the generic algorithm because the expected key length is equal to the mac key length.
     // noticed as dklen/hlen
 
-    // dklen = 256
-    // hlen = 512
-    val prf = Mac.getInstance("HmacSHA512")
-
-    prf.init(SecretKeySpec(password.toByteArray(), "HmacSHA512"))
-
     // 256 bits key length
     val dk = ByteArray(32)
-    val uc = ByteArray(64)
 
-    // U1 = PRF(Password, Salt || INT_32_BE(i)) with i goes from 1 to dklen/hlen
-    prf.update(salt.toByteArray())
-    val int32BE = byteArrayOf(0, 0, 0, 1)
-    prf.update(int32BE)
-    prf.doFinal(uc, 0)
+    measureTimeMillis {
+        // dklen = 256
+        // hlen = 512
+        val prf = Mac.getInstance("HmacSHA512")
 
-    // copy to the key
-    System.arraycopy(uc, 0, dk, 0, dk.size)
+        prf.init(SecretKeySpec(password.toByteArray(), "HmacSHA512"))
 
-    var lastProgress = -1
+        val uc = ByteArray(64)
 
-    for (index in 2..iterations) {
-        // Uc = PRF(Password, Uc-1)
-        prf.update(uc)
+        // U1 = PRF(Password, Salt || INT_32_BE(i)) with i goes from 1 to dklen/hlen
+        prf.update(salt.toByteArray())
+        val int32BE = byteArrayOf(0, 0, 0, 1)
+        prf.update(int32BE)
         prf.doFinal(uc, 0)
 
-        // F(Password, Salt, c, i) = U1 ^ U2 ^ ... ^ Uc
-        for (byteIndex in dk.indices) {
-            dk[byteIndex] = dk[byteIndex] xor uc[byteIndex]
-        }
+        // copy to the key
+        System.arraycopy(uc, 0, dk, 0, dk.size)
 
-        val progress = (index + 1) * 100 / iterations
-        if (progress != lastProgress) {
-            lastProgress = progress
-            progressListener?.onProgress(lastProgress, 100)
+        var lastProgress = -1
+
+        for (index in 2..iterations) {
+            // Uc = PRF(Password, Uc-1)
+            prf.update(uc)
+            prf.doFinal(uc, 0)
+
+            // F(Password, Salt, c, i) = U1 ^ U2 ^ ... ^ Uc
+            for (byteIndex in dk.indices) {
+                dk[byteIndex] = dk[byteIndex] xor uc[byteIndex]
+            }
+
+            val progress = (index + 1) * 100 / iterations
+            if (progress != lastProgress) {
+                lastProgress = progress
+                progressListener?.onProgress(lastProgress, 100)
+            }
         }
+    }.also {
+        Timber.v("KeysBackupPassword: deriveKeys() : $iterations in $it ms")
     }
 
-    Timber.v("KeysBackupPassword: deriveKeys() : " + iterations + " in " + (System.currentTimeMillis() - t0) + " ms")
-
     return dk
 }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmSessionWrapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmSessionWrapper.kt
index 927d049e..d7ce553f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmSessionWrapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/OlmSessionWrapper.kt
@@ -34,7 +34,7 @@ internal data class OlmSessionWrapper(
     /**
      * Notify that a message has been received on this olm session so that it updates `lastReceivedMessageTs`
      */
-    fun onMessageReceived() {
-        lastReceivedMessageTs = System.currentTimeMillis()
+    fun onMessageReceived(currentTimeMillis: Long) {
+        lastReceivedMessageTs = currentTimeMillis
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt
index 19e66635..8c877593 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt
@@ -66,7 +66,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
                                      key: SsssKeySpec?,
                                      keyName: String,
                                      keySigner: KeySigner?): SsssKeyCreationInfo {
-        return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.main) {
+        return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.computation) {
             val bytes = (key as? RawBytesKeySpec)?.privateKey
                     ?: ByteArray(32).also {
                         SecureRandom().nextBytes(it)
@@ -99,7 +99,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
                                                    passphrase: String,
                                                    keySigner: KeySigner,
                                                    progressListener: ProgressListener?): SsssKeyCreationInfo {
-        return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.main) {
+        return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.computation) {
             val privatePart = generatePrivateKeyWithPassword(passphrase, progressListener)
 
             val storageKeyContent = SecretStorageKeyContent(
@@ -158,7 +158,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
     }
 
     override suspend fun storeSecret(name: String, secretBase64: String, keys: List<SharedSecretStorageService.KeyRef>) {
-        withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.main) {
+        withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.computation) {
             val encryptedContents = HashMap<String, EncryptedSecretContent>()
             keys.forEach {
                 val keyId = it.keyId
@@ -174,7 +174,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
                             throw SharedSecretStorageError.UnknownAlgorithm(key.keyInfo.content.algorithm ?: "")
                         }
                     }
-                    is KeyInfoResult.Error -> throw key.error
+                    is KeyInfoResult.Error   -> throw key.error
                 }
             }
 
@@ -316,7 +316,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
         val algorithm = key.keyInfo.content
         if (SSSS_ALGORITHM_CURVE25519_AES_SHA2 == algorithm.algorithm) {
             val keySpec = secretKey as? RawBytesKeySpec ?: throw SharedSecretStorageError.BadKeyFormat
-            return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.main) {
+            return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.computation) {
                 // decrypt from recovery key
                 withOlmDecryption { olmPkDecryption ->
                     olmPkDecryption.setPrivateKey(keySpec.privateKey)
@@ -331,7 +331,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
             }
         } else if (SSSS_ALGORITHM_AES_HMAC_SHA2 == algorithm.algorithm) {
             val keySpec = secretKey as? RawBytesKeySpec ?: throw SharedSecretStorageError.BadKeyFormat
-            return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.main) {
+            return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.computation) {
                 decryptAesHmacSha2(keySpec, name, secretContent)
             }
         } else {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt
index 99adbbfb..a509315e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt
@@ -97,6 +97,7 @@ import org.matrix.android.sdk.internal.di.MoshiProvider
 import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.extensions.clearWith
 import org.matrix.android.sdk.internal.session.SessionScope
+import org.matrix.android.sdk.internal.util.time.Clock
 import org.matrix.olm.OlmAccount
 import org.matrix.olm.OlmException
 import org.matrix.olm.OlmOutboundGroupSession
@@ -110,7 +111,8 @@ internal class RealmCryptoStore @Inject constructor(
         @CryptoDatabase private val realmConfiguration: RealmConfiguration,
         private val crossSigningKeysMapper: CrossSigningKeysMapper,
         @UserId private val userId: String,
-        @DeviceId private val deviceId: String?
+        @DeviceId private val deviceId: String?,
+        private val clock: Clock,
 ) : IMXCryptoStore {
 
     /* ==========================================================================================
@@ -307,7 +309,7 @@ internal class RealmCryptoStore @Inject constructor(
                         // Add the device
                         Timber.d("Add device ${cryptoDeviceInfo.deviceId} of user $userId")
                         val newEntity = CryptoMapper.mapToEntity(cryptoDeviceInfo)
-                        newEntity.firstTimeSeenLocalTs = System.currentTimeMillis()
+                        newEntity.firstTimeSeenLocalTs = clock.epochMillis()
                         userEntity.devices.add(newEntity)
                     } else {
                         // Update the device
@@ -715,6 +717,7 @@ internal class RealmCryptoStore @Inject constructor(
         return doWithRealm(realmConfiguration) {
             it.where<OlmSessionEntity>()
                     .equalTo(OlmSessionEntityFields.DEVICE_KEY, deviceKey)
+                    .sort(OlmSessionEntityFields.LAST_RECEIVED_MESSAGE_TS, Sort.DESCENDING)
                     .findAll()
                     .mapNotNull { sessionEntity ->
                         sessionEntity.sessionId
@@ -791,7 +794,7 @@ internal class RealmCryptoStore @Inject constructor(
 
                 if (outboundGroupSession != null) {
                     val info = realm.createObject(OutboundGroupSessionInfoEntity::class.java).apply {
-                        creationTime = System.currentTimeMillis()
+                        creationTime = clock.epochMillis()
                         putOutboundGroupSession(outboundGroupSession)
                     }
                     entity.outboundSessionInfo = info
@@ -881,7 +884,8 @@ internal class RealmCryptoStore @Inject constructor(
                 try {
                     val key = OlmInboundGroupSessionEntity.createPrimaryKey(
                             olmInboundGroupSessionWrapper.olmInboundGroupSession?.sessionIdentifier(),
-                            olmInboundGroupSessionWrapper.senderKey)
+                            olmInboundGroupSessionWrapper.senderKey
+                    )
 
                     it.where<OlmInboundGroupSessionEntity>()
                             .equalTo(OlmInboundGroupSessionEntityFields.PRIMARY_KEY, key)
@@ -1056,13 +1060,16 @@ internal class RealmCryptoStore @Inject constructor(
                             localCreationTimestamp = 0
                     )
         }
-        return monarchy.findAllPagedWithChanges(realmDataSourceFactory,
-                LivePagedListBuilder(dataSourceFactory,
+        return monarchy.findAllPagedWithChanges(
+                realmDataSourceFactory,
+                LivePagedListBuilder(
+                        dataSourceFactory,
                         PagedList.Config.Builder()
                                 .setPageSize(20)
                                 .setEnablePlaceholders(false)
                                 .setPrefetchDistance(1)
-                                .build())
+                                .build()
+                )
         )
     }
 
@@ -1071,13 +1078,16 @@ internal class RealmCryptoStore @Inject constructor(
             realm.where<GossipingEventEntity>().sort(GossipingEventEntityFields.AGE_LOCAL_TS, Sort.DESCENDING)
         }
         val dataSourceFactory = realmDataSourceFactory.map { it.toModel() }
-        val trail = monarchy.findAllPagedWithChanges(realmDataSourceFactory,
-                LivePagedListBuilder(dataSourceFactory,
+        val trail = monarchy.findAllPagedWithChanges(
+                realmDataSourceFactory,
+                LivePagedListBuilder(
+                        dataSourceFactory,
                         PagedList.Config.Builder()
                                 .setPageSize(20)
                                 .setEnablePlaceholders(false)
                                 .setPrefetchDistance(1)
-                                .build())
+                                .build()
+                )
         )
         return trail
     }
@@ -1152,7 +1162,7 @@ internal class RealmCryptoStore @Inject constructor(
 
     override fun saveGossipingEvents(events: List<Event>) {
         monarchy.writeAsync { realm ->
-            val now = System.currentTimeMillis()
+            val now = clock.epochMillis()
             events.forEach { event ->
                 val ageLocalTs = event.unsignedData?.age?.let { now - it } ?: now
                 val entity = GossipingEventEntity(
@@ -1325,7 +1335,7 @@ internal class RealmCryptoStore @Inject constructor(
                     .findAll()
                     .mapNotNull { entity ->
                         when (entity.type) {
-                            GossipRequestType.KEY    -> {
+                            GossipRequestType.KEY -> {
                                 IncomingRoomKeyRequest(
                                         userId = entity.otherUserId,
                                         deviceId = entity.otherDeviceId,
@@ -1358,7 +1368,7 @@ internal class RealmCryptoStore @Inject constructor(
                 it.otherUserId = request.userId
                 it.requestId = request.requestId ?: ""
                 it.requestState = GossipingRequestState.PENDING
-                it.localCreationTimestamp = ageLocalTS ?: System.currentTimeMillis()
+                it.localCreationTimestamp = ageLocalTS ?: clock.epochMillis()
                 if (request is IncomingSecretShareRequest) {
                     it.type = GossipRequestType.SECRET
                     it.requestedInfoStr = request.secretName
@@ -1379,7 +1389,7 @@ internal class RealmCryptoStore @Inject constructor(
                     it.otherUserId = request.userId
                     it.requestId = request.requestId ?: ""
                     it.requestState = GossipingRequestState.PENDING
-                    it.localCreationTimestamp = request.localCreationTimestamp ?: System.currentTimeMillis()
+                    it.localCreationTimestamp = request.localCreationTimestamp ?: clock.epochMillis()
                     if (request is IncomingSecretShareRequest) {
                         it.type = GossipRequestType.SECRET
                         it.requestedInfoStr = request.secretName
@@ -1535,13 +1545,16 @@ internal class RealmCryptoStore @Inject constructor(
             it.toOutgoingGossipingRequest() as? OutgoingRoomKeyRequest
                     ?: OutgoingRoomKeyRequest(requestBody = null, requestId = "?", recipients = emptyMap(), state = OutgoingGossipingRequestState.CANCELLED)
         }
-        val trail = monarchy.findAllPagedWithChanges(realmDataSourceFactory,
-                LivePagedListBuilder(dataSourceFactory,
+        val trail = monarchy.findAllPagedWithChanges(
+                realmDataSourceFactory,
+                LivePagedListBuilder(
+                        dataSourceFactory,
                         PagedList.Config.Builder()
                                 .setPageSize(20)
                                 .setEnablePlaceholders(false)
                                 .setPrefetchDistance(1)
-                                .build())
+                                .build()
+                )
         )
         return trail
     }
@@ -1706,7 +1719,7 @@ internal class RealmCryptoStore @Inject constructor(
      * So we need to tidy up a bit
      */
     override fun tidyUpDataBase() {
-        val prevWeekTs = System.currentTimeMillis() - 7 * 24 * 60 * 60 * 1_000
+        val prevWeekTs = clock.epochMillis() - 7 * 24 * 60 * 60 * 1_000
         doRealmTransaction(realmConfiguration) { realm ->
 
             // Only keep one week history
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt
index cac64994..32f24c5d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt
@@ -33,10 +33,13 @@ import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo
 import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo013
 import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo014
 import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo015
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import javax.inject.Inject
 
-internal class RealmCryptoStoreMigration @Inject constructor() : RealmMigration {
+internal class RealmCryptoStoreMigration @Inject constructor(
+        private val clock: Clock,
+) : RealmMigration {
     /**
      * Forces all RealmCryptoStoreMigration instances to be equal
      * Avoids Realm throwing when multiple instances of the migration are set
@@ -59,7 +62,7 @@ internal class RealmCryptoStoreMigration @Inject constructor() : RealmMigration
         if (oldVersion < 5) MigrateCryptoTo005(realm).perform()
         if (oldVersion < 6) MigrateCryptoTo006(realm).perform()
         if (oldVersion < 7) MigrateCryptoTo007(realm).perform()
-        if (oldVersion < 8) MigrateCryptoTo008(realm).perform()
+        if (oldVersion < 8) MigrateCryptoTo008(realm, clock).perform()
         if (oldVersion < 9) MigrateCryptoTo009(realm).perform()
         if (oldVersion < 10) MigrateCryptoTo010(realm).perform()
         if (oldVersion < 11) MigrateCryptoTo011(realm).perform()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo008.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo008.kt
index 785e6a04..ad195e6e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo008.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo008.kt
@@ -21,8 +21,12 @@ import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntityFields
 import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntityFields
 import org.matrix.android.sdk.internal.util.database.RealmMigrator
+import org.matrix.android.sdk.internal.util.time.Clock
 
-internal class MigrateCryptoTo008(realm: DynamicRealm) : RealmMigrator(realm, 8) {
+internal class MigrateCryptoTo008(
+        realm: DynamicRealm,
+        private val clock: Clock,
+) : RealmMigrator(realm, 8) {
 
     override fun doMigrate(realm: DynamicRealm) {
         realm.schema.create("MyDeviceLastSeenInfoEntity")
@@ -33,7 +37,7 @@ internal class MigrateCryptoTo008(realm: DynamicRealm) : RealmMigrator(realm, 8)
                 .addField(MyDeviceLastSeenInfoEntityFields.LAST_SEEN_TS, Long::class.java)
                 .setNullable(MyDeviceLastSeenInfoEntityFields.LAST_SEEN_TS, true)
 
-        val now = System.currentTimeMillis()
+        val now = clock.epochMillis()
         realm.schema.get("DeviceInfoEntity")
                 ?.addField(DeviceInfoEntityFields.FIRST_TIME_SEEN_LOCAL_TS, Long::class.java)
                 ?.setNullable(DeviceInfoEntityFields.FIRST_TIME_SEEN_LOCAL_TS, true)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoRoomEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoRoomEntity.kt
index 6167314b..114a5969 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoRoomEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoRoomEntity.kt
@@ -31,8 +31,8 @@ internal open class CryptoRoomEntity(
         // a security to ensure that a room will never revert to not encrypted
         // even if a new state event with empty encryption, or state is reset somehow
         var wasEncryptedOnce: Boolean? = false
-        ) :
-    RealmObject() {
+) :
+        RealmObject() {
 
     companion object
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmInboundGroupSessionEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmInboundGroupSessionEntity.kt
index f330e882..83671b28 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmInboundGroupSessionEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmInboundGroupSessionEntity.kt
@@ -34,7 +34,7 @@ internal open class OlmInboundGroupSessionEntity(
         var olmInboundGroupSessionData: String? = null,
         // Indicate if the key has been backed up to the homeserver
         var backedUp: Boolean = false) :
-    RealmObject() {
+        RealmObject() {
 
     fun getInboundGroupSession(): OlmInboundGroupSessionWrapper2? {
         return try {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmSessionEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmSessionEntity.kt
index 0b69311c..1a637d76 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmSessionEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/OlmSessionEntity.kt
@@ -30,7 +30,7 @@ internal open class OlmSessionEntity(@PrimaryKey var primaryKey: String = "",
                                      var deviceKey: String? = null,
                                      var olmSessionData: String? = null,
                                      var lastReceivedMessageTs: Long = 0) :
-    RealmObject() {
+        RealmObject() {
 
     fun getOlmSession(): OlmSession? {
         return deserializeFromRealm(olmSessionData)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceTask.kt
index ca04bac5..0a77d33a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceTask.kt
@@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto.tasks
 
 import org.matrix.android.sdk.api.auth.UIABaseAuth
 import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
+import org.matrix.android.sdk.api.session.uia.UiaResult
 import org.matrix.android.sdk.internal.auth.registration.handleUIA
 import org.matrix.android.sdk.internal.crypto.api.CryptoApi
 import org.matrix.android.sdk.internal.crypto.model.rest.DeleteDeviceParams
@@ -47,13 +48,13 @@ internal class DefaultDeleteDeviceTask @Inject constructor(
             }
         } catch (throwable: Throwable) {
             if (params.userInteractiveAuthInterceptor == null ||
-                    !handleUIA(
+                    handleUIA(
                             failure = throwable,
                             interceptor = params.userInteractiveAuthInterceptor,
                             retryBlock = { authUpdate ->
                                 execute(params.copy(userAuthParam = authUpdate))
                             }
-                    )
+                    ) != UiaResult.SUCCESS
             ) {
                 Timber.d("## UIA: propagate failure")
                 throw throwable
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt
index eefdd250..53190c43 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt
@@ -20,6 +20,7 @@ import dagger.Lazy
 import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
 import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey
 import org.matrix.android.sdk.api.session.crypto.crosssigning.KeyUsage
+import org.matrix.android.sdk.api.session.uia.UiaResult
 import org.matrix.android.sdk.api.util.toBase64NoPadding
 import org.matrix.android.sdk.internal.auth.registration.handleUIA
 import org.matrix.android.sdk.internal.crypto.MXOlmDevice
@@ -126,13 +127,13 @@ internal class DefaultInitializeCrossSigningTask @Inject constructor(
                 uploadSigningKeysTask.execute(uploadSigningKeysParams)
             } catch (failure: Throwable) {
                 if (params.interactiveAuthInterceptor == null ||
-                        !handleUIA(
+                        handleUIA(
                                 failure = failure,
                                 interceptor = params.interactiveAuthInterceptor,
                                 retryBlock = { authUpdate ->
                                     uploadSigningKeysTask.execute(uploadSigningKeysParams.copy(userAuthParam = authUpdate))
                                 }
-                        )
+                        ) != UiaResult.SUCCESS
                 ) {
                     Timber.d("## UIA: propagate failure")
                     throw failure
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationTransaction.kt
index e203f03b..e0d912a9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationTransaction.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationTransaction.kt
@@ -123,7 +123,7 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction(
 //        val requestMessage = KeyVerificationRequest(
 //                fromDevice = session.sessionParams.deviceId ?: "",
 //                methods = listOf(KeyVerificationStart.VERIF_METHOD_SAS),
-//                timestamp = System.currentTimeMillis().toInt(),
+//                timestamp = clock.epochMillis().toInt(),
 //                transactionId = transactionId
 //        )
 //
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt
index 28bf1d70..d62ca550 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt
@@ -84,6 +84,7 @@ import org.matrix.android.sdk.internal.di.DeviceId
 import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.session.SessionScope
 import org.matrix.android.sdk.internal.task.TaskExecutor
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import java.util.UUID
 import javax.inject.Inject
@@ -104,7 +105,8 @@ internal class DefaultVerificationService @Inject constructor(
         private val verificationTransportToDeviceFactory: VerificationTransportToDeviceFactory,
         private val crossSigningService: CrossSigningService,
         private val cryptoCoroutineScope: CoroutineScope,
-        private val taskExecutor: TaskExecutor
+        private val taskExecutor: TaskExecutor,
+        private val clock: Clock,
 ) : DefaultVerificationTransaction.Listener, VerificationService {
 
     private val uiHandler = Handler(Looper.getMainLooper())
@@ -261,9 +263,11 @@ internal class DefaultVerificationService @Inject constructor(
     }
 
     override fun markedLocallyAsManuallyVerified(userId: String, deviceID: String) {
-        setDeviceVerificationAction.handle(DeviceTrustLevel(false, true),
+        setDeviceVerificationAction.handle(
+                DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true),
                 userId,
-                deviceID)
+                deviceID
+        )
 
         listeners.forEach {
             try {
@@ -313,7 +317,7 @@ internal class DefaultVerificationService @Inject constructor(
         val requestsForUser = pendingRequests.getOrPut(senderId) { mutableListOf() }
 
         val pendingVerificationRequest = PendingVerificationRequest(
-                ageLocalTs = event.ageLocalTs ?: System.currentTimeMillis(),
+                ageLocalTs = event.ageLocalTs ?: clock.epochMillis(),
                 isIncoming = true,
                 otherUserId = senderId, // requestInfo.toUserId,
                 roomId = null,
@@ -352,7 +356,7 @@ internal class DefaultVerificationService @Inject constructor(
         val requestsForUser = pendingRequests.getOrPut(senderId) { mutableListOf() }
 
         val pendingVerificationRequest = PendingVerificationRequest(
-                ageLocalTs = event.ageLocalTs ?: System.currentTimeMillis(),
+                ageLocalTs = event.ageLocalTs ?: clock.epochMillis(),
                 isIncoming = true,
                 otherUserId = senderId, // requestInfo.toUserId,
                 roomId = event.roomId,
@@ -552,7 +556,8 @@ internal class DefaultVerificationService @Inject constructor(
                             myDeviceInfoHolder.get().myDevice.fingerprint()!!,
                             startReq.transactionId,
                             otherUserId,
-                            autoAccept).also { txConfigure(it) }
+                            autoAccept
+                    ).also { txConfigure(it) }
                     addTransaction(tx)
                     tx.onVerificationStart(startReq)
                     return null
@@ -644,9 +649,11 @@ internal class DefaultVerificationService @Inject constructor(
 
         if (existingRequest != null) {
             // Mark this request as cancelled
-            updatePendingRequest(existingRequest.copy(
-                    cancelConclusion = safeValueOf(cancelReq.code)
-            ))
+            updatePendingRequest(
+                    existingRequest.copy(
+                            cancelConclusion = safeValueOf(cancelReq.code)
+                    )
+            )
         }
 
         existingTransaction?.state = VerificationTxState.Cancelled(safeValueOf(cancelReq.code), false)
@@ -809,15 +816,19 @@ internal class DefaultVerificationService @Inject constructor(
                             ?.let { vt ->
                                 val otherDeviceId = vt.otherDeviceId
                                 if (!crossSigningService.canCrossSign()) {
-                                    outgoingGossipingRequestManager.sendSecretShareRequest(MASTER_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId
-                                            ?: "*")))
-                                    outgoingGossipingRequestManager.sendSecretShareRequest(SELF_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId
-                                            ?: "*")))
-                                    outgoingGossipingRequestManager.sendSecretShareRequest(USER_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId
-                                            ?: "*")))
+                                    outgoingGossipingRequestManager.sendSecretShareRequest(
+                                            MASTER_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId ?: "*"))
+                                    )
+                                    outgoingGossipingRequestManager.sendSecretShareRequest(
+                                            SELF_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId ?: "*"))
+                                    )
+                                    outgoingGossipingRequestManager.sendSecretShareRequest(
+                                            USER_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId ?: "*"))
+                                    )
                                 }
-                                outgoingGossipingRequestManager.sendSecretShareRequest(KEYBACKUP_SECRET_SSSS_NAME, mapOf(userId to listOf(otherDeviceId
-                                        ?: "*")))
+                                outgoingGossipingRequestManager.sendSecretShareRequest(
+                                        KEYBACKUP_SECRET_SSSS_NAME, mapOf(userId to listOf(otherDeviceId ?: "*"))
+                                )
                             }
         }
     }
@@ -917,16 +928,19 @@ internal class DefaultVerificationService @Inject constructor(
                     qrCodeData = qrCodeData,
                     userId = userId,
                     deviceId = deviceId ?: "",
-                    isIncoming = false)
+                    isIncoming = false
+            )
 
             tx.transport = transportCreator.invoke(tx)
 
             addTransaction(tx)
         }
 
-        updatePendingRequest(existingRequest.copy(
-                readyInfo = readyReq
-        ))
+        updatePendingRequest(
+                existingRequest.copy(
+                        readyInfo = readyReq
+                )
+        )
     }
 
     private fun createQrCodeData(requestId: String?, otherUserId: String, otherDeviceId: String?): QrCodeData? {
@@ -1115,7 +1129,8 @@ internal class DefaultVerificationService @Inject constructor(
                     myDeviceInfoHolder.get().myDevice.fingerprint()!!,
                     txID,
                     otherUserId,
-                    otherDeviceId)
+                    otherDeviceId
+            )
             tx.transport = verificationTransportToDeviceFactory.createTransport(tx)
             addTransaction(tx)
 
@@ -1150,7 +1165,7 @@ internal class DefaultVerificationService @Inject constructor(
         val validLocalId = localId ?: LocalEcho.createLocalEchoId()
 
         val verificationRequest = PendingVerificationRequest(
-                ageLocalTs = System.currentTimeMillis(),
+                ageLocalTs = clock.epochMillis(),
                 isIncoming = false,
                 roomId = roomId,
                 localId = validLocalId,
@@ -1175,11 +1190,13 @@ internal class DefaultVerificationService @Inject constructor(
 
         transport.sendVerificationRequest(methodValues, validLocalId, otherUserId, roomId, null) { syncedId, info ->
             // We need to update with the syncedID
-            updatePendingRequest(verificationRequest.copy(
-                    transactionId = syncedId,
-                    // localId stays different
-                    requestInfo = info
-            ))
+            updatePendingRequest(
+                    verificationRequest.copy(
+                            transactionId = syncedId,
+                            // localId stays different
+                            requestInfo = info
+                    )
+            )
         }
 
         requestsForUser.add(verificationRequest)
@@ -1228,7 +1245,7 @@ internal class DefaultVerificationService @Inject constructor(
 
         val verificationRequest = PendingVerificationRequest(
                 transactionId = localId,
-                ageLocalTs = System.currentTimeMillis(),
+                ageLocalTs = clock.epochMillis(),
                 isIncoming = false,
                 roomId = null,
                 localId = localId,
@@ -1254,10 +1271,12 @@ internal class DefaultVerificationService @Inject constructor(
 
         transport.sendVerificationRequest(methodValues, localId, otherUserId, null, targetDevices) { _, info ->
             // Nothing special to do in to device mode
-            updatePendingRequest(verificationRequest.copy(
-                    // localId stays different
-                    requestInfo = info
-            ))
+            updatePendingRequest(
+                    verificationRequest.copy(
+                            // localId stays different
+                            requestInfo = info
+                    )
+            )
         }
 
         requestsForUser.add(verificationRequest)
@@ -1271,9 +1290,11 @@ internal class DefaultVerificationService @Inject constructor(
                 .cancelTransaction(transactionId, otherUserId, null, CancelCode.User)
 
         getExistingVerificationRequest(otherUserId, transactionId)?.let {
-            updatePendingRequest(it.copy(
-                    cancelConclusion = CancelCode.User
-            ))
+            updatePendingRequest(
+                    it.copy(
+                            cancelConclusion = CancelCode.User
+                    )
+            )
         }
     }
 
@@ -1307,7 +1328,8 @@ internal class DefaultVerificationService @Inject constructor(
                     myDeviceInfoHolder.get().myDevice.fingerprint()!!,
                     transactionId,
                     otherUserId,
-                    otherDeviceId)
+                    otherDeviceId
+            )
             tx.transport = verificationTransportRoomMessageFactory.createTransport(roomId, tx)
             addTransaction(tx)
 
@@ -1333,7 +1355,8 @@ internal class DefaultVerificationService @Inject constructor(
                     otherUserId,
                     existingRequest.requestInfo?.fromDevice ?: "",
                     existingRequest.requestInfo?.methods,
-                    methods) {
+                    methods
+            ) {
                 verificationTransportRoomMessageFactory.createTransport(roomId, it)
             }
             if (methods.isNullOrEmpty()) {
@@ -1343,7 +1366,8 @@ internal class DefaultVerificationService @Inject constructor(
             }
             // TODO this is not yet related to a transaction, maybe we should use another method like for cancel?
             val readyMsg = transport.createReady(transactionId, deviceId ?: "", computedMethods)
-            transport.sendToOther(EventType.KEY_VERIFICATION_READY,
+            transport.sendToOther(
+                    EventType.KEY_VERIFICATION_READY,
                     readyMsg,
                     VerificationTxState.None,
                     CancelCode.User,
@@ -1372,7 +1396,8 @@ internal class DefaultVerificationService @Inject constructor(
                     otherUserId,
                     existingRequest.requestInfo?.fromDevice ?: "",
                     existingRequest.requestInfo?.methods,
-                    methods) {
+                    methods
+            ) {
                 verificationTransportToDeviceFactory.createTransport(it)
             }
             if (methods.isNullOrEmpty()) {
@@ -1446,7 +1471,8 @@ internal class DefaultVerificationService @Inject constructor(
                         qrCodeData = qrCodeData,
                         userId = userId,
                         deviceId = deviceId ?: "",
-                        isIncoming = false)
+                        isIncoming = false
+                )
 
                 tx.transport = transportCreator.invoke(tx)
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SendVerificationMessageWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SendVerificationMessageWorker.kt
index a763c05e..0a175ae3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SendVerificationMessageWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/SendVerificationMessageWorker.kt
@@ -35,7 +35,7 @@ import javax.inject.Inject
  * Possible next worker    : None
  */
 internal class SendVerificationMessageWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
-    SessionSafeCoroutineWorker<SendVerificationMessageWorker.Params>(context, params, sessionManager, Params::class.java) {
+        SessionSafeCoroutineWorker<SendVerificationMessageWorker.Params>(context, params, sessionManager, Params::class.java) {
 
     @JsonClass(generateAdapter = true)
     internal data class Params(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationMessageProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationMessageProcessor.kt
index 52166761..ec4e1aa6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationMessageProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationMessageProcessor.kt
@@ -34,11 +34,13 @@ import org.matrix.android.sdk.internal.database.model.EventInsertType
 import org.matrix.android.sdk.internal.di.DeviceId
 import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import javax.inject.Inject
 
 internal class VerificationMessageProcessor @Inject constructor(
         private val eventDecryptor: EventDecryptor,
+        private val clock: Clock,
         private val verificationService: DefaultVerificationService,
         @UserId private val userId: String,
         @DeviceId private val deviceId: String?
@@ -71,8 +73,7 @@ internal class VerificationMessageProcessor @Inject constructor(
         // If the request is in the future by more than 5 minutes or more than 10 minutes in the past,
         // the message should be ignored by the receiver.
 
-        if (!VerificationService.isValidRequest(event.ageLocalTs
-                        ?: event.originServerTs)) return Unit.also {
+        if (!VerificationService.isValidRequest(event.ageLocalTs ?: event.originServerTs, clock.epochMillis())) return Unit.also {
             Timber.d("## SAS Verification live observer: msgId: ${event.eventId} is outdated")
         }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportRoomMessage.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportRoomMessage.kt
index bd118690..325a6f0b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportRoomMessage.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportRoomMessage.kt
@@ -47,6 +47,7 @@ import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_REC
 import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
 import org.matrix.android.sdk.internal.di.WorkManagerProvider
 import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
+import org.matrix.android.sdk.internal.util.time.Clock
 import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
 import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
 import timber.log.Timber
@@ -61,7 +62,8 @@ internal class VerificationTransportRoomMessage(
         private val roomId: String,
         private val localEchoEventFactory: LocalEchoEventFactory,
         private val tx: DefaultVerificationTransaction?,
-        private val coroutineScope: CoroutineScope
+        private val coroutineScope: CoroutineScope,
+        private val clock: Clock,
 ) : VerificationTransport {
 
     override fun <T> sendToOther(type: String,
@@ -77,10 +79,12 @@ internal class VerificationTransportRoomMessage(
                 content = verificationInfo.toEventContent()!!
         )
 
-        val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params(
-                sessionId = sessionId,
-                eventId = event.eventId ?: ""
-        ))
+        val workerParams = WorkerParamsFactory.toData(
+                SendVerificationMessageWorker.Params(
+                        sessionId = sessionId,
+                        eventId = event.eventId ?: ""
+                )
+        )
         val enqueueInfo = enqueueSendWork(workerParams)
 
         // I cannot just listen to the given work request, because when used in a uniqueWork,
@@ -155,7 +159,7 @@ internal class VerificationTransportRoomMessage(
                 transactionId = "",
                 fromDevice = userDeviceId ?: "",
                 methods = supportedMethods,
-                timestamp = System.currentTimeMillis()
+                timestamp = clock.epochMillis()
         )
 
         val info = MessageVerificationRequestContent(
@@ -175,10 +179,12 @@ internal class VerificationTransportRoomMessage(
                 content
         )
 
-        val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params(
-                sessionId = sessionId,
-                eventId = event.eventId ?: ""
-        ))
+        val workerParams = WorkerParamsFactory.toData(
+                SendVerificationMessageWorker.Params(
+                        sessionId = sessionId,
+                        eventId = event.eventId ?: ""
+                )
+        )
 
         val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder<SendVerificationMessageWorker>()
                 .setConstraints(WorkManagerProvider.workConstraints)
@@ -230,10 +236,12 @@ internal class VerificationTransportRoomMessage(
                 roomId = roomId,
                 content = MessageVerificationCancelContent.create(transactionId, code).toContent()
         )
-        val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params(
-                sessionId = sessionId,
-                eventId = event.eventId ?: ""
-        ))
+        val workerParams = WorkerParamsFactory.toData(
+                SendVerificationMessageWorker.Params(
+                        sessionId = sessionId,
+                        eventId = event.eventId ?: ""
+                )
+        )
         enqueueSendWork(workerParams)
     }
 
@@ -250,10 +258,12 @@ internal class VerificationTransportRoomMessage(
                         )
                 ).toContent()
         )
-        val workerParams = WorkerParamsFactory.toData(SendVerificationMessageWorker.Params(
-                sessionId = sessionId,
-                eventId = event.eventId ?: ""
-        ))
+        val workerParams = WorkerParamsFactory.toData(
+                SendVerificationMessageWorker.Params(
+                        sessionId = sessionId,
+                        eventId = event.eventId ?: ""
+                )
+        )
         val enqueueInfo = enqueueSendWork(workerParams)
 
         val workLiveData = workManagerProvider.workManager
@@ -296,13 +306,13 @@ internal class VerificationTransportRoomMessage(
                               messageAuthenticationCode: String,
                               shortAuthenticationStrings: List<String>): VerificationInfoAccept =
             MessageVerificationAcceptContent.create(
-            tid,
-            keyAgreementProtocol,
-            hash,
-            commitment,
-            messageAuthenticationCode,
-            shortAuthenticationStrings
-    )
+                    tid,
+                    keyAgreementProtocol,
+                    hash,
+                    commitment,
+                    messageAuthenticationCode,
+                    shortAuthenticationStrings
+            )
 
     override fun createKey(tid: String, pubKey: String): VerificationInfoKey = MessageVerificationKeyContent.create(tid, pubKey)
 
@@ -361,7 +371,7 @@ internal class VerificationTransportRoomMessage(
     private fun createEventAndLocalEcho(localId: String = LocalEcho.createLocalEchoId(), type: String, roomId: String, content: Content): Event {
         return Event(
                 roomId = roomId,
-                originServerTs = System.currentTimeMillis(),
+                originServerTs = clock.epochMillis(),
                 senderId = userId,
                 eventId = localId,
                 type = type,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportRoomMessageFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportRoomMessageFactory.kt
index f8912727..b1b7ad7a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportRoomMessageFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportRoomMessageFactory.kt
@@ -22,6 +22,7 @@ import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.di.WorkManagerProvider
 import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
 import org.matrix.android.sdk.internal.task.TaskExecutor
+import org.matrix.android.sdk.internal.util.time.Clock
 import javax.inject.Inject
 
 internal class VerificationTransportRoomMessageFactory @Inject constructor(
@@ -33,17 +34,21 @@ internal class VerificationTransportRoomMessageFactory @Inject constructor(
         @DeviceId
         private val deviceId: String?,
         private val localEchoEventFactory: LocalEchoEventFactory,
-        private val taskExecutor: TaskExecutor
+        private val taskExecutor: TaskExecutor,
+        private val clock: Clock,
 ) {
 
     fun createTransport(roomId: String, tx: DefaultVerificationTransaction?): VerificationTransportRoomMessage {
-        return VerificationTransportRoomMessage(workManagerProvider,
+        return VerificationTransportRoomMessage(
+                workManagerProvider,
                 sessionId,
                 userId,
                 deviceId,
                 roomId,
                 localEchoEventFactory,
                 tx,
-                taskExecutor.executorScope)
+                taskExecutor.executorScope,
+                clock
+        )
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDevice.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDevice.kt
index 40deda27..bc24ef29 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDevice.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDevice.kt
@@ -35,13 +35,16 @@ import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
 import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
 import org.matrix.android.sdk.internal.task.TaskExecutor
 import org.matrix.android.sdk.internal.task.configureWith
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 
+// TODO var could be val
 internal class VerificationTransportToDevice(
         private var tx: DefaultVerificationTransaction?,
         private var sendToDeviceTask: SendToDeviceTask,
         private val myDeviceId: String?,
-        private var taskExecutor: TaskExecutor
+        private var taskExecutor: TaskExecutor,
+        private val clock: Clock,
 ) : VerificationTransport {
 
     override fun sendVerificationRequest(supportedMethods: List<String>,
@@ -56,7 +59,7 @@ internal class VerificationTransportToDevice(
                 transactionId = localId,
                 fromDevice = myDeviceId ?: "",
                 methods = supportedMethods,
-                timestamp = System.currentTimeMillis()
+                timestamp = clock.epochMillis()
         )
         val keyReq = KeyVerificationRequest(
                 fromDevice = validKeyReq.fromDevice,
@@ -201,7 +204,8 @@ internal class VerificationTransportToDevice(
             hash,
             commitment,
             messageAuthenticationCode,
-            shortAuthenticationStrings)
+            shortAuthenticationStrings
+    )
 
     override fun createKey(tid: String, pubKey: String): VerificationInfoKey = KeyVerificationKey.create(tid, pubKey)
 
@@ -221,7 +225,8 @@ internal class VerificationTransportToDevice(
                 hashes,
                 messageAuthenticationCodes,
                 shortAuthenticationStrings,
-                null)
+                null
+        )
     }
 
     override fun createStartForQrCode(fromDevice: String,
@@ -235,7 +240,8 @@ internal class VerificationTransportToDevice(
                 null,
                 null,
                 null,
-                sharedSecret)
+                sharedSecret
+        )
     }
 
     override fun createReady(tid: String, fromDevice: String, methods: List<String>): VerificationInfoReady {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDeviceFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDeviceFactory.kt
index e9a2c65e..312d9118 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDeviceFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDeviceFactory.kt
@@ -19,14 +19,17 @@ package org.matrix.android.sdk.internal.crypto.verification
 import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
 import org.matrix.android.sdk.internal.di.DeviceId
 import org.matrix.android.sdk.internal.task.TaskExecutor
+import org.matrix.android.sdk.internal.util.time.Clock
 import javax.inject.Inject
 
 internal class VerificationTransportToDeviceFactory @Inject constructor(
         private val sendToDeviceTask: SendToDeviceTask,
         @DeviceId val myDeviceId: String?,
-        private val taskExecutor: TaskExecutor) {
+        private val taskExecutor: TaskExecutor,
+        private val clock: Clock,
+) {
 
     fun createTransport(tx: DefaultVerificationTransaction?): VerificationTransportToDevice {
-        return VerificationTransportToDevice(tx, sendToDeviceTask, myDeviceId, taskExecutor)
+        return VerificationTransportToDevice(tx, sendToDeviceTask, myDeviceId, taskExecutor, clock)
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/AsyncTransaction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/AsyncTransaction.kt
index 315d77d9..7d263f19 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/AsyncTransaction.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/AsyncTransaction.kt
@@ -24,6 +24,7 @@ import kotlinx.coroutines.isActive
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 import timber.log.Timber
+import kotlin.system.measureTimeMillis
 
 internal fun <T> CoroutineScope.asyncTransaction(monarchy: Monarchy, transaction: suspend (realm: Realm) -> T) {
     asyncTransaction(monarchy.realmConfiguration, transaction)
@@ -41,13 +42,13 @@ internal suspend fun <T> awaitTransaction(config: RealmConfiguration, transactio
             bgRealm.beginTransaction()
             val result: T
             try {
-                val start = System.currentTimeMillis()
-                result = transaction(bgRealm)
-                if (isActive) {
-                    bgRealm.commitTransaction()
-                    val end = System.currentTimeMillis()
-                    val time = end - start
-                    Timber.v("Execute transaction in $time millis")
+                measureTimeMillis {
+                    result = transaction(bgRealm)
+                    if (isActive) {
+                        bgRealm.commitTransaction()
+                    }
+                }.also {
+                    Timber.v("Execute transaction in $it millis")
                 }
             } finally {
                 if (bgRealm.isInTransaction) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt
index 115025cc..751992fa 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt
@@ -19,6 +19,9 @@ package org.matrix.android.sdk.internal.database
 import com.zhuinden.monarchy.Monarchy
 import io.realm.RealmConfiguration
 import io.realm.RealmResults
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.launch
 import org.matrix.android.sdk.internal.database.mapper.asDomain
 import org.matrix.android.sdk.internal.database.model.EventEntity
@@ -32,16 +35,28 @@ import javax.inject.Inject
 
 internal class EventInsertLiveObserver @Inject constructor(@SessionDatabase realmConfiguration: RealmConfiguration,
                                                            private val processors: Set<@JvmSuppressWildcards EventInsertLiveProcessor>) :
-    RealmLiveEntityObserver<EventInsertEntity>(realmConfiguration) {
+        RealmLiveEntityObserver<EventInsertEntity>(realmConfiguration) {
 
     override val query = Monarchy.Query {
         it.where(EventInsertEntity::class.java).equalTo(EventInsertEntityFields.CAN_BE_PROCESSED, true)
     }
 
+    private val onResultsChangedFlow = MutableSharedFlow<RealmResults<EventInsertEntity>>()
+
+    init {
+        onResultsChangedFlow
+                .onEach { handleChange(it) }
+                .launchIn(observerScope)
+    }
+
     override fun onChange(results: RealmResults<EventInsertEntity>) {
         if (!results.isLoaded || results.isEmpty()) {
             return
         }
+        observerScope.launch { onResultsChangedFlow.emit(results) }
+    }
+
+    private suspend fun handleChange(results: RealmResults<EventInsertEntity>) {
         val idsToDeleteAfterProcess = ArrayList<String>()
         val filteredEvents = ArrayList<EventInsertEntity>(results.size)
         Timber.v("EventInsertEntity updated with ${results.size} results in db")
@@ -58,30 +73,29 @@ internal class EventInsertLiveObserver @Inject constructor(@SessionDatabase real
             }
             idsToDeleteAfterProcess.add(it.eventId)
         }
-        observerScope.launch {
-            awaitTransaction(realmConfiguration) { realm ->
-                Timber.v("##Transaction: There are ${filteredEvents.size} events to process ")
-                filteredEvents.forEach { eventInsert ->
-                    val eventId = eventInsert.eventId
-                    val event = EventEntity.where(realm, eventId).findFirst()
-                    if (event == null) {
-                        Timber.v("Event $eventId not found")
-                        return@forEach
-                    }
-                    val domainEvent = event.asDomain()
-                    processors.filter {
-                        it.shouldProcess(eventId, domainEvent.getClearType(), eventInsert.insertType)
-                    }.forEach {
-                        it.process(realm, domainEvent)
-                    }
+
+        awaitTransaction(realmConfiguration) { realm ->
+            Timber.v("##Transaction: There are ${filteredEvents.size} events to process ")
+            filteredEvents.forEach { eventInsert ->
+                val eventId = eventInsert.eventId
+                val event = EventEntity.where(realm, eventId).findFirst()
+                if (event == null) {
+                    Timber.v("Event $eventId not found")
+                    return@forEach
+                }
+                val domainEvent = event.asDomain()
+                processors.filter {
+                    it.shouldProcess(eventId, domainEvent.getClearType(), eventInsert.insertType)
+                }.forEach {
+                    it.process(realm, domainEvent)
                 }
-                realm.where(EventInsertEntity::class.java)
-                        .`in`(EventInsertEntityFields.EVENT_ID, idsToDeleteAfterProcess.toTypedArray())
-                        .findAll()
-                        .deleteAllFromRealm()
             }
-            processors.forEach { it.onPostProcess() }
+            realm.where(EventInsertEntity::class.java)
+                    .`in`(EventInsertEntityFields.EVENT_ID, idsToDeleteAfterProcess.toTypedArray())
+                    .findAll()
+                    .deleteAllFromRealm()
         }
+        processors.forEach { it.onPostProcess() }
     }
 
     private fun shouldProcess(eventInsertEntity: EventInsertEntity): Boolean {
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 50eb086f..f2f88e21 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
@@ -35,7 +35,7 @@ import java.util.concurrent.atomic.AtomicReference
 internal interface LiveEntityObserver : SessionLifecycleObserver
 
 internal abstract class RealmLiveEntityObserver<T : RealmObject>(protected val realmConfiguration: RealmConfiguration) :
-    LiveEntityObserver, RealmChangeListener<RealmResults<T>> {
+        LiveEntityObserver, RealmChangeListener<RealmResults<T>> {
 
     private companion object {
         val BACKGROUND_HANDLER = createBackgroundHandler("LIVE_ENTITY_BACKGROUND")
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 8c62c345..e5b59155 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
@@ -33,7 +33,7 @@ import kotlin.concurrent.getOrSet
  */
 @SessionScope
 internal class RealmSessionProvider @Inject constructor(@SessionDatabase private val monarchy: Monarchy) :
-    SessionLifecycleObserver {
+        SessionLifecycleObserver {
 
     private val realmThreadLocal = ThreadLocal<Realm>()
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
index a57397da..04a6e83e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
@@ -44,6 +44,8 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo023
 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo024
 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo025
 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo026
+import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo027
+import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo028
 import org.matrix.android.sdk.internal.util.Normalizer
 import timber.log.Timber
 import javax.inject.Inject
@@ -58,7 +60,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
     override fun equals(other: Any?) = other is RealmSessionStoreMigration
     override fun hashCode() = 1000
 
-    val schemaVersion = 26L
+    val schemaVersion = 28L
 
     override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
         Timber.d("Migrating Realm Session from $oldVersion to $newVersion")
@@ -89,5 +91,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
         if (oldVersion < 24) MigrateSessionTo024(realm).perform()
         if (oldVersion < 25) MigrateSessionTo025(realm).perform()
         if (oldVersion < 26) MigrateSessionTo026(realm).perform()
+        if (oldVersion < 27) MigrateSessionTo027(realm).perform()
+        if (oldVersion < 28) MigrateSessionTo028(realm).perform()
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt
index 24de26ee..d052a7de 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt
@@ -20,6 +20,7 @@ import io.realm.Realm
 import io.realm.RealmQuery
 import io.realm.Sort
 import io.realm.kotlin.createObject
+import kotlinx.coroutines.runBlocking
 import org.matrix.android.sdk.api.session.crypto.CryptoService
 import org.matrix.android.sdk.api.session.crypto.MXCryptoError
 import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
@@ -127,7 +128,7 @@ private fun EventEntity.toTimelineEventEntity(roomMemberContentsByUser: HashMap<
     return timelineEventEntity
 }
 
-internal suspend fun ThreadSummaryEntity.Companion.createOrUpdate(
+internal fun ThreadSummaryEntity.Companion.createOrUpdate(
         threadSummaryType: ThreadSummaryUpdateType,
         realm: Realm,
         roomId: String,
@@ -136,7 +137,8 @@ internal suspend fun ThreadSummaryEntity.Companion.createOrUpdate(
         roomMemberContentsByUser: HashMap<String, RoomMemberContent?>,
         roomEntity: RoomEntity,
         userId: String,
-        cryptoService: CryptoService? = null
+        cryptoService: CryptoService? = null,
+        currentTimeMillis: Long,
 ) {
     when (threadSummaryType) {
         ThreadSummaryUpdateType.REPLACE -> {
@@ -152,11 +154,19 @@ internal suspend fun ThreadSummaryEntity.Companion.createOrUpdate(
                 Timber.i("###THREADS ThreadSummaryHelper REPLACE eventId:${it.rootThreadEventId} ")
             }
 
-            val rootThreadEventEntity = createEventEntity(roomId, rootThreadEvent, realm).also {
-                decryptIfNeeded(cryptoService, it, roomId)
+            val rootThreadEventEntity = createEventEntity(realm, roomId, rootThreadEvent, currentTimeMillis).also {
+                try {
+                    decryptIfNeeded(cryptoService, it, roomId)
+                } catch (e: InterruptedException) {
+                    Timber.i("Decryption got interrupted")
+                }
             }
-            val latestThreadEventEntity = createLatestEventEntity(roomId, rootThreadEvent, roomMemberContentsByUser, realm)?.also {
-                decryptIfNeeded(cryptoService, it, roomId)
+            val latestThreadEventEntity = createLatestEventEntity(realm, roomId, rootThreadEvent, roomMemberContentsByUser, currentTimeMillis)?.also {
+                try {
+                    decryptIfNeeded(cryptoService, it, roomId)
+                } catch (e: InterruptedException) {
+                    Timber.i("Decryption got interrupted")
+                }
             }
             val isUserParticipating = rootThreadEvent.unsignedData.relations.latestThread.isUserParticipating == true || rootThreadEvent.senderId == userId
             roomMemberContentsByUser.addSenderState(realm, roomId, rootThreadEvent.senderId)
@@ -204,14 +214,15 @@ internal suspend fun ThreadSummaryEntity.Companion.createOrUpdate(
     }
 }
 
-private suspend fun decryptIfNeeded(cryptoService: CryptoService?, eventEntity: EventEntity, roomId: String) {
+private fun decryptIfNeeded(cryptoService: CryptoService?, eventEntity: EventEntity, roomId: String) {
     cryptoService ?: return
     val event = eventEntity.asDomain()
     if (event.isEncrypted() && event.mxDecryptionResult == null && event.eventId != null) {
         try {
             Timber.i("###THREADS ThreadSummaryHelper request decryption for eventId:${event.eventId}")
             // Event from sync does not have roomId, so add it to the event first
-            val result = cryptoService.decryptEvent(event.copy(roomId = roomId), "")
+            // note: runBlocking should be used here while we are in realm single thread executor, to avoid thread switching
+            val result = runBlocking { cryptoService.decryptEvent(event.copy(roomId = roomId), "") }
             event.mxDecryptionResult = OlmDecryptionResult(
                     payload = result.clearEvent,
                     senderKey = result.senderCurve25519Key,
@@ -258,8 +269,8 @@ private fun HashMap<String, RoomMemberContent?>.addSenderState(realm: Realm, roo
 /**
  * Create an EventEntity for the root thread event or get an existing one
  */
-private fun createEventEntity(roomId: String, event: Event, realm: Realm): EventEntity {
-    val ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it }
+private fun createEventEntity(realm: Realm, roomId: String, event: Event, currentTimeMillis: Long): EventEntity {
+    val ageLocalTs = event.unsignedData?.age?.let { currentTimeMillis - it }
     return event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION)
 }
 
@@ -268,15 +279,17 @@ private fun createEventEntity(roomId: String, event: Event, realm: Realm): Event
  * state
  */
 private fun createLatestEventEntity(
+        realm: Realm,
         roomId: String,
         rootThreadEvent: Event,
         roomMemberContentsByUser: HashMap<String, RoomMemberContent?>,
-        realm: Realm): EventEntity? {
+        currentTimeMillis: Long,
+): EventEntity? {
     return getLatestEvent(rootThreadEvent)?.let {
         it.senderId?.let { senderId ->
             roomMemberContentsByUser.addSenderState(realm, roomId, senderId)
         }
-        createEventEntity(roomId, it, realm)
+        createEventEntity(realm, roomId, it, currentTimeMillis)
     }
 }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/TimelineEventEntityHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/TimelineEventEntityHelper.kt
index ea508731..8a5d08cd 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/TimelineEventEntityHelper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/TimelineEventEntityHelper.kt
@@ -19,9 +19,10 @@ package org.matrix.android.sdk.internal.database.helper
 import io.realm.Realm
 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.query.where
 
 internal fun TimelineEventEntity.Companion.nextId(realm: Realm): Long {
-    val currentIdNum = realm.where(TimelineEventEntity::class.java).max(TimelineEventEntityFields.LOCAL_ID)
+    val currentIdNum = TimelineEventEntity.where(realm).max(TimelineEventEntityFields.LOCAL_ID)
     return if (currentIdNum == null) {
         1
     } else {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventAnnotationsSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventAnnotationsSummaryMapper.kt
index 4a26b4c4..c747ad33 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventAnnotationsSummaryMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventAnnotationsSummaryMapper.kt
@@ -25,7 +25,6 @@ import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEnt
 internal object EventAnnotationsSummaryMapper {
     fun map(annotationsSummary: EventAnnotationsSummaryEntity): EventAnnotationsSummary {
         return EventAnnotationsSummary(
-                eventId = annotationsSummary.eventId,
                 reactionsSummary = annotationsSummary.reactionsSummary.toList().map {
                     ReactionAggregatedSummary(
                             it.key,
@@ -50,7 +49,6 @@ internal object EventAnnotationsSummaryMapper {
                         },
                 referencesAggregatedSummary = annotationsSummary.referencesSummaryEntity?.let {
                     ReferencesAggregatedSummary(
-                            it.eventId,
                             ContentMapper.map(it.content),
                             it.sourceEvents.toList(),
                             it.sourceLocalEcho.toList()
@@ -58,8 +56,10 @@ internal object EventAnnotationsSummaryMapper {
                 },
                 pollResponseSummary = annotationsSummary.pollResponseSummary?.let {
                     PollResponseAggregatedSummaryEntityMapper.map(it)
+                },
+                liveLocationShareAggregatedSummary = annotationsSummary.liveLocationShareAggregatedSummary?.let {
+                    LiveLocationShareAggregatedSummaryMapper.map(it)
                 }
-
         )
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt
index 3083df06..bc7d40bf 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt
@@ -30,13 +30,14 @@ import org.matrix.android.sdk.api.session.threads.ThreadNotificationState
 import org.matrix.android.sdk.internal.database.model.EventEntity
 import org.matrix.android.sdk.internal.di.MoshiProvider
 import timber.log.Timber
+import kotlin.random.Random
 
 internal object EventMapper {
 
     fun map(event: Event, roomId: String): EventEntity {
         val eventEntity = EventEntity()
         // TODO change this as we shouldn't use event everywhere
-        eventEntity.eventId = event.eventId ?: "$$roomId-${System.currentTimeMillis()}-${event.hashCode()}"
+        eventEntity.eventId = event.eventId ?: "$$roomId-${Random.nextLong()}-${event.hashCode()}"
         eventEntity.roomId = event.roomId ?: roomId
         eventEntity.content = ContentMapper.map(event.content)
         eventEntity.prevContent = ContentMapper.map(event.resolvedPrevContent())
@@ -126,7 +127,10 @@ internal fun EventEntity.asDomain(castJsonNumbers: Boolean = false): Event {
     return EventMapper.map(this, castJsonNumbers)
 }
 
-internal fun Event.toEntity(roomId: String, sendState: SendState, ageLocalTs: Long?, contentToInject: String? = null): EventEntity {
+internal fun Event.toEntity(roomId: String,
+                            sendState: SendState,
+                            ageLocalTs: Long?,
+                            contentToInject: String? = null): EventEntity {
     return EventMapper.map(this, roomId).apply {
         this.sendState = sendState
         this.ageLocalTs = ageLocalTs
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/LiveLocationShareAggregatedSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/LiveLocationShareAggregatedSummaryMapper.kt
new file mode 100644
index 00000000..71b36f88
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/LiveLocationShareAggregatedSummaryMapper.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * 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 org.matrix.android.sdk.internal.database.mapper
+
+import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary
+import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
+import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
+
+internal object LiveLocationShareAggregatedSummaryMapper {
+
+    fun map(entity: LiveLocationShareAggregatedSummaryEntity): LiveLocationShareAggregatedSummary {
+        return LiveLocationShareAggregatedSummary(
+                isActive = entity.isActive,
+                endOfLiveTimestampMillis = entity.endOfLiveTimestampMillis,
+                lastLocationDataContent = ContentMapper.map(entity.lastLocationContent).toModel<MessageBeaconLocationDataContent>()
+        )
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PushConditionMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PushConditionMapper.kt
index 5c0a2ba9..6521bf62 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PushConditionMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PushConditionMapper.kt
@@ -16,7 +16,7 @@
 
 package org.matrix.android.sdk.internal.database.mapper
 
-import org.matrix.android.sdk.api.pushrules.rest.PushCondition
+import org.matrix.android.sdk.api.session.pushrules.rest.PushCondition
 import org.matrix.android.sdk.internal.database.model.PushConditionEntity
 
 internal object PushConditionMapper {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PushRulesMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PushRulesMapper.kt
index 12eff8ef..0b077541 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PushRulesMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PushRulesMapper.kt
@@ -17,9 +17,9 @@ package org.matrix.android.sdk.internal.database.mapper
 
 import com.squareup.moshi.Types
 import io.realm.RealmList
-import org.matrix.android.sdk.api.pushrules.Kind
-import org.matrix.android.sdk.api.pushrules.rest.PushCondition
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
+import org.matrix.android.sdk.api.session.pushrules.Kind
+import org.matrix.android.sdk.api.session.pushrules.rest.PushCondition
+import org.matrix.android.sdk.api.session.pushrules.rest.PushRule
 import org.matrix.android.sdk.internal.database.model.PushRuleEntity
 import org.matrix.android.sdk.internal.di.MoshiProvider
 import timber.log.Timber
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo019.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo019.kt
index d63ef628..754a66bb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo019.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo019.kt
@@ -22,7 +22,7 @@ import org.matrix.android.sdk.internal.util.Normalizer
 import org.matrix.android.sdk.internal.util.database.RealmMigrator
 
 internal class MigrateSessionTo019(realm: DynamicRealm,
-                          private val normalizer: Normalizer) : RealmMigrator(realm, 19) {
+                                   private val normalizer: Normalizer) : RealmMigrator(realm, 19) {
 
     override fun doMigrate(realm: DynamicRealm) {
         realm.schema.get("RoomSummaryEntity")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo027.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo027.kt
new file mode 100644
index 00000000..fdd8c46d
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo027.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * 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 org.matrix.android.sdk.internal.database.migration
+
+import io.realm.DynamicRealm
+import io.realm.FieldAttribute
+import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntityFields
+import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+/**
+ * Migrating to:
+ * Live location sharing aggregated summary
+ */
+internal class MigrateSessionTo027(realm: DynamicRealm) : RealmMigrator(realm, 27) {
+
+    override fun doMigrate(realm: DynamicRealm) {
+        val liveLocationSummaryEntity = realm.schema.get("LiveLocationShareAggregatedSummaryEntity")
+                ?: realm.schema.create("LiveLocationShareAggregatedSummaryEntity")
+                        .addField(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, String::class.java, FieldAttribute.REQUIRED)
+                        .addField(LiveLocationShareAggregatedSummaryEntityFields.ROOM_ID, String::class.java, FieldAttribute.REQUIRED)
+                        .addField(LiveLocationShareAggregatedSummaryEntityFields.IS_ACTIVE, Boolean::class.java)
+                        .setNullable(LiveLocationShareAggregatedSummaryEntityFields.IS_ACTIVE, true)
+                        .addField(LiveLocationShareAggregatedSummaryEntityFields.END_OF_LIVE_TIMESTAMP_MILLIS, Long::class.java)
+                        .setNullable(LiveLocationShareAggregatedSummaryEntityFields.END_OF_LIVE_TIMESTAMP_MILLIS, true)
+                        .addField(LiveLocationShareAggregatedSummaryEntityFields.LAST_LOCATION_CONTENT, String::class.java)
+                ?: return
+
+        realm.schema.get("EventAnnotationsSummaryEntity")
+                ?.addRealmObjectField(EventAnnotationsSummaryEntityFields.LIVE_LOCATION_SHARE_AGGREGATED_SUMMARY.`$`, liveLocationSummaryEntity)
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo028.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo028.kt
new file mode 100644
index 00000000..1d0c638d
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo028.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * 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 org.matrix.android.sdk.internal.database.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+/**
+ * Migrating to:
+ * Live location sharing aggregated summary
+ */
+internal class MigrateSessionTo028(realm: DynamicRealm) : RealmMigrator(realm, 28) {
+
+    override fun doMigrate(realm: DynamicRealm) {
+        realm.schema.get("LiveLocationShareAggregatedSummaryEntity")
+                ?.takeIf { !it.hasPrimaryKey() }
+                ?.addPrimaryKey(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID)
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt
index 88eb821a..822bc1bd 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt
@@ -24,19 +24,20 @@ import io.realm.annotations.LinkingObjects
 import org.matrix.android.sdk.internal.extensions.assertIsManaged
 import org.matrix.android.sdk.internal.extensions.clearWith
 
-internal open class ChunkEntity(@Index var prevToken: String? = null,
+internal open class ChunkEntity(
+        @Index var prevToken: String? = null,
         // Because of gaps we can have several chunks with nextToken == null
-                                @Index var nextToken: String? = null,
-                                var prevChunk: ChunkEntity? = null,
-                                var nextChunk: ChunkEntity? = null,
-                                var stateEvents: RealmList<EventEntity> = RealmList(),
-                                var timelineEvents: RealmList<TimelineEventEntity> = RealmList(),
+        @Index var nextToken: String? = null,
+        var prevChunk: ChunkEntity? = null,
+        var nextChunk: ChunkEntity? = null,
+        var stateEvents: RealmList<EventEntity> = RealmList(),
+        var timelineEvents: RealmList<TimelineEventEntity> = RealmList(),
         // Only one chunk will have isLastForward == true
-                                @Index var isLastForward: Boolean = false,
-                                @Index var isLastBackward: Boolean = false,
+        @Index var isLastForward: Boolean = false,
+        @Index var isLastBackward: Boolean = false,
         // Threads
-                                @Index var rootThreadEventId: String? = null,
-                                @Index var isLastForwardThread: Boolean = false,
+        @Index var rootThreadEventId: String? = null,
+        @Index var isLastForwardThread: Boolean = false,
 ) : RealmObject() {
 
     fun identifier() = "${prevToken}_$nextToken"
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventAnnotationsSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventAnnotationsSummaryEntity.kt
index 3e881304..c3abd8b0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventAnnotationsSummaryEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventAnnotationsSummaryEntity.kt
@@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.database.model
 import io.realm.RealmList
 import io.realm.RealmObject
 import io.realm.annotations.PrimaryKey
+import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
 import timber.log.Timber
 
 internal open class EventAnnotationsSummaryEntity(
@@ -27,7 +28,8 @@ internal open class EventAnnotationsSummaryEntity(
         var reactionsSummary: RealmList<ReactionAggregatedSummaryEntity> = RealmList(),
         var editSummary: EditAggregatedSummaryEntity? = null,
         var referencesSummaryEntity: ReferencesAggregatedSummaryEntity? = null,
-        var pollResponseSummary: PollResponseAggregatedSummaryEntity? = null
+        var pollResponseSummary: PollResponseAggregatedSummaryEntity? = null,
+        var liveLocationShareAggregatedSummary: LiveLocationShareAggregatedSummaryEntity? = null,
 ) : RealmObject() {
 
     /**
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/GroupEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/GroupEntity.kt
index 527f7823..0120bb91 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/GroupEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/GroupEntity.kt
@@ -25,7 +25,7 @@ import org.matrix.android.sdk.api.session.room.model.Membership
  * Then GetGroupDataTask is called regularly to fetch group information from the homeserver.
  */
 internal open class GroupEntity(@PrimaryKey var groupId: String = "") :
-    RealmObject() {
+        RealmObject() {
 
     private var membershipStr: String = Membership.NONE.name
     var membership: Membership
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PushRulesEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PushRulesEntity.kt
index 4125d908..62bf40c1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PushRulesEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PushRulesEntity.kt
@@ -17,7 +17,7 @@ package org.matrix.android.sdk.internal.database.model
 
 import io.realm.RealmList
 import io.realm.RealmObject
-import org.matrix.android.sdk.api.pushrules.RuleKind
+import org.matrix.android.sdk.api.session.pushrules.RuleKind
 import org.matrix.android.sdk.internal.extensions.clearWith
 
 internal open class PushRulesEntity(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt
index 4a6f6a7b..d8e6b8af 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt
@@ -48,8 +48,10 @@ internal open class RoomEntity(@PrimaryKey var roomId: String = "",
         set(value) {
             membersLoadStatusStr = value.name
         }
+
     companion object
 }
+
 internal fun RoomEntity.removeThreadSummaryIfNeeded(eventId: String) {
     assertIsManaged()
     threadSummaries.findRootOrLatest(eventId)?.let {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt
index d0d23dd4..9a92b145 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt
@@ -17,6 +17,7 @@
 package org.matrix.android.sdk.internal.database.model
 
 import io.realm.annotations.RealmModule
+import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
 import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity
 import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity
 
@@ -47,6 +48,7 @@ import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntit
             EditAggregatedSummaryEntity::class,
             EditionOfEvent::class,
             PollResponseAggregatedSummaryEntity::class,
+            LiveLocationShareAggregatedSummaryEntity::class,
             ReferencesAggregatedSummaryEntity::class,
             PushRulesEntity::class,
             PushRuleEntity::class,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/TimelineEventEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/TimelineEventEntity.kt
index aacd6570..477c04fe 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/TimelineEventEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/TimelineEventEntity.kt
@@ -32,8 +32,8 @@ internal open class TimelineEventEntity(var localId: Long = 0,
                                         var isUniqueDisplayName: Boolean = false,
                                         var senderAvatar: String? = null,
                                         var senderMembershipEventId: String? = null,
-                                        // ownedByThreadChunk indicates that the current TimelineEventEntity belongs
-                                        // to a thread chunk and is a temporarily event.
+        // ownedByThreadChunk indicates that the current TimelineEventEntity belongs
+        // to a thread chunk and is a temporarily event.
                                         var ownedByThreadChunk: Boolean = false,
                                         var readReceipts: ReadReceiptsSummaryEntity? = null
 ) : RealmObject() {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/livelocation/LiveLocationShareAggregatedSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/livelocation/LiveLocationShareAggregatedSummaryEntity.kt
new file mode 100644
index 00000000..1376839f
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/livelocation/LiveLocationShareAggregatedSummaryEntity.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * 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 org.matrix.android.sdk.internal.database.model.livelocation
+
+import io.realm.RealmObject
+import io.realm.annotations.PrimaryKey
+
+/**
+ * Aggregation info concerning a live location share.
+ */
+internal open class LiveLocationShareAggregatedSummaryEntity(
+        /**
+         * Event id of the event that started the live.
+         */
+        @PrimaryKey
+        var eventId: String = "",
+
+        var roomId: String = "",
+
+        var isActive: Boolean? = null,
+
+        var endOfLiveTimestampMillis: Long? = null,
+
+        /**
+         * For now we persist this as a JSON for greater flexibility
+         * @see [org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent]
+         */
+        var lastLocationContent: String? = null,
+) : RealmObject() {
+    companion object
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ChunkEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ChunkEntityQueries.kt
index a33ba82f..93501021 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ChunkEntityQueries.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ChunkEntityQueries.kt
@@ -56,18 +56,21 @@ internal fun ChunkEntity.Companion.findLastForwardChunkOfRoom(realm: Realm, room
             .equalTo(ChunkEntityFields.IS_LAST_FORWARD, true)
             .findFirst()
 }
+
 internal fun ChunkEntity.Companion.findLastForwardChunkOfThread(realm: Realm, roomId: String, rootThreadEventId: String): ChunkEntity? {
     return where(realm, roomId)
             .equalTo(ChunkEntityFields.ROOT_THREAD_EVENT_ID, rootThreadEventId)
             .equalTo(ChunkEntityFields.IS_LAST_FORWARD_THREAD, true)
             .findFirst()
 }
+
 internal fun ChunkEntity.Companion.findEventInThreadChunk(realm: Realm, roomId: String, event: String): ChunkEntity? {
     return where(realm, roomId)
             .`in`(ChunkEntityFields.TIMELINE_EVENTS.EVENT_ID, arrayListOf(event).toTypedArray())
             .equalTo(ChunkEntityFields.IS_LAST_FORWARD_THREAD, true)
             .findFirst()
 }
+
 internal fun ChunkEntity.Companion.findAllIncludingEvents(realm: Realm, eventIds: List<String>): RealmResults<ChunkEntity> {
     return realm.where<ChunkEntity>()
             .`in`(ChunkEntityFields.TIMELINE_EVENTS.EVENT_ID, eventIds.toTypedArray())
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LiveLocationShareAggregatedSummaryEntityQuery.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LiveLocationShareAggregatedSummaryEntityQuery.kt
new file mode 100644
index 00000000..2e2e939f
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LiveLocationShareAggregatedSummaryEntityQuery.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * 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 org.matrix.android.sdk.internal.database.query
+
+import io.realm.Realm
+import io.realm.RealmQuery
+import io.realm.kotlin.where
+import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity
+import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
+import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntityFields
+
+internal fun LiveLocationShareAggregatedSummaryEntity.Companion.where(
+        realm: Realm,
+        roomId: String,
+        eventId: String,
+): RealmQuery<LiveLocationShareAggregatedSummaryEntity> {
+    return realm.where<LiveLocationShareAggregatedSummaryEntity>()
+            .equalTo(LiveLocationShareAggregatedSummaryEntityFields.ROOM_ID, roomId)
+            .equalTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, eventId)
+}
+
+internal fun LiveLocationShareAggregatedSummaryEntity.Companion.create(
+        realm: Realm,
+        roomId: String,
+        eventId: String,
+): LiveLocationShareAggregatedSummaryEntity {
+    val obj = realm.createObject(LiveLocationShareAggregatedSummaryEntity::class.java, eventId).apply {
+        this.roomId = roomId
+    }
+    val annotationSummary = EventAnnotationsSummaryEntity.getOrCreate(realm, roomId = roomId, eventId = eventId)
+    annotationSummary.liveLocationShareAggregatedSummary = obj
+
+    return obj
+}
+
+internal fun LiveLocationShareAggregatedSummaryEntity.Companion.getOrCreate(
+        realm: Realm,
+        roomId: String,
+        eventId: String,
+): LiveLocationShareAggregatedSummaryEntity {
+    return LiveLocationShareAggregatedSummaryEntity.where(realm, roomId, eventId).findFirst()
+            ?: LiveLocationShareAggregatedSummaryEntity.create(realm, roomId, eventId)
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/PushersQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/PushersQueries.kt
index 1f6b2102..3cea19a6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/PushersQueries.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/PushersQueries.kt
@@ -18,7 +18,7 @@ package org.matrix.android.sdk.internal.database.query
 import io.realm.Realm
 import io.realm.RealmQuery
 import io.realm.kotlin.where
-import org.matrix.android.sdk.api.pushrules.RuleKind
+import org.matrix.android.sdk.api.session.pushrules.RuleKind
 import org.matrix.android.sdk.internal.database.model.PushRuleEntity
 import org.matrix.android.sdk.internal.database.model.PushRuleEntityFields
 import org.matrix.android.sdk.internal.database.model.PushRulesEntity
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ThreadSummaryEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ThreadSummaryEntityQueries.kt
index 517d43d7..eab27404 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ThreadSummaryEntityQueries.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ThreadSummaryEntityQueries.kt
@@ -39,9 +39,11 @@ internal fun ThreadSummaryEntity.Companion.getOrCreate(realm: Realm, roomId: Str
         this.rootThreadEventId = rootThreadEventId
     }
 }
+
 internal fun ThreadSummaryEntity.Companion.getOrNull(realm: Realm, roomId: String, rootThreadEventId: String): ThreadSummaryEntity? {
     return where(realm, roomId, rootThreadEventId).findFirst()
 }
+
 internal fun RealmList<ThreadSummaryEntity>.find(rootThreadEventId: String): ThreadSummaryEntity? {
     return this.where()
             .equalTo(ThreadSummaryEntityFields.ROOT_THREAD_EVENT_ID, rootThreadEventId)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt
index 81d5ac83..215ab34f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt
@@ -29,26 +29,35 @@ import org.matrix.android.sdk.internal.database.model.RoomEntity
 import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
 import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
 
-internal fun TimelineEventEntity.Companion.where(realm: Realm, roomId: String, eventId: String): RealmQuery<TimelineEventEntity> {
-    return realm.where<TimelineEventEntity>()
+internal fun TimelineEventEntity.Companion.where(realm: Realm): RealmQuery<TimelineEventEntity> {
+    return realm.where()
+}
+
+internal fun TimelineEventEntity.Companion.where(realm: Realm,
+                                                 roomId: String,
+                                                 eventId: String): RealmQuery<TimelineEventEntity> {
+    return where(realm)
             .equalTo(TimelineEventEntityFields.ROOM_ID, roomId)
             .equalTo(TimelineEventEntityFields.EVENT_ID, eventId)
 }
 
-internal fun TimelineEventEntity.Companion.where(realm: Realm, roomId: String, eventIds: List<String>): RealmQuery<TimelineEventEntity> {
-    return realm.where<TimelineEventEntity>()
+internal fun TimelineEventEntity.Companion.where(realm: Realm,
+                                                 roomId: String,
+                                                 eventIds: List<String>): RealmQuery<TimelineEventEntity> {
+    return where(realm)
             .equalTo(TimelineEventEntityFields.ROOM_ID, roomId)
             .`in`(TimelineEventEntityFields.EVENT_ID, eventIds.toTypedArray())
 }
 
 internal fun TimelineEventEntity.Companion.whereRoomId(realm: Realm,
                                                        roomId: String): RealmQuery<TimelineEventEntity> {
-    return realm.where<TimelineEventEntity>()
+    return where(realm)
             .equalTo(TimelineEventEntityFields.ROOM_ID, roomId)
 }
 
-internal fun TimelineEventEntity.Companion.findWithSenderMembershipEvent(realm: Realm, senderMembershipEventId: String): List<TimelineEventEntity> {
-    return realm.where<TimelineEventEntity>()
+internal fun TimelineEventEntity.Companion.findWithSenderMembershipEvent(realm: Realm,
+                                                                         senderMembershipEventId: String): List<TimelineEventEntity> {
+    return where(realm)
             .equalTo(TimelineEventEntityFields.SENDER_MEMBERSHIP_EVENT_ID, senderMembershipEventId)
             .findAll()
 }
@@ -110,12 +119,12 @@ internal fun RealmQuery<TimelineEventEntity>.filterTypes(filterTypes: List<Strin
     return if (filterTypes.isEmpty()) {
         this
     } else {
-        this.`in`(TimelineEventEntityFields.ROOT.TYPE, filterTypes.toTypedArray())
+        `in`(TimelineEventEntityFields.ROOT.TYPE, filterTypes.toTypedArray())
     }
 }
 
 internal fun RealmList<TimelineEventEntity>.find(eventId: String): TimelineEventEntity? {
-    return this.where()
+    return where()
             .equalTo(TimelineEventEntityFields.EVENT_ID, eventId)
             .findFirst()
 }
@@ -132,3 +141,14 @@ internal fun RealmQuery<TimelineEventEntity>.filterSendStates(sendStates: List<S
     val sendStatesStr = sendStates.map { it.name }.toTypedArray()
     return `in`(TimelineEventEntityFields.ROOT.SEND_STATE_STR, sendStatesStr)
 }
+
+/**
+ * Find all TimelineEventEntity items where sender is in senderIds collection, excluding state events
+ */
+internal fun TimelineEventEntity.Companion.findAllFrom(realm: Realm,
+                                                       senderIds: Collection<String>): RealmResults<TimelineEventEntity> {
+    return where(realm)
+            .`in`(TimelineEventEntityFields.ROOT.SENDER, senderIds.toTypedArray())
+            .isNull(TimelineEventEntityFields.ROOT.STATE_KEY)
+            .findAll()
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConnectivityChecker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConnectivityChecker.kt
index cd7c99b8..3d2b2bfb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConnectivityChecker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConnectivityChecker.kt
@@ -44,7 +44,7 @@ internal interface NetworkConnectivityChecker {
 internal class DefaultNetworkConnectivityChecker @Inject constructor(private val homeServerPinger: HomeServerPinger,
                                                                      private val backgroundDetectionObserver: BackgroundDetectionObserver,
                                                                      private val networkCallbackStrategy: NetworkCallbackStrategy) :
-    NetworkConnectivityChecker {
+        NetworkConnectivityChecker {
 
     private val hasInternetAccess = AtomicBoolean(true)
     private val listeners = Collections.synchronizedSet(LinkedHashSet<NetworkConnectivityChecker.Listener>())
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RequestExecutor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RequestExecutor.kt
index 5cd2d880..71df7c08 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RequestExecutor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RequestExecutor.kt
@@ -15,6 +15,7 @@
  */
 
 package org.matrix.android.sdk.internal.network
+
 import org.matrix.android.sdk.internal.network.executeRequest as internalExecuteRequest
 
 internal interface RequestExecutor {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt
index ac097f57..78f1c84f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt
@@ -38,6 +38,7 @@ import org.matrix.android.sdk.internal.di.SessionDownloadsDirectory
 import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificateWithProgress
 import org.matrix.android.sdk.internal.session.download.DownloadProgressInterceptor.Companion.DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER
 import org.matrix.android.sdk.internal.util.file.AtomicFileCreator
+import org.matrix.android.sdk.internal.util.time.Clock
 import org.matrix.android.sdk.internal.util.writeToFile
 import timber.log.Timber
 import java.io.File
@@ -51,7 +52,8 @@ internal class DefaultFileService @Inject constructor(
         private val contentUrlResolver: ContentUrlResolver,
         @UnauthenticatedWithCertificateWithProgress
         private val okHttpClient: OkHttpClient,
-        private val coroutineDispatchers: MatrixCoroutineDispatchers
+        private val coroutineDispatchers: MatrixCoroutineDispatchers,
+        private val clock: Clock,
 ) : FileService {
 
     // Legacy folder, will be deleted
@@ -123,7 +125,7 @@ internal class DefaultFileService @Inject constructor(
                     val resolvedUrl = contentUrlResolver.resolveForDownload(url, elementToDecrypt) ?: throw IllegalArgumentException("url is null")
 
                     val request = when (resolvedUrl) {
-                        is ContentUrlResolver.ResolvedMethod.GET -> {
+                        is ContentUrlResolver.ResolvedMethod.GET  -> {
                             Request.Builder()
                                     .url(resolvedUrl.url)
                                     .header(DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER, url)
@@ -182,7 +184,8 @@ internal class DefaultFileService @Inject constructor(
                             MXEncryptedAttachments.decryptAttachment(
                                     inputStream,
                                     elementToDecrypt,
-                                    outputStream
+                                    outputStream,
+                                    clock
                             )
                         }
                     }
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 1e533158..5f77cfb2 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
@@ -26,7 +26,6 @@ import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
 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.EventStreamService
 import org.matrix.android.sdk.api.session.Session
 import org.matrix.android.sdk.api.session.SessionLifecycleObserver
@@ -53,6 +52,7 @@ import org.matrix.android.sdk.api.session.permalinks.PermalinkService
 import org.matrix.android.sdk.api.session.presence.PresenceService
 import org.matrix.android.sdk.api.session.profile.ProfileService
 import org.matrix.android.sdk.api.session.pushers.PushersService
+import org.matrix.android.sdk.api.session.pushrules.PushRuleService
 import org.matrix.android.sdk.api.session.room.RoomDirectoryService
 import org.matrix.android.sdk.api.session.room.RoomService
 import org.matrix.android.sdk.api.session.search.SearchService
@@ -124,12 +124,12 @@ internal class DefaultSession @Inject constructor(
         private val syncStatusService: Lazy<SyncStatusService>,
         private val homeServerCapabilitiesService: Lazy<HomeServerCapabilitiesService>,
         private val accountDataService: Lazy<SessionAccountDataService>,
-        private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>,
+        private val sharedSecretStorageService: Lazy<SharedSecretStorageService>,
         private val accountService: Lazy<AccountService>,
         private val eventService: Lazy<EventService>,
         private val contentScannerService: Lazy<ContentScannerService>,
-        private val identityService: IdentityService,
-        private val integrationManagerService: IntegrationManagerService,
+        private val identityService: Lazy<IdentityService>,
+        private val integrationManagerService: Lazy<IntegrationManagerService>,
         private val thirdPartyService: Lazy<ThirdPartyService>,
         private val callSignalingService: Lazy<CallSignalingService>,
         private val spaceService: Lazy<SpaceService>,
@@ -140,28 +140,7 @@ internal class DefaultSession @Inject constructor(
         @UnauthenticatedWithCertificate
         private val unauthenticatedWithCertificateOkHttpClient: Lazy<OkHttpClient>
 ) : Session,
-        GlobalErrorHandler.Listener,
-        RoomService by roomService.get(),
-        RoomDirectoryService by roomDirectoryService.get(),
-        GroupService by groupService.get(),
-        UserService by userService.get(),
-        SignOutService by signOutService.get(),
-        FilterService by filterService.get(),
-        PushRuleService by pushRuleService.get(),
-        PushersService by pushersService.get(),
-        EventService by eventService.get(),
-        TermsService by termsService.get(),
-        SyncStatusService by syncStatusService.get(),
-        SecureStorageService by secureStorageService.get(),
-        HomeServerCapabilitiesService by homeServerCapabilitiesService.get(),
-        ProfileService by profileService.get(),
-        PresenceService by presenceService.get(),
-        AccountService by accountService.get(),
-        ToDeviceService by toDeviceService.get(),
-        EventStreamService by eventStreamService.get() {
-
-    override val sharedSecretStorageService: SharedSecretStorageService
-        get() = _sharedSecretStorageService.get()
+        GlobalErrorHandler.Listener {
 
     private var isOpen = false
 
@@ -274,42 +253,44 @@ internal class DefaultSession @Inject constructor(
     }
 
     override fun contentUrlResolver() = contentUrlResolver
-
     override fun contentUploadProgressTracker() = contentUploadProgressTracker
-
     override fun typingUsersTracker() = typingUsersTracker
-
     override fun contentDownloadProgressTracker(): ContentDownloadStateTracker = contentDownloadStateTracker
 
     override fun cryptoService(): CryptoService = cryptoService.get()
-
     override fun contentScannerService(): ContentScannerService = contentScannerService.get()
-
-    override fun identityService() = identityService
-
+    override fun identityService(): IdentityService = identityService.get()
+    override fun homeServerCapabilitiesService(): HomeServerCapabilitiesService = homeServerCapabilitiesService.get()
+    override fun roomService(): RoomService = roomService.get()
+    override fun roomDirectoryService(): RoomDirectoryService = roomDirectoryService.get()
+    override fun groupService(): GroupService = groupService.get()
+    override fun userService(): UserService = userService.get()
+    override fun signOutService(): SignOutService = signOutService.get()
+    override fun filterService(): FilterService = filterService.get()
+    override fun pushRuleService(): PushRuleService = pushRuleService.get()
+    override fun pushersService(): PushersService = pushersService.get()
+    override fun eventService(): EventService = eventService.get()
+    override fun termsService(): TermsService = termsService.get()
+    override fun syncStatusService(): SyncStatusService = syncStatusService.get()
+    override fun secureStorageService(): SecureStorageService = secureStorageService.get()
+    override fun profileService(): ProfileService = profileService.get()
+    override fun presenceService(): PresenceService = presenceService.get()
+    override fun accountService(): AccountService = accountService.get()
+    override fun toDeviceService(): ToDeviceService = toDeviceService.get()
+    override fun eventStreamService(): EventStreamService = eventStreamService.get()
     override fun fileService(): FileService = defaultFileService.get()
-
     override fun permalinkService(): PermalinkService = permalinkService.get()
-
     override fun widgetService(): WidgetService = widgetService.get()
-
     override fun mediaService(): MediaService = mediaService.get()
-
-    override fun integrationManagerService() = integrationManagerService
-
+    override fun integrationManagerService(): IntegrationManagerService = integrationManagerService.get()
     override fun callSignalingService(): CallSignalingService = callSignalingService.get()
-
     override fun searchService(): SearchService = searchService.get()
-
     override fun federationService(): FederationService = federationService.get()
-
     override fun thirdPartyService(): ThirdPartyService = thirdPartyService.get()
-
     override fun spaceService(): SpaceService = spaceService.get()
-
     override fun openIdService(): OpenIdService = openIdService.get()
-
     override fun accountDataService(): SessionAccountDataService = accountDataService.get()
+    override fun sharedSecretStorageService(): SharedSecretStorageService = sharedSecretStorageService.get()
 
     override fun getOkHttpClient(): OkHttpClient {
         return unauthenticatedWithCertificateOkHttpClient.get()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DeactivateAccountTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DeactivateAccountTask.kt
index 752856b9..9f3f1f64 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DeactivateAccountTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DeactivateAccountTask.kt
@@ -18,6 +18,8 @@ package org.matrix.android.sdk.internal.session.account
 
 import org.matrix.android.sdk.api.auth.UIABaseAuth
 import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
+import org.matrix.android.sdk.api.session.uia.UiaResult
+import org.matrix.android.sdk.api.session.uia.exceptions.UiaCancelledException
 import org.matrix.android.sdk.internal.auth.registration.handleUIA
 import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
@@ -51,18 +53,24 @@ internal class DefaultDeactivateAccountTask @Inject constructor(
             }
             true
         } catch (throwable: Throwable) {
-            if (!handleUIA(
-                            failure = throwable,
-                            interceptor = params.userInteractiveAuthInterceptor,
-                            retryBlock = { authUpdate ->
-                                execute(params.copy(userAuthParam = authUpdate))
-                            }
-                    )
-            ) {
-                Timber.d("## UIA: propagate failure")
-                throw throwable
-            } else {
-                false
+            when (handleUIA(
+                    failure = throwable,
+                    interceptor = params.userInteractiveAuthInterceptor,
+                    retryBlock = { authUpdate ->
+                        execute(params.copy(userAuthParam = authUpdate))
+                    }
+            )) {
+                UiaResult.SUCCESS   -> {
+                    false
+                }
+                UiaResult.FAILURE   -> {
+                    Timber.d("## UIA: propagate failure")
+                    throw throwable
+                }
+                UiaResult.CANCELLED -> {
+                    Timber.d("## UIA: cancelled")
+                    throw UiaCancelledException()
+                }
             }
         }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt
index 3f199c5c..b15a6474 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt
@@ -30,7 +30,7 @@ private val loggerTag = LoggerTag("CallEventProcessor", LoggerTag.VOIP)
 
 @SessionScope
 internal class CallEventProcessor @Inject constructor(private val callSignalingHandler: CallSignalingHandler) :
-    EventInsertLiveProcessor {
+        EventInsertLiveProcessor {
 
     private val allowedTypes = listOf(
             EventType.CALL_ANSWER,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt
index 59058bf9..c4f711a9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt
@@ -34,6 +34,7 @@ import org.matrix.android.sdk.api.session.room.model.call.CallSelectAnswerConten
 import org.matrix.android.sdk.api.session.room.model.call.CallSignalingContent
 import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.session.SessionScope
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import javax.inject.Inject
 
@@ -41,9 +42,12 @@ private val loggerTag = LoggerTag("CallSignalingHandler", LoggerTag.VOIP)
 private const val MAX_AGE_TO_RING = 40_000
 
 @SessionScope
-internal class CallSignalingHandler @Inject constructor(private val activeCallHandler: ActiveCallHandler,
-                                                        private val mxCallFactory: MxCallFactory,
-                                                        @UserId private val userId: String) {
+internal class CallSignalingHandler @Inject constructor(
+        private val activeCallHandler: ActiveCallHandler,
+        private val mxCallFactory: MxCallFactory,
+        @UserId private val userId: String,
+        private val clock: Clock,
+) {
 
     private val invitedCallIds = mutableSetOf<String>()
     private val callListeners = mutableSetOf<CallListener>()
@@ -184,7 +188,7 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
         if (event.roomId == null || event.senderId == null) {
             return
         }
-        val now = System.currentTimeMillis()
+        val now = clock.epochMillis()
         val age = now - (event.ageLocalTs ?: now)
         if (age > MAX_AGE_TO_RING) {
             Timber.tag(loggerTag.value).w("Call invite is too old to ring.")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/MxCallFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/MxCallFactory.kt
index 547be225..9ec892b6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/MxCallFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/MxCallFactory.kt
@@ -28,6 +28,7 @@ import org.matrix.android.sdk.internal.session.call.model.MxCallImpl
 import org.matrix.android.sdk.internal.session.profile.GetProfileInfoTask
 import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
 import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
+import org.matrix.android.sdk.internal.util.time.Clock
 import javax.inject.Inject
 
 internal class MxCallFactory @Inject constructor(
@@ -36,7 +37,8 @@ internal class MxCallFactory @Inject constructor(
         private val eventSenderProcessor: EventSenderProcessor,
         private val matrixConfiguration: MatrixConfiguration,
         private val getProfileInfoTask: GetProfileInfoTask,
-        @UserId private val userId: String
+        @UserId private val userId: String,
+        private val clock: Clock,
 ) {
 
     fun createIncomingCall(roomId: String, opponentUserId: String, content: CallInviteContent): MxCall? {
@@ -51,7 +53,8 @@ internal class MxCallFactory @Inject constructor(
                 localEchoEventFactory = localEchoEventFactory,
                 eventSenderProcessor = eventSenderProcessor,
                 matrixConfiguration = matrixConfiguration,
-                getProfileInfoTask = getProfileInfoTask
+                getProfileInfoTask = getProfileInfoTask,
+                clock = clock,
         ).apply {
             updateOpponentData(opponentUserId, content, content.capabilities)
         }
@@ -68,7 +71,8 @@ internal class MxCallFactory @Inject constructor(
                 localEchoEventFactory = localEchoEventFactory,
                 eventSenderProcessor = eventSenderProcessor,
                 matrixConfiguration = matrixConfiguration,
-                getProfileInfoTask = getProfileInfoTask
+                getProfileInfoTask = getProfileInfoTask,
+                clock = clock,
         ).apply {
             // Setup with this userId, might be updated when processing the Answer event
             this.opponentUserId = opponentUserId
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt
index a8971387..796e8331 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt
@@ -46,6 +46,7 @@ import org.matrix.android.sdk.internal.session.call.DefaultCallSignalingService
 import org.matrix.android.sdk.internal.session.profile.GetProfileInfoTask
 import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
 import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import java.math.BigDecimal
 
@@ -61,7 +62,8 @@ internal class MxCallImpl(
         private val localEchoEventFactory: LocalEchoEventFactory,
         private val eventSenderProcessor: EventSenderProcessor,
         private val matrixConfiguration: MatrixConfiguration,
-        private val getProfileInfoTask: GetProfileInfoTask
+        private val getProfileInfoTask: GetProfileInfoTask,
+        private val clock: Clock,
 ) : MxCall {
 
     override var opponentPartyId: Optional<String>? = null
@@ -250,7 +252,7 @@ internal class MxCallImpl(
     private fun createEventAndLocalEcho(localId: String = LocalEcho.createLocalEchoId(), type: String, roomId: String, content: Content): Event {
         return Event(
                 roomId = roomId,
-                originServerTs = System.currentTimeMillis(),
+                originServerTs = clock.epochMillis(),
                 senderId = userId,
                 eventId = localId,
                 type = type,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt
index e9cb4238..f96a019f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt
@@ -148,8 +148,8 @@ internal class FileUploader @Inject constructor(
                 .post(requestBody)
                 .build()
 
-       return withContext(coroutineDispatchers.io) {
-             okHttpClient.newCall(request).awaitResponse().use { response ->
+        return withContext(coroutineDispatchers.io) {
+            okHttpClient.newCall(request).awaitResponse().use { response ->
                 if (!response.isSuccessful) {
                     throw response.toFailure(globalErrorReceiver)
                 } else {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageCompressor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageCompressor.kt
index 01eb52ff..c5aa6cd5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageCompressor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageCompressor.kt
@@ -68,16 +68,16 @@ internal class ImageCompressor @Inject constructor(
                     val orientation = exifInfo.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
                     val matrix = Matrix()
                     when (orientation) {
-                        ExifInterface.ORIENTATION_ROTATE_270 -> matrix.postRotate(270f)
-                        ExifInterface.ORIENTATION_ROTATE_180 -> matrix.postRotate(180f)
-                        ExifInterface.ORIENTATION_ROTATE_90 -> matrix.postRotate(90f)
+                        ExifInterface.ORIENTATION_ROTATE_270      -> matrix.postRotate(270f)
+                        ExifInterface.ORIENTATION_ROTATE_180      -> matrix.postRotate(180f)
+                        ExifInterface.ORIENTATION_ROTATE_90       -> matrix.postRotate(90f)
                         ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> matrix.preScale(-1f, 1f)
-                        ExifInterface.ORIENTATION_FLIP_VERTICAL -> matrix.preScale(1f, -1f)
-                        ExifInterface.ORIENTATION_TRANSPOSE -> {
+                        ExifInterface.ORIENTATION_FLIP_VERTICAL   -> matrix.preScale(1f, -1f)
+                        ExifInterface.ORIENTATION_TRANSPOSE       -> {
                             matrix.preRotate(-90f)
                             matrix.preScale(-1f, 1f)
                         }
-                        ExifInterface.ORIENTATION_TRANSVERSE -> {
+                        ExifInterface.ORIENTATION_TRANSVERSE      -> {
                             matrix.preRotate(90f)
                             matrix.preScale(-1f, 1f)
                         }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt
index 75606f2e..75a79abc 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt
@@ -46,6 +46,7 @@ import org.matrix.android.sdk.internal.session.room.send.LocalEchoIdentifiers
 import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
 import org.matrix.android.sdk.internal.session.room.send.MultipleEventSendingDispatcherWorker
 import org.matrix.android.sdk.internal.util.TemporaryFileCreator
+import org.matrix.android.sdk.internal.util.time.Clock
 import org.matrix.android.sdk.internal.util.toMatrixErrorStr
 import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
 import org.matrix.android.sdk.internal.worker.SessionWorkerParams
@@ -87,6 +88,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
     @Inject lateinit var thumbnailExtractor: ThumbnailExtractor
     @Inject lateinit var localEchoRepository: LocalEchoRepository
     @Inject lateinit var temporaryFileCreator: TemporaryFileCreator
+    @Inject lateinit var clock: Clock
 
     override fun injectWith(injector: SessionComponent) {
         injector.inject(this)
@@ -243,7 +245,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
                             .also { filesToDelete.add(it) }
 
                     uploadedFileEncryptedFileInfo =
-                            MXEncryptedAttachments.encrypt(fileToUpload.inputStream(), encryptedFile) { read, total ->
+                            MXEncryptedAttachments.encrypt(fileToUpload.inputStream(), encryptedFile, clock) { read, total ->
                                 notifyTracker(params) {
                                     contentUploadStateTracker.setEncrypting(it, read.toLong(), total.toLong())
                                 }
@@ -329,7 +331,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
                         if (params.isEncrypted) {
                             Timber.v("Encrypt thumbnail")
                             notifyTracker(params) { contentUploadStateTracker.setEncryptingThumbnail(it) }
-                            val encryptionResult = MXEncryptedAttachments.encryptAttachment(thumbnailData.bytes.inputStream())
+                            val encryptionResult = MXEncryptedAttachments.encryptAttachment(thumbnailData.bytes.inputStream(), clock)
                             val contentUploadResponse = fileUploader.uploadByteArray(
                                     byteArray = encryptionResult.encryptedByteArray,
                                     filename = null,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/DefaultContentScannerService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/DefaultContentScannerService.kt
index da7e2d10..5aaf0587 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/DefaultContentScannerService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/DefaultContentScannerService.kt
@@ -33,6 +33,7 @@ import org.matrix.android.sdk.internal.session.contentscanner.tasks.GetServerPub
 import org.matrix.android.sdk.internal.session.contentscanner.tasks.ScanEncryptedTask
 import org.matrix.android.sdk.internal.session.contentscanner.tasks.ScanMediaTask
 import org.matrix.android.sdk.internal.task.TaskExecutor
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import javax.inject.Inject
 
@@ -46,7 +47,8 @@ internal class DefaultContentScannerService @Inject constructor(
         private val getServerPublicKeyTask: GetServerPublicKeyTask,
         private val scanEncryptedTask: ScanEncryptedTask,
         private val scanMediaTask: ScanMediaTask,
-        private val taskExecutor: TaskExecutor
+        private val taskExecutor: TaskExecutor,
+        private val clock: Clock,
 ) : ContentScannerService {
 
     // Cache public key in memory
@@ -71,11 +73,13 @@ internal class DefaultContentScannerService @Inject constructor(
 
     override suspend fun getScanResultForAttachment(mxcUrl: String, fileInfo: ElementToDecrypt?): ScanStatusInfo {
         val result = if (fileInfo != null) {
-            scanEncryptedTask.execute(ScanEncryptedTask.Params(
-                    mxcUrl = mxcUrl,
-                    publicServerKey = getServerPublicKey(false),
-                    encryptedInfo = fileInfo
-            ))
+            scanEncryptedTask.execute(
+                    ScanEncryptedTask.Params(
+                            mxcUrl = mxcUrl,
+                            publicServerKey = getServerPublicKey(false),
+                            encryptedInfo = fileInfo
+                    )
+            )
         } else {
             scanMediaTask.execute(ScanMediaTask.Params(mxcUrl))
         }
@@ -83,7 +87,7 @@ internal class DefaultContentScannerService @Inject constructor(
         return ScanStatusInfo(
                 state = if (result.clean) ScanState.TRUSTED else ScanState.INFECTED,
                 humanReadableMessage = result.info,
-                scanDateTimestamp = System.currentTimeMillis()
+                scanDateTimestamp = clock.epochMillis()
         )
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScannerEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScannerEntityQueries.kt
index b47be235..e4b64a1a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScannerEntityQueries.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/ContentScannerEntityQueries.kt
@@ -31,11 +31,14 @@ internal fun ContentScanResultEntity.Companion.get(realm: Realm, attachmentUrl:
             .findFirst()
 }
 
-internal fun ContentScanResultEntity.Companion.getOrCreate(realm: Realm, attachmentUrl: String, contentScannerUrl: String?): ContentScanResultEntity {
+internal fun ContentScanResultEntity.Companion.getOrCreate(realm: Realm,
+                                                           attachmentUrl: String,
+                                                           contentScannerUrl: String?,
+                                                           currentTimeMillis: Long): ContentScanResultEntity {
     return ContentScanResultEntity.get(realm, attachmentUrl, contentScannerUrl)
             ?: realm.createObject<ContentScanResultEntity>().also {
                 it.mediaUrl = attachmentUrl
-                it.scanDateTimestamp = System.currentTimeMillis()
+                it.scanDateTimestamp = currentTimeMillis
                 it.scannerUrl = contentScannerUrl
             }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/RealmContentScannerStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/RealmContentScannerStore.kt
index 947a66c8..27729d38 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/RealmContentScannerStore.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/contentscanner/db/RealmContentScannerStore.kt
@@ -32,12 +32,14 @@ import org.matrix.android.sdk.internal.di.ContentScannerDatabase
 import org.matrix.android.sdk.internal.session.SessionScope
 import org.matrix.android.sdk.internal.session.contentscanner.data.ContentScannerStore
 import org.matrix.android.sdk.internal.util.isValidUrl
+import org.matrix.android.sdk.internal.util.time.Clock
 import javax.inject.Inject
 
 @SessionScope
 internal class RealmContentScannerStore @Inject constructor(
         @ContentScannerDatabase
-        private val realmConfiguration: RealmConfiguration
+        private val realmConfiguration: RealmConfiguration,
+        private val clock: Clock,
 ) : ContentScannerStore {
 
     private val monarchy = Monarchy.Builder()
@@ -82,15 +84,15 @@ internal class RealmContentScannerStore @Inject constructor(
 
     override fun updateStateForContent(mxcUrl: String, state: ScanState, scannerUrl: String?) {
         monarchy.runTransactionSync {
-            ContentScanResultEntity.getOrCreate(it, mxcUrl, scannerUrl).scanResult = state
+            ContentScanResultEntity.getOrCreate(it, mxcUrl, scannerUrl, clock.epochMillis()).scanResult = state
         }
     }
 
     override fun updateScanResultForContent(mxcUrl: String, scannerUrl: String?, state: ScanState, humanReadable: String) {
         monarchy.runTransactionSync {
-            ContentScanResultEntity.getOrCreate(it, mxcUrl, scannerUrl).apply {
+            ContentScanResultEntity.getOrCreate(it, mxcUrl, scannerUrl, clock.epochMillis()).apply {
                 scanResult = state
-                scanDateTimestamp = System.currentTimeMillis()
+                scanDateTimestamp = clock.epochMillis()
                 humanReadableMessage = humanReadable
             }
         }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/initsync/DefaultSyncStatusService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/initsync/DefaultSyncStatusService.kt
index 4a1e6661..c138c1a4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/initsync/DefaultSyncStatusService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/initsync/DefaultSyncStatusService.kt
@@ -24,7 +24,7 @@ import javax.inject.Inject
 
 @SessionScope
 internal class DefaultSyncStatusService @Inject constructor() :
-    SyncStatusService,
+        SyncStatusService,
         ProgressReporter {
 
     private val status = MutableLiveData<SyncStatusService.Status>()
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 30b15891..1b96931c 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
@@ -59,7 +59,7 @@ internal class IntegrationManager @Inject constructor(matrixConfiguration: Matri
                                                       private val updateUserAccountDataTask: UpdateUserAccountDataTask,
                                                       private val accountDataDataSource: UserAccountDataDataSource,
                                                       private val widgetFactory: WidgetFactory) :
-    SessionLifecycleObserver {
+        SessionLifecycleObserver {
 
     private val currentConfigs = ArrayList<IntegrationManagerConfig>()
     private val lifecycleOwner: LifecycleOwner = LifecycleOwner { lifecycleRegistry }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt
index dff82cb4..d20cf8f1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt
@@ -72,6 +72,7 @@ internal class ViaParameterFinder @Inject constructor(
      */
     private fun getUserIdsOfJoinedMembers(roomId: String): Set<String> {
         return roomGetterProvider.get().getRoom(roomId)
+                ?.membershipService()
                 ?.getRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.JOIN) })
                 ?.map { it.userId }
                 .orEmpty()
@@ -84,6 +85,7 @@ internal class ViaParameterFinder @Inject constructor(
     // It may not be possible for a user to join a room if there's no overlap between these
     fun computeViaParamsForRestricted(roomId: String, max: Int): List<String> {
         val userThatCanInvite = roomGetterProvider.get().getRoom(roomId)
+                ?.membershipService()
                 ?.getRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.JOIN) })
                 ?.map { it.userId }
                 ?.filter { userCanInvite(userId, roomId) }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddingThreePidTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddingThreePidTask.kt
index 6ff4efaf..501aff63 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddingThreePidTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddingThreePidTask.kt
@@ -22,6 +22,7 @@ import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
 import org.matrix.android.sdk.api.failure.Failure
 import org.matrix.android.sdk.api.failure.toRegistrationFlowResponse
 import org.matrix.android.sdk.api.session.identity.ThreePid
+import org.matrix.android.sdk.api.session.uia.UiaResult
 import org.matrix.android.sdk.internal.auth.registration.handleUIA
 import org.matrix.android.sdk.internal.database.model.PendingThreePidEntity
 import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields
@@ -72,13 +73,13 @@ internal class DefaultFinalizeAddingThreePidTask @Inject constructor(
                 true
             } catch (throwable: Throwable) {
                 if (params.userInteractiveAuthInterceptor == null ||
-                        !handleUIA(
+                        handleUIA(
                                 failure = throwable,
                                 interceptor = params.userInteractiveAuthInterceptor,
                                 retryBlock = { authUpdate ->
                                     execute(params.copy(userAuthParam = authUpdate))
                                 }
-                        )
+                        ) != UiaResult.SUCCESS
                 ) {
                     Timber.d("## UIA: propagate failure")
                     throw throwable.toRegistrationFlowResponse()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPushRuleTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPushRuleTask.kt
index b2176871..c46474cf 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPushRuleTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPushRuleTask.kt
@@ -15,8 +15,8 @@
  */
 package org.matrix.android.sdk.internal.session.pushers
 
-import org.matrix.android.sdk.api.pushrules.RuleKind
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
+import org.matrix.android.sdk.api.session.pushrules.RuleKind
+import org.matrix.android.sdk.api.session.pushrules.rest.PushRule
 import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherWorker.kt
index ce29efaa..00425580 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherWorker.kt
@@ -26,7 +26,7 @@ import org.matrix.android.sdk.internal.worker.SessionWorkerParams
 import javax.inject.Inject
 
 internal class AddPusherWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
-    SessionSafeCoroutineWorker<AddPusherWorker.Params>(context, params, sessionManager, Params::class.java) {
+        SessionSafeCoroutineWorker<AddPusherWorker.Params>(context, params, sessionManager, Params::class.java) {
 
     @JsonClass(generateAdapter = true)
     internal data class Params(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultConditionResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultConditionResolver.kt
index 84a05067..67fba390 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultConditionResolver.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultConditionResolver.kt
@@ -15,14 +15,15 @@
  */
 package org.matrix.android.sdk.internal.session.pushers
 
-import org.matrix.android.sdk.api.pushrules.ConditionResolver
-import org.matrix.android.sdk.api.pushrules.ContainsDisplayNameCondition
-import org.matrix.android.sdk.api.pushrules.EventMatchCondition
-import org.matrix.android.sdk.api.pushrules.RoomMemberCountCondition
-import org.matrix.android.sdk.api.pushrules.SenderNotificationPermissionCondition
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.pushrules.ConditionResolver
+import org.matrix.android.sdk.api.session.pushrules.ContainsDisplayNameCondition
+import org.matrix.android.sdk.api.session.pushrules.EventMatchCondition
+import org.matrix.android.sdk.api.session.pushrules.RoomMemberCountCondition
+import org.matrix.android.sdk.api.session.pushrules.SenderNotificationPermissionCondition
+import org.matrix.android.sdk.api.session.room.getStateEvent
 import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
 import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.session.room.RoomGetter
@@ -60,7 +61,7 @@ internal class DefaultConditionResolver @Inject constructor(
                                                      condition: ContainsDisplayNameCondition): Boolean {
         val roomId = event.roomId ?: return false
         val room = roomGetter.getRoom(roomId) ?: return false
-        val myDisplayName = room.getRoomMember(userId)?.displayName ?: return false
+        val myDisplayName = room.membershipService().getRoomMember(userId)?.displayName ?: return false
         return condition.isSatisfied(event, myDisplayName)
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/GetPushRulesResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesResponse.kt
similarity index 90%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/GetPushRulesResponse.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesResponse.kt
index 35b4d77c..de038196 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/GetPushRulesResponse.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesResponse.kt
@@ -13,10 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.api.pushrules.rest
+package org.matrix.android.sdk.internal.session.pushers
 
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
+import org.matrix.android.sdk.api.session.pushrules.rest.RuleSet
 
 /**
  * All push rulesets for a user.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt
index 994b4860..dab6d373 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt
@@ -15,8 +15,7 @@
  */
 package org.matrix.android.sdk.internal.session.pushers
 
-import org.matrix.android.sdk.api.pushrules.rest.GetPushRulesResponse
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
+import org.matrix.android.sdk.api.session.pushrules.rest.PushRule
 import org.matrix.android.sdk.internal.network.NetworkConstants
 import retrofit2.http.Body
 import retrofit2.http.DELETE
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt
index d53a4eed..4528c95e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt
@@ -19,14 +19,14 @@ package org.matrix.android.sdk.internal.session.pushers
 import dagger.Binds
 import dagger.Module
 import dagger.Provides
-import org.matrix.android.sdk.api.pushrules.ConditionResolver
-import org.matrix.android.sdk.api.pushrules.PushRuleService
 import org.matrix.android.sdk.api.session.pushers.PushersService
-import org.matrix.android.sdk.internal.session.notification.DefaultProcessEventForPushTask
-import org.matrix.android.sdk.internal.session.notification.DefaultPushRuleService
-import org.matrix.android.sdk.internal.session.notification.ProcessEventForPushTask
+import org.matrix.android.sdk.api.session.pushrules.ConditionResolver
+import org.matrix.android.sdk.api.session.pushrules.PushRuleService
 import org.matrix.android.sdk.internal.session.pushers.gateway.DefaultPushGatewayNotifyTask
 import org.matrix.android.sdk.internal.session.pushers.gateway.PushGatewayNotifyTask
+import org.matrix.android.sdk.internal.session.pushrules.DefaultProcessEventForPushTask
+import org.matrix.android.sdk.internal.session.pushrules.DefaultPushRuleService
+import org.matrix.android.sdk.internal.session.pushrules.ProcessEventForPushTask
 import org.matrix.android.sdk.internal.session.room.notification.DefaultSetRoomNotificationStateTask
 import org.matrix.android.sdk.internal.session.room.notification.SetRoomNotificationStateTask
 import retrofit2.Retrofit
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePushRuleTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePushRuleTask.kt
index bae89360..9b0bf793 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePushRuleTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePushRuleTask.kt
@@ -15,7 +15,7 @@
  */
 package org.matrix.android.sdk.internal.session.pushers
 
-import org.matrix.android.sdk.api.pushrules.RuleKind
+import org.matrix.android.sdk.api.session.pushrules.RuleKind
 import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/SavePushRulesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/SavePushRulesTask.kt
index 6a4b891e..ff685e92 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/SavePushRulesTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/SavePushRulesTask.kt
@@ -16,9 +16,8 @@
 package org.matrix.android.sdk.internal.session.pushers
 
 import com.zhuinden.monarchy.Monarchy
-import org.matrix.android.sdk.api.pushrules.RuleScope
-import org.matrix.android.sdk.api.pushrules.RuleSetKey
-import org.matrix.android.sdk.api.pushrules.rest.GetPushRulesResponse
+import org.matrix.android.sdk.api.session.pushrules.RuleScope
+import org.matrix.android.sdk.api.session.pushrules.RuleSetKey
 import org.matrix.android.sdk.internal.database.mapper.PushRulesMapper
 import org.matrix.android.sdk.internal.database.model.PushRulesEntity
 import org.matrix.android.sdk.internal.database.model.deleteOnCascade
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt
index 33589dc5..454b9cdd 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt
@@ -15,9 +15,9 @@
  */
 package org.matrix.android.sdk.internal.session.pushers
 
-import org.matrix.android.sdk.api.pushrules.Action
-import org.matrix.android.sdk.api.pushrules.RuleKind
-import org.matrix.android.sdk.api.pushrules.toJson
+import org.matrix.android.sdk.api.session.pushrules.Action
+import org.matrix.android.sdk.api.session.pushrules.RuleKind
+import org.matrix.android.sdk.api.session.pushrules.toJson
 import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
@@ -38,18 +38,18 @@ internal class DefaultUpdatePushRuleActionsTask @Inject constructor(
 ) : UpdatePushRuleActionsTask {
 
     override suspend fun execute(params: UpdatePushRuleActionsTask.Params) {
+        executeRequest(globalErrorReceiver) {
+            pushRulesApi.updateEnableRuleStatus(
+                    params.kind.value,
+                    params.ruleId,
+                    EnabledBody(params.enable)
+            )
+        }
+        if (params.actions != null) {
+            val body = mapOf("actions" to params.actions.toJson())
             executeRequest(globalErrorReceiver) {
-                pushRulesApi.updateEnableRuleStatus(
-                        params.kind.value,
-                        params.ruleId,
-                        EnabledBody(params.enable)
-                )
-            }
-            if (params.actions != null) {
-                val body = mapOf("actions" to params.actions.toJson())
-                executeRequest(globalErrorReceiver) {
-                    pushRulesApi.updateRuleActions(params.kind.value, params.ruleId, body)
-                }
+                pushRulesApi.updateRuleActions(params.kind.value, params.ruleId, body)
             }
+        }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt
index 3fe16146..815661a1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt
@@ -15,8 +15,8 @@
  */
 package org.matrix.android.sdk.internal.session.pushers
 
-import org.matrix.android.sdk.api.pushrules.RuleKind
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
+import org.matrix.android.sdk.api.session.pushrules.RuleKind
+import org.matrix.android.sdk.api.session.pushrules.rest.PushRule
 import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.task.Task
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/DefaultPushRuleService.kt
similarity index 90%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/DefaultPushRuleService.kt
index cdc7350f..ace23f1f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/DefaultPushRuleService.kt
@@ -13,23 +13,23 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.matrix.android.sdk.internal.session.notification
+package org.matrix.android.sdk.internal.session.pushrules
 
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.Transformations
 import com.zhuinden.monarchy.Monarchy
-import org.matrix.android.sdk.api.pushrules.Action
-import org.matrix.android.sdk.api.pushrules.ConditionResolver
-import org.matrix.android.sdk.api.pushrules.PushEvents
-import org.matrix.android.sdk.api.pushrules.PushRuleService
-import org.matrix.android.sdk.api.pushrules.RuleKind
-import org.matrix.android.sdk.api.pushrules.RuleScope
-import org.matrix.android.sdk.api.pushrules.RuleSetKey
-import org.matrix.android.sdk.api.pushrules.SenderNotificationPermissionCondition
-import org.matrix.android.sdk.api.pushrules.getActions
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
-import org.matrix.android.sdk.api.pushrules.rest.RuleSet
 import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.api.session.pushrules.Action
+import org.matrix.android.sdk.api.session.pushrules.ConditionResolver
+import org.matrix.android.sdk.api.session.pushrules.PushEvents
+import org.matrix.android.sdk.api.session.pushrules.PushRuleService
+import org.matrix.android.sdk.api.session.pushrules.RuleKind
+import org.matrix.android.sdk.api.session.pushrules.RuleScope
+import org.matrix.android.sdk.api.session.pushrules.RuleSetKey
+import org.matrix.android.sdk.api.session.pushrules.SenderNotificationPermissionCondition
+import org.matrix.android.sdk.api.session.pushrules.getActions
+import org.matrix.android.sdk.api.session.pushrules.rest.PushRule
+import org.matrix.android.sdk.api.session.pushrules.rest.RuleSet
 import org.matrix.android.sdk.internal.database.mapper.PushRulesMapper
 import org.matrix.android.sdk.internal.database.model.PushRuleEntity
 import org.matrix.android.sdk.internal.database.model.PushRulesEntity
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/ProcessEventForPushTask.kt
similarity index 95%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/ProcessEventForPushTask.kt
index 899bce4c..91d092a2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/ProcessEventForPushTask.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.internal.session.notification
+package org.matrix.android.sdk.internal.session.pushrules
 
-import org.matrix.android.sdk.api.pushrules.PushEvents
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.events.model.isInvitation
+import org.matrix.android.sdk.api.session.pushrules.PushEvents
+import org.matrix.android.sdk.api.session.pushrules.rest.PushRule
 import org.matrix.android.sdk.api.session.sync.model.RoomsSyncResponse
 import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.task.Task
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/PushRuleFinder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/PushRuleFinder.kt
similarity index 86%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/PushRuleFinder.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/PushRuleFinder.kt
index 6e302d37..b9d06a93 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/PushRuleFinder.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushrules/PushRuleFinder.kt
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.internal.session.notification
+package org.matrix.android.sdk.internal.session.pushrules
 
-import org.matrix.android.sdk.api.pushrules.ConditionResolver
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
 import org.matrix.android.sdk.api.session.events.model.Event
+import org.matrix.android.sdk.api.session.pushrules.ConditionResolver
+import org.matrix.android.sdk.api.session.pushrules.rest.PushRule
 import javax.inject.Inject
 
 internal class PushRuleFinder @Inject constructor(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt
index 3f129c4d..7326adee 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt
@@ -18,13 +18,11 @@ package org.matrix.android.sdk.internal.session.room
 
 import androidx.lifecycle.LiveData
 import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
-import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
-import org.matrix.android.sdk.api.session.crypto.CryptoService
-import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.room.Room
 import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataService
 import org.matrix.android.sdk.api.session.room.alias.AliasService
 import org.matrix.android.sdk.api.session.room.call.RoomCallService
+import org.matrix.android.sdk.api.session.room.crypto.RoomCryptoService
 import org.matrix.android.sdk.api.session.room.members.MembershipService
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
 import org.matrix.android.sdk.api.session.room.model.RoomType
@@ -42,62 +40,37 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineService
 import org.matrix.android.sdk.api.session.room.typing.TypingService
 import org.matrix.android.sdk.api.session.room.uploads.UploadsService
 import org.matrix.android.sdk.api.session.room.version.RoomVersionService
-import org.matrix.android.sdk.api.session.search.SearchResult
 import org.matrix.android.sdk.api.session.space.Space
 import org.matrix.android.sdk.api.util.Optional
-import org.matrix.android.sdk.api.util.awaitCallback
 import org.matrix.android.sdk.internal.session.permalinks.ViaParameterFinder
-import org.matrix.android.sdk.internal.session.room.state.SendStateTask
 import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
-import org.matrix.android.sdk.internal.session.search.SearchTask
 import org.matrix.android.sdk.internal.session.space.DefaultSpace
-import java.security.InvalidParameterException
 
-internal class DefaultRoom(override val roomId: String,
-                           private val roomSummaryDataSource: RoomSummaryDataSource,
-                           private val timelineService: TimelineService,
-                           private val threadsService: ThreadsService,
-                           private val threadsLocalService: ThreadsLocalService,
-                           private val sendService: SendService,
-                           private val draftService: DraftService,
-                           private val stateService: StateService,
-                           private val uploadsService: UploadsService,
-                           private val reportingService: ReportingService,
-                           private val roomCallService: RoomCallService,
-                           private val readService: ReadService,
-                           private val typingService: TypingService,
-                           private val aliasService: AliasService,
-                           private val tagsService: TagsService,
-                           private val cryptoService: CryptoService,
-                           private val relationService: RelationService,
-                           private val roomMembersService: MembershipService,
-                           private val roomPushRuleService: RoomPushRuleService,
-                           private val roomAccountDataService: RoomAccountDataService,
-                           private val roomVersionService: RoomVersionService,
-                           private val sendStateTask: SendStateTask,
-                           private val viaParameterFinder: ViaParameterFinder,
-                           private val searchTask: SearchTask,
-                           override val coroutineDispatchers: MatrixCoroutineDispatchers
-) :
-        Room,
-        TimelineService by timelineService,
-        ThreadsService by threadsService,
-        ThreadsLocalService by threadsLocalService,
-        SendService by sendService,
-        DraftService by draftService,
-        StateService by stateService,
-        UploadsService by uploadsService,
-        ReportingService by reportingService,
-        RoomCallService by roomCallService,
-        ReadService by readService,
-        TypingService by typingService,
-        AliasService by aliasService,
-        TagsService by tagsService,
-        RelationService by relationService,
-        MembershipService by roomMembersService,
-        RoomPushRuleService by roomPushRuleService,
-        RoomAccountDataService by roomAccountDataService,
-        RoomVersionService by roomVersionService {
+internal class DefaultRoom(
+        override val roomId: String,
+        private val roomSummaryDataSource: RoomSummaryDataSource,
+        private val roomCryptoService: RoomCryptoService,
+        private val timelineService: TimelineService,
+        private val threadsService: ThreadsService,
+        private val threadsLocalService: ThreadsLocalService,
+        private val sendService: SendService,
+        private val draftService: DraftService,
+        private val stateService: StateService,
+        private val uploadsService: UploadsService,
+        private val reportingService: ReportingService,
+        private val roomCallService: RoomCallService,
+        private val readService: ReadService,
+        private val typingService: TypingService,
+        private val aliasService: AliasService,
+        private val tagsService: TagsService,
+        private val relationService: RelationService,
+        private val roomMembersService: MembershipService,
+        private val roomPushRuleService: RoomPushRuleService,
+        private val roomAccountDataService: RoomAccountDataService,
+        private val roomVersionService: RoomVersionService,
+        private val viaParameterFinder: ViaParameterFinder,
+        override val coroutineDispatchers: MatrixCoroutineDispatchers
+) : Room {
 
     override fun getRoomSummaryLive(): LiveData<Optional<RoomSummary>> {
         return roomSummaryDataSource.getRoomSummaryLive(roomId)
@@ -107,69 +80,28 @@ internal class DefaultRoom(override val roomId: String,
         return roomSummaryDataSource.getRoomSummary(roomId)
     }
 
-    override fun isEncrypted(): Boolean {
-        return cryptoService.isRoomEncrypted(roomId)
-    }
-
-    override fun encryptionAlgorithm(): String? {
-        return cryptoService.getEncryptionAlgorithm(roomId)
-    }
-
-    override fun shouldEncryptForInvitedMembers(): Boolean {
-        return cryptoService.shouldEncryptForInvitedMembers(roomId)
-    }
-
-    override suspend fun prepareToEncrypt() {
-        awaitCallback<Unit> {
-            cryptoService.prepareToEncrypt(roomId, it)
-        }
-    }
-
-    override suspend fun enableEncryption(algorithm: String, force: Boolean) {
-        when {
-            (!force && isEncrypted() && encryptionAlgorithm() == MXCRYPTO_ALGORITHM_MEGOLM) -> {
-                throw IllegalStateException("Encryption is already enabled for this room")
-            }
-            (!force && algorithm != MXCRYPTO_ALGORITHM_MEGOLM)                              -> {
-                throw InvalidParameterException("Only MXCRYPTO_ALGORITHM_MEGOLM algorithm is supported")
-            }
-            else                                                                            -> {
-                val params = SendStateTask.Params(
-                        roomId = roomId,
-                        stateKey = "",
-                        eventType = EventType.STATE_ROOM_ENCRYPTION,
-                        body = mapOf(
-                                "algorithm" to algorithm
-                        ))
-
-                sendStateTask.execute(params)
-            }
-        }
-    }
-
-    override suspend fun search(searchTerm: String,
-                                nextBatch: String?,
-                                orderByRecent: Boolean,
-                                limit: Int,
-                                beforeLimit: Int,
-                                afterLimit: Int,
-                                includeProfile: Boolean): SearchResult {
-        return searchTask.execute(
-                SearchTask.Params(
-                        searchTerm = searchTerm,
-                        roomId = roomId,
-                        nextBatch = nextBatch,
-                        orderByRecent = orderByRecent,
-                        limit = limit,
-                        beforeLimit = beforeLimit,
-                        afterLimit = afterLimit,
-                        includeProfile = includeProfile
-                )
-        )
-    }
-
     override fun asSpace(): Space? {
         if (roomSummary()?.roomType != RoomType.SPACE) return null
         return DefaultSpace(this, roomSummaryDataSource, viaParameterFinder)
     }
+
+    override fun timelineService() = timelineService
+    override fun threadsService() = threadsService
+    override fun threadsLocalService() = threadsLocalService
+    override fun sendService() = sendService
+    override fun draftService() = draftService
+    override fun stateService() = stateService
+    override fun uploadsService() = uploadsService
+    override fun reportingService() = reportingService
+    override fun roomCallService() = roomCallService
+    override fun readService() = readService
+    override fun typingService() = typingService
+    override fun aliasService() = aliasService
+    override fun tagsService() = tagsService
+    override fun relationService() = relationService
+    override fun roomCryptoService() = roomCryptoService
+    override fun membershipService() = roomMembersService
+    override fun roomPushRuleService() = roomPushRuleService
+    override fun roomAccountDataService() = roomAccountDataService
+    override fun roomVersionService() = roomVersionService
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
index 15ce5810..7e0b44a3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
@@ -28,14 +28,16 @@ import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventCon
 import org.matrix.android.sdk.api.session.events.model.getRelationContent
 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.room.getTimelineEvent
 import org.matrix.android.sdk.api.session.room.model.PollSummaryContent
 import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
 import org.matrix.android.sdk.api.session.room.model.ReferencesAggregatedContent
 import org.matrix.android.sdk.api.session.room.model.VoteInfo
 import org.matrix.android.sdk.api.session.room.model.VoteSummary
+import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent
+import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollContent
-import org.matrix.android.sdk.api.session.room.model.message.MessageLiveLocationContent
 import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
 import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent
@@ -67,6 +69,7 @@ import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor
 import org.matrix.android.sdk.internal.session.room.aggregation.livelocation.LiveLocationAggregationProcessor
 import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import javax.inject.Inject
 
@@ -75,7 +78,8 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
         private val stateEventDataSource: StateEventDataSource,
         @SessionId private val sessionId: String,
         private val sessionManager: SessionManager,
-        private val liveLocationAggregationProcessor: LiveLocationAggregationProcessor
+        private val liveLocationAggregationProcessor: LiveLocationAggregationProcessor,
+        private val clock: Clock,
 ) : EventInsertLiveProcessor {
 
     private val allowedTypes = listOf(
@@ -91,7 +95,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
             // EventType.KEY_VERIFICATION_READY,
             EventType.KEY_VERIFICATION_KEY,
             EventType.ENCRYPTED
-    ) + EventType.POLL_START + EventType.POLL_RESPONSE + EventType.POLL_END + EventType.BEACON_LOCATION_DATA
+    ) + EventType.POLL_START + EventType.POLL_RESPONSE + EventType.POLL_END + EventType.STATE_ROOM_BEACON_INFO + EventType.BEACON_LOCATION_DATA
 
     override fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean {
         return allowedTypes.contains(eventType)
@@ -106,12 +110,12 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
             }
             val isLocalEcho = LocalEcho.isLocalEchoId(event.eventId ?: "")
             when (event.type) {
-                EventType.REACTION                -> {
+                EventType.REACTION                  -> {
                     // we got a reaction!!
                     Timber.v("###REACTION in room $roomId , reaction eventID ${event.eventId}")
                     handleReaction(realm, event, roomId, isLocalEcho)
                 }
-                EventType.MESSAGE                 -> {
+                EventType.MESSAGE                   -> {
                     if (event.unsignedData?.relations?.annotations != null) {
                         Timber.v("###REACTION Aggregation in room $roomId for event ${event.eventId}")
                         handleInitialAggregatedRelations(realm, event, roomId, event.unsignedData.relations.annotations)
@@ -137,7 +141,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
                 EventType.KEY_VERIFICATION_START,
                 EventType.KEY_VERIFICATION_MAC,
                 EventType.KEY_VERIFICATION_READY,
-                EventType.KEY_VERIFICATION_KEY    -> {
+                EventType.KEY_VERIFICATION_KEY      -> {
                     Timber.v("## SAS REF in room $roomId for event ${event.eventId}")
                     event.content.toModel<MessageRelationContent>()?.relatesTo?.let {
                         if (it.type == RelationType.REFERENCE && it.eventId != null) {
@@ -146,7 +150,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
                     }
                 }
 
-                EventType.ENCRYPTED               -> {
+                EventType.ENCRYPTED                 -> {
                     // Relation type is in clear
                     val encryptedEventContent = event.content.toModel<EncryptedEventContent>()
                     if (encryptedEventContent?.relatesTo?.type == RelationType.REPLACE ||
@@ -189,8 +193,8 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
                                 }
                             }
                             in EventType.BEACON_LOCATION_DATA -> {
-                                event.content.toModel<MessageLiveLocationContent>(catchError = true)?.let {
-                                    liveLocationAggregationProcessor.handleLiveLocation(realm, event, it, roomId, isLocalEcho)
+                                event.getClearContent().toModel<MessageBeaconLocationDataContent>(catchError = true)?.let {
+                                    liveLocationAggregationProcessor.handleBeaconLocationData(realm, event, it, roomId, isLocalEcho)
                                 }
                             }
                         }
@@ -213,7 +217,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
 //                                 }
 //                    }
                 }
-                EventType.REDACTION               -> {
+                EventType.REDACTION                 -> {
                     val eventToPrune = event.redacts?.let { EventEntity.where(realm, eventId = it).findFirst() }
                             ?: return
                     when (eventToPrune.type) {
@@ -233,7 +237,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
                         }
                     }
                 }
-                in EventType.POLL_START           -> {
+                in EventType.POLL_START             -> {
                     val content: MessagePollContent? = event.content.toModel()
                     if (content?.relatesTo?.type == RelationType.REPLACE) {
                         Timber.v("###REPLACE in room $roomId for event ${event.eventId}")
@@ -241,22 +245,22 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
                         handleReplace(realm, event, content, roomId, isLocalEcho)
                     }
                 }
-                in EventType.POLL_RESPONSE        -> {
+                in EventType.POLL_RESPONSE          -> {
                     event.content.toModel<MessagePollResponseContent>(catchError = true)?.let {
                         handleResponse(realm, event, it, roomId, isLocalEcho)
                     }
                 }
-                in EventType.POLL_END             -> {
+                in EventType.POLL_END               -> {
                     event.content.toModel<MessageEndPollContent>(catchError = true)?.let {
                         handleEndPoll(realm, event, it, roomId, isLocalEcho)
                     }
                 }
-                in EventType.BEACON_LOCATION_DATA -> {
-                    event.content.toModel<MessageLiveLocationContent>(catchError = true)?.let {
-                        liveLocationAggregationProcessor.handleLiveLocation(realm, event, it, roomId, isLocalEcho)
+                in EventType.STATE_ROOM_BEACON_INFO -> {
+                    event.content.toModel<MessageBeaconInfoContent>(catchError = true)?.let {
+                        liveLocationAggregationProcessor.handleBeaconInfo(realm, event, it, roomId, isLocalEcho)
                     }
                 }
-                else                              -> Timber.v("UnHandled event ${event.eventId}")
+                else                                -> Timber.v("UnHandled event ${event.eventId}")
             }
         } catch (t: Throwable) {
             Timber.e(t, "## Should not happen ")
@@ -325,7 +329,8 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
                                         totalVotes = 0,
                                         winnerVoteCount = 0,
                                 )
-                                        .toContent())
+                                        .toContent()
+                        )
                     }
 
             val txId = event.unsignedData?.transactionId
@@ -335,7 +340,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
                 Timber.v("###REPLACE Receiving remote echo of edit (edit already done)")
                 existingSummary.editions.firstOrNull { it.eventId == txId }?.let {
                     it.eventId = event.eventId
-                    it.timestamp = event.originServerTs ?: System.currentTimeMillis()
+                    it.timestamp = event.originServerTs ?: clock.epochMillis()
                     it.isLocalEcho = false
                 }
             } else {
@@ -346,10 +351,10 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
                                 eventId = event.eventId,
                                 content = ContentMapper.map(newContent),
                                 timestamp = if (isLocalEcho) {
-                                    System.currentTimeMillis()
+                                    clock.epochMillis()
                                 } else {
                                     // Do not take local echo originServerTs here, could mess up ordering (keep old ts)
-                                    event.originServerTs ?: System.currentTimeMillis()
+                                    event.originServerTs ?: clock.epochMillis()
                                 },
                                 isLocalEcho = isLocalEcho
                         )
@@ -542,7 +547,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
 
     private fun getPollEvent(roomId: String, eventId: String): TimelineEvent? {
         val session = sessionManager.getSessionComponent(sessionId)?.session()
-        return session?.getRoom(roomId)?.getTimelineEvent(eventId) ?: return null.also {
+        return session?.roomService()?.getRoom(roomId)?.getTimelineEvent(eventId) ?: return null.also {
             Timber.v("## POLL target poll event $eventId not found in room $roomId")
         }
     }
@@ -729,11 +734,13 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
                 EventType.KEY_VERIFICATION_READY,
                 EventType.KEY_VERIFICATION_KEY,
                 EventType.KEY_VERIFICATION_MAC    -> currentState.toState(VerificationState.WAITING)
-                EventType.KEY_VERIFICATION_CANCEL -> currentState.toState(if (event.senderId == userId) {
-                    VerificationState.CANCELED_BY_ME
-                } else {
-                    VerificationState.CANCELED_BY_OTHER
-                })
+                EventType.KEY_VERIFICATION_CANCEL -> currentState.toState(
+                        if (event.senderId == userId) {
+                            VerificationState.CANCELED_BY_ME
+                        } else {
+                            VerificationState.CANCELED_BY_OTHER
+                        }
+                )
                 EventType.KEY_VERIFICATION_DONE   -> currentState.toState(VerificationState.DONE)
                 else                              -> VerificationState.REQUEST
             }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt
index 10f75473..65ef9499 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt
@@ -194,7 +194,8 @@ internal interface RoomAPI {
     @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/state/{state_event_type}")
     suspend fun sendStateEvent(@Path("roomId") roomId: String,
                                @Path("state_event_type") stateEventType: String,
-                               @Body params: JsonDict)
+                               @Body params: JsonDict
+    ): SendResponse
 
     /**
      * Send a generic state event
@@ -208,7 +209,8 @@ internal interface RoomAPI {
     suspend fun sendStateEvent(@Path("roomId") roomId: String,
                                @Path("state_event_type") stateEventType: String,
                                @Path("state_key") stateKey: String,
-                               @Body params: JsonDict)
+                               @Body params: JsonDict
+    ): SendResponse
 
     /**
      * Get state events of a room
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt
index 72a3f9ab..01c4fd15 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt
@@ -17,13 +17,13 @@
 package org.matrix.android.sdk.internal.session.room
 
 import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
-import org.matrix.android.sdk.api.session.crypto.CryptoService
 import org.matrix.android.sdk.api.session.room.Room
 import org.matrix.android.sdk.internal.session.SessionScope
 import org.matrix.android.sdk.internal.session.permalinks.ViaParameterFinder
 import org.matrix.android.sdk.internal.session.room.accountdata.DefaultRoomAccountDataService
 import org.matrix.android.sdk.internal.session.room.alias.DefaultAliasService
 import org.matrix.android.sdk.internal.session.room.call.DefaultRoomCallService
+import org.matrix.android.sdk.internal.session.room.crypto.DefaultRoomCryptoService
 import org.matrix.android.sdk.internal.session.room.draft.DefaultDraftService
 import org.matrix.android.sdk.internal.session.room.membership.DefaultMembershipService
 import org.matrix.android.sdk.internal.session.room.notification.DefaultRoomPushRuleService
@@ -32,7 +32,6 @@ import org.matrix.android.sdk.internal.session.room.relation.DefaultRelationServ
 import org.matrix.android.sdk.internal.session.room.reporting.DefaultReportingService
 import org.matrix.android.sdk.internal.session.room.send.DefaultSendService
 import org.matrix.android.sdk.internal.session.room.state.DefaultStateService
-import org.matrix.android.sdk.internal.session.room.state.SendStateTask
 import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
 import org.matrix.android.sdk.internal.session.room.tags.DefaultTagsService
 import org.matrix.android.sdk.internal.session.room.threads.DefaultThreadsService
@@ -41,7 +40,6 @@ import org.matrix.android.sdk.internal.session.room.timeline.DefaultTimelineServ
 import org.matrix.android.sdk.internal.session.room.typing.DefaultTypingService
 import org.matrix.android.sdk.internal.session.room.uploads.DefaultUploadsService
 import org.matrix.android.sdk.internal.session.room.version.DefaultRoomVersionService
-import org.matrix.android.sdk.internal.session.search.SearchTask
 import javax.inject.Inject
 
 internal interface RoomFactory {
@@ -49,36 +47,36 @@ internal interface RoomFactory {
 }
 
 @SessionScope
-internal class DefaultRoomFactory @Inject constructor(private val cryptoService: CryptoService,
-                                                      private val roomSummaryDataSource: RoomSummaryDataSource,
-                                                      private val timelineServiceFactory: DefaultTimelineService.Factory,
-                                                      private val threadsServiceFactory: DefaultThreadsService.Factory,
-                                                      private val threadsLocalServiceFactory: DefaultThreadsLocalService.Factory,
-                                                      private val sendServiceFactory: DefaultSendService.Factory,
-                                                      private val draftServiceFactory: DefaultDraftService.Factory,
-                                                      private val stateServiceFactory: DefaultStateService.Factory,
-                                                      private val uploadsServiceFactory: DefaultUploadsService.Factory,
-                                                      private val reportingServiceFactory: DefaultReportingService.Factory,
-                                                      private val roomCallServiceFactory: DefaultRoomCallService.Factory,
-                                                      private val readServiceFactory: DefaultReadService.Factory,
-                                                      private val typingServiceFactory: DefaultTypingService.Factory,
-                                                      private val aliasServiceFactory: DefaultAliasService.Factory,
-                                                      private val tagsServiceFactory: DefaultTagsService.Factory,
-                                                      private val relationServiceFactory: DefaultRelationService.Factory,
-                                                      private val membershipServiceFactory: DefaultMembershipService.Factory,
-                                                      private val roomPushRuleServiceFactory: DefaultRoomPushRuleService.Factory,
-                                                      private val roomVersionServiceFactory: DefaultRoomVersionService.Factory,
-                                                      private val roomAccountDataServiceFactory: DefaultRoomAccountDataService.Factory,
-                                                      private val sendStateTask: SendStateTask,
-                                                      private val viaParameterFinder: ViaParameterFinder,
-                                                      private val searchTask: SearchTask,
-                                                      private val coroutineDispatchers: MatrixCoroutineDispatchers) :
-        RoomFactory {
+internal class DefaultRoomFactory @Inject constructor(
+        private val roomSummaryDataSource: RoomSummaryDataSource,
+        private val timelineServiceFactory: DefaultTimelineService.Factory,
+        private val threadsServiceFactory: DefaultThreadsService.Factory,
+        private val threadsLocalServiceFactory: DefaultThreadsLocalService.Factory,
+        private val sendServiceFactory: DefaultSendService.Factory,
+        private val draftServiceFactory: DefaultDraftService.Factory,
+        private val stateServiceFactory: DefaultStateService.Factory,
+        private val uploadsServiceFactory: DefaultUploadsService.Factory,
+        private val reportingServiceFactory: DefaultReportingService.Factory,
+        private val roomCallServiceFactory: DefaultRoomCallService.Factory,
+        private val readServiceFactory: DefaultReadService.Factory,
+        private val typingServiceFactory: DefaultTypingService.Factory,
+        private val aliasServiceFactory: DefaultAliasService.Factory,
+        private val tagsServiceFactory: DefaultTagsService.Factory,
+        private val relationServiceFactory: DefaultRelationService.Factory,
+        private val roomCryptoServiceFactory: DefaultRoomCryptoService.Factory,
+        private val membershipServiceFactory: DefaultMembershipService.Factory,
+        private val roomPushRuleServiceFactory: DefaultRoomPushRuleService.Factory,
+        private val roomVersionServiceFactory: DefaultRoomVersionService.Factory,
+        private val roomAccountDataServiceFactory: DefaultRoomAccountDataService.Factory,
+        private val viaParameterFinder: ViaParameterFinder,
+        private val coroutineDispatchers: MatrixCoroutineDispatchers
+) : RoomFactory {
 
     override fun create(roomId: String): Room {
         return DefaultRoom(
                 roomId = roomId,
                 roomSummaryDataSource = roomSummaryDataSource,
+                roomCryptoService = roomCryptoServiceFactory.create(roomId),
                 timelineService = timelineServiceFactory.create(roomId),
                 threadsService = threadsServiceFactory.create(roomId),
                 threadsLocalService = threadsLocalServiceFactory.create(roomId),
@@ -92,14 +90,11 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService:
                 typingService = typingServiceFactory.create(roomId),
                 aliasService = aliasServiceFactory.create(roomId),
                 tagsService = tagsServiceFactory.create(roomId),
-                cryptoService = cryptoService,
                 relationService = relationServiceFactory.create(roomId),
                 roomMembersService = membershipServiceFactory.create(roomId),
                 roomPushRuleService = roomPushRuleServiceFactory.create(roomId),
                 roomAccountDataService = roomAccountDataServiceFactory.create(roomId),
                 roomVersionService = roomVersionServiceFactory.create(roomId),
-                sendStateTask = sendStateTask,
-                searchTask = searchTask,
                 viaParameterFinder = viaParameterFinder,
                 coroutineDispatchers = coroutineDispatchers
         )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/DefaultLiveLocationAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/DefaultLiveLocationAggregationProcessor.kt
index 95e196c7..997e31a1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/DefaultLiveLocationAggregationProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/DefaultLiveLocationAggregationProcessor.kt
@@ -17,69 +17,78 @@
 package org.matrix.android.sdk.internal.session.room.aggregation.livelocation
 
 import io.realm.Realm
-import org.matrix.android.sdk.api.extensions.orFalse
+import org.matrix.android.sdk.api.extensions.orTrue
 import org.matrix.android.sdk.api.session.events.model.Event
-import org.matrix.android.sdk.api.session.events.model.EventType
 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.room.model.livelocation.LiveLocationBeaconContent
-import org.matrix.android.sdk.api.session.room.model.message.MessageLiveLocationContent
+import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent
+import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
 import org.matrix.android.sdk.internal.database.mapper.ContentMapper
-import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity
-import org.matrix.android.sdk.internal.database.query.getOrNull
+import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
+import org.matrix.android.sdk.internal.database.query.getOrCreate
 import timber.log.Timber
 import javax.inject.Inject
 
 internal class DefaultLiveLocationAggregationProcessor @Inject constructor() : LiveLocationAggregationProcessor {
 
-    override fun handleLiveLocation(realm: Realm, event: Event, content: MessageLiveLocationContent, roomId: String, isLocalEcho: Boolean) {
-        val locationSenderId = event.senderId ?: return
-
-        // We shouldn't process local echos
-        if (isLocalEcho) {
+    override fun handleBeaconInfo(realm: Realm, event: Event, content: MessageBeaconInfoContent, roomId: String, isLocalEcho: Boolean) {
+        if (event.senderId.isNullOrEmpty() || isLocalEcho) {
             return
         }
 
-        // A beacon info state event has to be sent before sending location
-        // TODO handle missing check of m_relatesTo field
-        var beaconInfoEntity: CurrentStateEventEntity? = null
-        val eventTypesIterator = EventType.STATE_ROOM_BEACON_INFO.iterator()
-        while (beaconInfoEntity == null && eventTypesIterator.hasNext()) {
-            beaconInfoEntity = CurrentStateEventEntity.getOrNull(realm, roomId, locationSenderId, eventTypesIterator.next())
+        val targetEventId = if (content.isLive.orTrue()) {
+            event.eventId
+        } else {
+            // when live is set to false, we use the id of the event that should have been replaced
+            event.unsignedData?.replacesState
         }
 
-        if (beaconInfoEntity == null) {
-            Timber.v("## LIVE LOCATION. There is not any beacon info which should be emitted before sending location updates")
+        if (targetEventId.isNullOrEmpty()) {
+            Timber.w("no target event id found for the beacon content")
             return
         }
-        val beaconInfoContent = ContentMapper.map(beaconInfoEntity.root?.content)?.toModel<LiveLocationBeaconContent>(catchError = true)
-        if (beaconInfoContent == null) {
-            Timber.v("## LIVE LOCATION. Beacon info content is invalid")
+
+        val aggregatedSummary = LiveLocationShareAggregatedSummaryEntity.getOrCreate(
+                realm = realm,
+                roomId = roomId,
+                eventId = targetEventId
+        )
+
+        Timber.d("updating summary of id=$targetEventId with isLive=${content.isLive}")
+
+        aggregatedSummary.endOfLiveTimestampMillis = content.getBestTimestampMillis()?.let { it + (content.timeout ?: 0) }
+        aggregatedSummary.isActive = content.isLive
+    }
+
+    override fun handleBeaconLocationData(realm: Realm, event: Event, content: MessageBeaconLocationDataContent, roomId: String, isLocalEcho: Boolean) {
+        if (event.senderId.isNullOrEmpty() || isLocalEcho) {
             return
         }
 
-        // Check if live location is ended
-        if (!beaconInfoContent.getBestBeaconInfo()?.isLive.orFalse()) {
-            Timber.v("## LIVE LOCATION. Beacon info is not live anymore")
+        val targetEventId = content.relatesTo?.eventId
+
+        if (targetEventId.isNullOrEmpty()) {
+            Timber.w("no target event id found for the live location content")
             return
         }
 
-        // Check if beacon info is outdated
-        if (isBeaconInfoOutdated(beaconInfoContent, content)) {
-            Timber.v("## LIVE LOCATION. Beacon info has timeout")
-            beaconInfoContent.hasTimedOut = true
-        } else {
-            beaconInfoContent.lastLocationContent = content
-        }
+        val aggregatedSummary = LiveLocationShareAggregatedSummaryEntity.getOrCreate(
+                realm = realm,
+                roomId = roomId,
+                eventId = targetEventId
+        )
+        val updatedLocationTimestamp = content.getBestTimestampMillis() ?: 0
+        val currentLocationTimestamp = ContentMapper
+                .map(aggregatedSummary.lastLocationContent)
+                .toModel<MessageBeaconLocationDataContent>()
+                ?.getBestTimestampMillis()
+                ?: 0
 
-        beaconInfoEntity.root?.content = ContentMapper.map(beaconInfoContent.toContent())
+        if (updatedLocationTimestamp.isMoreRecentThan(currentLocationTimestamp)) {
+            Timber.d("updating last location of the summary of id=$targetEventId")
+            aggregatedSummary.lastLocationContent = ContentMapper.map(content.toContent())
+        }
     }
 
-    private fun isBeaconInfoOutdated(beaconInfoContent: LiveLocationBeaconContent,
-                                     liveLocationContent: MessageLiveLocationContent): Boolean {
-        val beaconInfoStartTime = beaconInfoContent.getBestTimestampAsMilliseconds() ?: 0
-        val liveLocationEventTime = liveLocationContent.getBestTimestampAsMilliseconds() ?: 0
-        val timeout = beaconInfoContent.getBestBeaconInfo()?.timeout ?: 0
-        return liveLocationEventTime - beaconInfoStartTime > timeout
-    }
+    private fun Long.isMoreRecentThan(timestamp: Long) = this > timestamp
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt
index 7b5f23e2..c0be96f8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt
@@ -18,12 +18,23 @@ package org.matrix.android.sdk.internal.session.room.aggregation.livelocation
 
 import io.realm.Realm
 import org.matrix.android.sdk.api.session.events.model.Event
-import org.matrix.android.sdk.api.session.room.model.message.MessageLiveLocationContent
+import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent
+import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
 
 internal interface LiveLocationAggregationProcessor {
-    fun handleLiveLocation(realm: Realm,
-                           event: Event,
-                           content: MessageLiveLocationContent,
-                           roomId: String,
-                           isLocalEcho: Boolean)
+    fun handleBeaconInfo(
+            realm: Realm,
+            event: Event,
+            content: MessageBeaconInfoContent,
+            roomId: String,
+            isLocalEcho: Boolean,
+    )
+
+    fun handleBeaconLocationData(
+            realm: Realm,
+            event: Event,
+            content: MessageBeaconLocationDataContent,
+            roomId: String,
+            isLocalEcho: Boolean,
+    )
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt
index dc3ea55a..b25ef7ba 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt
@@ -52,7 +52,7 @@ internal class DefaultGetRoomIdByAliasTask @Inject constructor(
         } else if (!params.searchOnServer) {
             Optional.from(null)
         } else {
-            val description  = tryOrNull("## Failed to get roomId from alias") {
+            val description = tryOrNull("## Failed to get roomId from alias") {
                 executeRequest(globalErrorReceiver) {
                     directoryAPI.getRoomIdByAlias(params.roomAlias)
                 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt
index 9bd15a02..6dd2c910 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt
@@ -41,6 +41,7 @@ import org.matrix.android.sdk.internal.session.user.accountdata.DirectChatsHelpe
 import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask
 import org.matrix.android.sdk.internal.task.Task
 import org.matrix.android.sdk.internal.util.awaitTransaction
+import org.matrix.android.sdk.internal.util.time.Clock
 import java.util.concurrent.TimeUnit
 import javax.inject.Inject
 
@@ -56,7 +57,8 @@ internal class DefaultCreateRoomTask @Inject constructor(
         @SessionDatabase
         private val realmConfiguration: RealmConfiguration,
         private val createRoomBodyBuilder: CreateRoomBodyBuilder,
-        private val globalErrorReceiver: GlobalErrorReceiver
+        private val globalErrorReceiver: GlobalErrorReceiver,
+        private val clock: Clock,
 ) : CreateRoomTask {
 
     override suspend fun execute(params: CreateRoomParams): String {
@@ -106,7 +108,7 @@ internal class DefaultCreateRoomTask @Inject constructor(
         }
 
         awaitTransaction(realmConfiguration) {
-            RoomSummaryEntity.where(it, roomId).findFirst()?.lastActivityTime = System.currentTimeMillis()
+            RoomSummaryEntity.where(it, roomId).findFirst()?.lastActivityTime = clock.epochMillis()
         }
 
         if (otherUserId != null) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/crypto/DefaultRoomCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/crypto/DefaultRoomCryptoService.kt
new file mode 100644
index 00000000..2546c58c
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/crypto/DefaultRoomCryptoService.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * 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 org.matrix.android.sdk.internal.session.room.crypto
+
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
+import org.matrix.android.sdk.api.session.crypto.CryptoService
+import org.matrix.android.sdk.api.session.events.model.EventType
+import org.matrix.android.sdk.api.session.room.crypto.RoomCryptoService
+import org.matrix.android.sdk.api.util.awaitCallback
+import org.matrix.android.sdk.internal.session.room.state.SendStateTask
+import java.security.InvalidParameterException
+
+internal class DefaultRoomCryptoService @AssistedInject constructor(
+        @Assisted private val roomId: String,
+        private val cryptoService: CryptoService,
+        private val sendStateTask: SendStateTask,
+) : RoomCryptoService {
+
+    @AssistedFactory
+    interface Factory {
+        fun create(roomId: String): DefaultRoomCryptoService
+    }
+
+    override fun isEncrypted(): Boolean {
+        return cryptoService.isRoomEncrypted(roomId)
+    }
+
+    override fun encryptionAlgorithm(): String? {
+        return cryptoService.getEncryptionAlgorithm(roomId)
+    }
+
+    override fun shouldEncryptForInvitedMembers(): Boolean {
+        return cryptoService.shouldEncryptForInvitedMembers(roomId)
+    }
+
+    override suspend fun prepareToEncrypt() {
+        awaitCallback<Unit> {
+            cryptoService.prepareToEncrypt(roomId, it)
+        }
+    }
+
+    override suspend fun enableEncryption(algorithm: String, force: Boolean) {
+        when {
+            (!force && isEncrypted() && encryptionAlgorithm() == MXCRYPTO_ALGORITHM_MEGOLM) -> {
+                throw IllegalStateException("Encryption is already enabled for this room")
+            }
+            (!force && algorithm != MXCRYPTO_ALGORITHM_MEGOLM)                              -> {
+                throw InvalidParameterException("Only MXCRYPTO_ALGORITHM_MEGOLM algorithm is supported")
+            }
+            else                                                                            -> {
+                val params = SendStateTask.Params(
+                        roomId = roomId,
+                        stateKey = "",
+                        eventType = EventType.STATE_ROOM_ENCRYPTION,
+                        body = mapOf(
+                                "algorithm" to algorithm
+                        )
+                )
+
+                sendStateTask.execute(params)
+            }
+        }
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt
index 70ba9287..d3d1cb85 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt
@@ -42,6 +42,7 @@ import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater
 import org.matrix.android.sdk.internal.session.sync.SyncTokenStore
 import org.matrix.android.sdk.internal.task.Task
 import org.matrix.android.sdk.internal.util.awaitTransaction
+import org.matrix.android.sdk.internal.util.time.Clock
 import java.util.concurrent.TimeUnit
 import javax.inject.Inject
 
@@ -61,7 +62,8 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(
         private val roomMemberEventHandler: RoomMemberEventHandler,
         private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
         private val deviceListManager: DeviceListManager,
-        private val globalErrorReceiver: GlobalErrorReceiver
+        private val globalErrorReceiver: GlobalErrorReceiver,
+        private val clock: Clock,
 ) : LoadRoomMembersTask {
 
     override suspend fun execute(params: LoadRoomMembersTask.Params) {
@@ -107,7 +109,7 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(
             // We ignore all the already known members
             val roomEntity = RoomEntity.where(realm, roomId).findFirst()
                     ?: realm.createObject(roomId)
-            val now = System.currentTimeMillis()
+            val now = clock.epochMillis()
             for (roomMemberEvent in response.roomMemberEvents) {
                 if (roomMemberEvent.eventId == null || roomMemberEvent.stateKey == null || roomMemberEvent.type == null) {
                     continue
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt
index f883cc33..6306f3c6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt
@@ -35,6 +35,7 @@ import org.matrix.android.sdk.internal.session.room.RoomAPI
 import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource
 import org.matrix.android.sdk.internal.session.room.read.SetReadMarkersTask
 import org.matrix.android.sdk.internal.task.Task
+import org.matrix.android.sdk.internal.util.time.Clock
 import java.util.concurrent.TimeUnit
 import javax.inject.Inject
 
@@ -53,7 +54,8 @@ internal class DefaultJoinRoomTask @Inject constructor(
         @SessionDatabase
         private val realmConfiguration: RealmConfiguration,
         private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
-        private val globalErrorReceiver: GlobalErrorReceiver
+        private val globalErrorReceiver: GlobalErrorReceiver,
+        private val clock: Clock,
 ) : JoinRoomTask {
 
     override suspend fun execute(params: JoinRoomTask.Params) {
@@ -90,7 +92,7 @@ internal class DefaultJoinRoomTask @Inject constructor(
             throw JoinRoomFailure.JoinedWithTimeout
         }
         awaitTransaction(realmConfiguration) {
-            RoomSummaryEntity.where(it, roomId).findFirst()?.lastActivityTime = System.currentTimeMillis()
+            RoomSummaryEntity.where(it, roomId).findFirst()?.lastActivityTime = clock.epochMillis()
         }
         setReadMarkers(roomId)
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/DefaultRoomPushRuleService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/DefaultRoomPushRuleService.kt
index 8f1aefb7..85f53e13 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/DefaultRoomPushRuleService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/DefaultRoomPushRuleService.kt
@@ -22,7 +22,7 @@ import com.zhuinden.monarchy.Monarchy
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
-import org.matrix.android.sdk.api.pushrules.RuleScope
+import org.matrix.android.sdk.api.session.pushrules.RuleScope
 import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
 import org.matrix.android.sdk.api.session.room.notification.RoomPushRuleService
 import org.matrix.android.sdk.internal.database.model.PushRuleEntity
@@ -32,7 +32,7 @@ import org.matrix.android.sdk.internal.di.SessionDatabase
 internal class DefaultRoomPushRuleService @AssistedInject constructor(@Assisted private val roomId: String,
                                                                       private val setRoomNotificationStateTask: SetRoomNotificationStateTask,
                                                                       @SessionDatabase private val monarchy: Monarchy) :
-    RoomPushRuleService {
+        RoomPushRuleService {
 
     @AssistedFactory
     interface Factory {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/RoomPushRule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/RoomPushRule.kt
index d2c0d13b..2c74e2a1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/RoomPushRule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/RoomPushRule.kt
@@ -16,8 +16,8 @@
 
 package org.matrix.android.sdk.internal.session.room.notification
 
-import org.matrix.android.sdk.api.pushrules.RuleKind
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
+import org.matrix.android.sdk.api.session.pushrules.RuleKind
+import org.matrix.android.sdk.api.session.pushrules.rest.PushRule
 
 internal data class RoomPushRule(
         val kind: RuleKind,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/RoomPushRuleMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/RoomPushRuleMapper.kt
index 86d2e6c6..a5a5ab58 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/RoomPushRuleMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/RoomPushRuleMapper.kt
@@ -16,13 +16,13 @@
 
 package org.matrix.android.sdk.internal.session.room.notification
 
-import org.matrix.android.sdk.api.pushrules.Action
-import org.matrix.android.sdk.api.pushrules.Kind
-import org.matrix.android.sdk.api.pushrules.RuleSetKey
-import org.matrix.android.sdk.api.pushrules.getActions
-import org.matrix.android.sdk.api.pushrules.rest.PushCondition
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
-import org.matrix.android.sdk.api.pushrules.toJson
+import org.matrix.android.sdk.api.session.pushrules.Action
+import org.matrix.android.sdk.api.session.pushrules.Kind
+import org.matrix.android.sdk.api.session.pushrules.RuleSetKey
+import org.matrix.android.sdk.api.session.pushrules.getActions
+import org.matrix.android.sdk.api.session.pushrules.rest.PushCondition
+import org.matrix.android.sdk.api.session.pushrules.rest.PushRule
+import org.matrix.android.sdk.api.session.pushrules.toJson
 import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
 import org.matrix.android.sdk.internal.database.mapper.PushRulesMapper
 import org.matrix.android.sdk.internal.database.model.PushRuleEntity
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/SetRoomNotificationStateTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/SetRoomNotificationStateTask.kt
index feb8c27b..021d7dbe 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/SetRoomNotificationStateTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/SetRoomNotificationStateTask.kt
@@ -18,7 +18,7 @@ package org.matrix.android.sdk.internal.session.room.notification
 
 import com.zhuinden.monarchy.Monarchy
 import io.realm.Realm
-import org.matrix.android.sdk.api.pushrules.RuleScope
+import org.matrix.android.sdk.api.session.pushrules.RuleScope
 import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
 import org.matrix.android.sdk.internal.database.model.PushRuleEntity
 import org.matrix.android.sdk.internal.database.query.where
@@ -38,7 +38,7 @@ internal interface SetRoomNotificationStateTask : Task<SetRoomNotificationStateT
 internal class DefaultSetRoomNotificationStateTask @Inject constructor(@SessionDatabase private val monarchy: Monarchy,
                                                                        private val removePushRuleTask: RemovePushRuleTask,
                                                                        private val addPushRuleTask: AddPushRuleTask) :
-    SetRoomNotificationStateTask {
+        SetRoomNotificationStateTask {
 
     override suspend fun execute(params: SetRoomNotificationStateTask.Params) {
         val currentRoomPushRule = Realm.getInstance(monarchy.realmConfiguration).use {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt
index 64f1cc34..a124a8a4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt
@@ -34,6 +34,7 @@ import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHand
 import org.matrix.android.sdk.internal.session.sync.handler.room.RoomFullyReadHandler
 import org.matrix.android.sdk.internal.task.Task
 import org.matrix.android.sdk.internal.util.awaitTransaction
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import javax.inject.Inject
 import kotlin.collections.set
@@ -58,7 +59,8 @@ internal class DefaultSetReadMarkersTask @Inject constructor(
         private val roomFullyReadHandler: RoomFullyReadHandler,
         private val readReceiptHandler: ReadReceiptHandler,
         @UserId private val userId: String,
-        private val globalErrorReceiver: GlobalErrorReceiver
+        private val globalErrorReceiver: GlobalErrorReceiver,
+        private val clock: Clock,
 ) : SetReadMarkersTask {
 
     override suspend fun execute(params: SetReadMarkersTask.Params) {
@@ -125,7 +127,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor(
                 roomFullyReadHandler.handle(realm, roomId, FullyReadContent(readMarkerId))
             }
             if (readReceiptId != null) {
-                val readReceiptContent = ReadReceiptHandler.createContent(userId, readReceiptId)
+                val readReceiptContent = ReadReceiptHandler.createContent(userId, readReceiptId, clock.epochMillis())
                 readReceiptHandler.handle(realm, roomId, readReceiptContent, false, null)
             }
             if (shouldUpdateRoomSummary) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt
index b54cd71e..7bf7d6b5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt
@@ -27,12 +27,16 @@ import org.matrix.android.sdk.internal.database.mapper.toEntity
 import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
 import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
 import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import javax.inject.Inject
 
-internal class EventEditor @Inject constructor(private val eventSenderProcessor: EventSenderProcessor,
-                                               private val eventFactory: LocalEchoEventFactory,
-                                               private val localEchoRepository: LocalEchoRepository) {
+internal class EventEditor @Inject constructor(
+        private val eventSenderProcessor: EventSenderProcessor,
+        private val eventFactory: LocalEchoEventFactory,
+        private val localEchoRepository: LocalEchoRepository,
+        private val clock: Clock,
+) {
 
     fun editTextMessage(targetEvent: TimelineEvent,
                         msgType: String,
@@ -126,7 +130,7 @@ internal class EventEditor @Inject constructor(private val eventSenderProcessor:
     }
 
     private fun updateFailedEchoWithEvent(roomId: String, failedEchoEventId: String, editedEvent: Event) {
-        val editedEventEntity = editedEvent.toEntity(roomId, SendState.UNSENT, System.currentTimeMillis())
+        val editedEventEntity = editedEvent.toEntity(roomId, SendState.UNSENT, clock.epochMillis())
         localEchoRepository.updateEchoAsync(failedEchoEventId) { _, entity ->
             entity.content = editedEventEntity.content
             entity.ageLocalTs = editedEventEntity.ageLocalTs
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadSummariesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadSummariesTask.kt
index b596f228..c5f9bd13 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadSummariesTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadSummariesTask.kt
@@ -33,6 +33,7 @@ import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection
 import org.matrix.android.sdk.internal.session.room.timeline.PaginationResponse
 import org.matrix.android.sdk.internal.task.Task
 import org.matrix.android.sdk.internal.util.awaitTransaction
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import javax.inject.Inject
 
@@ -55,12 +56,14 @@ internal class DefaultFetchThreadSummariesTask @Inject constructor(
         @SessionDatabase private val monarchy: Monarchy,
         private val cryptoService: DefaultCryptoService,
         @UserId private val userId: String,
+        private val clock: Clock,
 ) : FetchThreadSummariesTask {
 
     override suspend fun execute(params: FetchThreadSummariesTask.Params): Result {
         val filter = FilterFactory.createThreadsFilter(
                 numberOfEvents = params.limit,
-                userId = if (params.isUserParticipating) userId else null).toJSONString()
+                userId = if (params.isUserParticipating) userId else null
+        ).toJSONString()
 
         val response = executeRequest(
                 globalErrorReceiver,
@@ -94,7 +97,9 @@ internal class DefaultFetchThreadSummariesTask @Inject constructor(
                         roomMemberContentsByUser = roomMemberContentsByUser,
                         roomEntity = roomEntity,
                         userId = userId,
-                        cryptoService = cryptoService)
+                        cryptoService = cryptoService,
+                        currentTimeMillis = clock.epochMillis(),
+                )
             }
         }
         return Result.SUCCESS
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt
index 116d4aa0..8d35a8fe 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt
@@ -23,7 +23,6 @@ import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
 import org.matrix.android.sdk.api.session.room.send.SendState
-import org.matrix.android.sdk.internal.crypto.CryptoSessionInfoProvider
 import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
 import org.matrix.android.sdk.internal.database.helper.addTimelineEvent
 import org.matrix.android.sdk.internal.database.mapper.asDomain
@@ -42,7 +41,6 @@ import org.matrix.android.sdk.internal.database.query.getOrCreate
 import org.matrix.android.sdk.internal.database.query.getOrNull
 import org.matrix.android.sdk.internal.database.query.where
 import org.matrix.android.sdk.internal.di.SessionDatabase
-import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.events.getFixedRoomMemberContent
@@ -51,6 +49,7 @@ import org.matrix.android.sdk.internal.session.room.relation.RelationsResponse
 import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection
 import org.matrix.android.sdk.internal.task.Task
 import org.matrix.android.sdk.internal.util.awaitTransaction
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import javax.inject.Inject
 
@@ -85,10 +84,9 @@ internal interface FetchThreadTimelineTask : Task<FetchThreadTimelineTask.Params
 internal class DefaultFetchThreadTimelineTask @Inject constructor(
         private val roomAPI: RoomAPI,
         private val globalErrorReceiver: GlobalErrorReceiver,
-        private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
         @SessionDatabase private val monarchy: Monarchy,
-        @UserId private val userId: String,
-        private val cryptoService: DefaultCryptoService
+        private val cryptoService: DefaultCryptoService,
+        private val clock: Clock,
 ) : FetchThreadTimelineTask {
 
     enum class Result {
@@ -156,7 +154,8 @@ internal class DefaultFetchThreadTimelineTask @Inject constructor(
                             eventEntity = eventEntity,
                             direction = PaginationDirection.FORWARDS,
                             ownedByThreadChunk = true,
-                            roomMemberContentsByUser = roomMemberContentsByUser)
+                            roomMemberContentsByUser = roomMemberContentsByUser
+                    )
                 }
             }
 
@@ -178,7 +177,8 @@ internal class DefaultFetchThreadTimelineTask @Inject constructor(
                             eventEntity = eventEntity,
                             direction = PaginationDirection.FORWARDS,
                             ownedByThreadChunk = true,
-                            roomMemberContentsByUser = roomMemberContentsByUser)
+                            roomMemberContentsByUser = roomMemberContentsByUser
+                    )
                 }
             }
         }
@@ -207,7 +207,7 @@ internal class DefaultFetchThreadTimelineTask @Inject constructor(
      * Create an EventEntity to be added in the TimelineEventEntity
      */
     private fun createEventEntity(roomId: String, event: Event, realm: Realm): EventEntity {
-        val ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it }
+        val ageLocalTs = event.unsignedData?.age?.let { clock.epochMillis() - it }
         return event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION)
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt
index bb16563f..d019ffad 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt
@@ -37,13 +37,13 @@ import org.matrix.android.sdk.api.session.room.model.message.LocationAsset
 import org.matrix.android.sdk.api.session.room.model.message.LocationAssetType
 import org.matrix.android.sdk.api.session.room.model.message.LocationInfo
 import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent
+import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageContentWithFormattedBody
 import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageFileContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageFormat
 import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent
-import org.matrix.android.sdk.api.session.room.model.message.MessageLiveLocationContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageLocationContent
 import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
 import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent
@@ -70,8 +70,8 @@ import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.session.content.ThumbnailExtractor
 import org.matrix.android.sdk.internal.session.permalinks.PermalinkFactory
 import org.matrix.android.sdk.internal.session.room.send.pills.TextPillsUtils
+import org.matrix.android.sdk.internal.util.time.Clock
 import java.util.UUID
-import java.util.concurrent.TimeUnit
 import javax.inject.Inject
 
 /**
@@ -91,7 +91,8 @@ internal class LocalEchoEventFactory @Inject constructor(
         private val thumbnailExtractor: ThumbnailExtractor,
         private val waveformSanitizer: WaveFormSanitizer,
         private val localEchoRepository: LocalEchoRepository,
-        private val permalinkFactory: PermalinkFactory
+        private val permalinkFactory: PermalinkFactory,
+        private val clock: Clock,
 ) {
     fun createTextEvent(roomId: String, msgType: String, text: CharSequence, autoMarkdown: Boolean): Event {
         if (msgType == MessageType.MSGTYPE_TEXT || msgType == MessageType.MSGTYPE_EMOTE) {
@@ -124,7 +125,8 @@ internal class LocalEchoEventFactory @Inject constructor(
                                newBodyAutoMarkdown: Boolean,
                                msgType: String,
                                compatibilityText: String): Event {
-        return createMessageEvent(roomId,
+        return createMessageEvent(
+                roomId,
                 MessageTextContent(
                         msgType = msgType,
                         body = compatibilityText,
@@ -132,7 +134,8 @@ internal class LocalEchoEventFactory @Inject constructor(
                         newContent = createTextContent(newBodyText, newBodyAutoMarkdown)
                                 .toMessageTextContent(msgType)
                                 .toContent()
-                ))
+                )
+        )
     }
 
     private fun createPollContent(question: String,
@@ -188,7 +191,8 @@ internal class LocalEchoEventFactory @Inject constructor(
                 eventId = localId,
                 type = EventType.POLL_RESPONSE.first(),
                 content = content.toContent(),
-                unsignedData = UnsignedData(age = null, transactionId = localId))
+                unsignedData = UnsignedData(age = null, transactionId = localId)
+        )
     }
 
     fun createPollEvent(roomId: String,
@@ -204,7 +208,8 @@ internal class LocalEchoEventFactory @Inject constructor(
                 eventId = localId,
                 type = EventType.POLL_START.first(),
                 content = content.toContent(),
-                unsignedData = UnsignedData(age = null, transactionId = localId))
+                unsignedData = UnsignedData(age = null, transactionId = localId)
+        )
     }
 
     fun createEndPollEvent(roomId: String,
@@ -223,7 +228,8 @@ internal class LocalEchoEventFactory @Inject constructor(
                 eventId = localId,
                 type = EventType.POLL_END.first(),
                 content = content.toContent(),
-                unsignedData = UnsignedData(age = null, transactionId = localId))
+                unsignedData = UnsignedData(age = null, transactionId = localId)
+        )
     }
 
     fun createLocationEvent(roomId: String,
@@ -238,7 +244,7 @@ internal class LocalEchoEventFactory @Inject constructor(
                 body = geoUri,
                 unstableLocationInfo = LocationInfo(geoUri = geoUri, description = geoUri),
                 unstableLocationAsset = LocationAsset(type = assetType),
-                unstableTs = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()),
+                unstableTimestampMillis = clock.epochMillis(),
                 unstableText = geoUri
         )
         return createMessageEvent(roomId, content)
@@ -250,14 +256,14 @@ internal class LocalEchoEventFactory @Inject constructor(
                                 longitude: Double,
                                 uncertainty: Double?): Event {
         val geoUri = buildGeoUri(latitude, longitude, uncertainty)
-        val content = MessageLiveLocationContent(
+        val content = MessageBeaconLocationDataContent(
                 body = geoUri,
                 relatesTo = RelationDefaultContent(
                         type = RelationType.REFERENCE,
                         eventId = beaconInfoEventId
                 ),
                 unstableLocationInfo = LocationInfo(geoUri = geoUri, description = geoUri),
-                unstableTimestampAsMilliseconds = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()),
+                unstableTimestampMillis = clock.epochMillis(),
         )
         val localId = LocalEcho.createLocalEchoId()
         return Event(
@@ -267,7 +273,8 @@ internal class LocalEchoEventFactory @Inject constructor(
                 eventId = localId,
                 type = EventType.BEACON_LOCATION_DATA.first(),
                 content = content.toContent(),
-                unsignedData = UnsignedData(age = null, transactionId = localId))
+                unsignedData = UnsignedData(age = null, transactionId = localId)
+        )
     }
 
     fun createReplaceTextOfReply(roomId: String,
@@ -297,7 +304,8 @@ internal class LocalEchoEventFactory @Inject constructor(
         //
         val replyFallback = buildReplyFallback(body, originalEvent.root.senderId ?: "", newBodyText)
 
-        return createMessageEvent(roomId,
+        return createMessageEvent(
+                roomId,
                 MessageTextContent(
                         msgType = msgType,
                         body = compatibilityText,
@@ -309,7 +317,8 @@ internal class LocalEchoEventFactory @Inject constructor(
                                 formattedBody = replyFormatted
                         )
                                 .toContent()
-                ))
+                )
+        )
     }
 
     fun createMediaEvent(roomId: String,
@@ -341,7 +350,8 @@ internal class LocalEchoEventFactory @Inject constructor(
                 eventId = localId,
                 type = EventType.REACTION,
                 content = content.toContent(),
-                unsignedData = UnsignedData(age = null, transactionId = localId))
+                unsignedData = UnsignedData(age = null, transactionId = localId)
+        )
     }
 
     private fun createImageEvent(roomId: String, attachment: ContentAttachmentData, rootThreadEventId: String?): Event {
@@ -532,12 +542,14 @@ internal class LocalEchoEventFactory @Inject constructor(
                 content.toThreadTextContent(
                         rootThreadEventId = rootThreadEventId,
                         latestThreadEventId = localEchoRepository.getLatestThreadEvent(rootThreadEventId),
-                        msgType = msgType)
-                        .toContent())
+                        msgType = msgType
+                )
+                        .toContent()
+        )
     }
 
     private fun dummyOriginServerTs(): Long {
-        return System.currentTimeMillis()
+        return clock.epochMillis()
     }
 
     /**
@@ -582,7 +594,9 @@ internal class LocalEchoEventFactory @Inject constructor(
                 relatesTo = generateReplyRelationContent(
                         eventId = eventId,
                         rootThreadEventId = rootThreadEventId,
-                        showInThread = showInThread))
+                        showInThread = showInThread
+                )
+        )
         return createMessageEvent(roomId, content)
     }
 
@@ -605,7 +619,8 @@ internal class LocalEchoEventFactory @Inject constructor(
                         eventId = it,
                         isFallingBack = showInThread,
                         // False when is a rich reply from within a thread, and true when is a reply that should be visible from threads
-                        inReplyTo = ReplyToContent(eventId = eventId))
+                        inReplyTo = ReplyToContent(eventId = eventId)
+                )
             } ?: RelationDefaultContent(null, null, ReplyToContent(eventId = eventId))
 
     private fun buildFormattedReply(permalink: String, userLink: String, userId: String, bodyFormatted: String, newBodyFormatted: String): String {
@@ -740,13 +755,15 @@ internal class LocalEchoEventFactory @Inject constructor(
                             .toThreadTextContent(
                                     rootThreadEventId = rootThreadEventId,
                                     latestThreadEventId = localEchoRepository.getLatestThreadEvent(rootThreadEventId),
-                                    msgType = MessageType.MSGTYPE_TEXT)
+                                    msgType = MessageType.MSGTYPE_TEXT
+                            )
             )
         } else {
             createFormattedTextEvent(
                     roomId,
                     markdownParser.parse(quoteText, force = true, advanced = autoMarkdown),
-                    MessageType.MSGTYPE_TEXT)
+                    MessageType.MSGTYPE_TEXT
+            )
         }
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt
index 1b1a66a1..9fd45b91 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt
@@ -43,16 +43,20 @@ import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater
 import org.matrix.android.sdk.internal.session.room.timeline.TimelineInput
 import org.matrix.android.sdk.internal.task.TaskExecutor
 import org.matrix.android.sdk.internal.util.awaitTransaction
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import java.util.UUID
 import javax.inject.Inject
 
-internal class LocalEchoRepository @Inject constructor(@SessionDatabase private val monarchy: Monarchy,
-                                                       private val taskExecutor: TaskExecutor,
-                                                       private val realmSessionProvider: RealmSessionProvider,
-                                                       private val roomSummaryUpdater: RoomSummaryUpdater,
-                                                       private val timelineInput: TimelineInput,
-                                                       private val timelineEventMapper: TimelineEventMapper) {
+internal class LocalEchoRepository @Inject constructor(
+        @SessionDatabase private val monarchy: Monarchy,
+        private val taskExecutor: TaskExecutor,
+        private val realmSessionProvider: RealmSessionProvider,
+        private val roomSummaryUpdater: RoomSummaryUpdater,
+        private val timelineInput: TimelineInput,
+        private val timelineEventMapper: TimelineEventMapper,
+        private val clock: Clock,
+) {
 
     fun createLocalEcho(event: Event) {
         val roomId = event.roomId ?: throw IllegalStateException("You should have set a roomId for your event")
@@ -61,7 +65,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private
         event.type ?: throw IllegalStateException("You should have set a type for your event")
 
         val timelineEventEntity = realmSessionProvider.withRealm { realm ->
-            val eventEntity = event.toEntity(roomId, SendState.UNSENT, System.currentTimeMillis())
+            val eventEntity = event.toEntity(roomId, SendState.UNSENT, clock.epochMillis())
             val roomMemberHelper = RoomMemberHelper(realm, roomId)
             val myUser = roomMemberHelper.getLastRoomMember(senderId)
             val localId = UUID.randomUUID().mostSignificantBits
@@ -88,7 +92,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private
     }
 
     fun updateSendState(eventId: String, roomId: String?, sendState: SendState, sendStateDetails: String? = null) {
-        Timber.v("## SendEvent: [${System.currentTimeMillis()}] Update local state of $eventId to ${sendState.name}")
+        Timber.v("## SendEvent: [${clock.epochMillis()}] Update local state of $eventId to ${sendState.name}")
         timelineInput.onLocalEchoUpdated(roomId = roomId ?: "", eventId = eventId, sendState = sendState)
         updateEchoAsync(eventId) { realm, sendingEventEntity ->
             if (sendState == SendState.SENT && sendingEventEntity.sendState == SendState.SYNCED) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MultipleEventSendingDispatcherWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MultipleEventSendingDispatcherWorker.kt
index b59f1b17..ecc81492 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MultipleEventSendingDispatcherWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/MultipleEventSendingDispatcherWorker.kt
@@ -39,7 +39,7 @@ import javax.inject.Inject
  * Possible next worker    : None, but it will post new work to send events, encrypted or not
  */
 internal class MultipleEventSendingDispatcherWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
-    SessionSafeCoroutineWorker<MultipleEventSendingDispatcherWorker.Params>(context, params, sessionManager, Params::class.java) {
+        SessionSafeCoroutineWorker<MultipleEventSendingDispatcherWorker.Params>(context, params, sessionManager, Params::class.java) {
 
     @JsonClass(generateAdapter = true)
     internal data class Params(
@@ -76,10 +76,10 @@ internal class MultipleEventSendingDispatcherWorker(context: Context, params: Wo
         params.localEchoIds.forEach { localEchoIds ->
             val roomId = localEchoIds.roomId
             val eventId = localEchoIds.eventId
-                localEchoRepository.updateSendState(eventId, roomId, SendState.SENDING)
-                Timber.v("## SendEvent: [${System.currentTimeMillis()}] Schedule send event $eventId")
-                val sendWork = createSendEventWork(params.sessionId, eventId, true)
-                timelineSendEventWorkCommon.postWork(roomId, sendWork)
+            localEchoRepository.updateSendState(eventId, roomId, SendState.SENDING)
+            Timber.v("## SendEvent: Schedule send event $eventId")
+            val sendWork = createSendEventWork(params.sessionId, eventId, true)
+            timelineSendEventWorkCommon.postWork(roomId, sendWork)
         }
 
         return Result.success()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt
index c03d1fa8..83c61d28 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt
@@ -34,7 +34,7 @@ import javax.inject.Inject
  * Possible next worker    : None
  */
 internal class RedactEventWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
-    SessionSafeCoroutineWorker<RedactEventWorker.Params>(context, params, sessionManager, Params::class.java) {
+        SessionSafeCoroutineWorker<RedactEventWorker.Params>(context, params, sessionManager, Params::class.java) {
 
     @JsonClass(generateAdapter = true)
     internal data class Params(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt
index 7f24688e..ddbe8a61 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt
@@ -40,7 +40,7 @@ import javax.inject.Inject
  * Possible next worker    : None
  */
 internal class SendEventWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
-    SessionSafeCoroutineWorker<SendEventWorker.Params>(context, params, sessionManager, Params::class.java) {
+        SessionSafeCoroutineWorker<SendEventWorker.Params>(context, params, sessionManager, Params::class.java) {
 
     @JsonClass(generateAdapter = true)
     internal data class Params(
@@ -89,13 +89,13 @@ internal class SendEventWorker(context: Context, params: WorkerParameters, sessi
                     .also { Timber.e("Work cancelled due to input error from parent") }
         }
 
-        Timber.v("## SendEvent: [${System.currentTimeMillis()}] Send event ${params.eventId}")
+        Timber.v("## SendEvent: Send event ${params.eventId}")
         return try {
             sendEventTask.execute(SendEventTask.Params(event, params.isEncrypted ?: cryptoService.isRoomEncrypted(event.roomId)))
             Result.success()
         } catch (exception: Throwable) {
             if (/*currentAttemptCount >= MAX_NUMBER_OF_RETRY_BEFORE_FAILING ||**/ !exception.shouldBeRetried()) {
-                Timber.e("## SendEvent: [${System.currentTimeMillis()}]  Send event Failed cannot retry ${params.eventId} > ${exception.localizedMessage}")
+                Timber.e("## SendEvent: Send event Failed cannot retry ${params.eventId} > ${exception.localizedMessage}")
                 localEchoRepository.updateSendState(
                         eventId = event.eventId,
                         roomId = event.roomId,
@@ -104,7 +104,7 @@ internal class SendEventWorker(context: Context, params: WorkerParameters, sessi
                 )
                 Result.success()
             } else {
-                Timber.e("## SendEvent: [${System.currentTimeMillis()}]  Send event Failed schedule retry ${params.eventId} > ${exception.localizedMessage}")
+                Timber.e("## SendEvent: Send event Failed schedule retry ${params.eventId} > ${exception.localizedMessage}")
                 Result.retry()
             }
         }
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 5b4efa5d..a1d3e2c0 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
@@ -191,7 +191,7 @@ internal class EventSenderProcessorCoroutine @Inject constructor(
 
     private suspend fun QueuedTask.waitForNetwork() = waitForNetworkSequencer.post {
         while (!canReachServer.get()) {
-            Timber.v("## $this cannot reach server wait ts:${System.currentTimeMillis()}")
+            Timber.v("## $this cannot reach server wait for $$RETRY_WAIT_TIME_MS ms")
             delay(RETRY_WAIT_TIME_MS)
             withContext(Dispatchers.IO) {
                 val hostAvailable = HomeServerAvailabilityChecker(sessionParams).check()
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 1ee31391..301f8cb9 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
@@ -136,7 +136,7 @@ internal class EventSenderProcessorThread @Inject constructor(
     private var retryNoNetworkTask: TimerTask? = null
 
     override fun run() {
-        Timber.v("## SendThread started ts:${System.currentTimeMillis()}")
+        Timber.v("## SendThread started")
         try {
             while (!isInterrupted) {
                 Timber.v("## SendThread wait for task to process")
@@ -151,7 +151,7 @@ internal class EventSenderProcessorThread @Inject constructor(
                 }
                 // we check for network connectivity
                 while (!canReachServer) {
-                    Timber.v("## SendThread cannot reach server, wait ts:${System.currentTimeMillis()}")
+                    Timber.v("## SendThread cannot reach server")
                     // schedule to retry
                     waitForNetwork()
                     // if thread as been killed meanwhile
@@ -175,7 +175,7 @@ internal class EventSenderProcessorThread @Inject constructor(
                                     canReachServer = false
                                     if (task.retryCount.getAndIncrement() >= 3) task.onTaskFailed()
                                     while (!canReachServer) {
-                                        Timber.v("## SendThread retryLoop cannot reach server, wait ts:${System.currentTimeMillis()}")
+                                        Timber.v("## SendThread retryLoop cannot reach server")
                                         // schedule to retry
                                         waitForNetwork()
                                     }
@@ -218,7 +218,7 @@ internal class EventSenderProcessorThread @Inject constructor(
 //        state = State.KILLED
         // is this needed?
         retryNoNetworkTask?.cancel()
-        Timber.w("## SendThread finished ${System.currentTimeMillis()}")
+        Timber.w("## SendThread finished")
     }
 
     private fun waitForNetwork() {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueueMemento.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueueMemento.kt
index 116c8d5c..545fc417 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueueMemento.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueueMemento.kt
@@ -74,7 +74,7 @@ internal class QueueMemento @Inject constructor(context: Context,
                     encrypt = task.encrypt,
                     order = order
             )
-            is RedactQueuedTask -> RedactEventTaskInfo(
+            is RedactQueuedTask    -> RedactEventTaskInfo(
                     redactionLocalEcho = task.redactionLocalEchoId,
                     order = order
             )
@@ -92,7 +92,7 @@ internal class QueueMemento @Inject constructor(context: Context,
                 ?.forEach { info ->
                     try {
                         when (info) {
-                            is SendEventTaskInfo -> {
+                            is SendEventTaskInfo   -> {
                                 localEchoRepository.getUpToDateEcho(info.localEchoId)?.let {
                                     if (it.sendState.isSending() && it.eventId != null && it.roomId != null) {
                                         localEchoRepository.updateSendState(it.eventId, it.roomId, SendState.UNSENT)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt
index 89d33f98..e5c7a75c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt
@@ -33,8 +33,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
 import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
 import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry
 import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
-import org.matrix.android.sdk.api.session.room.model.livelocation.BeaconInfo
-import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationBeaconContent
+import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent
 import org.matrix.android.sdk.api.session.room.state.StateService
 import org.matrix.android.sdk.api.util.JsonDict
 import org.matrix.android.sdk.api.util.MimeTypes
@@ -74,14 +73,14 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
             eventType: String,
             stateKey: String,
             body: JsonDict
-    ) {
+    ): String {
         val params = SendStateTask.Params(
                 roomId = roomId,
                 stateKey = stateKey,
                 eventType = eventType,
                 body = body.toSafeJson(eventType)
         )
-        sendStateTask.executeRetry(params, 3)
+        return sendStateTask.executeRetry(params, 3)
     }
 
     private fun JsonDict.toSafeJson(eventType: String): JsonDict {
@@ -193,20 +192,13 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
 
     override suspend fun stopLiveLocation(userId: String) {
         getLiveLocationBeaconInfo(userId, true)?.let { beaconInfoStateEvent ->
-            beaconInfoStateEvent.getClearContent()?.toModel<LiveLocationBeaconContent>()?.let { content ->
-                val beaconContent = LiveLocationBeaconContent(
-                        unstableBeaconInfo = BeaconInfo(
-                                description = content.getBestBeaconInfo()?.description,
-                                timeout = content.getBestBeaconInfo()?.timeout,
-                                isLive = false,
-                        ),
-                        unstableTimestampAsMilliseconds = System.currentTimeMillis()
-                ).toContent()
+            beaconInfoStateEvent.getClearContent()?.toModel<MessageBeaconInfoContent>()?.let { content ->
+                val updatedContent = content.copy(isLive = false).toContent()
 
                 beaconInfoStateEvent.stateKey?.let {
                     sendStateEvent(
                             eventType = EventType.STATE_ROOM_BEACON_INFO.first(),
-                            body = beaconContent,
+                            body = updatedContent,
                             stateKey = it
                     )
                 }
@@ -225,7 +217,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
                 }
                 .firstOrNull { beaconInfoEvent ->
                     !filterOnlyLive ||
-                            beaconInfoEvent.getClearContent()?.toModel<LiveLocationBeaconContent>()?.getBestBeaconInfo()?.isLive.orFalse()
+                            beaconInfoEvent.getClearContent()?.toModel<MessageBeaconInfoContent>()?.isLive.orFalse()
                 }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SafePowerLevelContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SafePowerLevelContent.kt
index 197b4f86..1f2ec093 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SafePowerLevelContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SafePowerLevelContent.kt
@@ -51,7 +51,7 @@ internal fun JsonDict.toSafePowerLevelsContentDict(): JsonDict {
                         usersDefault = content.usersDefault,
                         users = content.users,
                         stateDefault = content.stateDefault,
-                        notifications = content.notifications?.mapValues { content.notificationLevel(it.key)  }
+                        notifications = content.notifications?.mapValues { content.notificationLevel(it.key) }
                 )
             }
             ?.toContent()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt
index 56c69a05..59c9de29 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt
@@ -21,9 +21,10 @@ import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.room.RoomAPI
 import org.matrix.android.sdk.internal.task.Task
+import timber.log.Timber
 import javax.inject.Inject
 
-internal interface SendStateTask : Task<SendStateTask.Params, Unit> {
+internal interface SendStateTask : Task<SendStateTask.Params, String> {
     data class Params(
             val roomId: String,
             val stateKey: String,
@@ -37,9 +38,9 @@ internal class DefaultSendStateTask @Inject constructor(
         private val globalErrorReceiver: GlobalErrorReceiver
 ) : SendStateTask {
 
-    override suspend fun execute(params: SendStateTask.Params) {
+    override suspend fun execute(params: SendStateTask.Params): String {
         return executeRequest(globalErrorReceiver) {
-            if (params.stateKey.isEmpty()) {
+            val response = if (params.stateKey.isEmpty()) {
                 roomAPI.sendStateEvent(
                         roomId = params.roomId,
                         stateEventType = params.eventType,
@@ -53,6 +54,9 @@ internal class DefaultSendStateTask @Inject constructor(
                         params = params.body
                 )
             }
+            response.eventId.also {
+                Timber.d("State event: $it just sent in room ${params.roomId}")
+            }
         }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/GraphUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/GraphUtils.kt
index e3a21544..52879d71 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/GraphUtils.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/GraphUtils.kt
@@ -101,11 +101,11 @@ internal class Graph {
                         // it's a candidate
                         destination = it.destination
                     }
-                    inPath -> {
+                    inPath     -> {
                         // Cycle!!
                         backwardEdges.add(it)
                     }
-                    completed -> {
+                    completed  -> {
                         // dead end
                     }
                 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt
index 18a4f805..96e8d3c7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt
@@ -26,7 +26,6 @@ import com.zhuinden.monarchy.Monarchy
 import io.realm.Realm
 import io.realm.RealmQuery
 import io.realm.kotlin.where
-import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
 import org.matrix.android.sdk.api.query.ActiveSpaceFilter
 import org.matrix.android.sdk.api.query.RoomCategoryFilter
 import org.matrix.android.sdk.api.query.isNormalized
@@ -43,7 +42,6 @@ import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotification
 import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams
 import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.api.util.toOptional
-import org.matrix.android.sdk.internal.database.RealmSessionProvider
 import org.matrix.android.sdk.internal.database.mapper.RoomSummaryMapper
 import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
 import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
@@ -57,10 +55,8 @@ import javax.inject.Inject
 
 internal class RoomSummaryDataSource @Inject constructor(
         @SessionDatabase private val monarchy: Monarchy,
-        private val realmSessionProvider: RealmSessionProvider,
         private val roomSummaryMapper: RoomSummaryMapper,
         private val queryStringValueProcessor: QueryStringValueProcessor,
-        private val coroutineDispatchers: MatrixCoroutineDispatchers
 ) {
 
     fun getRoomSummary(roomIdOrAlias: String): RoomSummary? {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
index 5064ebf4..18fbe73c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
@@ -43,28 +43,32 @@ import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHand
 import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler
 import org.matrix.android.sdk.internal.task.SemaphoreCoroutineSequencer
 import org.matrix.android.sdk.internal.util.createBackgroundHandler
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import java.util.UUID
 import java.util.concurrent.CopyOnWriteArrayList
 import java.util.concurrent.atomic.AtomicBoolean
 import java.util.concurrent.atomic.AtomicReference
 
-internal class DefaultTimeline(private val roomId: String,
-                               private val initialEventId: String?,
-                               private val realmConfiguration: RealmConfiguration,
-                               private val loadRoomMembersTask: LoadRoomMembersTask,
-                               private val readReceiptHandler: ReadReceiptHandler,
-                               private val settings: TimelineSettings,
-                               private val coroutineDispatchers: MatrixCoroutineDispatchers,
-                               paginationTask: PaginationTask,
-                               getEventTask: GetContextOfEventTask,
-                               fetchTokenAndPaginateTask: FetchTokenAndPaginateTask,
-                               fetchThreadTimelineTask: FetchThreadTimelineTask,
-                               timelineEventMapper: TimelineEventMapper,
-                               timelineInput: TimelineInput,
-                               threadsAwarenessHandler: ThreadsAwarenessHandler,
-                               lightweightSettingsStorage: LightweightSettingsStorage,
-                               eventDecryptor: TimelineEventDecryptor) : Timeline {
+internal class DefaultTimeline(
+        private val roomId: String,
+        private val initialEventId: String?,
+        private val realmConfiguration: RealmConfiguration,
+        private val loadRoomMembersTask: LoadRoomMembersTask,
+        private val readReceiptHandler: ReadReceiptHandler,
+        private val settings: TimelineSettings,
+        private val coroutineDispatchers: MatrixCoroutineDispatchers,
+        private val clock: Clock,
+        paginationTask: PaginationTask,
+        getEventTask: GetContextOfEventTask,
+        fetchTokenAndPaginateTask: FetchTokenAndPaginateTask,
+        fetchThreadTimelineTask: FetchThreadTimelineTask,
+        timelineEventMapper: TimelineEventMapper,
+        timelineInput: TimelineInput,
+        threadsAwarenessHandler: ThreadsAwarenessHandler,
+        lightweightSettingsStorage: LightweightSettingsStorage,
+        eventDecryptor: TimelineEventDecryptor,
+) : Timeline {
 
     companion object {
         val BACKGROUND_HANDLER = createBackgroundHandler("DefaultTimeline_Thread")
@@ -100,6 +104,7 @@ internal class DefaultTimeline(private val roomId: String,
             threadsAwarenessHandler = threadsAwarenessHandler,
             lightweightSettingsStorage = lightweightSettingsStorage,
             onEventsUpdated = this::sendSignalToPostSnapshot,
+            onEventsDeleted = this::onEventsDeleted,
             onLimitedTimeline = this::onLimitedTimeline,
             onNewTimelineEvents = this::onNewTimelineEvents
     )
@@ -304,6 +309,12 @@ internal class DefaultTimeline(private val roomId: String,
         }
     }
 
+    private fun onEventsDeleted() {
+        // Some event have been deleted, for instance when a user has been ignored.
+        // Restart the timeline (live)
+        restartWithEventId(null)
+    }
+
     private suspend fun postSnapshot() {
         val snapshot = strategy.buildSnapshot()
         Timber.v("Post snapshot of ${snapshot.size} events")
@@ -363,7 +374,8 @@ internal class DefaultTimeline(private val roomId: String,
                 roomId = roomId,
                 timelineId = timelineID,
                 mode = mode,
-                dependencies = strategyDependencies
+                dependencies = strategyDependencies,
+                clock = clock,
         )
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt
index 826c9d7c..849d7bd7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt
@@ -34,6 +34,7 @@ import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTa
 import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask
 import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler
 import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler
+import org.matrix.android.sdk.internal.util.time.Clock
 
 internal class DefaultTimelineService @AssistedInject constructor(
         @Assisted private val roomId: String,
@@ -50,8 +51,9 @@ internal class DefaultTimelineService @AssistedInject constructor(
         private val lightweightSettingsStorage: LightweightSettingsStorage,
         private val readReceiptHandler: ReadReceiptHandler,
         private val coroutineDispatchers: MatrixCoroutineDispatchers,
-        private val timelineEventDataSource: TimelineEventDataSource
-) : TimelineService {
+        private val timelineEventDataSource: TimelineEventDataSource,
+        private val clock: Clock,
+        ) : TimelineService {
 
     @AssistedFactory
     interface Factory {
@@ -75,7 +77,8 @@ internal class DefaultTimelineService @AssistedInject constructor(
                 readReceiptHandler = readReceiptHandler,
                 getEventTask = contextOfEventTask,
                 threadsAwarenessHandler = threadsAwarenessHandler,
-                lightweightSettingsStorage = lightweightSettingsStorage
+                lightweightSettingsStorage = lightweightSettingsStorage,
+                clock = clock
         )
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt
index 5bca5118..aef9e24c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt
@@ -24,6 +24,7 @@ import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.room.RoomAPI
 import org.matrix.android.sdk.internal.task.Task
+import org.matrix.android.sdk.internal.util.time.Clock
 import javax.inject.Inject
 
 internal interface GetEventTask : Task<GetEventTask.Params, Event> {
@@ -36,7 +37,8 @@ internal interface GetEventTask : Task<GetEventTask.Params, Event> {
 internal class DefaultGetEventTask @Inject constructor(
         private val roomAPI: RoomAPI,
         private val globalErrorReceiver: GlobalErrorReceiver,
-        private val eventDecryptor: EventDecryptor
+        private val eventDecryptor: EventDecryptor,
+        private val clock: Clock,
 ) : GetEventTask {
 
     override suspend fun execute(params: GetEventTask.Params): Event {
@@ -59,7 +61,7 @@ internal class DefaultGetEventTask @Inject constructor(
                     }
         }
 
-        event.ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it }
+        event.ageLocalTs = event.unsignedData?.age?.let { clock.epochMillis() - it }
 
         return event
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LiveTimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LiveTimelineEvent.kt
index 64b1a4ff..e765e055 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LiveTimelineEvent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LiveTimelineEvent.kt
@@ -41,7 +41,7 @@ internal class LiveTimelineEvent(private val monarchy: Monarchy,
                                  private val timelineEventMapper: TimelineEventMapper,
                                  private val roomId: String,
                                  private val eventId: String) :
-    MediatorLiveData<Optional<TimelineEvent>>() {
+        MediatorLiveData<Optional<TimelineEvent>>() {
 
     init {
         buildAndObserveQuery()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt
index ff986d04..bcf20296 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt
@@ -42,6 +42,7 @@ import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfThre
 import org.matrix.android.sdk.internal.database.query.where
 import org.matrix.android.sdk.internal.session.room.relation.threads.FetchThreadTimelineTask
 import org.matrix.android.sdk.internal.session.sync.handler.room.ThreadsAwarenessHandler
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import java.util.concurrent.atomic.AtomicReference
 
@@ -53,11 +54,13 @@ import java.util.concurrent.atomic.AtomicReference
  * Once we got a ChunkEntity we wrap it with TimelineChunk class so we dispatch any methods for loading data.
  */
 
-internal class LoadTimelineStrategy(
+internal class LoadTimelineStrategy constructor(
         private val roomId: String,
         private val timelineId: String,
         private val mode: Mode,
-        private val dependencies: Dependencies) {
+        private val dependencies: Dependencies,
+        clock: Clock,
+) {
 
     sealed interface Mode {
         object Live : Mode
@@ -95,6 +98,7 @@ internal class LoadTimelineStrategy(
             val threadsAwarenessHandler: ThreadsAwarenessHandler,
             val lightweightSettingsStorage: LightweightSettingsStorage,
             val onEventsUpdated: (Boolean) -> Unit,
+            val onEventsDeleted: () -> Unit,
             val onLimitedTimeline: () -> Unit,
             val onNewTimelineEvents: (List<String>) -> Unit
     )
@@ -152,7 +156,7 @@ internal class LoadTimelineStrategy(
         }
     }
 
-    private val uiEchoManager = UIEchoManager(uiEchoManagerListener)
+    private val uiEchoManager = UIEchoManager(uiEchoManagerListener, clock)
     private val sendingEventsDataSource: SendingEventsDataSource = RealmSendingEventsDataSource(
             roomId = roomId,
             realm = dependencies.realm,
@@ -302,7 +306,8 @@ internal class LoadTimelineStrategy(
                     threadsAwarenessHandler = dependencies.threadsAwarenessHandler,
                     lightweightSettingsStorage = dependencies.lightweightSettingsStorage,
                     initialEventId = mode.originEventId(),
-                    onBuiltEvents = dependencies.onEventsUpdated
+                    onBuiltEvents = dependencies.onEventsUpdated,
+                    onEventsDeleted = dependencies.onEventsDeleted,
             )
         }
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt
index 4ead1d4e..27f4245b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt
@@ -49,21 +49,24 @@ import java.util.concurrent.atomic.AtomicBoolean
  * It does mainly listen to the db timeline events.
  * It also triggers pagination to the server when needed, or dispatch to the prev or next chunk if any.
  */
-internal class TimelineChunk(private val chunkEntity: ChunkEntity,
-                             private val timelineSettings: TimelineSettings,
-                             private val roomId: String,
-                             private val timelineId: String,
-                             private val fetchThreadTimelineTask: FetchThreadTimelineTask,
-                             private val eventDecryptor: TimelineEventDecryptor,
-                             private val paginationTask: PaginationTask,
-                             private val realmConfiguration: RealmConfiguration,
-                             private val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask,
-                             private val timelineEventMapper: TimelineEventMapper,
-                             private val uiEchoManager: UIEchoManager? = null,
-                             private val threadsAwarenessHandler: ThreadsAwarenessHandler,
-                             private val lightweightSettingsStorage: LightweightSettingsStorage,
-                             private val initialEventId: String?,
-                             private val onBuiltEvents: (Boolean) -> Unit) {
+internal class TimelineChunk(
+        private val chunkEntity: ChunkEntity,
+        private val timelineSettings: TimelineSettings,
+        private val roomId: String,
+        private val timelineId: String,
+        private val fetchThreadTimelineTask: FetchThreadTimelineTask,
+        private val eventDecryptor: TimelineEventDecryptor,
+        private val paginationTask: PaginationTask,
+        private val realmConfiguration: RealmConfiguration,
+        private val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask,
+        private val timelineEventMapper: TimelineEventMapper,
+        private val uiEchoManager: UIEchoManager?,
+        private val threadsAwarenessHandler: ThreadsAwarenessHandler,
+        private val lightweightSettingsStorage: LightweightSettingsStorage,
+        private val initialEventId: String?,
+        private val onBuiltEvents: (Boolean) -> Unit,
+        private val onEventsDeleted: () -> Unit,
+) {
 
     private val isLastForward = AtomicBoolean(chunkEntity.isLastForward)
     private val isLastBackward = AtomicBoolean(chunkEntity.isLastBackward)
@@ -505,6 +508,11 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity,
         if (insertions.isNotEmpty() || modifications.isNotEmpty()) {
             onBuiltEvents(true)
         }
+
+        val deletions = changeSet.deletions
+        if (deletions.isNotEmpty()) {
+            onEventsDeleted()
+        }
     }
 
     private fun getNextDisplayIndex(direction: Timeline.Direction): Int? {
@@ -543,7 +551,8 @@ internal class TimelineChunk(private val chunkEntity: ChunkEntity,
                 threadsAwarenessHandler = threadsAwarenessHandler,
                 lightweightSettingsStorage = lightweightSettingsStorage,
                 initialEventId = null,
-                onBuiltEvents = this.onBuiltEvents
+                onBuiltEvents = this.onBuiltEvents,
+                onEventsDeleted = this.onEventsDeleted
         )
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDataSource.kt
index 638866a4..8b58d3ca 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDataSource.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDataSource.kt
@@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.room.timeline
 import androidx.lifecycle.LiveData
 import com.zhuinden.monarchy.Monarchy
 import io.realm.Sort
-import io.realm.kotlin.where
 import org.matrix.android.sdk.api.session.events.model.isImageMessage
 import org.matrix.android.sdk.api.session.events.model.isVideoMessage
 import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
@@ -29,6 +28,7 @@ import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
 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.query.where
+import org.matrix.android.sdk.internal.database.query.whereRoomId
 import org.matrix.android.sdk.internal.di.SessionDatabase
 import org.matrix.android.sdk.internal.task.TaskExecutor
 import javax.inject.Inject
@@ -53,8 +53,7 @@ internal class TimelineEventDataSource @Inject constructor(private val realmSess
     fun getAttachmentMessages(roomId: String): List<TimelineEvent> {
         // TODO pretty bad query.. maybe we should denormalize clear type in base?
         return realmSessionProvider.withRealm { realm ->
-            realm.where<TimelineEventEntity>()
-                    .equalTo(TimelineEventEntityFields.ROOM_ID, roomId)
+            TimelineEventEntity.whereRoomId(realm, roomId)
                     .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.ASCENDING)
                     .findAll()
                     ?.mapNotNull { timelineEventMapper.map(it).takeIf { it.root.isImageMessage() || it.root.isVideoMessage() } }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt
index 5c30dc20..de79661d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt
@@ -99,9 +99,7 @@ internal class TimelineEventDecryptor @Inject constructor(
         executor?.execute {
             Realm.getInstance(realmConfiguration).use { realm ->
                 try {
-                    runBlocking {
-                        processDecryptRequest(request, realm)
-                    }
+                    processDecryptRequest(request, realm)
                 } catch (e: InterruptedException) {
                     Timber.i("Decryption got interrupted")
                 }
@@ -121,7 +119,7 @@ internal class TimelineEventDecryptor @Inject constructor(
         }
     }
 
-    private suspend fun processDecryptRequest(request: DecryptionRequest, realm: Realm) {
+    private fun processDecryptRequest(request: DecryptionRequest, realm: Realm) {
         val event = request.event
         val timelineId = request.timelineId
 
@@ -132,7 +130,8 @@ internal class TimelineEventDecryptor @Inject constructor(
             return
         }
         try {
-            val result = cryptoService.decryptEvent(request.event, timelineId)
+            // note: runBlocking should be used here while we are in realm single thread executor, to avoid thread switching
+            val result = runBlocking { cryptoService.decryptEvent(request.event, timelineId) }
             Timber.v("Successfully decrypted event ${event.eventId}")
             realm.executeTransaction {
                 val eventId = event.eventId ?: return@executeTransaction
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt
index d3f24a85..e5dd8aab 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt
@@ -44,6 +44,7 @@ import org.matrix.android.sdk.internal.di.SessionDatabase
 import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.session.StreamEventsManager
 import org.matrix.android.sdk.internal.util.awaitTransaction
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import javax.inject.Inject
 
@@ -54,7 +55,9 @@ internal class TokenChunkEventPersistor @Inject constructor(
         @SessionDatabase private val monarchy: Monarchy,
         @UserId private val userId: String,
         private val lightweightSettingsStorage: LightweightSettingsStorage,
-        private val liveEventManager: Lazy<StreamEventsManager>) {
+        private val liveEventManager: Lazy<StreamEventsManager>,
+        private val clock: Clock,
+) {
 
     enum class Result {
         SHOULD_FETCH_MORE,
@@ -166,7 +169,7 @@ internal class TokenChunkEventPersistor @Inject constructor(
         val eventList = receivedChunk.events
         val stateEvents = receivedChunk.stateEvents
 
-        val now = System.currentTimeMillis()
+        val now = clock.epochMillis()
 
         stateEvents?.forEach { stateEvent ->
             val ageLocalTs = stateEvent.unsignedData?.age?.let { now - it }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt
index bb926232..828e0195 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt
@@ -24,10 +24,14 @@ import org.matrix.android.sdk.api.session.room.model.ReactionAggregatedSummary
 import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent
 import org.matrix.android.sdk.api.session.room.send.SendState
 import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import java.util.Collections
 
-internal class UIEchoManager(private val listener: Listener) {
+internal class UIEchoManager(
+        private val listener: Listener,
+        private val clock: Clock,
+) {
 
     interface Listener {
         fun rebuildEvent(eventId: String, builder: (TimelineEvent) -> TimelineEvent?): Boolean
@@ -98,9 +102,7 @@ internal class UIEchoManager(private val listener: Listener) {
         val relatedEventID = timelineEvent.eventId
         val contents = inMemoryReactions[relatedEventID] ?: return timelineEvent
 
-        var existingAnnotationSummary = timelineEvent.annotations ?: EventAnnotationsSummary(
-                relatedEventID
-        )
+        var existingAnnotationSummary = timelineEvent.annotations ?: EventAnnotationsSummary()
         val updateReactions = existingAnnotationSummary.reactionsSummary.toMutableList()
 
         contents.forEach { uiEchoReaction ->
@@ -111,7 +113,7 @@ internal class UIEchoManager(private val listener: Listener) {
                         key = uiEchoReaction.reaction,
                         count = 1,
                         addedByMe = true,
-                        firstTimestamp = System.currentTimeMillis(),
+                        firstTimestamp = clock.epochMillis(),
                         sourceEvents = emptyList(),
                         localEchoEvents = listOf(uiEchoReaction.localEchoId)
                 ).let { updateReactions.add(it) }
@@ -145,7 +147,7 @@ internal class UIEchoManager(private val listener: Listener) {
     fun updateSentStateWithUiEcho(timelineEvent: TimelineEvent): TimelineEvent {
         if (timelineEvent.root.sendState.isSent()) return timelineEvent
         val inMemoryState = inMemorySendingStates[timelineEvent.eventId] ?: return timelineEvent
-        // Timber.v("## ${System.currentTimeMillis()} Send event refresh echo with live state $inMemoryState from state ${element.root.sendState}")
+        // Timber.v("## ${clock.epochMillis()} Send event refresh echo with live state $inMemoryState from state ${element.root.sendState}")
         return timelineEvent.copy(
                 root = timelineEvent.root.copyAll()
                         .also { it.sendState = inMemoryState }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchTask.kt
index 3ba7d11c..fcaf3b60 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchTask.kt
@@ -87,7 +87,7 @@ internal class DefaultSearchTask @Inject constructor(
                 results = searchCategories.roomEvents?.results?.map { searchResponseItem ->
 
                     val localThreadEventDetails = localTimelineEvents
-                            ?.firstOrNull { it.eventId ==  searchResponseItem.event.eventId }
+                            ?.firstOrNull { it.eventId == searchResponseItem.event.eventId }
                             ?.root
                             ?.asDomain()
                             ?.threadDetails
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt
index 303eda49..178a29a5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt
@@ -60,7 +60,7 @@ internal class DefaultSpace(
                         }
                         ?: viaParameterFinder.computeViaParams(roomId, 3))
 
-        room.sendStateEvent(
+        room.stateService().sendStateEvent(
                 eventType = EventType.STATE_SPACE_CHILD,
                 stateKey = roomId,
                 body = SpaceChildContent(
@@ -79,7 +79,7 @@ internal class DefaultSpace(
 //                return
 
         // edit state event and set via to null
-        room.sendStateEvent(
+        room.stateService().sendStateEvent(
                 eventType = EventType.STATE_SPACE_CHILD,
                 stateKey = roomId,
                 body = SpaceChildContent(
@@ -91,19 +91,19 @@ internal class DefaultSpace(
     }
 
     override fun getChildInfo(roomId: String): SpaceChildContent? {
-        return room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId))
+        return room.stateService().getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId))
                 .firstOrNull()
                 ?.content.toModel<SpaceChildContent>()
     }
 
     override suspend fun setChildrenOrder(roomId: String, order: String?) {
-        val existing = room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId))
+        val existing = room.stateService().getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId))
                 .firstOrNull()
                 ?.content.toModel<SpaceChildContent>()
                 ?: throw IllegalArgumentException("$roomId is not a child of this space")
 
         // edit state event and set via to null
-        room.sendStateEvent(
+        room.stateService().sendStateEvent(
                 eventType = EventType.STATE_SPACE_CHILD,
                 stateKey = roomId,
                 body = SpaceChildContent(
@@ -140,7 +140,7 @@ internal class DefaultSpace(
 //    }
 
     override suspend fun setChildrenSuggested(roomId: String, suggested: Boolean) {
-        val existing = room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId))
+        val existing = room.stateService().getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId))
                 .firstOrNull()
                 ?.content.toModel<SpaceChildContent>()
                 ?: throw IllegalArgumentException("$roomId is not a child of this space")
@@ -150,7 +150,7 @@ internal class DefaultSpace(
             return
         }
         // edit state event and set via to null
-        room.sendStateEvent(
+        room.stateService().sendStateEvent(
                 eventType = EventType.STATE_SPACE_CHILD,
                 stateKey = roomId,
                 body = SpaceChildContent(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt
index 355039b2..93206656 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt
@@ -26,6 +26,7 @@ import org.matrix.android.sdk.api.session.events.model.EventType
 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.room.RoomSortOrder
+import org.matrix.android.sdk.api.session.room.getStateEvent
 import org.matrix.android.sdk.api.session.room.model.GuestAccess
 import org.matrix.android.sdk.api.session.room.model.Membership
 import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
@@ -258,7 +259,7 @@ internal class DefaultSpaceService @Inject constructor(
         val room = roomGetter.getRoom(childRoomId)
                 ?: throw IllegalArgumentException("Unknown Room $childRoomId")
 
-        room.sendStateEvent(
+        room.stateService().sendStateEvent(
                 eventType = EventType.STATE_SPACE_PARENT,
                 stateKey = parentSpaceId,
                 body = SpaceParentContent(
@@ -276,7 +277,7 @@ internal class DefaultSpaceService @Inject constructor(
         if (existingEvent != null) {
             // Should i check if it was sent by me?
             // we don't check power level, it will throw if you cannot do that
-            room.sendStateEvent(
+            room.stateService().sendStateEvent(
                     eventType = EventType.STATE_SPACE_PARENT,
                     stateKey = parentSpaceId,
                     body = SpaceParentContent(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/InitialSyncStatusRepository.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/InitialSyncStatusRepository.kt
index bd20ada2..8e0c3422 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/InitialSyncStatusRepository.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/InitialSyncStatusRepository.kt
@@ -20,6 +20,7 @@ import com.squareup.moshi.JsonClass
 import okio.buffer
 import okio.source
 import org.matrix.android.sdk.internal.di.MoshiProvider
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import java.io.File
 
@@ -46,7 +47,10 @@ internal interface InitialSyncStatusRepository {
 /**
  * This class handle the current status of an initial sync and persist it on the disk, to be robust against crash
  */
-internal class FileInitialSyncStatusRepository(directory: File) : InitialSyncStatusRepository {
+internal class FileInitialSyncStatusRepository(
+        directory: File,
+        private val clock: Clock,
+) : InitialSyncStatusRepository {
 
     companion object {
         // After 2 hours, we consider that the downloaded file is outdated:
@@ -64,7 +68,7 @@ internal class FileInitialSyncStatusRepository(directory: File) : InitialSyncSta
         ensureCache()
         val state = cache?.step ?: InitialSyncStatus.STEP_INIT
         return if (state >= InitialSyncStatus.STEP_DOWNLOADED &&
-                System.currentTimeMillis() > (cache?.downloadedDate ?: 0) + INIT_SYNC_FILE_LIFETIME) {
+                clock.epochMillis() > (cache?.downloadedDate ?: 0) + INIT_SYNC_FILE_LIFETIME) {
             Timber.d("INIT_SYNC downloaded file is outdated, download it again")
             // The downloaded file is outdated
             setStep(InitialSyncStatus.STEP_INIT)
@@ -79,7 +83,7 @@ internal class FileInitialSyncStatusRepository(directory: File) : InitialSyncSta
         if (step == InitialSyncStatus.STEP_DOWNLOADED) {
             // Also store the downloaded date
             newStatus = newStatus.copy(
-                    downloadedDate = System.currentTimeMillis()
+                    downloadedDate = clock.epochMillis()
             )
         }
         cache = newStatus
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncPresence.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncPresence.kt
index 4f1fe43b..42cd972e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncPresence.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncPresence.kt
@@ -34,11 +34,12 @@ internal enum class SyncPresence(val value: String) {
     companion object {
         fun from(presenceEnum: PresenceEnum): SyncPresence {
             return when (presenceEnum) {
-                PresenceEnum.ONLINE -> Online
-                PresenceEnum.OFFLINE -> Offline
+                PresenceEnum.ONLINE      -> Online
+                PresenceEnum.OFFLINE     -> Offline
                 PresenceEnum.UNAVAILABLE -> Unavailable
             }
         }
+
         fun from(s: String?): SyncPresence? = values().find { it.value == s }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt
index 97850e81..02a7a9a3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt
@@ -18,9 +18,9 @@ package org.matrix.android.sdk.internal.session.sync
 
 import androidx.work.ExistingPeriodicWorkPolicy
 import com.zhuinden.monarchy.Monarchy
-import org.matrix.android.sdk.api.pushrules.PushRuleService
-import org.matrix.android.sdk.api.pushrules.RuleScope
 import org.matrix.android.sdk.api.session.initsync.InitSyncStep
+import org.matrix.android.sdk.api.session.pushrules.PushRuleService
+import org.matrix.android.sdk.api.session.pushrules.RuleScope
 import org.matrix.android.sdk.api.session.sync.model.GroupsSyncResponse
 import org.matrix.android.sdk.api.session.sync.model.RoomsSyncResponse
 import org.matrix.android.sdk.api.session.sync.model.SyncResponse
@@ -34,7 +34,7 @@ import org.matrix.android.sdk.internal.session.dispatchTo
 import org.matrix.android.sdk.internal.session.group.GetGroupDataWorker
 import org.matrix.android.sdk.internal.session.initsync.ProgressReporter
 import org.matrix.android.sdk.internal.session.initsync.reportSubtask
-import org.matrix.android.sdk.internal.session.notification.ProcessEventForPushTask
+import org.matrix.android.sdk.internal.session.pushrules.ProcessEventForPushTask
 import org.matrix.android.sdk.internal.session.sync.handler.CryptoSyncHandler
 import org.matrix.android.sdk.internal.session.sync.handler.GroupSyncHandler
 import org.matrix.android.sdk.internal.session.sync.handler.PresenceSyncHandler
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt
index b56f8977..f88d9731 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt
@@ -44,6 +44,7 @@ import org.matrix.android.sdk.internal.session.sync.parsing.InitialSyncResponseP
 import org.matrix.android.sdk.internal.session.user.UserStore
 import org.matrix.android.sdk.internal.task.Task
 import org.matrix.android.sdk.internal.util.logDuration
+import org.matrix.android.sdk.internal.util.time.Clock
 import retrofit2.Response
 import retrofit2.awaitResponse
 import timber.log.Timber
@@ -78,11 +79,12 @@ internal class DefaultSyncTask @Inject constructor(
         @SessionFilesDirectory
         private val fileDirectory: File,
         private val syncResponseParser: InitialSyncResponseParser,
-        private val roomSyncEphemeralTemporaryStore: RoomSyncEphemeralTemporaryStore
+        private val roomSyncEphemeralTemporaryStore: RoomSyncEphemeralTemporaryStore,
+        private val clock: Clock,
 ) : SyncTask {
 
     private val workingDir = File(fileDirectory, "is")
-    private val initialSyncStatusRepository: InitialSyncStatusRepository = FileInitialSyncStatusRepository(workingDir)
+    private val initialSyncStatusRepository: InitialSyncStatusRepository = FileInitialSyncStatusRepository(workingDir, clock)
 
     override suspend fun execute(params: SyncTask.Params): SyncResponse {
         return syncTaskSequencer.post {
@@ -107,11 +109,12 @@ internal class DefaultSyncTask @Inject constructor(
         val isInitialSync = token == null
         if (isInitialSync) {
             // We might want to get the user information in parallel too
-            val user = tryOrNull { session.getProfileAsUser(userId) }
+            val user = tryOrNull { session.profileService().getProfileAsUser(userId) }
             userStore.createOrUpdate(
                     userId = userId,
                     displayName = user?.displayName,
-                    avatarUrl = user?.avatarUrl)
+                    avatarUrl = user?.avatarUrl
+            )
             defaultSyncStatusService.startRoot(InitSyncStep.ImportingAccount, 100)
         }
         // Maybe refresh the homeserver capabilities data we know
@@ -124,7 +127,7 @@ internal class DefaultSyncTask @Inject constructor(
         if (isInitialSync) {
             Timber.tag(loggerTag.value).d("INIT_SYNC with filter: ${requestParams["filter"]}")
             val initSyncStrategy = initialSyncStrategy
-            logDuration("INIT_SYNC strategy: $initSyncStrategy", loggerTag) {
+            logDuration("INIT_SYNC strategy: $initSyncStrategy", loggerTag, clock) {
                 if (initSyncStrategy is InitialSyncStrategy.Optimized) {
                     roomSyncEphemeralTemporaryStore.reset()
                     workingDir.mkdirs()
@@ -135,7 +138,7 @@ internal class DefaultSyncTask @Inject constructor(
                     // Delete all files
                     workingDir.deleteRecursively()
                 } else {
-                    val syncResponse = logDuration("INIT_SYNC Request", loggerTag) {
+                    val syncResponse = logDuration("INIT_SYNC Request", loggerTag, clock) {
                         executeRequest(globalErrorReceiver) {
                             syncAPI.sync(
                                     params = requestParams,
@@ -146,7 +149,7 @@ internal class DefaultSyncTask @Inject constructor(
                     // We cannot distinguish request and download in this case.
                     syncStatisticsData.requestInitSyncTime = SystemClock.elapsedRealtime()
                     syncStatisticsData.downloadInitSyncTime = syncStatisticsData.requestInitSyncTime
-                    logDuration("INIT_SYNC Database insertion", loggerTag) {
+                    logDuration("INIT_SYNC Database insertion", loggerTag, clock) {
                         syncResponseHandler.handleResponse(syncResponse, token, defaultSyncStatusService)
                     }
                     syncResponseToReturn = syncResponse
@@ -172,12 +175,14 @@ internal class DefaultSyncTask @Inject constructor(
             val nbToDevice = syncResponse.toDevice?.events.orEmpty().size
             val nextBatch = syncResponse.nextBatch
             Timber.tag(loggerTag.value).d(
-                "Incremental sync request parsing, $nbRooms room(s) $nbToDevice toDevice(s). Got nextBatch: $nextBatch"
+                    "Incremental sync request parsing, $nbRooms room(s) $nbToDevice toDevice(s). Got nextBatch: $nextBatch"
+            )
+            defaultSyncStatusService.setStatus(
+                    SyncStatusService.Status.IncrementalSyncParsing(
+                            rooms = nbRooms,
+                            toDevice = nbToDevice
+                    )
             )
-            defaultSyncStatusService.setStatus(SyncStatusService.Status.IncrementalSyncParsing(
-                    rooms = nbRooms,
-                    toDevice = nbToDevice
-            ))
             syncResponseHandler.handleResponse(syncResponse, token, null)
             syncResponseToReturn = syncResponse
             Timber.tag(loggerTag.value).d("Incremental sync done")
@@ -201,14 +206,14 @@ internal class DefaultSyncTask @Inject constructor(
             }
         } else {
             initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_DOWNLOADING)
-            val syncResponse = logDuration("INIT_SYNC Perform server request", loggerTag) {
+            val syncResponse = logDuration("INIT_SYNC Perform server request", loggerTag, clock) {
                 reportSubtask(defaultSyncStatusService, InitSyncStep.ServerComputing, 1, 0.2f) {
                     getSyncResponse(requestParams, MAX_NUMBER_OF_RETRY_AFTER_TIMEOUT)
                 }
             }
             syncStatisticsData.requestInitSyncTime = SystemClock.elapsedRealtime()
             if (syncResponse.isSuccessful) {
-                logDuration("INIT_SYNC Download and save to file", loggerTag) {
+                logDuration("INIT_SYNC Download and save to file", loggerTag, clock) {
                     reportSubtask(defaultSyncStatusService, InitSyncStep.Downloading, 1, 0.1f) {
                         syncResponse.body()?.byteStream()?.use { inputStream ->
                             workingFile.outputStream().use { outputStream ->
@@ -247,8 +252,8 @@ internal class DefaultSyncTask @Inject constructor(
     }
 
     private suspend fun handleSyncFile(workingFile: File, initSyncStrategy: InitialSyncStrategy.Optimized): SyncResponse {
-        return logDuration("INIT_SYNC handleSyncFile()", loggerTag) {
-            val syncResponse = logDuration("INIT_SYNC Read file and parse", loggerTag) {
+        return logDuration("INIT_SYNC handleSyncFile()", loggerTag, clock) {
+            val syncResponse = logDuration("INIT_SYNC Read file and parse", loggerTag, clock) {
                 syncResponseParser.parse(initSyncStrategy, workingFile)
             }
             initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_PARSED)
@@ -257,7 +262,7 @@ internal class DefaultSyncTask @Inject constructor(
             val nbOfJoinedRoomsInFile = syncResponse.rooms?.join?.values?.count { it.ephemeral is LazyRoomSyncEphemeral.Stored }
             Timber.tag(loggerTag.value).d("INIT_SYNC $nbOfJoinedRooms rooms, $nbOfJoinedRoomsInFile ephemeral stored into files")
 
-            logDuration("INIT_SYNC Database insertion", loggerTag) {
+            logDuration("INIT_SYNC Database insertion", loggerTag, clock) {
                 syncResponseHandler.handleResponse(syncResponse, null, defaultSyncStatusService)
             }
             initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_SUCCESS)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UserAccountDataSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UserAccountDataSyncHandler.kt
index 7f80486c..c213ea4b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UserAccountDataSyncHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UserAccountDataSyncHandler.kt
@@ -20,17 +20,19 @@ import com.zhuinden.monarchy.Monarchy
 import io.realm.Realm
 import io.realm.RealmList
 import io.realm.kotlin.where
-import org.matrix.android.sdk.api.pushrules.RuleScope
-import org.matrix.android.sdk.api.pushrules.RuleSetKey
-import org.matrix.android.sdk.api.pushrules.rest.GetPushRulesResponse
+import org.matrix.android.sdk.api.failure.GlobalError
+import org.matrix.android.sdk.api.failure.InitialSyncRequestReason
 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
 import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.pushrules.RuleScope
+import org.matrix.android.sdk.api.session.pushrules.RuleSetKey
 import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
 import org.matrix.android.sdk.api.session.sync.model.InvitedRoomSync
 import org.matrix.android.sdk.api.session.sync.model.UserAccountDataSync
+import org.matrix.android.sdk.internal.SessionManager
 import org.matrix.android.sdk.internal.database.mapper.ContentMapper
 import org.matrix.android.sdk.internal.database.mapper.PushRulesMapper
 import org.matrix.android.sdk.internal.database.mapper.asDomain
@@ -39,14 +41,20 @@ import org.matrix.android.sdk.internal.database.model.IgnoredUserEntity
 import org.matrix.android.sdk.internal.database.model.PushRulesEntity
 import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
 import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
+import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
 import org.matrix.android.sdk.internal.database.model.UserAccountDataEntity
 import org.matrix.android.sdk.internal.database.model.UserAccountDataEntityFields
 import org.matrix.android.sdk.internal.database.model.deleteOnCascade
+import org.matrix.android.sdk.internal.database.query.findAllFrom
 import org.matrix.android.sdk.internal.database.query.getDirectRooms
 import org.matrix.android.sdk.internal.database.query.getOrCreate
 import org.matrix.android.sdk.internal.database.query.where
 import org.matrix.android.sdk.internal.di.SessionDatabase
+import org.matrix.android.sdk.internal.di.SessionId
 import org.matrix.android.sdk.internal.di.UserId
+import org.matrix.android.sdk.internal.session.SessionListeners
+import org.matrix.android.sdk.internal.session.dispatchTo
+import org.matrix.android.sdk.internal.session.pushers.GetPushRulesResponse
 import org.matrix.android.sdk.internal.session.room.RoomAvatarResolver
 import org.matrix.android.sdk.internal.session.room.membership.RoomDisplayNameResolver
 import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
@@ -65,7 +73,10 @@ internal class UserAccountDataSyncHandler @Inject constructor(
         private val directChatsHelper: DirectChatsHelper,
         private val updateUserAccountDataTask: UpdateUserAccountDataTask,
         private val roomAvatarResolver: RoomAvatarResolver,
-        private val roomDisplayNameResolver: RoomDisplayNameResolver
+        private val roomDisplayNameResolver: RoomDisplayNameResolver,
+        @SessionId private val sessionId: String,
+        private val sessionManager: SessionManager,
+        private val sessionListeners: SessionListeners
 ) {
 
     fun handle(realm: Realm, accountData: UserAccountDataSync?) {
@@ -184,12 +195,36 @@ internal class UserAccountDataSyncHandler @Inject constructor(
 
     private fun handleIgnoredUsers(realm: Realm, event: UserAccountDataEvent) {
         val userIds = event.content.toModel<IgnoredUsersContent>()?.ignoredUsers?.keys ?: return
-        realm.where(IgnoredUserEntity::class.java)
-                .findAll()
-                .deleteAllFromRealm()
+        val currentIgnoredUsers = realm.where(IgnoredUserEntity::class.java).findAll()
+        val currentIgnoredUserIds = currentIgnoredUsers.map { it.userId }
+        // Delete the previous list
+        currentIgnoredUsers.deleteAllFromRealm()
         // And save the new received list
         userIds.forEach { realm.createObject(IgnoredUserEntity::class.java).apply { userId = it } }
-        // TODO If not initial sync, we should execute a init sync
+
+        // Delete all the TimelineEvents for all the ignored users
+        // See https://spec.matrix.org/latest/client-server-api/#client-behaviour-22 :
+        // "Once ignored, the client will no longer receive events sent by that user, with the exception of state events"
+        // So just delete all non-state events from our local storage.
+        TimelineEventEntity.findAllFrom(realm, userIds)
+                .also { Timber.d("Deleting ${it.size} TimelineEventEntity from ignored users") }
+                .forEach {
+                    it.deleteOnCascade(true)
+                }
+
+        // Handle the case when some users are unignored from another session
+        val mustRefreshCache = currentIgnoredUserIds.any { currentIgnoredUserId -> currentIgnoredUserId !in userIds }
+        if (mustRefreshCache) {
+            Timber.d("A user has been unignored from another session, an initial sync should be performed")
+            dispatchMustRefresh()
+        }
+    }
+
+    private fun dispatchMustRefresh() {
+        val session = sessionManager.getSessionComponent(sessionId)?.session()
+        session.dispatchTo(sessionListeners) { safeSession, listener ->
+            listener.onGlobalError(safeSession, GlobalError.InitialSyncRequest(InitialSyncRequestReason.IGNORED_USERS_LIST_CHANGE))
+        }
     }
 
     private fun handleBreadcrumbs(realm: Realm, event: UserAccountDataEvent) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ReadReceiptHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ReadReceiptHandler.kt
index 2c84f1e6..77bee18d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ReadReceiptHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ReadReceiptHandler.kt
@@ -44,12 +44,14 @@ internal class ReadReceiptHandler @Inject constructor(
 
     companion object {
 
-        fun createContent(userId: String, eventId: String): ReadReceiptContent {
+        fun createContent(userId: String,
+                          eventId: String,
+                          currentTimeMillis: Long): ReadReceiptContent {
             return mapOf(
                     eventId to mapOf(
                             READ_KEY to mapOf(
                                     userId to mapOf(
-                                            TIMESTAMP_KEY to System.currentTimeMillis().toDouble()
+                                            TIMESTAMP_KEY to currentTimeMillis.toDouble()
                                     )
                             )
                     )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
index afd8e1bb..5437a015 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
@@ -79,22 +79,26 @@ import org.matrix.android.sdk.internal.session.room.typing.TypingEventContent
 import org.matrix.android.sdk.internal.session.sync.SyncResponsePostTreatmentAggregator
 import org.matrix.android.sdk.internal.session.sync.parsing.RoomSyncAccountDataHandler
 import org.matrix.android.sdk.internal.util.computeBestChunkSize
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 import javax.inject.Inject
 
-internal class RoomSyncHandler @Inject constructor(private val readReceiptHandler: ReadReceiptHandler,
-                                                   private val roomSummaryUpdater: RoomSummaryUpdater,
-                                                   private val roomAccountDataHandler: RoomSyncAccountDataHandler,
-                                                   private val cryptoService: DefaultCryptoService,
-                                                   private val roomMemberEventHandler: RoomMemberEventHandler,
-                                                   private val roomTypingUsersHandler: RoomTypingUsersHandler,
-                                                   private val threadsAwarenessHandler: ThreadsAwarenessHandler,
-                                                   private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
-                                                   @UserId private val userId: String,
-                                                   private val homeServerCapabilitiesService: HomeServerCapabilitiesService,
-                                                   private val lightweightSettingsStorage: LightweightSettingsStorage,
-                                                   private val timelineInput: TimelineInput,
-                                                   private val liveEventService: Lazy<StreamEventsManager>) {
+internal class RoomSyncHandler @Inject constructor(
+        private val readReceiptHandler: ReadReceiptHandler,
+        private val roomSummaryUpdater: RoomSummaryUpdater,
+        private val roomAccountDataHandler: RoomSyncAccountDataHandler,
+        private val cryptoService: DefaultCryptoService,
+        private val roomMemberEventHandler: RoomMemberEventHandler,
+        private val roomTypingUsersHandler: RoomTypingUsersHandler,
+        private val threadsAwarenessHandler: ThreadsAwarenessHandler,
+        private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
+        @UserId private val userId: String,
+        private val homeServerCapabilitiesService: HomeServerCapabilitiesService,
+        private val lightweightSettingsStorage: LightweightSettingsStorage,
+        private val timelineInput: TimelineInput,
+        private val liveEventService: Lazy<StreamEventsManager>,
+        private val clock: Clock,
+) {
 
     sealed class HandlingStrategy {
         data class JOINED(val data: Map<String, RoomSync>) : HandlingStrategy()
@@ -102,11 +106,11 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
         data class LEFT(val data: Map<String, RoomSync>) : HandlingStrategy()
     }
 
-    suspend fun handle(realm: Realm,
-                       roomsSyncResponse: RoomsSyncResponse,
-                       isInitialSync: Boolean,
-                       aggregator: SyncResponsePostTreatmentAggregator,
-                       reporter: ProgressReporter? = null) {
+    fun handle(realm: Realm,
+               roomsSyncResponse: RoomsSyncResponse,
+               isInitialSync: Boolean,
+               aggregator: SyncResponsePostTreatmentAggregator,
+               reporter: ProgressReporter? = null) {
         handleRoomSync(realm, HandlingStrategy.JOINED(roomsSyncResponse.join), isInitialSync, aggregator, reporter)
         handleRoomSync(realm, HandlingStrategy.INVITED(roomsSyncResponse.invite), isInitialSync, aggregator, reporter)
         handleRoomSync(realm, HandlingStrategy.LEFT(roomsSyncResponse.leave), isInitialSync, aggregator, reporter)
@@ -120,17 +124,17 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
     }
     // PRIVATE METHODS *****************************************************************************
 
-    private suspend fun handleRoomSync(realm: Realm,
-                                       handlingStrategy: HandlingStrategy,
-                                       isInitialSync: Boolean,
-                                       aggregator: SyncResponsePostTreatmentAggregator,
-                                       reporter: ProgressReporter?) {
+    private fun handleRoomSync(realm: Realm,
+                               handlingStrategy: HandlingStrategy,
+                               isInitialSync: Boolean,
+                               aggregator: SyncResponsePostTreatmentAggregator,
+                               reporter: ProgressReporter?) {
         val insertType = if (isInitialSync) {
             EventInsertType.INITIAL_SYNC
         } else {
             EventInsertType.INCREMENTAL_SYNC
         }
-        val syncLocalTimeStampMillis = System.currentTimeMillis()
+        val syncLocalTimeStampMillis = clock.epochMillis()
         val rooms = when (handlingStrategy) {
             is HandlingStrategy.JOINED  -> {
                 if (isInitialSync && initialSyncStrategy is InitialSyncStrategy.Optimized) {
@@ -157,11 +161,11 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
         realm.insertOrUpdate(rooms)
     }
 
-    private suspend fun insertJoinRoomsFromInitSync(realm: Realm,
-                                                    handlingStrategy: HandlingStrategy.JOINED,
-                                                    syncLocalTimeStampMillis: Long,
-                                                    aggregator: SyncResponsePostTreatmentAggregator,
-                                                    reporter: ProgressReporter?) {
+    private fun insertJoinRoomsFromInitSync(realm: Realm,
+                                            handlingStrategy: HandlingStrategy.JOINED,
+                                            syncLocalTimeStampMillis: Long,
+                                            aggregator: SyncResponsePostTreatmentAggregator,
+                                            reporter: ProgressReporter?) {
         val bestChunkSize = computeBestChunkSize(
                 listSize = handlingStrategy.data.keys.size,
                 limit = (initialSyncStrategy as? InitialSyncStrategy.Optimized)?.maxRoomsToInsert ?: Int.MAX_VALUE
@@ -199,12 +203,12 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
         }
     }
 
-    private suspend fun handleJoinedRoom(realm: Realm,
-                                         roomId: String,
-                                         roomSync: RoomSync,
-                                         insertType: EventInsertType,
-                                         syncLocalTimestampMillis: Long,
-                                         aggregator: SyncResponsePostTreatmentAggregator): RoomEntity {
+    private fun handleJoinedRoom(realm: Realm,
+                                 roomId: String,
+                                 roomSync: RoomSync,
+                                 insertType: EventInsertType,
+                                 syncLocalTimestampMillis: Long,
+                                 aggregator: SyncResponsePostTreatmentAggregator): RoomEntity {
         Timber.v("Handle join sync for room $roomId")
         val isInitialSync = insertType == EventInsertType.INITIAL_SYNC
 
@@ -353,15 +357,15 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
         return roomEntity
     }
 
-    private suspend fun handleTimelineEvents(realm: Realm,
-                                             roomId: String,
-                                             roomEntity: RoomEntity,
-                                             eventList: List<Event>,
-                                             prevToken: String? = null,
-                                             isLimited: Boolean = true,
-                                             insertType: EventInsertType,
-                                             syncLocalTimestampMillis: Long,
-                                             aggregator: SyncResponsePostTreatmentAggregator): ChunkEntity {
+    private fun handleTimelineEvents(realm: Realm,
+                                     roomId: String,
+                                     roomEntity: RoomEntity,
+                                     eventList: List<Event>,
+                                     prevToken: String? = null,
+                                     isLimited: Boolean = true,
+                                     insertType: EventInsertType,
+                                     syncLocalTimestampMillis: Long,
+                                     aggregator: SyncResponsePostTreatmentAggregator): ChunkEntity {
         val lastChunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomEntity.roomId)
         if (isLimited && lastChunk != null) {
             lastChunk.deleteOnCascade(deleteStateEvents = false, canDeleteRoot = true)
@@ -389,8 +393,10 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
             liveEventService.get().dispatchLiveEventReceived(event, roomId, isInitialSync)
 
             if (event.isEncrypted() && !isInitialSync) {
-                runBlocking {
+                try {
                     decryptIfNeeded(event, roomId)
+                } catch (e: InterruptedException) {
+                    Timber.i("Decryption got interrupted")
                 }
             }
             var contentToInject: String? = null
@@ -421,7 +427,8 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
                     roomId = roomId,
                     eventEntity = eventEntity,
                     direction = PaginationDirection.FORWARDS,
-                    roomMemberContentsByUser = roomMemberContentsByUser)
+                    roomMemberContentsByUser = roomMemberContentsByUser
+            )
             if (lightweightSettingsStorage.areThreadMessagesEnabled()) {
                 eventEntity.rootThreadEventId?.let {
                     // This is a thread event
@@ -437,7 +444,9 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
                                 threadEventEntity = eventEntity,
                                 roomMemberContentsByUser = roomMemberContentsByUser,
                                 userId = userId,
-                                roomEntity = roomEntity)
+                                roomEntity = roomEntity,
+                                currentTimeMillis = clock.epochMillis(),
+                        )
                     }
                 } ?: run {
                     // This is a normal event or a root thread one
@@ -475,7 +484,8 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
                     roomId = roomId,
                     realm = realm,
                     chunkEntity = chunkEntity,
-                    currentUserId = userId)
+                    currentUserId = userId
+            )
         }
 
         // posting new events to timeline if any is registered
@@ -505,10 +515,11 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
         }
     }
 
-    private suspend fun decryptIfNeeded(event: Event, roomId: String) {
+    private fun decryptIfNeeded(event: Event, roomId: String) {
         try {
             // Event from sync does not have roomId, so add it to the event first
-            val result = cryptoService.decryptEvent(event.copy(roomId = roomId), "")
+            // note: runBlocking should be used here while we are in realm single thread executor, to avoid thread switching
+            val result = runBlocking { cryptoService.decryptEvent(event.copy(roomId = roomId), "") }
             event.mxDecryptionResult = OlmDecryptionResult(
                     payload = result.clearEvent,
                     senderKey = result.senderCurve25519Key,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt
index c67c0e35..f183c4cb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt
@@ -42,7 +42,7 @@ private const val DEFAULT_DELAY_MILLIS = 30_000L
  * Possible next worker    : None
  */
 internal class SyncWorker(context: Context, workerParameters: WorkerParameters, sessionManager: SessionManager) :
-    SessionSafeCoroutineWorker<SyncWorker.Params>(context, workerParameters, sessionManager, Params::class.java) {
+        SessionSafeCoroutineWorker<SyncWorker.Params>(context, workerParameters, sessionManager, Params::class.java) {
 
     @JsonClass(generateAdapter = true)
     internal data class Params(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/DefaultThirdPartyService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/DefaultThirdPartyService.kt
index fdd5524f..210cb192 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/DefaultThirdPartyService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/DefaultThirdPartyService.kt
@@ -23,7 +23,7 @@ import javax.inject.Inject
 
 internal class DefaultThirdPartyService @Inject constructor(private val getThirdPartyProtocolTask: GetThirdPartyProtocolsTask,
                                                             private val getThirdPartyUserTask: GetThirdPartyUserTask) :
-    ThirdPartyService {
+        ThirdPartyService {
 
     override suspend fun getThirdPartyProtocols(): Map<String, ThirdPartyProtocol> {
         return getThirdPartyProtocolTask.execute(Unit)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserModule.kt
index 4dfc7586..c205c4f1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserModule.kt
@@ -21,9 +21,7 @@ import dagger.Module
 import dagger.Provides
 import org.matrix.android.sdk.api.session.user.UserService
 import org.matrix.android.sdk.internal.session.SessionScope
-import org.matrix.android.sdk.internal.session.user.accountdata.DefaultSaveIgnoredUsersTask
 import org.matrix.android.sdk.internal.session.user.accountdata.DefaultUpdateIgnoredUserIdsTask
-import org.matrix.android.sdk.internal.session.user.accountdata.SaveIgnoredUsersTask
 import org.matrix.android.sdk.internal.session.user.accountdata.UpdateIgnoredUserIdsTask
 import org.matrix.android.sdk.internal.session.user.model.DefaultSearchUserTask
 import org.matrix.android.sdk.internal.session.user.model.SearchUserTask
@@ -48,9 +46,6 @@ internal abstract class UserModule {
     @Binds
     abstract fun bindSearchUserTask(task: DefaultSearchUserTask): SearchUserTask
 
-    @Binds
-    abstract fun bindSaveIgnoredUsersTask(task: DefaultSaveIgnoredUsersTask): SaveIgnoredUsersTask
-
     @Binds
     abstract fun bindUpdateIgnoredUserIdsTask(task: DefaultUpdateIgnoredUserIdsTask): UpdateIgnoredUserIdsTask
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/SaveIgnoredUsersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/SaveIgnoredUsersTask.kt
deleted file mode 100644
index 63c0ce64..00000000
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/SaveIgnoredUsersTask.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2020 The Matrix.org Foundation C.I.C.
- *
- * 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 org.matrix.android.sdk.internal.session.user.accountdata
-
-import com.zhuinden.monarchy.Monarchy
-import org.matrix.android.sdk.internal.database.model.IgnoredUserEntity
-import org.matrix.android.sdk.internal.di.SessionDatabase
-import org.matrix.android.sdk.internal.task.Task
-import org.matrix.android.sdk.internal.util.awaitTransaction
-import javax.inject.Inject
-
-/**
- * Save the ignored users list in DB
- */
-internal interface SaveIgnoredUsersTask : Task<SaveIgnoredUsersTask.Params, Unit> {
-    data class Params(
-            val userIds: List<String>
-    )
-}
-
-internal class DefaultSaveIgnoredUsersTask @Inject constructor(@SessionDatabase private val monarchy: Monarchy) : SaveIgnoredUsersTask {
-
-    override suspend fun execute(params: SaveIgnoredUsersTask.Params) {
-        monarchy.awaitTransaction { realm ->
-            // clear current ignored users
-            realm.where(IgnoredUserEntity::class.java)
-                    .findAll()
-                    .deleteAllFromRealm()
-
-            // And save the new received list
-            params.userIds.forEach { realm.createObject(IgnoredUserEntity::class.java).apply { userId = it } }
-        }
-    }
-}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt
index 445b7810..173161f8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt
@@ -38,7 +38,6 @@ internal interface UpdateIgnoredUserIdsTask : Task<UpdateIgnoredUserIdsTask.Para
 internal class DefaultUpdateIgnoredUserIdsTask @Inject constructor(
         private val accountDataApi: AccountDataAPI,
         @SessionDatabase private val monarchy: Monarchy,
-        private val saveIgnoredUsersTask: SaveIgnoredUsersTask,
         @UserId private val userId: String,
         private val globalErrorReceiver: GlobalErrorReceiver
 ) : UpdateIgnoredUserIdsTask {
@@ -66,8 +65,5 @@ internal class DefaultUpdateIgnoredUserIdsTask @Inject constructor(
         executeRequest(globalErrorReceiver) {
             accountDataApi.setAccountData(userId, UserAccountDataTypes.TYPE_IGNORED_USER_LIST, body)
         }
-
-        // Update the DB right now (do not wait for the sync to come back with updated data, for a faster UI update)
-        saveIgnoredUsersTask.execute(SaveIgnoredUsersTask.Params(list))
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/CreateWidgetTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/CreateWidgetTask.kt
index 18a043be..06508def 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/CreateWidgetTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/CreateWidgetTask.kt
@@ -29,9 +29,10 @@ import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.room.RoomAPI
 import org.matrix.android.sdk.internal.task.Task
+import timber.log.Timber
 import javax.inject.Inject
 
-internal interface CreateWidgetTask : Task<CreateWidgetTask.Params, Unit> {
+internal interface CreateWidgetTask : Task<CreateWidgetTask.Params, String> {
 
     data class Params(
             val roomId: String,
@@ -45,8 +46,8 @@ internal class DefaultCreateWidgetTask @Inject constructor(@SessionDatabase priv
                                                            @UserId private val userId: String,
                                                            private val globalErrorReceiver: GlobalErrorReceiver) : CreateWidgetTask {
 
-    override suspend fun execute(params: CreateWidgetTask.Params) {
-        executeRequest(globalErrorReceiver) {
+    override suspend fun execute(params: CreateWidgetTask.Params): String {
+        val response = executeRequest(globalErrorReceiver) {
             roomAPI.sendStateEvent(
                     roomId = params.roomId,
                     stateEventType = EventType.STATE_ROOM_WIDGET_LEGACY,
@@ -60,5 +61,8 @@ internal class DefaultCreateWidgetTask @Inject constructor(@SessionDatabase priv
                     .and()
                     .equalTo(CurrentStateEventEntityFields.ROOT.SENDER, userId)
         }
+        return response.eventId.also {
+            Timber.d("Widget state event: $it just sent in room ${params.roomId}")
+        }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetPostAPIMediator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetPostAPIMediator.kt
index 10b4f7f7..9f42688f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetPostAPIMediator.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetPostAPIMediator.kt
@@ -29,7 +29,7 @@ import javax.inject.Inject
 
 internal class DefaultWidgetPostAPIMediator @Inject constructor(private val moshi: Moshi,
                                                                 private val widgetPostMessageAPIProvider: WidgetPostMessageAPIProvider) :
-    WidgetPostAPIMediator {
+        WidgetPostAPIMediator {
 
     private val jsonAdapter = moshi.adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE)
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetService.kt
index 89e827ae..53a435d2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetService.kt
@@ -29,7 +29,7 @@ import javax.inject.Provider
 internal class DefaultWidgetService @Inject constructor(private val widgetManager: WidgetManager,
                                                         private val widgetURLFormatter: WidgetURLFormatter,
                                                         private val widgetPostAPIMediator: Provider<WidgetPostAPIMediator>) :
-    WidgetService {
+        WidgetService {
 
     override fun getWidgetURLFormatter(): WidgetURLFormatter {
         return widgetURLFormatter
@@ -52,7 +52,7 @@ internal class DefaultWidgetService @Inject constructor(private val widgetManage
         return widgetManager.getWidgetComputedUrl(widget, isLightTheme)
     }
 
-override fun getRoomWidgetsLive(
+    override fun getRoomWidgetsLive(
             roomId: String,
             widgetId: QueryStringValue,
             widgetTypes: Set<String>?,
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 e18117d2..07ed91c1 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
@@ -52,7 +52,7 @@ internal class WidgetManager @Inject constructor(private val integrationManager:
                                                  private val widgetFactory: WidgetFactory,
                                                  @UserId private val userId: String) :
 
-    IntegrationManagerService.Listener, SessionLifecycleObserver {
+        IntegrationManagerService.Listener, SessionLifecycleObserver {
 
     private val lifecycleOwner: LifecycleOwner = LifecycleOwner { lifecycleRegistry }
     private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(lifecycleOwner)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/LogUtil.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/LogUtil.kt
index 6fd907d3..ffad0b85 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/LogUtil.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/LogUtil.kt
@@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.util
 
 import org.matrix.android.sdk.BuildConfig
 import org.matrix.android.sdk.api.logger.LoggerTag
+import org.matrix.android.sdk.internal.util.time.Clock
 import timber.log.Timber
 
 internal fun <T> Collection<T>.logLimit(maxQuantity: Int = 5): String {
@@ -34,13 +35,14 @@ internal fun <T> Collection<T>.logLimit(maxQuantity: Int = 5): String {
 
 internal suspend fun <T> logDuration(message: String,
                                      loggerTag: LoggerTag,
+                                     clock: Clock,
                                      block: suspend () -> T): T {
     Timber.tag(loggerTag.value).d("$message -- BEGIN")
-    val start = System.currentTimeMillis()
+    val start = clock.epochMillis()
     val result = logRamUsage(message) {
         block()
     }
-    val duration = System.currentTimeMillis() - start
+    val duration = clock.epochMillis() - start
     Timber.tag(loggerTag.value).d("$message -- END duration: $duration ms")
 
     return result
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/TemporaryFileCreator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/TemporaryFileCreator.kt
index 2790ffba..dddd7892 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/TemporaryFileCreator.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/TemporaryFileCreator.kt
@@ -29,6 +29,7 @@ internal class TemporaryFileCreator @Inject constructor(
     suspend fun create(): File {
         return withContext(Dispatchers.IO) {
             File.createTempFile(UUID.randomUUID().toString(), null, context.cacheDir)
+                    .apply { mkdirs() }
         }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/database/RealmMigrator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/database/RealmMigrator.kt
index f22f0810..8f3c89f2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/database/RealmMigrator.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/database/RealmMigrator.kt
@@ -21,7 +21,7 @@ import io.realm.RealmObjectSchema
 import timber.log.Timber
 
 internal abstract class RealmMigrator(private val realm: DynamicRealm,
-                             private val targetSchemaVersion: Int) {
+                                      private val targetSchemaVersion: Int) {
     fun perform() {
         Timber.d("Migrate ${realm.configuration.realmFileName} to $targetSchemaVersion")
         doMigrate(realm)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/DefaultBuildVersionSdkIntProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/DefaultBuildVersionSdkIntProvider.kt
index 2d5e4b94..806c6e97 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/DefaultBuildVersionSdkIntProvider.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/DefaultBuildVersionSdkIntProvider.kt
@@ -20,6 +20,6 @@ import android.os.Build
 import javax.inject.Inject
 
 internal class DefaultBuildVersionSdkIntProvider @Inject constructor() :
-    BuildVersionSdkIntProvider {
+        BuildVersionSdkIntProvider {
     override fun get() = Build.VERSION.SDK_INT
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/SystemModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/SystemModule.kt
index 8a7b5017..396d12f3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/SystemModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/SystemModule.kt
@@ -18,10 +18,15 @@ package org.matrix.android.sdk.internal.util.system
 
 import dagger.Binds
 import dagger.Module
+import org.matrix.android.sdk.internal.util.time.Clock
+import org.matrix.android.sdk.internal.util.time.DefaultClock
 
 @Module
 internal abstract class SystemModule {
 
     @Binds
     abstract fun bindBuildVersionSdkIntProvider(provider: DefaultBuildVersionSdkIntProvider): BuildVersionSdkIntProvider
+
+    @Binds
+    abstract fun bindClock(clock: DefaultClock): Clock
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/time/Clock.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/time/Clock.kt
new file mode 100644
index 00000000..4fe9069b
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/time/Clock.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * 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 org.matrix.android.sdk.internal.util.time
+
+import javax.inject.Inject
+
+internal interface Clock {
+    fun epochMillis(): Long
+}
+
+internal class DefaultClock @Inject constructor() : Clock {
+
+    /**
+     * Provides a UTC epoch in milliseconds
+     *
+     * This value is not guaranteed to be correct with reality
+     * as a User can override the system time and date to any values.
+     */
+    override fun epochMillis(): Long {
+        return System.currentTimeMillis()
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/AlwaysSuccessfulWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/AlwaysSuccessfulWorker.kt
index 856d3deb..c92b51fc 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/AlwaysSuccessfulWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/AlwaysSuccessfulWorker.kt
@@ -20,7 +20,7 @@ import androidx.work.Worker
 import androidx.work.WorkerParameters
 
 internal class AlwaysSuccessfulWorker(context: Context, params: WorkerParameters) :
-    Worker(context, params) {
+        Worker(context, params) {
 
     override fun doWork(): Result {
         return Result.success()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/MatrixWorkerFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/MatrixWorkerFactory.kt
index 0b451e9c..e56b359f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/MatrixWorkerFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/MatrixWorkerFactory.kt
@@ -93,7 +93,7 @@ internal class MatrixWorkerFactory @Inject constructor(private val sessionManage
     class CheckFactoryWorker(context: Context,
                              workerParameters: WorkerParameters,
                              private val isCreatedByMatrixWorkerFactory: Boolean) :
-        CoroutineWorker(context, workerParameters) {
+            CoroutineWorker(context, workerParameters) {
 
         // Called by WorkManager if there is no MatrixWorkerFactory
         constructor(context: Context, workerParameters: WorkerParameters) : this(context,
diff --git a/matrix-sdk-android/src/release/java/org/matrix/android/sdk/internal/network/interceptors/CurlLoggingInterceptor.kt b/matrix-sdk-android/src/release/java/org/matrix/android/sdk/internal/network/interceptors/CurlLoggingInterceptor.kt
index 47f68694..0b7bac0d 100644
--- a/matrix-sdk-android/src/release/java/org/matrix/android/sdk/internal/network/interceptors/CurlLoggingInterceptor.kt
+++ b/matrix-sdk-android/src/release/java/org/matrix/android/sdk/internal/network/interceptors/CurlLoggingInterceptor.kt
@@ -27,7 +27,7 @@ import javax.inject.Inject
  */
 @MatrixScope
 internal class CurlLoggingInterceptor @Inject constructor() :
-    Interceptor {
+        Interceptor {
 
     @Throws(IOException::class)
     override fun intercept(chain: Interceptor.Chain): Response {
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/pushrules/PushRuleActionsTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/session/pushrules/PushRuleActionsTest.kt
similarity index 95%
rename from matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/pushrules/PushRuleActionsTest.kt
rename to matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/session/pushrules/PushRuleActionsTest.kt
index 9bfdea54..1879e819 100644
--- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/pushrules/PushRuleActionsTest.kt
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/session/pushrules/PushRuleActionsTest.kt
@@ -14,14 +14,14 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertNotNull
 import org.junit.Assert.assertTrue
 import org.junit.Test
 import org.matrix.android.sdk.MatrixTest
-import org.matrix.android.sdk.api.pushrules.rest.PushRule
+import org.matrix.android.sdk.api.session.pushrules.rest.PushRule
 import org.matrix.android.sdk.internal.di.MoshiProvider
 
 class PushRuleActionsTest : MatrixTest {
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/pushrules/PushRulesConditionTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/session/pushrules/PushRulesConditionTest.kt
similarity index 95%
rename from matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/pushrules/PushRulesConditionTest.kt
rename to matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/session/pushrules/PushRulesConditionTest.kt
index c0b869a9..95787173 100644
--- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/pushrules/PushRulesConditionTest.kt
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/session/pushrules/PushRulesConditionTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.api.pushrules
+package org.matrix.android.sdk.api.session.pushrules
 
 import io.mockk.every
 import io.mockk.mockk
@@ -26,6 +26,7 @@ import org.matrix.android.sdk.MatrixTest
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.toContent
 import org.matrix.android.sdk.api.session.room.Room
+import org.matrix.android.sdk.api.session.room.members.MembershipService
 import org.matrix.android.sdk.api.session.room.model.Membership
 import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
@@ -148,14 +149,22 @@ class PushRulesConditionTest : MatrixTest {
         val room2JoinedId = "2joined"
         val room3JoinedId = "3joined"
 
-        val roomStub2Joined = mockk<Room> {
+        val roomMembershipService2 = mockk<MembershipService> {
             every { getNumberOfJoinedMembers() } returns 2
         }
 
-        val roomStub3Joined = mockk<Room> {
+        val roomMembershipService3 = mockk<MembershipService> {
             every { getNumberOfJoinedMembers() } returns 3
         }
 
+        val roomStub2Joined = mockk<Room> {
+            every { membershipService() } returns roomMembershipService2
+        }
+
+        val roomStub3Joined = mockk<Room> {
+            every { membershipService() } returns roomMembershipService3
+        }
+
         val roomGetterStub = mockk<RoomGetter> {
             every { getRoom(room2JoinedId) } returns roomStub2Joined
             every { getRoom(room3JoinedId) } returns roomStub3Joined
-- 
GitLab