From 29cd550214657342d913bc15e103cbb8b254a148 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Mon, 27 Sep 2021 14:59:41 +0200
Subject: [PATCH] Import v1.3.0 from Element Android

---
 dependencies.gradle                           | 130 ++++++++++++++
 matrix-sdk-android/build.gradle               | 105 +++++------
 .../java/org/matrix/android/sdk/api/Matrix.kt |   2 +-
 .../android/sdk/common/CommonTestHelper.kt    |  18 +-
 .../sdk/session/space/SpaceHierarchyTest.kt   | 168 ++++++++++++++++++
 .../java/org/matrix/android/sdk/api/Matrix.kt |   2 +-
 .../android/sdk/api/logger/LoggerTag.kt       |   1 +
 .../matrix/android/sdk/api/session/Session.kt |   4 +-
 .../sdk/api/session/events/model/Event.kt     |  12 +-
 ...rogressService.kt => SyncStatusService.kt} |  26 ++-
 .../android/sdk/api/session/pushers/Pusher.kt |   9 +-
 .../sdk/api/session/pushers/PushersService.kt |  41 ++++-
 .../session/room/model/RoomStrippedState.kt   | 111 ++++++++++++
 .../room/model/message/MessageAudioContent.kt |   2 +-
 .../room/model/message/MessageContent.kt      |   5 +
 .../model/message/MessageDefaultContent.kt    |   2 +-
 .../room/model/message/MessageEmoteContent.kt |   2 +-
 .../room/model/message/MessageFileContent.kt  |   2 +-
 .../room/model/message/MessageImageContent.kt |   2 +-
 .../model/message/MessageLocationContent.kt   |   2 +-
 .../model/message/MessageNoticeContent.kt     |   2 +-
 .../model/message/MessageOptionsContent.kt    |   2 +-
 .../message/MessagePollResponseContent.kt     |   2 +-
 .../room/model/message/MessageTextContent.kt  |   2 +-
 .../MessageVerificationRequestContent.kt      |   2 +-
 .../room/model/message/MessageVideoContent.kt |   2 +-
 .../sdk/api/session/space/SpaceService.kt     |   2 +
 .../crypto/InboundGroupSessionStore.kt        |  10 +-
 .../sdk/internal/crypto/MXOlmDevice.kt        |   5 +-
 .../sdk/internal/network/UserAgentHolder.kt   |   6 +-
 .../sdk/internal/session/DefaultSession.kt    |   6 +-
 .../sdk/internal/session/SessionComponent.kt  |   4 +-
 .../sdk/internal/session/SessionModule.kt     |   6 +-
 .../identity/DefaultIdentityService.kt        |  20 ++-
 ...Service.kt => DefaultSyncStatusService.kt} |  21 ++-
 ...HttpPusherWorker.kt => AddPusherWorker.kt} |   4 +-
 .../session/pushers/DefaultPushersService.kt  |  77 +++++---
 .../session/pushers/JsonPusherData.kt         |   5 +-
 .../session/room/GetRoomSummaryTask.kt        |  42 +++++
 .../sdk/internal/session/room/RoomAPI.kt      |  13 +-
 .../sdk/internal/session/room/RoomModule.kt   |   3 +
 .../session/room/peeking/PeekRoomTask.kt      |  21 +++
 .../room/summary/RoomSummaryUpdater.kt        | 139 ++++++++++-----
 .../internal/session/space/DefaultSpace.kt    |   1 -
 .../session/space/DefaultSpaceService.kt      |  22 ++-
 .../sdk/internal/session/sync/SyncTask.kt     |  83 +++++----
 .../internal/session/sync/job/SyncThread.kt   |  43 ++---
 .../android/sdk/internal/util/LogUtil.kt      |   6 +-
 48 files changed, 943 insertions(+), 254 deletions(-)
 create mode 100644 dependencies.gradle
 rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/initsync/{InitialSyncProgressService.kt => SyncStatusService.kt} (55%)
 create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomStrippedState.kt
 rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/initsync/{DefaultInitialSyncProgressService.kt => DefaultSyncStatusService.kt} (78%)
 rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/{AddHttpPusherWorker.kt => AddPusherWorker.kt} (95%)
 create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/GetRoomSummaryTask.kt

diff --git a/dependencies.gradle b/dependencies.gradle
new file mode 100644
index 00000000..a4e2c603
--- /dev/null
+++ b/dependencies.gradle
@@ -0,0 +1,130 @@
+ext.versions = [
+
+        'minSdk'            : 21,
+        'compileSdk'        : 30,
+        'targetSdk'         : 30,
+        'sourceCompat'      : JavaVersion.VERSION_11,
+        'targetCompat'      : JavaVersion.VERSION_11,
+]
+
+def gradle = "7.0.2"
+// Ref: https://kotlinlang.org/releases.html
+def kotlin = "1.5.30"
+def kotlinCoroutines = "1.5.1"
+def dagger = "2.38.1"
+def retrofit = "2.9.0"
+def arrow = "0.8.2"
+def markwon = "4.6.2"
+def moshi = "1.12.0"
+def lifecycle = "2.2.0"
+def rxBinding = "3.1.0"
+def epoxy = "4.6.2"
+def glide = "4.12.0"
+def bigImageViewer = "1.8.1"
+def jjwt = "0.11.2"
+
+// Testing
+def mockk = "1.12.0"
+def espresso = "3.4.0"
+def androidxTest = "1.4.0"
+
+
+ext.libs = [
+        gradle      : [
+                'gradlePlugin'            : "com.android.tools.build:gradle:$gradle",
+                'kotlinPlugin'            : "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin"
+        ],
+        jetbrains   : [
+                'kotlinStdlibJdk7'        : "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin",
+                'kotlinStdlib'            : "org.jetbrains.kotlin:kotlin-stdlib:$kotlin",
+                'coroutinesCore'          : "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutines",
+                'coroutinesAndroid'       : "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinCoroutines",
+                'coroutinesRx2'           : "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$kotlinCoroutines"
+        ],
+        androidx    : [
+                'appCompat'               : "androidx.appcompat:appcompat:1.3.1",
+                'core'                    : "androidx.core:core-ktx:1.6.0",
+                'recyclerview'            : "androidx.recyclerview:recyclerview:1.2.1",
+                'exifinterface'           : "androidx.exifinterface:exifinterface:1.3.3",
+                'fragmentKtx'             : "androidx.fragment:fragment-ktx:1.3.6",
+                'constraintLayout'        : "androidx.constraintlayout:constraintlayout:2.1.0",
+                'work'                    : "androidx.work:work-runtime-ktx:2.5.0",
+                'autoFill'                : "androidx.autofill:autofill:1.1.0",
+                'preferenceKtx'           : "androidx.preference:preference-ktx:1.1.1",
+                'junit'                   : "androidx.test.ext:junit:1.1.3",
+                'lifecycleExtensions'     : "androidx.lifecycle:lifecycle-extensions:$lifecycle",
+                'lifecycleJava8'          : "androidx.lifecycle:lifecycle-common-java8:$lifecycle",
+                'lifecycleLivedata'       : "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1",
+                'datastore'               : "androidx.datastore:datastore:1.0.0",
+                'datastorepreferences'    : "androidx.datastore:datastore-preferences:1.0.0",
+                'pagingRuntimeKtx'        : "androidx.paging:paging-runtime-ktx:2.1.2",
+                'coreTesting'             : "androidx.arch.core:core-testing:2.1.0",
+                'testCore'                : "androidx.test:core:$androidxTest",
+                'orchestrator'            : "androidx.test:orchestrator:$androidxTest",
+                'testRunner'              : "androidx.test:runner:$androidxTest",
+                'testRules'               : "androidx.test:rules:$androidxTest",
+                'espressoCore'            : "androidx.test.espresso:espresso-core:$espresso",
+                'espressoContrib'         : "androidx.test.espresso:espresso-contrib:$espresso",
+                'espressoIntents'         : "androidx.test.espresso:espresso-intents:$espresso"
+        ],
+        google      : [
+                'material'                : "com.google.android.material:material:1.4.0"
+        ],
+        dagger      : [
+                'dagger'                  : "com.google.dagger:dagger:$dagger",
+                'daggerCompiler'          : "com.google.dagger:dagger-compiler:$dagger"
+        ],
+        squareup    : [
+                'moshi'                  : "com.squareup.moshi:moshi-adapters:$moshi",
+                'moshiKotlin'            : "com.squareup.moshi:moshi-kotlin-codegen:$moshi",
+                'retrofit'               : "com.squareup.retrofit2:retrofit:$retrofit",
+                'retrofitMoshi'          : "com.squareup.retrofit2:converter-moshi:$retrofit"
+        ],
+        rx          : [
+                'rxKotlin'               : "io.reactivex.rxjava2:rxkotlin:2.4.0",
+                'rxAndroid'              : "io.reactivex.rxjava2:rxandroid:2.1.1"
+        ],
+        arrow       : [
+                'core'                   : "io.arrow-kt:arrow-core:$arrow",
+                'instances'              : "io.arrow-kt:arrow-instances-core:$arrow"
+        ],
+        markwon     : [
+                'core'                   : "io.noties.markwon:core:$markwon",
+                'html'                   : "io.noties.markwon:html:$markwon"
+        ],
+        airbnb      : [
+                'epoxy'                  : "com.airbnb.android:epoxy:$epoxy",
+                'epoxyGlide'             : "com.airbnb.android:epoxy-glide-preloading:$epoxy",
+                'epoxyProcessor'         : "com.airbnb.android:epoxy-processor:$epoxy",
+                'epoxyPaging'            : "com.airbnb.android:epoxy-paging:$epoxy",
+                'mvrx'                   : "com.airbnb.android:mvrx:1.5.1"
+        ],
+        mockk      : [
+                'mockk'                   : "io.mockk:mockk:$mockk",
+                'mockkAndroid'            : "io.mockk:mockk-android:$mockk"
+        ],
+        github     : [
+                'glide'                  : "com.github.bumptech.glide:glide:$glide",
+                'glideCompiler'          : "com.github.bumptech.glide:compiler:$glide",
+                'bigImageViewer'         : "com.github.piasy:BigImageViewer:$bigImageViewer",
+                'glideImageLoader'       : "com.github.piasy:GlideImageLoader:$bigImageViewer",
+                'progressPieIndicator'   : "com.github.piasy:ProgressPieIndicator:$bigImageViewer",
+                'glideImageViewFactory'  : "com.github.piasy:GlideImageViewFactory:$bigImageViewer"
+        ],
+        jakewharton : [
+                'timber'                 : "com.jakewharton.timber:timber:5.0.1",
+                'rxbinding'              : "com.jakewharton.rxbinding3:rxbinding:$rxBinding",
+                'rxbindingAppcompat'     : "com.jakewharton.rxbinding3:rxbinding-appcompat:$rxBinding",
+                'rxbindingMaterial'      : "com.jakewharton.rxbinding3:rxbinding-material:$rxBinding"
+        ],
+        jsonwebtoken: [
+                'jjwtApi'                : "io.jsonwebtoken:jjwt-api:$jjwt",
+                'jjwtImpl'               : "io.jsonwebtoken:jjwt-impl:$jjwt",
+                'jjwtOrgjson'            : "io.jsonwebtoken:jjwt-orgjson:$jjwt"
+        ],
+        tests       : [
+                'kluent'                 : "org.amshove.kluent:kluent-android:1.68",
+                'timberJunitRule'        : "net.lachlanmckee:timber-junit-rule:1.0.1",
+                'junit'                  : "junit:junit:4.13.2"
+        ]
+]
\ No newline at end of file
diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle
index e1e1383d..536089ab 100644
--- a/matrix-sdk-android/build.gradle
+++ b/matrix-sdk-android/build.gradle
@@ -15,14 +15,14 @@ buildscript {
 }
 
 android {
-    compileSdkVersion 30
     testOptions.unitTests.includeAndroidResources = true
 
+    compileSdk versions.compileSdk
+
     defaultConfig {
-        minSdkVersion 21
-        targetSdkVersion 30
-        versionCode 1
-        versionName "1.2.2"
+        minSdk versions.minSdk
+        targetSdk versions.targetSdk
+
         // Multidex is useful for tests
         multiDexEnabled true
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -32,9 +32,7 @@ android {
         // that the app's state is completely cleared between tests.
         testInstrumentationRunnerArguments clearPackageData: 'true'
 
-        // Seems that the build tools 4.1.0 does not generate BuildConfig.VERSION_NAME anymore.
-        // Add it manually here. We may remove this trick in the future
-        buildConfigField "String", "VERSION_NAME", "\"0.0.1\""
+        buildConfigField "String", "SDK_VERSION", "\"1.3.0\""
 
         buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
         resValue "string", "git_sdk_revision", "\"${gitRevision()}\""
@@ -69,8 +67,8 @@ android {
     }
 
     compileOptions {
-        sourceCompatibility JavaVersion.VERSION_11
-        targetCompatibility JavaVersion.VERSION_11
+        sourceCompatibility versions.sourceCompat
+        targetCompatibility versions.targetCompat
     }
 
     kotlinOptions {
@@ -87,7 +85,7 @@ android {
     }
 }
 
-static def gitRevision() {
+    static def gitRevision() {
     def cmd = "git rev-parse --short=8 HEAD"
     return cmd.execute().text.trim()
 }
@@ -104,92 +102,83 @@ static def gitRevisionDate() {
 
 dependencies {
 
-    def arrow_version = "0.8.2"
-    def moshi_version = '1.12.0'
-    def lifecycle_version = '2.2.0'
-    def arch_version = '2.1.0'
-    def markwon_version = '3.1.0'
-    def daggerVersion = '2.38.1'
-    def work_version = '2.5.0'
-    def retrofit_version = '2.9.0'
-
-    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
-    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
-    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
+    implementation libs.jetbrains.kotlinStdlibJdk7
+    implementation libs.jetbrains.coroutinesCore
+    implementation libs.jetbrains.coroutinesAndroid
 
-    implementation "androidx.appcompat:appcompat:1.3.1"
-    implementation "androidx.core:core-ktx:1.6.0"
+    implementation libs.androidx.appCompat
+    implementation libs.androidx.core
 
-    implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
-    implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
+    implementation libs.androidx.lifecycleExtensions
+    implementation  libs.androidx.lifecycleJava8
 
     // Network
-    implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
-    implementation "com.squareup.retrofit2:converter-moshi:$retrofit_version"
+    implementation libs.squareup.retrofit
+    implementation libs.squareup.retrofitMoshi
 
     implementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.1"))
     implementation 'com.squareup.okhttp3:okhttp'
     implementation 'com.squareup.okhttp3:logging-interceptor'
     implementation 'com.squareup.okhttp3:okhttp-urlconnection'
 
-    implementation "com.squareup.moshi:moshi-adapters:$moshi_version"
-    kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version"
+    implementation libs.squareup.moshi
+    kapt libs.squareup.moshiKotlin
 
-    implementation "ru.noties.markwon:core:$markwon_version"
+    implementation libs.markwon.core
 
     // Image
-    implementation 'androidx.exifinterface:exifinterface:1.3.3'
+    implementation libs.androidx.exifinterface
 
     // Database
     implementation 'com.github.Zhuinden:realm-monarchy:0.7.1'
     kapt 'dk.ilios:realmfieldnameshelper:2.0.0'
 
     // Work
-    implementation "androidx.work:work-runtime-ktx:$work_version"
+    implementation libs.androidx.work
 
     // FP
-    implementation "io.arrow-kt:arrow-core:$arrow_version"
-    implementation "io.arrow-kt:arrow-instances-core:$arrow_version"
+    implementation libs.arrow.core
+    implementation libs.arrow.instances
 
     // olm lib is now hosted by jitpack: https://jitpack.io/#org.matrix.gitlab.matrix-org/olm
     implementation 'org.matrix.gitlab.matrix-org:olm:3.2.4'
 
     // DI
-    implementation "com.google.dagger:dagger:$daggerVersion"
-    kapt "com.google.dagger:dagger-compiler:$daggerVersion"
+    implementation libs.dagger.dagger
+    kapt libs.dagger.daggerCompiler
 
     // Logging
-    implementation 'com.jakewharton.timber:timber:5.0.1'
+    implementation libs.jakewharton.timber
     implementation 'com.facebook.stetho:stetho-okhttp3:1.6.0'
 
     // Video compression
-    implementation 'com.otaliastudios:transcoder:0.10.3'
+    implementation 'com.otaliastudios:transcoder:0.10.4'
 
     // Phone number https://github.com/google/libphonenumber
-    implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.31'
+    implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.33'
 
-    testImplementation 'junit:junit:4.13.2'
-    testImplementation 'org.robolectric:robolectric:4.5.1'
+    testImplementation libs.tests.junit
+    testImplementation 'org.robolectric:robolectric:4.6.1'
     //testImplementation 'org.robolectric:shadows-support-v4:3.0'
     // Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
-    testImplementation 'io.mockk:mockk:1.12.0'
-    testImplementation 'org.amshove.kluent:kluent-android:1.68'
-    testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
+    testImplementation libs.mockk.mockk
+    testImplementation libs.tests.kluent
+    implementation libs.jetbrains.coroutinesAndroid
     // Plant Timber tree for test
     testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'
 
-    kaptAndroidTest "com.google.dagger:dagger-compiler:$daggerVersion"
-    androidTestImplementation 'androidx.test:core:1.4.0'
-    androidTestImplementation 'androidx.test:runner:1.4.0'
-    androidTestImplementation 'androidx.test:rules:1.4.0'
-    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
-    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
-    androidTestImplementation 'org.amshove.kluent:kluent-android:1.68'
-    androidTestImplementation 'io.mockk:mockk-android:1.12.0'
-    androidTestImplementation "androidx.arch.core:core-testing:$arch_version"
-    androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
+    kaptAndroidTest libs.dagger.daggerCompiler
+    androidTestImplementation libs.androidx.testCore
+    androidTestImplementation libs.androidx.testRunner
+    androidTestImplementation libs.androidx.testRules
+    androidTestImplementation libs.androidx.junit
+    androidTestImplementation libs.androidx.espressoCore
+    androidTestImplementation libs.tests.kluent
+    androidTestImplementation libs.mockk.mockkAndroid
+    androidTestImplementation libs.androidx.coreTesting
+    androidTestImplementation libs.jetbrains.coroutinesAndroid
     // Plant Timber tree for test
-    androidTestImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'
+    androidTestImplementation libs.tests.timberJunitRule
 
-    androidTestUtil 'androidx.test:orchestrator:1.4.0'
+    androidTestUtil libs.androidx.orchestrator
 }
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/Matrix.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/Matrix.kt
index c439da84..8b9b6efa 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/Matrix.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/Matrix.kt
@@ -117,7 +117,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
         }
 
         fun getSdkVersion(): String {
-            return BuildConfig.VERSION_NAME + " (" + BuildConfig.GIT_SDK_REVISION + ")"
+            return BuildConfig.SDK_VERSION + " (" + BuildConfig.GIT_SDK_REVISION + ")"
         }
     }
 }
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 7817351e..cf9b8f87 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
@@ -19,6 +19,8 @@ package org.matrix.android.sdk.common
 import android.content.Context
 import android.net.Uri
 import androidx.lifecycle.Observer
+import androidx.test.internal.runner.junit4.statement.UiThreadStatement
+import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.GlobalScope
 import kotlinx.coroutines.delay
@@ -59,13 +61,15 @@ class CommonTestHelper(context: Context) {
     fun getTestInterceptor(session: Session): MockOkHttpInterceptor? = TestNetworkModule.interceptorForSession(session.sessionId) as? MockOkHttpInterceptor
 
     init {
-        Matrix.initialize(
-                context,
-                MatrixConfiguration(
-                        applicationFlavor = "TestFlavor",
-                        roomDisplayNameFallbackProvider = TestRoomDisplayNameFallbackProvider()
-                )
-        )
+        UiThreadStatement.runOnUiThread {
+            Matrix.initialize(
+                    context,
+                    MatrixConfiguration(
+                            applicationFlavor = "TestFlavor",
+                            roomDisplayNameFallbackProvider = TestRoomDisplayNameFallbackProvider()
+                    )
+            )
+        }
         matrix = Matrix.getInstance(context)
     }
 
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 301cdea4..436daf00 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
@@ -32,10 +32,19 @@ import org.junit.runners.JUnit4
 import org.junit.runners.MethodSorters
 import org.matrix.android.sdk.InstrumentedTest
 import org.matrix.android.sdk.api.query.ActiveSpaceFilter
+import org.matrix.android.sdk.api.query.QueryStringValue
 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.room.model.PowerLevelsContent
+import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
 import org.matrix.android.sdk.api.session.room.model.RoomType
 import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
+import org.matrix.android.sdk.api.session.room.model.create.RestrictedRoomPreset
+import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
+import org.matrix.android.sdk.api.session.room.powerlevels.Role
 import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
 import org.matrix.android.sdk.common.CommonTestHelper
 import org.matrix.android.sdk.common.SessionTestParams
@@ -386,6 +395,8 @@ class SpaceHierarchyTest : InstrumentedTest {
             // The room should have disapear from flat children
             GlobalScope.launch(Dispatchers.Main) { flatAChildren.observeForever(childObserver) }
         }
+
+        commonTestHelper.signOutAndClose(session)
     }
 
     data class TestSpaceCreationResult(
@@ -434,6 +445,57 @@ class SpaceHierarchyTest : InstrumentedTest {
         return TestSpaceCreationResult(spaceId, roomIds)
     }
 
+    @Suppress("EXPERIMENTAL_API_USAGE")
+    private fun createPrivateSpace(session: Session,
+                                   spaceName: String,
+                                   childInfo: List<Triple<String, Boolean, Boolean?>>
+            /** Name, auto-join, canonical*/
+    ): TestSpaceCreationResult {
+        var spaceId = ""
+        commonTestHelper.waitWithLatch {
+            GlobalScope.launch {
+                spaceId = session.spaceService().createSpace(spaceName, "My Private Space", null, false)
+                it.countDown()
+            }
+        }
+
+        val syncedSpace = session.spaceService().getSpace(spaceId)
+        val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
+
+        val roomIds =
+                childInfo.map { entry ->
+                    var roomId = ""
+                    commonTestHelper.waitWithLatch {
+                        GlobalScope.launch {
+                            val homeServerCapabilities = session
+                                    .getHomeServerCapabilities()
+                            roomId = session.createRoom(CreateRoomParams().apply {
+                                name = entry.first
+                                this.featurePreset = RestrictedRoomPreset(
+                                        homeServerCapabilities,
+                                        listOf(
+                                                RoomJoinRulesAllowEntry.restrictedToRoom(spaceId)
+                                        )
+                                )
+                            })
+                            it.countDown()
+                        }
+                    }
+                    roomId
+                }
+
+        roomIds.forEachIndexed { index, roomId ->
+            runBlocking {
+                syncedSpace!!.addChildren(roomId, viaServers, null, childInfo[index].second)
+                val canonical = childInfo[index].third
+                if (canonical != null) {
+                    session.spaceService().setSpaceParent(roomId, spaceId, canonical, viaServers)
+                }
+            }
+        }
+        return TestSpaceCreationResult(spaceId, roomIds)
+    }
+
     @Test
     fun testRootSpaces() {
         val session = commonTestHelper.createAccount("John", SessionTestParams(true))
@@ -473,5 +535,111 @@ class SpaceHierarchyTest : InstrumentedTest {
         val rootSpaces = session.spaceService().getRootSpaceSummaries()
 
         assertEquals("Unexpected number of root spaces ${rootSpaces.map { it.name }}", 2, rootSpaces.size)
+
+        commonTestHelper.signOutAndClose(session)
+    }
+
+    @Test
+    fun testParentRelation() {
+        val aliceSession = commonTestHelper.createAccount("Alice", SessionTestParams(true))
+        val bobSession = commonTestHelper.createAccount("Bib", SessionTestParams(true))
+
+        val spaceAInfo = createPrivateSpace(aliceSession, "Private Space A", listOf(
+                Triple("General", true /*suggested*/, true/*canonical*/),
+                Triple("Random", true, true)
+        ))
+
+        commonTestHelper.runBlockingTest {
+            aliceSession.getRoom(spaceAInfo.spaceId)!!.invite(bobSession.myUserId, null)
+        }
+
+        commonTestHelper.runBlockingTest {
+            bobSession.joinRoom(spaceAInfo.spaceId, null, emptyList())
+        }
+
+        var bobRoomId = ""
+        commonTestHelper.waitWithLatch {
+            GlobalScope.launch {
+                bobRoomId = bobSession.createRoom(CreateRoomParams().apply { name = "A Bob Room" })
+                bobSession.getRoom(bobRoomId)!!.invite(aliceSession.myUserId)
+                it.countDown()
+            }
+        }
+
+        commonTestHelper.runBlockingTest {
+            aliceSession.joinRoom(bobRoomId)
+        }
+
+        commonTestHelper.waitWithLatch { latch ->
+            commonTestHelper.retryPeriodicallyWithLatch(latch) {
+                aliceSession.getRoomSummary(bobRoomId)?.membership?.isActive() == true
+            }
+        }
+
+        commonTestHelper.waitWithLatch {
+            GlobalScope.launch {
+                bobSession.spaceService().setSpaceParent(bobRoomId, spaceAInfo.spaceId, false, listOf(bobSession.sessionParams.homeServerHost ?: ""))
+                it.countDown()
+            }
+        }
+
+        commonTestHelper.waitWithLatch { latch ->
+            commonTestHelper.retryPeriodicallyWithLatch(latch) {
+                val stateEvent = aliceSession.getRoom(bobRoomId)!!.getStateEvent(EventType.STATE_SPACE_PARENT, QueryStringValue.Equals(spaceAInfo.spaceId))
+                stateEvent != null
+            }
+        }
+
+        // This should be an invalid space parent relation, because no opposite child and bob is not admin of the space
+        commonTestHelper.runBlockingTest {
+            // we can see the state event
+            // but it is not valid and room is not in hierarchy
+            assertTrue("Bob Room should not be listed as a child of the space", aliceSession.getRoomSummary(bobRoomId)?.flattenParentIds?.isEmpty() == true)
+        }
+
+        // Let's now try to make alice admin of the room
+
+        commonTestHelper.waitWithLatch {
+            GlobalScope.launch {
+                val room = bobSession.getRoom(bobRoomId)!!
+                val currentPLContent = room
+                        .getStateEvent(EventType.STATE_ROOM_POWER_LEVELS)
+                        ?.let { it.content.toModel<PowerLevelsContent>() }
+
+                val newPowerLevelsContent = currentPLContent
+                        ?.setUserPowerLevel(aliceSession.myUserId, Role.Admin.value)
+                        ?.toContent()
+
+                room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, null, newPowerLevelsContent!!)
+                it.countDown()
+            }
+        }
+
+        commonTestHelper.waitWithLatch { latch ->
+            commonTestHelper.retryPeriodicallyWithLatch(latch) {
+                val powerLevelsHelper = aliceSession.getRoom(bobRoomId)!!
+                        .getStateEvent(EventType.STATE_ROOM_POWER_LEVELS)
+                        ?.content
+                        ?.toModel<PowerLevelsContent>()
+                        ?.let { PowerLevelsHelper(it) }
+                powerLevelsHelper!!.isUserAllowedToSend(aliceSession.myUserId, true, EventType.STATE_SPACE_PARENT)
+            }
+        }
+
+        commonTestHelper.waitWithLatch {
+            GlobalScope.launch {
+                aliceSession.spaceService().setSpaceParent(bobRoomId, spaceAInfo.spaceId, false, listOf(bobSession.sessionParams.homeServerHost ?: ""))
+                it.countDown()
+            }
+        }
+
+        commonTestHelper.waitWithLatch { latch ->
+            commonTestHelper.retryPeriodicallyWithLatch(latch) {
+                bobSession.getRoomSummary(bobRoomId)?.flattenParentIds?.contains(spaceAInfo.spaceId) == true
+            }
+        }
+
+        commonTestHelper.signOutAndClose(aliceSession)
+        commonTestHelper.signOutAndClose(bobSession)
     }
 }
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 99802592..8a4526a5 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
@@ -111,7 +111,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
         }
 
         fun getSdkVersion(): String {
-            return BuildConfig.VERSION_NAME + " (" + BuildConfig.GIT_SDK_REVISION + ")"
+            return BuildConfig.SDK_VERSION + " (" + BuildConfig.GIT_SDK_REVISION + ")"
         }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/logger/LoggerTag.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/logger/LoggerTag.kt
index 51f9b506..0d204edc 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/logger/LoggerTag.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/logger/LoggerTag.kt
@@ -24,6 +24,7 @@ package org.matrix.android.sdk.api.logger
  */
 open class LoggerTag(_value: String, parentTag: LoggerTag? = null) {
 
+    object SYNC : LoggerTag("SYNC")
     object VOIP : LoggerTag("VOIP")
 
     val value: String = if (parentTag == null) {
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 2f981ffb..1443a8d3 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
@@ -36,7 +36,7 @@ import org.matrix.android.sdk.api.session.file.FileService
 import org.matrix.android.sdk.api.session.group.GroupService
 import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
 import org.matrix.android.sdk.api.session.identity.IdentityService
-import org.matrix.android.sdk.api.session.initsync.InitialSyncProgressService
+import org.matrix.android.sdk.api.session.initsync.SyncStatusService
 import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService
 import org.matrix.android.sdk.api.session.media.MediaService
 import org.matrix.android.sdk.api.session.openid.OpenIdService
@@ -75,7 +75,7 @@ interface Session :
         ProfileService,
         PushRuleService,
         PushersService,
-        InitialSyncProgressService,
+        SyncStatusService,
         HomeServerCapabilitiesService,
         SecureStorageService,
         AccountService {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
index 3d82846e..1f8471c1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
@@ -239,7 +239,7 @@ data class Event(
 
 fun Event.isTextMessage(): Boolean {
     return getClearType() == EventType.MESSAGE
-            && when (getClearContent()?.toModel<MessageContent>()?.msgType) {
+            && when (getClearContent()?.get(MessageContent.MSG_TYPE_JSON_KEY)) {
         MessageType.MSGTYPE_TEXT,
         MessageType.MSGTYPE_EMOTE,
         MessageType.MSGTYPE_NOTICE -> true
@@ -249,7 +249,7 @@ fun Event.isTextMessage(): Boolean {
 
 fun Event.isImageMessage(): Boolean {
     return getClearType() == EventType.MESSAGE
-            && when (getClearContent()?.toModel<MessageContent>()?.msgType) {
+            && when (getClearContent()?.get(MessageContent.MSG_TYPE_JSON_KEY)) {
         MessageType.MSGTYPE_IMAGE -> true
         else                      -> false
     }
@@ -257,7 +257,7 @@ fun Event.isImageMessage(): Boolean {
 
 fun Event.isVideoMessage(): Boolean {
     return getClearType() == EventType.MESSAGE
-            && when (getClearContent()?.toModel<MessageContent>()?.msgType) {
+            && when (getClearContent()?.get(MessageContent.MSG_TYPE_JSON_KEY)) {
         MessageType.MSGTYPE_VIDEO -> true
         else                      -> false
     }
@@ -265,7 +265,7 @@ fun Event.isVideoMessage(): Boolean {
 
 fun Event.isAudioMessage(): Boolean {
     return getClearType() == EventType.MESSAGE
-            && when (getClearContent()?.toModel<MessageContent>()?.msgType) {
+            && when (getClearContent()?.get(MessageContent.MSG_TYPE_JSON_KEY)) {
         MessageType.MSGTYPE_AUDIO -> true
         else                      -> false
     }
@@ -273,7 +273,7 @@ fun Event.isAudioMessage(): Boolean {
 
 fun Event.isFileMessage(): Boolean {
     return getClearType() == EventType.MESSAGE
-            && when (getClearContent()?.toModel<MessageContent>()?.msgType) {
+            && when (getClearContent()?.get(MessageContent.MSG_TYPE_JSON_KEY)) {
         MessageType.MSGTYPE_FILE -> true
         else                     -> false
     }
@@ -281,7 +281,7 @@ fun Event.isFileMessage(): Boolean {
 
 fun Event.isAttachmentMessage(): Boolean {
     return getClearType() == EventType.MESSAGE
-            && when (getClearContent()?.toModel<MessageContent>()?.msgType) {
+            && when (getClearContent()?.get(MessageContent.MSG_TYPE_JSON_KEY)) {
         MessageType.MSGTYPE_IMAGE,
         MessageType.MSGTYPE_AUDIO,
         MessageType.MSGTYPE_VIDEO,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/initsync/InitialSyncProgressService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/initsync/SyncStatusService.kt
similarity index 55%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/initsync/InitialSyncProgressService.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/initsync/SyncStatusService.kt
index b5d4ef4d..38d47ae1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/initsync/InitialSyncProgressService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/initsync/SyncStatusService.kt
@@ -17,15 +17,33 @@ package org.matrix.android.sdk.api.session.initsync
 
 import androidx.lifecycle.LiveData
 
-interface InitialSyncProgressService {
+interface SyncStatusService {
 
-    fun getInitialSyncProgressStatus(): LiveData<Status>
+    fun getSyncStatusLive(): LiveData<Status>
 
     sealed class Status {
-        object Idle : Status()
+        /**
+         * For initial sync
+         */
+        abstract class InitialSyncStatus: Status()
+
+        object Idle : InitialSyncStatus()
         data class Progressing(
                 val initSyncStep: InitSyncStep,
                 val percentProgress: Int = 0
-        ) : Status()
+        ) : InitialSyncStatus()
+
+        /**
+         * For incremental sync
+         */
+        abstract class IncrementalSyncStatus: Status()
+
+        object IncrementalSyncIdle : IncrementalSyncStatus()
+        data class IncrementalSyncParsing(
+                val rooms: Int,
+                val toDevice: Int
+        ) : IncrementalSyncStatus()
+        object IncrementalSyncError : IncrementalSyncStatus()
+        object IncrementalSyncDone : IncrementalSyncStatus()
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/Pusher.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/Pusher.kt
index eed75c9d..b85ab32b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/Pusher.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/Pusher.kt
@@ -26,7 +26,14 @@ data class Pusher(
         val data: PusherData,
 
         val state: PusherState
-)
+) {
+    companion object {
+
+        const val KIND_EMAIL = "email"
+        const val KIND_HTTP = "http"
+        const val APP_ID_EMAIL = "m.email"
+    }
+}
 
 enum class PusherState {
     UNREGISTERED,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt
index a5ec100f..2cd17952 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt
@@ -27,14 +27,12 @@ interface PushersService {
 
     /**
      * Add a new HTTP pusher.
-     * Note that only `http` kind is supported by the SDK for now.
      * Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-pushers-set
      *
      * @param pushkey           This is a unique identifier for this pusher. The value you should use for
      *                          this is the routing or destination address information for the notification,
      *                          for example, the APNS token for APNS or the Registration ID for GCM. If your
      *                          notification client has no such concept, use any unique identifier. Max length, 512 chars.
-     *                          If the kind is "email", this is the email address to send notifications to.
      * @param appId             the application id
      *                          This is a reverse-DNS style identifier for the application. It is recommended
      *                          that this end with the platform, such that different platform versions get
@@ -64,6 +62,30 @@ interface PushersService {
                       append: Boolean,
                       withEventIdOnly: Boolean): UUID
 
+    /**
+     * Add a new Email pusher.
+     * Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-pushers-set
+     *
+     * @param email             The email address to send notifications to.
+     * @param lang              The preferred language for receiving notifications (e.g. "en" or "en-US").
+     * @param emailBranding     The branding placeholder to include in the email communications.
+     * @param appDisplayName    A human readable string that will allow the user to identify what application owns this pusher.
+     * @param deviceDisplayName A human readable string that will allow the user to identify what device owns this pusher.
+     * @param append            If true, the homeserver should add another pusher with the given pushkey and App ID in addition
+     *                          to any others with different user IDs. Otherwise, the homeserver must remove any other pushers
+     *                          with the same App ID and pushkey for different users. Typically We always want to append for
+     *                          email pushers since we don't want to stop other accounts notifying to the same email address.
+     * @return                  A work request uuid. Can be used to listen to the status
+     *                          (LiveData<WorkInfo> status = workManager.getWorkInfoByIdLiveData(<UUID>))
+     * @throws [InvalidParameterException] if a parameter is not correct
+     */
+    fun addEmailPusher(email: String,
+                       lang: String,
+                       emailBranding: String,
+                       appDisplayName: String,
+                       deviceDisplayName: String,
+                       append: Boolean = true): UUID
+
     /**
      * Directly ask the push gateway to send a push to this device
      * If successful, the push gateway has accepted the request. In this case, the app should receive a Push with the provided eventId.
@@ -80,10 +102,23 @@ interface PushersService {
                          eventId: String)
 
     /**
-     * Remove the http pusher
+     * Remove a registered pusher
+     * @param pusher the pusher to remove, can be http or email
+     */
+    suspend fun removePusher(pusher: Pusher)
+
+    /**
+     * Remove a Http pusher by its pushkey and appId
+     * @see addHttpPusher
      */
     suspend fun removeHttpPusher(pushkey: String, appId: String)
 
+    /**
+     * Remove an Email pusher
+     * @see addEmailPusher
+     */
+    suspend fun removeEmailPusher(email: String)
+
     /**
      * Get the current pushers, as a LiveData
      */
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomStrippedState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomStrippedState.kt
new file mode 100644
index 00000000..dc0c00b2
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomStrippedState.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.api.session.room.model
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+/**
+ * These are the same fields as those returned by /publicRooms, with a few additions: room_type, membership and is_encrypted.
+ */
+@JsonClass(generateAdapter = true)
+data class RoomStrippedState(
+        /**
+         * Aliases of the room. May be empty.
+         */
+        @Json(name = "aliases")
+        val aliases: List<String>? = null,
+
+        /**
+         * The canonical alias of the room, if any.
+         */
+        @Json(name = "canonical_alias")
+        val canonicalAlias: String? = null,
+
+        /**
+         * The name of the room, if any.
+         */
+        @Json(name = "name")
+        val name: String? = null,
+
+        /**
+         * Required. The number of members joined to the room.
+         */
+        @Json(name = "num_joined_members")
+        val numJoinedMembers: Int = 0,
+
+        /**
+         * Required. The ID of the room.
+         */
+        @Json(name = "room_id")
+        val roomId: String,
+
+        /**
+         * The topic of the room, if any.
+         */
+        @Json(name = "topic")
+        val topic: String? = null,
+
+        /**
+         * Required. Whether the room may be viewed by guest users without joining.
+         */
+        @Json(name = "world_readable")
+        val worldReadable: Boolean = false,
+
+        /**
+         * Required. Whether guest users may join the room and participate in it. If they can,
+         * they will be subject to ordinary power level rules like any other user.
+         */
+        @Json(name = "guest_can_join")
+        val guestCanJoin: Boolean = false,
+
+        /**
+         * The URL for the room's avatar, if one is set.
+         */
+        @Json(name = "avatar_url")
+        val avatarUrl: String? = null,
+
+        /**
+         * Undocumented item
+         */
+        @Json(name = "m.federate")
+        val isFederated: Boolean = false,
+
+        /**
+         * Optional. If the room is encrypted. This is already accessible as stripped state.
+         */
+        @Json(name = "is_encrypted")
+        val isEncrypted: Boolean?,
+
+        /**
+         * Optional. Type of the room, if any, i.e. m.space
+         */
+        @Json(name = "room_type")
+        val roomType: String?,
+
+        /**
+         * The current membership of this user in the room. Usually leave if the room is fetched over federation.
+         */
+        @Json(name = "membership")
+        val membership: String?
+) {
+    /**
+     * Return the canonical alias, or the first alias from the list of aliases, or null
+     */
+    fun getPrimaryAlias(): String? {
+        return canonicalAlias ?: aliases?.firstOrNull()
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioContent.kt
index 1bcb10d8..ebf3d127 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioContent.kt
@@ -28,7 +28,7 @@ data class MessageAudioContent(
         /**
          * Required. Must be 'm.audio'.
          */
-        @Json(name = "msgtype") override val msgType: String,
+        @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String,
 
         /**
          * Required. A description of the audio e.g. 'Bee Gees - Stayin' Alive', or some kind of content description for accessibility e.g. 'audio attachment'.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageContent.kt
index df5641a6..5a1b66c9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageContent.kt
@@ -20,6 +20,11 @@ import org.matrix.android.sdk.api.session.events.model.Content
 import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
 
 interface MessageContent {
+
+    companion object {
+        const val MSG_TYPE_JSON_KEY = "msgtype"
+    }
+
     val msgType: String
     val body: String
     val relatesTo: RelationDefaultContent?
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageDefaultContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageDefaultContent.kt
index 65e89cdf..1dadc922 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageDefaultContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageDefaultContent.kt
@@ -23,7 +23,7 @@ import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultCon
 
 @JsonClass(generateAdapter = true)
 data class MessageDefaultContent(
-        @Json(name = "msgtype") override val msgType: String,
+        @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String,
         @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
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageEmoteContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageEmoteContent.kt
index 77983a03..a2ada416 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageEmoteContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageEmoteContent.kt
@@ -26,7 +26,7 @@ data class MessageEmoteContent(
         /**
          * Required. Must be 'm.emote'.
          */
-        @Json(name = "msgtype") override val msgType: String,
+        @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String,
 
         /**
          * Required. The emote action to perform.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageFileContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageFileContent.kt
index 96877b4d..78f9a5d2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageFileContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageFileContent.kt
@@ -28,7 +28,7 @@ data class MessageFileContent(
         /**
          * Required. Must be 'm.file'.
          */
-        @Json(name = "msgtype") override val msgType: String,
+        @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String,
 
         /**
          * Required. A human-readable description of the file. This is recommended to be the filename of the original upload.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageImageContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageImageContent.kt
index 73fd1eab..ea7ab506 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageImageContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageImageContent.kt
@@ -27,7 +27,7 @@ data class MessageImageContent(
         /**
          * Required. Must be 'm.image'.
          */
-        @Json(name = "msgtype") override val msgType: String,
+        @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String,
 
         /**
          * Required. A textual representation of the image. This could be the alt text of the image, the filename of the image,
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 bdb54910..6881c099 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
@@ -26,7 +26,7 @@ data class MessageLocationContent(
         /**
          * Required. Must be 'm.location'.
          */
-        @Json(name = "msgtype") override val msgType: String,
+        @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String,
 
         /**
          * Required. A description of the location e.g. 'Big Ben, London, UK', or some kind
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageNoticeContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageNoticeContent.kt
index b2fd8cb0..dd960355 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageNoticeContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageNoticeContent.kt
@@ -26,7 +26,7 @@ data class MessageNoticeContent(
         /**
          * Required. Must be 'm.notice'.
          */
-        @Json(name = "msgtype") override val msgType: String,
+        @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String,
 
         /**
          * Required. The notice text to send.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageOptionsContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageOptionsContent.kt
index 79244698..7a1a99bd 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageOptionsContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageOptionsContent.kt
@@ -30,7 +30,7 @@ const val OPTION_TYPE_BUTTONS = "org.matrix.buttons"
  */
 @JsonClass(generateAdapter = true)
 data class MessageOptionsContent(
-        @Json(name = "msgtype") override val msgType: String = MessageType.MSGTYPE_OPTIONS,
+        @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String = MessageType.MSGTYPE_OPTIONS,
         @Json(name = "type") val optionType: String? = null,
         @Json(name = "body") override val body: String,
         @Json(name = "label") val label: String?,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollResponseContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollResponseContent.kt
index d8274752..9edfe118 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollResponseContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessagePollResponseContent.kt
@@ -26,7 +26,7 @@ import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultCon
  */
 @JsonClass(generateAdapter = true)
 data class MessagePollResponseContent(
-        @Json(name = "msgtype") override val msgType: String = MessageType.MSGTYPE_RESPONSE,
+        @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String = MessageType.MSGTYPE_RESPONSE,
         @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
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageTextContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageTextContent.kt
index e45245a9..5968fecd 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageTextContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageTextContent.kt
@@ -26,7 +26,7 @@ data class MessageTextContent(
         /**
          * Required. Must be 'm.text'.
          */
-        @Json(name = "msgtype") override val msgType: String,
+        @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String,
 
         /**
          * Required. The body of the message.
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 25b5f448..b2b3cdac 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 = "msgtype") 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 3f5d2dab..e1b0cd86 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 = "msgtype") 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/space/SpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt
index bcc36b57..f4057251 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
@@ -94,5 +94,7 @@ interface SpaceService {
      */
     suspend fun setSpaceParent(childRoomId: String, parentSpaceId: String, canonical: Boolean, viaServers: List<String>)
 
+    suspend fun removeSpaceParent(childRoomId: String, parentSpaceId: String)
+
     fun getRootSpaceSummaries(): List<RoomSummary>
 }
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 06c667ee..3825a5da 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
@@ -71,18 +71,24 @@ internal class InboundGroupSessionStore @Inject constructor(
     }
 
     @Synchronized
-    fun storeInBoundGroupSession(wrapper: OlmInboundGroupSessionWrapper2) {
+    fun storeInBoundGroupSession(wrapper: OlmInboundGroupSessionWrapper2, sessionId: String, senderKey: String) {
         Timber.v("## Inbound: getInboundGroupSession mark as dirty ${wrapper.roomId}-${wrapper.senderKey}")
         // We want to batch this a bit for performances
         dirtySession.add(wrapper)
 
+        if (sessionCache[CacheKey(sessionId, senderKey)] == null) {
+            // first time seen, put it in memory cache while waiting for batch insert
+            // If it's already known, no need to update cache it's already there
+            sessionCache.put(CacheKey(sessionId, senderKey), wrapper)
+        }
+
         timerTask?.cancel()
         timerTask = object : TimerTask() {
             override fun run() {
                 batchSave()
             }
         }
-        timer.schedule(timerTask!!, 2_000)
+        timer.schedule(timerTask!!, 300)
     }
 
     @Synchronized
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 b8f1a9ab..441dfe4a 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
@@ -577,7 +577,8 @@ internal class MXOlmDevice @Inject constructor(
         session.keysClaimed = keysClaimed
         session.forwardingCurve25519KeyChain = forwardingCurve25519KeyChain
 
-        store.storeInboundGroupSessions(listOf(session))
+        inboundGroupSessionStore.storeInBoundGroupSession(session, sessionId, senderKey)
+//        store.storeInboundGroupSessions(listOf(session))
 
         return true
     }
@@ -703,7 +704,7 @@ internal class MXOlmDevice @Inject constructor(
                 timelineSet.add(messageIndexKey)
             }
 
-            inboundGroupSessionStore.storeInBoundGroupSession(session)
+            inboundGroupSessionStore.storeInBoundGroupSession(session, sessionId, senderKey)
             val payload = try {
                 val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE)
                 val payloadString = convertFromUTF8(decryptResult.mDecryptedMessage)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/UserAgentHolder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/UserAgentHolder.kt
index 1a884041..57eab6a8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/UserAgentHolder.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/UserAgentHolder.kt
@@ -36,7 +36,7 @@ internal class UserAgentHolder @Inject constructor(private val context: Context,
 
     /**
      * Create an user agent with the application version.
-     * Ex: Element/1.0.0 (Linux; U; Android 6.0.1; SM-A510F Build/MMB29; Flavour GPlay; MatrixAndroidSDK_X 1.0)
+     * Ex: Element/1.0.0 (Linux; U; Android 6.0.1; SM-A510F Build/MMB29; Flavour GPlay; MatrixAndroidSdk2 1.0)
      *
      * @param flavorDescription the flavor description
      */
@@ -74,13 +74,13 @@ internal class UserAgentHolder @Inject constructor(private val context: Context,
         // if there is no user agent or cannot parse it
         if (null == systemUserAgent || systemUserAgent.lastIndexOf(")") == -1 || !systemUserAgent.contains("(")) {
             userAgent = (appName + "/" + appVersion + " ( Flavour " + flavorDescription
-                    + "; MatrixAndroidSDK_X " + BuildConfig.VERSION_NAME + ")")
+                    + "; MatrixAndroidSdk2 " + BuildConfig.SDK_VERSION + ")")
         } else {
             // update
             userAgent = appName + "/" + appVersion + " " +
                     systemUserAgent.substring(systemUserAgent.indexOf("("), systemUserAgent.lastIndexOf(")") - 1) +
                     "; Flavour " + flavorDescription +
-                    "; MatrixAndroidSDK_X " + BuildConfig.VERSION_NAME + ")"
+                    "; MatrixAndroidSdk2 " + BuildConfig.SDK_VERSION + ")"
         }
     }
 }
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 c2bd1e24..22167bc7 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
@@ -40,7 +40,7 @@ import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker
 import org.matrix.android.sdk.api.session.file.FileService
 import org.matrix.android.sdk.api.session.group.GroupService
 import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
-import org.matrix.android.sdk.api.session.initsync.InitialSyncProgressService
+import org.matrix.android.sdk.api.session.initsync.SyncStatusService
 import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService
 import org.matrix.android.sdk.api.session.media.MediaService
 import org.matrix.android.sdk.api.session.openid.OpenIdService
@@ -115,7 +115,7 @@ internal class DefaultSession @Inject constructor(
         private val contentUploadProgressTracker: ContentUploadStateTracker,
         private val typingUsersTracker: TypingUsersTracker,
         private val contentDownloadStateTracker: ContentDownloadStateTracker,
-        private val initialSyncProgressService: Lazy<InitialSyncProgressService>,
+        private val syncStatusService: Lazy<SyncStatusService>,
         private val homeServerCapabilitiesService: Lazy<HomeServerCapabilitiesService>,
         private val accountDataService: Lazy<SessionAccountDataService>,
         private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>,
@@ -141,7 +141,7 @@ internal class DefaultSession @Inject constructor(
         PushersService by pushersService.get(),
         EventService by eventService.get(),
         TermsService by termsService.get(),
-        InitialSyncProgressService by initialSyncProgressService.get(),
+        SyncStatusService by syncStatusService.get(),
         SecureStorageService by secureStorageService.get(),
         HomeServerCapabilitiesService by homeServerCapabilitiesService.get(),
         ProfileService by profileService.get(),
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt
index 9a936b73..2003a66c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt
@@ -43,7 +43,7 @@ import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationMan
 import org.matrix.android.sdk.internal.session.media.MediaModule
 import org.matrix.android.sdk.internal.session.openid.OpenIdModule
 import org.matrix.android.sdk.internal.session.profile.ProfileModule
-import org.matrix.android.sdk.internal.session.pushers.AddHttpPusherWorker
+import org.matrix.android.sdk.internal.session.pushers.AddPusherWorker
 import org.matrix.android.sdk.internal.session.pushers.PushersModule
 import org.matrix.android.sdk.internal.session.room.RoomModule
 import org.matrix.android.sdk.internal.session.room.relation.SendRelationWorker
@@ -127,7 +127,7 @@ internal interface SessionComponent {
 
     fun inject(worker: SyncWorker)
 
-    fun inject(worker: AddHttpPusherWorker)
+    fun inject(worker: AddPusherWorker)
 
     fun inject(worker: SendVerificationMessageWorker)
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt
index cb29cb48..dc59277f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt
@@ -37,7 +37,7 @@ import org.matrix.android.sdk.api.session.SessionLifecycleObserver
 import org.matrix.android.sdk.api.session.accountdata.SessionAccountDataService
 import org.matrix.android.sdk.api.session.events.EventService
 import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
-import org.matrix.android.sdk.api.session.initsync.InitialSyncProgressService
+import org.matrix.android.sdk.api.session.initsync.SyncStatusService
 import org.matrix.android.sdk.api.session.openid.OpenIdService
 import org.matrix.android.sdk.api.session.permalinks.PermalinkService
 import org.matrix.android.sdk.api.session.securestorage.SecureStorageService
@@ -81,7 +81,7 @@ import org.matrix.android.sdk.internal.session.download.DownloadProgressIntercep
 import org.matrix.android.sdk.internal.session.events.DefaultEventService
 import org.matrix.android.sdk.internal.session.homeserver.DefaultHomeServerCapabilitiesService
 import org.matrix.android.sdk.internal.session.identity.DefaultIdentityService
-import org.matrix.android.sdk.internal.session.initsync.DefaultInitialSyncProgressService
+import org.matrix.android.sdk.internal.session.initsync.DefaultSyncStatusService
 import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManager
 import org.matrix.android.sdk.internal.session.openid.DefaultOpenIdService
 import org.matrix.android.sdk.internal.session.permalinks.DefaultPermalinkService
@@ -355,7 +355,7 @@ internal abstract class SessionModule {
     abstract fun bindEventSenderProcessorAsSessionLifecycleObserver(processor: EventSenderProcessorCoroutine): SessionLifecycleObserver
 
     @Binds
-    abstract fun bindInitialSyncProgressService(service: DefaultInitialSyncProgressService): InitialSyncProgressService
+    abstract fun bindSyncStatusService(service: DefaultSyncStatusService): SyncStatusService
 
     @Binds
     abstract fun bindSecureStorageService(service: DefaultSecureStorageService): SecureStorageService
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt
index fdb6caf5..acd16345 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt
@@ -20,10 +20,16 @@ import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.LifecycleRegistry
 import dagger.Lazy
+import kotlinx.coroutines.withContext
+import okhttp3.OkHttpClient
 import org.matrix.android.sdk.api.auth.data.SessionParams
+import org.matrix.android.sdk.api.extensions.orFalse
 import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.api.failure.Failure
 import org.matrix.android.sdk.api.failure.MatrixError
+import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.SessionLifecycleObserver
+import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
 import org.matrix.android.sdk.api.session.events.model.toModel
 import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
 import org.matrix.android.sdk.api.session.identity.FoundThreePid
@@ -36,23 +42,17 @@ import org.matrix.android.sdk.internal.di.AuthenticatedIdentity
 import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate
 import org.matrix.android.sdk.internal.extensions.observeNotNull
 import org.matrix.android.sdk.internal.network.RetrofitFactory
-import org.matrix.android.sdk.api.session.SessionLifecycleObserver
 import org.matrix.android.sdk.internal.session.SessionScope
 import org.matrix.android.sdk.internal.session.identity.data.IdentityStore
+import org.matrix.android.sdk.internal.session.identity.model.SignInvitationResult
 import org.matrix.android.sdk.internal.session.openid.GetOpenIdTokenTask
 import org.matrix.android.sdk.internal.session.profile.BindThreePidsTask
 import org.matrix.android.sdk.internal.session.profile.UnbindThreePidsTask
 import org.matrix.android.sdk.internal.session.sync.model.accountdata.IdentityServerContent
-import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
-import org.matrix.android.sdk.internal.session.user.accountdata.UserAccountDataDataSource
 import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask
+import org.matrix.android.sdk.internal.session.user.accountdata.UserAccountDataDataSource
 import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
 import org.matrix.android.sdk.internal.util.ensureProtocol
-import kotlinx.coroutines.withContext
-import okhttp3.OkHttpClient
-import org.matrix.android.sdk.api.extensions.orFalse
-import org.matrix.android.sdk.api.session.Session
-import org.matrix.android.sdk.internal.session.identity.model.SignInvitationResult
 import timber.log.Timber
 import javax.inject.Inject
 import javax.net.ssl.HttpsURLConnection
@@ -202,6 +202,8 @@ internal class DefaultIdentityService @Inject constructor(
 
             identityStore.setUrl(urlCandidate)
             identityStore.setToken(token)
+            // could we remember if it was previously given?
+            identityStore.setUserConsent(false)
             updateIdentityAPI(urlCandidate)
 
             updateAccountData(urlCandidate)
@@ -230,6 +232,8 @@ internal class DefaultIdentityService @Inject constructor(
     }
 
     override suspend fun lookUp(threePids: List<ThreePid>): List<FoundThreePid> {
+        if (getCurrentIdentityServerUrl() == null) throw IdentityServiceError.NoIdentityServerConfigured
+
         if (!getUserConsent()) {
             throw IdentityServiceError.UserConsentNotProvided
         }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/initsync/DefaultInitialSyncProgressService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/initsync/DefaultSyncStatusService.kt
similarity index 78%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/initsync/DefaultInitialSyncProgressService.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/initsync/DefaultSyncStatusService.kt
index eb3e3066..6dac9bff 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/initsync/DefaultInitialSyncProgressService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/initsync/DefaultSyncStatusService.kt
@@ -18,23 +18,28 @@ package org.matrix.android.sdk.internal.session.initsync
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
 import org.matrix.android.sdk.api.session.initsync.InitSyncStep
-import org.matrix.android.sdk.api.session.initsync.InitialSyncProgressService
+import org.matrix.android.sdk.api.session.initsync.SyncStatusService
 import org.matrix.android.sdk.internal.session.SessionScope
 import javax.inject.Inject
 
 @SessionScope
-internal class DefaultInitialSyncProgressService @Inject constructor()
-    : InitialSyncProgressService,
+internal class DefaultSyncStatusService @Inject constructor()
+    : SyncStatusService,
         ProgressReporter {
 
-    private val status = MutableLiveData<InitialSyncProgressService.Status>()
+    private val status = MutableLiveData<SyncStatusService.Status>()
 
     private var rootTask: TaskInfo? = null
 
-    override fun getInitialSyncProgressStatus(): LiveData<InitialSyncProgressService.Status> {
+    override fun getSyncStatusLive(): LiveData<SyncStatusService.Status> {
         return status
     }
 
+    // Only to be used for incremental sync
+    fun setStatus(newStatus: SyncStatusService.Status.IncrementalSyncStatus) {
+        status.postValue(newStatus)
+    }
+
     /**
      * Create a rootTask
      */
@@ -67,7 +72,7 @@ internal class DefaultInitialSyncProgressService @Inject constructor()
                 // Update the progress of the leaf and all its parents
                 leaf.setProgress(progress)
                 // Then update the live data using leaf wording and root progress
-                status.postValue(InitialSyncProgressService.Status.Progressing(leaf.initSyncStep, root.currentProgress.toInt()))
+                status.postValue(SyncStatusService.Status.Progressing(leaf.initSyncStep, root.currentProgress.toInt()))
             }
         }
     }
@@ -82,13 +87,13 @@ internal class DefaultInitialSyncProgressService @Inject constructor()
                 // And close it
                 endedTask.parent.child = null
             } else {
-                status.postValue(InitialSyncProgressService.Status.Idle)
+                status.postValue(SyncStatusService.Status.Idle)
             }
         }
     }
 
     fun endAll() {
         rootTask = null
-        status.postValue(InitialSyncProgressService.Status.Idle)
+        status.postValue(SyncStatusService.Status.Idle)
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddHttpPusherWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherWorker.kt
similarity index 95%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddHttpPusherWorker.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherWorker.kt
index c9d7ad21..079fd1d3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddHttpPusherWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherWorker.kt
@@ -33,8 +33,8 @@ import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
 import org.matrix.android.sdk.internal.worker.SessionWorkerParams
 import javax.inject.Inject
 
-internal class AddHttpPusherWorker(context: Context, params: WorkerParameters)
-    : SessionSafeCoroutineWorker<AddHttpPusherWorker.Params>(context, params, Params::class.java) {
+internal class AddPusherWorker(context: Context, params: WorkerParameters)
+    : SessionSafeCoroutineWorker<AddPusherWorker.Params>(context, params, 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/DefaultPushersService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt
index a772cf5e..9a50abfe 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt
@@ -66,27 +66,45 @@ internal class DefaultPushersService @Inject constructor(
                                deviceDisplayName: String,
                                url: String,
                                append: Boolean,
-                               withEventIdOnly: Boolean)
-            : UUID {
-        // Do some parameter checks. It's ok to throw Exception, to inform developer of the problem
-        if (pushkey.length > 512) throw InvalidParameterException("pushkey should not exceed 512 chars")
-        if (appId.length > 64) throw InvalidParameterException("appId should not exceed 64 chars")
-        if ("/_matrix/push/v1/notify" !in url) throw InvalidParameterException("url should contain '/_matrix/push/v1/notify'")
+                               withEventIdOnly: Boolean
+    ) = addPusher(
+            JsonPusher(
+                    pushKey = pushkey,
+                    kind = Pusher.KIND_HTTP,
+                    appId = appId,
+                    profileTag = profileTag,
+                    lang = lang,
+                    appDisplayName = appDisplayName,
+                    deviceDisplayName = deviceDisplayName,
+                    data = JsonPusherData(url, EVENT_ID_ONLY.takeIf { withEventIdOnly }),
+                    append = append
+            )
+    )
 
-        val pusher = JsonPusher(
-                pushKey = pushkey,
-                kind = "http",
-                appId = appId,
-                appDisplayName = appDisplayName,
-                deviceDisplayName = deviceDisplayName,
-                profileTag = profileTag,
-                lang = lang,
-                data = JsonPusherData(url, EVENT_ID_ONLY.takeIf { withEventIdOnly }),
-                append = append)
+    override fun addEmailPusher(email: String,
+                                lang: String,
+                                emailBranding: String,
+                                appDisplayName: String,
+                                deviceDisplayName: String,
+                                append: Boolean
+    ) = addPusher(
+            JsonPusher(
+                    pushKey = email,
+                    kind = Pusher.KIND_EMAIL,
+                    appId = Pusher.APP_ID_EMAIL,
+                    profileTag = "",
+                    lang = lang,
+                    appDisplayName = appDisplayName,
+                    deviceDisplayName = deviceDisplayName,
+                    data = JsonPusherData(brand = emailBranding),
+                    append = append
+            )
+    )
 
-        val params = AddHttpPusherWorker.Params(sessionId, pusher)
-
-        val request = workManagerProvider.matrixOneTimeWorkRequestBuilder<AddHttpPusherWorker>()
+    private fun addPusher(pusher: JsonPusher): UUID {
+        pusher.validateParameters()
+        val params = AddPusherWorker.Params(sessionId, pusher)
+        val request = workManagerProvider.matrixOneTimeWorkRequestBuilder<AddPusherWorker>()
                 .setConstraints(WorkManagerProvider.workConstraints)
                 .setInputData(WorkerParamsFactory.toData(params))
                 .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS)
@@ -95,8 +113,27 @@ internal class DefaultPushersService @Inject constructor(
         return request.id
     }
 
+    private fun JsonPusher.validateParameters() {
+        // Do some parameter checks. It's ok to throw Exception, to inform developer of the problem
+        if (pushKey.length > 512) throw InvalidParameterException("pushkey should not exceed 512 chars")
+        if (appId.length > 64) throw InvalidParameterException("appId should not exceed 64 chars")
+        data?.url?.let { url -> if ("/_matrix/push/v1/notify" !in url) throw InvalidParameterException("url should contain '/_matrix/push/v1/notify'") }
+    }
+
+    override suspend fun removePusher(pusher: Pusher) {
+        removePusher(pusher.pushKey, pusher.appId)
+    }
+
     override suspend fun removeHttpPusher(pushkey: String, appId: String) {
-        val params = RemovePusherTask.Params(pushkey, appId)
+        removePusher(pushkey, appId)
+    }
+
+    override suspend fun removeEmailPusher(email: String) {
+        removePusher(pushKey = email, Pusher.APP_ID_EMAIL)
+    }
+
+    private suspend fun removePusher(pushKey: String, pushAppId: String) {
+        val params = RemovePusherTask.Params(pushKey, pushAppId)
         removePusherTask.execute(params)
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/JsonPusherData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/JsonPusherData.kt
index c8d4d77f..42a8fa6f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/JsonPusherData.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/JsonPusherData.kt
@@ -32,5 +32,8 @@ internal data class JsonPusherData(
          * Currently the only format available is 'event_id_only'.
          */
         @Json(name = "format")
-        val format: String? = null
+        val format: String? = null,
+
+        @Json(name = "brand")
+        val brand: String? = null
 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/GetRoomSummaryTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/GetRoomSummaryTask.kt
new file mode 100644
index 00000000..d9547d9e
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/GetRoomSummaryTask.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.session.room
+
+import org.matrix.android.sdk.api.session.room.model.RoomStrippedState
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
+import org.matrix.android.sdk.internal.network.executeRequest
+import org.matrix.android.sdk.internal.task.Task
+import javax.inject.Inject
+
+internal interface GetRoomSummaryTask : Task<GetRoomSummaryTask.Params, RoomStrippedState> {
+    data class Params(
+            val roomId: String,
+            val viaServers: List<String>?
+    )
+}
+
+internal class DefaultGetRoomSummaryTask @Inject constructor(
+        private val roomAPI: RoomAPI,
+        private val globalErrorReceiver: GlobalErrorReceiver
+) : GetRoomSummaryTask {
+
+    override suspend fun execute(params: GetRoomSummaryTask.Params): RoomStrippedState {
+        return executeRequest(globalErrorReceiver) {
+            roomAPI.getRoomSummary(params.roomId, params.viaServers)
+        }
+    }
+}
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 535fa9df..98e76592 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
@@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.room
 import org.matrix.android.sdk.api.session.events.model.Content
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.room.model.Membership
+import org.matrix.android.sdk.api.session.room.model.RoomStrippedState
 import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams
 import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsResponse
 import org.matrix.android.sdk.api.util.JsonDict
@@ -254,7 +255,7 @@ internal interface RoomAPI {
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "join/{roomIdOrAlias}")
     suspend fun join(@Path("roomIdOrAlias") roomIdOrAlias: String,
                      @Query("server_name") viaServers: List<String>,
-                     @Body params:  JsonDict): JoinRoomResponse
+                     @Body params: JsonDict): JoinRoomResponse
 
     /**
      * Leave the given room.
@@ -381,4 +382,14 @@ internal interface RoomAPI {
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/upgrade")
     suspend fun upgradeRoom(@Path("roomId") roomId: String,
                             @Body body: RoomUpgradeBody): RoomUpgradeResponse
+
+    /**
+     * The API returns the summary of the specified room, if the room could be found and the client should be able to view
+     * its contents according to the join_rules, history visibility, space membership and similar rules outlined in MSC3173
+     * as well as if the user is already a member of that room.
+     * https://github.com/deepbluev7/matrix-doc/blob/room-summaries/proposals/3266-room-summary.md
+     */
+    @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "im.nheko.summary/rooms/{roomIdOrAlias}/summary")
+    suspend fun getRoomSummary(@Path("roomIdOrAlias") roomidOrAlias: String,
+                               @Query("via") viaServers: List<String>?): RoomStrippedState
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt
index 79497070..dbd0ae6f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt
@@ -253,4 +253,7 @@ internal abstract class RoomModule {
 
     @Binds
     abstract fun bindSign3pidInvitationTask(task: DefaultSign3pidInvitationTask): Sign3pidInvitationTask
+
+    @Binds
+    abstract fun bindGetRoomSummaryTask(task: DefaultGetRoomSummaryTask): GetRoomSummaryTask
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/PeekRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/PeekRoomTask.kt
index 219e9c90..63fc26e9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/PeekRoomTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/PeekRoomTask.kt
@@ -33,6 +33,7 @@ import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsFi
 import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams
 import org.matrix.android.sdk.api.session.room.peeking.PeekResult
 import org.matrix.android.sdk.api.util.MatrixItem
+import org.matrix.android.sdk.internal.session.room.GetRoomSummaryTask
 import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask
 import org.matrix.android.sdk.internal.session.room.directory.GetPublicRoomTask
 import org.matrix.android.sdk.internal.session.room.directory.GetRoomDirectoryVisibilityTask
@@ -49,6 +50,7 @@ internal class DefaultPeekRoomTask @Inject constructor(
         private val getRoomIdByAliasTask: GetRoomIdByAliasTask,
         private val getRoomDirectoryVisibilityTask: GetRoomDirectoryVisibilityTask,
         private val getPublicRoomTask: GetPublicRoomTask,
+        private val getRoomSummaryTask: GetRoomSummaryTask,
         private val resolveRoomStateTask: ResolveRoomStateTask
 ) : PeekRoomTask {
 
@@ -70,6 +72,25 @@ internal class DefaultPeekRoomTask @Inject constructor(
             serverList = emptyList()
         }
 
+        // If the room summary API is available on the Home Server we should try it first
+        val strippedState = tryOrNull("Failed to get room stripped state roomId:$roomId") {
+            getRoomSummaryTask.execute(GetRoomSummaryTask.Params(roomId, serverList))
+        }
+        if (strippedState != null) {
+            return PeekResult.Success(
+                    roomId = strippedState.roomId,
+                    alias = strippedState.getPrimaryAlias() ?: params.roomIdOrAlias.takeIf { isAlias },
+                    avatarUrl = strippedState.avatarUrl,
+                    name = strippedState.name,
+                    topic = strippedState.topic,
+                    numJoinedMembers = strippedState.numJoinedMembers,
+                    viaServers = serverList,
+                    roomType = strippedState.roomType,
+                    someMembers = null,
+                    isPublic = strippedState.worldReadable
+            )
+        }
+
         // Is it a public room?
         val visibilityRes = tryOrNull("## PEEK: failed to get visibility") {
             getRoomDirectoryVisibilityTask.execute(GetRoomDirectoryVisibilityTask.Params(roomId))
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt
index 89a35339..4a6e27b7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt
@@ -23,6 +23,7 @@ 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.accountdata.RoomAccountDataTypes
 import org.matrix.android.sdk.api.session.room.model.Membership
+import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
 import org.matrix.android.sdk.api.session.room.model.RoomAliasesContent
 import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent
 import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
@@ -31,6 +32,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomTopicContent
 import org.matrix.android.sdk.api.session.room.model.RoomType
 import org.matrix.android.sdk.api.session.room.model.VersioningState
 import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
+import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
 import org.matrix.android.sdk.api.session.room.send.SendState
 import org.matrix.android.sdk.internal.crypto.EventDecryptor
 import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
@@ -207,63 +209,102 @@ internal class RoomSummaryUpdater @Inject constructor(
                     }
                     .toMap()
 
-            lookupMap.keys.forEach { lookedUp ->
-                if (lookedUp.roomType == RoomType.SPACE) {
-                    // get childrens
+            // First handle child relations
+            lookupMap.keys.asSequence()
+                    .filter { it.roomType == RoomType.SPACE }
+                    .forEach { lookedUp ->
+                        // get childrens
 
-                    lookedUp.children.clearWith { it.deleteFromRealm() }
+                        lookedUp.children.clearWith { it.deleteFromRealm() }
 
-                    RoomChildRelationInfo(realm, lookedUp.roomId).getDirectChildrenDescriptions().forEach { child ->
+                        RoomChildRelationInfo(realm, lookedUp.roomId).getDirectChildrenDescriptions().forEach { child ->
 
-                        lookedUp.children.add(
-                                realm.createObject<SpaceChildSummaryEntity>().apply {
-                                    this.childRoomId = child.roomId
-                                    this.childSummaryEntity = RoomSummaryEntity.where(realm, child.roomId).findFirst()
-                                    this.order = child.order
+                            lookedUp.children.add(
+                                    realm.createObject<SpaceChildSummaryEntity>().apply {
+                                        this.childRoomId = child.roomId
+                                        this.childSummaryEntity = RoomSummaryEntity.where(realm, child.roomId).findFirst()
+                                        this.order = child.order
 //                                    this.autoJoin = child.autoJoin
-                                    this.viaServers.addAll(child.viaServers)
-                                }
-                        )
-
-                        RoomSummaryEntity.where(realm, child.roomId)
-                                .process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
-                                .findFirst()
-                                ?.let { childSum ->
-                                    lookupMap.entries.firstOrNull { it.key.roomId == lookedUp.roomId }?.let { entry ->
-                                        if (entry.value.indexOfFirst { it.roomId == childSum.roomId } == -1) {
-                                            // add looked up as a parent
-                                            entry.value.add(childSum)
+                                        this.viaServers.addAll(child.viaServers)
+                                    }
+                            )
+
+                            RoomSummaryEntity.where(realm, child.roomId)
+                                    .process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
+                                    .findFirst()
+                                    ?.let { childSum ->
+                                        lookupMap.entries.firstOrNull { it.key.roomId == lookedUp.roomId }?.let { entry ->
+                                            if (entry.value.indexOfFirst { it.roomId == childSum.roomId } == -1) {
+                                                // add looked up as a parent
+                                                entry.value.add(childSum)
+                                            }
                                         }
                                     }
+                        }
+                    }
+
+            // Now let's check parent relations
+
+            lookupMap.keys
+                    .forEach { lookedUp ->
+                        lookedUp.parents.clearWith { it.deleteFromRealm() }
+                        // can we check parent relations here??
+                        /**
+                         * rooms can claim parents via the m.space.parent state event.
+                         * canonical determines whether this is the main parent for the space.
+                         *
+                         * To avoid abuse where a room admin falsely claims that a room is part of a space that it should not be,
+                         * clients could ignore such m.space.parent events unless either
+                         * (a) there is a corresponding m.space.child event in the claimed parent, or
+                         * (b) the sender of the m.space.child event has a sufficient power-level to send such an m.space.child event in the parent.
+                         * (It is not necessarily required that that user currently be a member of the parent room -
+                         * only the m.room.power_levels event is inspected.)
+                         * [Checking the power-level rather than requiring an actual m.space.child event in the parent allows for "secret" rooms (see below).]
+                         */
+                        RoomChildRelationInfo(realm, lookedUp.roomId).getParentDescriptions()
+                                .map { parentInfo ->
+                                    // Is it a valid parent relation?
+                                    // Check if it's a child of the parent?
+                                    val isValidRelation: Boolean
+                                    val parent = lookupMap.firstNotNullOfOrNull { if (it.key.roomId == parentInfo.roomId) it.value else null }
+                                    if (parent?.firstOrNull { it.roomId == lookedUp.roomId } != null) {
+                                        // there is a corresponding m.space.child event in the claimed parent
+                                        isValidRelation = true
+                                    } else {
+                                        // check if sender can post child relation in parent?
+                                        val senderId = parentInfo.stateEventSender
+                                        val parentRoomId = parentInfo.roomId
+                                        val powerLevelsHelper = CurrentStateEventEntity
+                                                .getOrNull(realm, parentRoomId, "", EventType.STATE_ROOM_POWER_LEVELS)
+                                                ?.root
+                                                ?.let { ContentMapper.map(it.content).toModel<PowerLevelsContent>() }
+                                                ?.let { PowerLevelsHelper(it) }
+
+                                        isValidRelation = powerLevelsHelper?.isUserAllowedToSend(senderId, true, EventType.STATE_SPACE_CHILD) ?: false
+                                    }
+
+                                    if (isValidRelation) {
+                                        lookedUp.parents.add(
+                                                realm.createObject<SpaceParentSummaryEntity>().apply {
+                                                    this.parentRoomId = parentInfo.roomId
+                                                    this.parentSummaryEntity = RoomSummaryEntity.where(realm, parentInfo.roomId).findFirst()
+                                                    this.canonical = parentInfo.canonical
+                                                    this.viaServers.addAll(parentInfo.viaServers)
+                                                }
+                                        )
+
+                                        RoomSummaryEntity.where(realm, parentInfo.roomId)
+                                                .process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
+                                                .findFirst()
+                                                ?.let { parentSum ->
+                                                    if (lookupMap[parentSum]?.indexOfFirst { it.roomId == lookedUp.roomId } == -1) {
+                                                        // add lookedup as a parent
+                                                        lookupMap[parentSum]?.add(lookedUp)
+                                                    }
+                                                }
+                                    }
                                 }
                     }
-                } else {
-                    lookedUp.parents.clearWith { it.deleteFromRealm() }
-                    // can we check parent relations here??
-                    RoomChildRelationInfo(realm, lookedUp.roomId).getParentDescriptions()
-                            .map { parentInfo ->
-
-                                lookedUp.parents.add(
-                                        realm.createObject<SpaceParentSummaryEntity>().apply {
-                                            this.parentRoomId = parentInfo.roomId
-                                            this.parentSummaryEntity = RoomSummaryEntity.where(realm, parentInfo.roomId).findFirst()
-                                            this.canonical = parentInfo.canonical
-                                            this.viaServers.addAll(parentInfo.viaServers)
-                                        }
-                                )
-
-                                RoomSummaryEntity.where(realm, parentInfo.roomId)
-                                        .process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
-                                        .findFirst()
-                                        ?.let { parentSum ->
-                                            if (lookupMap[parentSum]?.indexOfFirst { it.roomId == lookedUp.roomId } == -1) {
-                                                // add lookedup as a parent
-                                                lookupMap[parentSum]?.add(lookedUp)
-                                            }
-                                        }
-                            }
-                }
-            }
 
             // Simple algorithm to break cycles
             // Need more work to decide how to break, probably need to be as consistent as possible
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 8a6bbc18..8589db27 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
@@ -89,7 +89,6 @@ internal class DefaultSpace(
                 body = SpaceChildContent(
                         order = null,
                         via = null,
-//                        autoJoin = null,
                         suggested = null
                 ).toContent()
         )
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 7be4cdcd..ac20c790 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
@@ -33,6 +33,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary
 import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
 import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset
 import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
+import org.matrix.android.sdk.api.session.room.powerlevels.Role
 import org.matrix.android.sdk.api.session.space.CreateSpaceParams
 import org.matrix.android.sdk.api.session.space.JoinSpaceResult
 import org.matrix.android.sdk.api.session.space.Space
@@ -77,7 +78,7 @@ internal class DefaultSpaceService @Inject constructor(
             if (isPublic) {
                 this.roomAliasName = roomAliasLocalPart
                 this.powerLevelContentOverride = (powerLevelContentOverride ?: PowerLevelsContent()).copy(
-                        invite = 0
+                        invite = if (isPublic) Role.Default.value else Role.Moderator.value
                 )
                 this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
                 this.historyVisibility = RoomHistoryVisibility.WORLD_READABLE
@@ -221,4 +222,23 @@ internal class DefaultSpaceService @Inject constructor(
                 ).toContent()
         )
     }
+
+    override suspend fun removeSpaceParent(childRoomId: String, parentSpaceId: String) {
+        val room = roomGetter.getRoom(childRoomId)
+                ?: throw IllegalArgumentException("Unknown Room $childRoomId")
+
+        val existingEvent = room.getStateEvent(EventType.STATE_SPACE_PARENT, QueryStringValue.Equals(parentSpaceId))
+        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(
+                    eventType = EventType.STATE_SPACE_PARENT,
+                    stateKey = parentSpaceId,
+                    body = SpaceParentContent(
+                            via = null,
+                            canonical = null
+                    ).toContent()
+            )
+        }
+    }
 }
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 c80fbe60..df3d8492 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
@@ -17,7 +17,9 @@
 package org.matrix.android.sdk.internal.session.sync
 
 import okhttp3.ResponseBody
+import org.matrix.android.sdk.api.logger.LoggerTag
 import org.matrix.android.sdk.api.session.initsync.InitSyncStep
+import org.matrix.android.sdk.api.session.initsync.SyncStatusService
 import org.matrix.android.sdk.internal.di.SessionFilesDirectory
 import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
@@ -26,7 +28,7 @@ import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.network.toFailure
 import org.matrix.android.sdk.internal.session.filter.FilterRepository
 import org.matrix.android.sdk.internal.session.homeserver.GetHomeServerCapabilitiesTask
-import org.matrix.android.sdk.internal.session.initsync.DefaultInitialSyncProgressService
+import org.matrix.android.sdk.internal.session.initsync.DefaultSyncStatusService
 import org.matrix.android.sdk.internal.session.initsync.reportSubtask
 import org.matrix.android.sdk.internal.session.sync.model.LazyRoomSyncEphemeral
 import org.matrix.android.sdk.internal.session.sync.parsing.InitialSyncResponseParser
@@ -40,6 +42,8 @@ import java.io.File
 import java.net.SocketTimeoutException
 import javax.inject.Inject
 
+private val loggerTag = LoggerTag("SyncTask", LoggerTag.SYNC)
+
 internal interface SyncTask : Task<SyncTask.Params, Unit> {
 
     data class Params(
@@ -53,7 +57,7 @@ internal class DefaultSyncTask @Inject constructor(
         @UserId private val userId: String,
         private val filterRepository: FilterRepository,
         private val syncResponseHandler: SyncResponseHandler,
-        private val initialSyncProgressService: DefaultInitialSyncProgressService,
+        private val defaultSyncStatusService: DefaultSyncStatusService,
         private val syncTokenStore: SyncTokenStore,
         private val getHomeServerCapabilitiesTask: GetHomeServerCapabilitiesTask,
         private val userStore: UserStore,
@@ -75,7 +79,7 @@ internal class DefaultSyncTask @Inject constructor(
     }
 
     private suspend fun doSync(params: SyncTask.Params) {
-        Timber.v("Sync task started on Thread: ${Thread.currentThread().name}")
+        Timber.tag(loggerTag.value).d("Sync task started on Thread: ${Thread.currentThread().name}")
 
         val requestParams = HashMap<String, String>()
         var timeout = 0L
@@ -92,7 +96,7 @@ internal class DefaultSyncTask @Inject constructor(
         if (isInitialSync) {
             // We might want to get the user information in parallel too
             userStore.createOrUpdate(userId)
-            initialSyncProgressService.startRoot(InitSyncStep.ImportingAccount, 100)
+            defaultSyncStatusService.startRoot(InitSyncStep.ImportingAccount, 100)
         }
         // Maybe refresh the homeserver capabilities data we know
         getHomeServerCapabilitiesTask.execute(GetHomeServerCapabilitiesTask.Params(forceRefresh = false))
@@ -100,20 +104,20 @@ internal class DefaultSyncTask @Inject constructor(
         val readTimeOut = (params.timeout + TIMEOUT_MARGIN).coerceAtLeast(TimeOutInterceptor.DEFAULT_LONG_TIMEOUT)
 
         if (isInitialSync) {
-            Timber.d("INIT_SYNC with filter: ${requestParams["filter"]}")
+            Timber.tag(loggerTag.value).d("INIT_SYNC with filter: ${requestParams["filter"]}")
             val initSyncStrategy = initialSyncStrategy
-            logDuration("INIT_SYNC strategy: $initSyncStrategy") {
+            logDuration("INIT_SYNC strategy: $initSyncStrategy", loggerTag) {
                 if (initSyncStrategy is InitialSyncStrategy.Optimized) {
                     roomSyncEphemeralTemporaryStore.reset()
                     workingDir.mkdirs()
                     val file = downloadInitSyncResponse(requestParams)
-                    reportSubtask(initialSyncProgressService, InitSyncStep.ImportingAccount, 1, 0.7F) {
+                    reportSubtask(defaultSyncStatusService, InitSyncStep.ImportingAccount, 1, 0.7F) {
                         handleSyncFile(file, initSyncStrategy)
                     }
                     // Delete all files
                     workingDir.deleteRecursively()
                 } else {
-                    val syncResponse = logDuration("INIT_SYNC Request") {
+                    val syncResponse = logDuration("INIT_SYNC Request", loggerTag) {
                         executeRequest(globalErrorReceiver) {
                             syncAPI.sync(
                                     params = requestParams,
@@ -122,43 +126,60 @@ internal class DefaultSyncTask @Inject constructor(
                         }
                     }
 
-                    logDuration("INIT_SYNC Database insertion") {
-                        syncResponseHandler.handleResponse(syncResponse, token, initialSyncProgressService)
+                    logDuration("INIT_SYNC Database insertion", loggerTag) {
+                        syncResponseHandler.handleResponse(syncResponse, token, defaultSyncStatusService)
                     }
                 }
             }
-            initialSyncProgressService.endAll()
+            defaultSyncStatusService.endAll()
         } else {
-            val syncResponse = executeRequest(globalErrorReceiver) {
-                syncAPI.sync(
-                        params = requestParams,
-                        readTimeOut = readTimeOut
-                )
+            Timber.tag(loggerTag.value).d("Start incremental sync request")
+            defaultSyncStatusService.setStatus(SyncStatusService.Status.IncrementalSyncIdle)
+            val syncResponse = try {
+                executeRequest(globalErrorReceiver) {
+                    syncAPI.sync(
+                            params = requestParams,
+                            readTimeOut = readTimeOut
+                    )
+                }
+            } catch (throwable: Throwable) {
+                Timber.tag(loggerTag.value).e(throwable, "Incremental sync request error")
+                defaultSyncStatusService.setStatus(SyncStatusService.Status.IncrementalSyncError)
+                throw throwable
             }
+            val nbRooms = syncResponse.rooms?.invite.orEmpty().size + syncResponse.rooms?.join.orEmpty().size + syncResponse.rooms?.leave.orEmpty().size
+            val nbToDevice = syncResponse.toDevice?.events.orEmpty().size
+            Timber.tag(loggerTag.value).d("Incremental sync request parsing, $nbRooms room(s) $nbToDevice toDevice(s)")
+            defaultSyncStatusService.setStatus(SyncStatusService.Status.IncrementalSyncParsing(
+                    rooms = nbRooms,
+                    toDevice = nbToDevice
+            ))
             syncResponseHandler.handleResponse(syncResponse, token, null)
+            Timber.tag(loggerTag.value).d("Incremental sync done")
+            defaultSyncStatusService.setStatus(SyncStatusService.Status.IncrementalSyncDone)
         }
-        Timber.v("Sync task finished on Thread: ${Thread.currentThread().name}")
+        Timber.tag(loggerTag.value).d("Sync task finished on Thread: ${Thread.currentThread().name}")
     }
 
     private suspend fun downloadInitSyncResponse(requestParams: Map<String, String>): File {
         val workingFile = File(workingDir, "initSync.json")
         val status = initialSyncStatusRepository.getStep()
         if (workingFile.exists() && status >= InitialSyncStatus.STEP_DOWNLOADED) {
-            Timber.d("INIT_SYNC file is already here")
-            reportSubtask(initialSyncProgressService, InitSyncStep.Downloading, 1, 0.3f) {
+            Timber.tag(loggerTag.value).d("INIT_SYNC file is already here")
+            reportSubtask(defaultSyncStatusService, InitSyncStep.Downloading, 1, 0.3f) {
                 // Empty task
             }
         } else {
             initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_DOWNLOADING)
-            val syncResponse = logDuration("INIT_SYNC Perform server request") {
-                reportSubtask(initialSyncProgressService, InitSyncStep.ServerComputing, 1, 0.2f) {
+            val syncResponse = logDuration("INIT_SYNC Perform server request", loggerTag) {
+                reportSubtask(defaultSyncStatusService, InitSyncStep.ServerComputing, 1, 0.2f) {
                     getSyncResponse(requestParams, MAX_NUMBER_OF_RETRY_AFTER_TIMEOUT)
                 }
             }
 
             if (syncResponse.isSuccessful) {
-                logDuration("INIT_SYNC Download and save to file") {
-                    reportSubtask(initialSyncProgressService, InitSyncStep.Downloading, 1, 0.1f) {
+                logDuration("INIT_SYNC Download and save to file", loggerTag) {
+                    reportSubtask(defaultSyncStatusService, InitSyncStep.Downloading, 1, 0.1f) {
                         syncResponse.body()?.byteStream()?.use { inputStream ->
                             workingFile.outputStream().use { outputStream ->
                                 inputStream.copyTo(outputStream)
@@ -168,7 +189,7 @@ internal class DefaultSyncTask @Inject constructor(
                 }
             } else {
                 throw syncResponse.toFailure(globalErrorReceiver)
-                        .also { Timber.w("INIT_SYNC request failure: $this") }
+                        .also { Timber.tag(loggerTag.value).w("INIT_SYNC request failure: $this") }
             }
             initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_DOWNLOADED)
         }
@@ -185,9 +206,9 @@ internal class DefaultSyncTask @Inject constructor(
                 ).awaitResponse()
             } catch (throwable: Throwable) {
                 if (throwable is SocketTimeoutException && retry > 0) {
-                    Timber.w("INIT_SYNC timeout retry left: $retry")
+                    Timber.tag(loggerTag.value).w("INIT_SYNC timeout retry left: $retry")
                 } else {
-                    Timber.e(throwable, "INIT_SYNC timeout, no retry left, or other error")
+                    Timber.tag(loggerTag.value).e(throwable, "INIT_SYNC timeout, no retry left, or other error")
                     throw throwable
                 }
             }
@@ -195,18 +216,18 @@ internal class DefaultSyncTask @Inject constructor(
     }
 
     private suspend fun handleSyncFile(workingFile: File, initSyncStrategy: InitialSyncStrategy.Optimized) {
-        logDuration("INIT_SYNC handleSyncFile()") {
-            val syncResponse = logDuration("INIT_SYNC Read file and parse") {
+        logDuration("INIT_SYNC handleSyncFile()", loggerTag) {
+            val syncResponse = logDuration("INIT_SYNC Read file and parse", loggerTag) {
                 syncResponseParser.parse(initSyncStrategy, workingFile)
             }
             initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_PARSED)
             // Log some stats
             val nbOfJoinedRooms = syncResponse.rooms?.join?.size ?: 0
             val nbOfJoinedRoomsInFile = syncResponse.rooms?.join?.values?.count { it.ephemeral is LazyRoomSyncEphemeral.Stored }
-            Timber.d("INIT_SYNC $nbOfJoinedRooms rooms, $nbOfJoinedRoomsInFile ephemeral stored into files")
+            Timber.tag(loggerTag.value).d("INIT_SYNC $nbOfJoinedRooms rooms, $nbOfJoinedRoomsInFile ephemeral stored into files")
 
-            logDuration("INIT_SYNC Database insertion") {
-                syncResponseHandler.handleResponse(syncResponse, null, initialSyncProgressService)
+            logDuration("INIT_SYNC Database insertion", loggerTag) {
+                syncResponseHandler.handleResponse(syncResponse, null, defaultSyncStatusService)
             }
             initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_SUCCESS)
         }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt
index de8d0098..b3a6cafb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt
@@ -36,6 +36,7 @@ import kotlinx.coroutines.cancelChildren
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
+import org.matrix.android.sdk.api.logger.LoggerTag
 import org.matrix.android.sdk.api.session.call.MxCall
 import org.matrix.android.sdk.internal.session.call.ActiveCallHandler
 import org.matrix.android.sdk.internal.session.sync.SyncPresence
@@ -49,6 +50,8 @@ import kotlin.concurrent.schedule
 private const val RETRY_WAIT_TIME_MS = 10_000L
 private const val DEFAULT_LONG_POOL_TIMEOUT = 30_000L
 
+private val loggerTag = LoggerTag("SyncThread", LoggerTag.SYNC)
+
 internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
                                               private val networkConnectivityChecker: NetworkConnectivityChecker,
                                               private val backgroundDetectionObserver: BackgroundDetectionObserver,
@@ -83,7 +86,7 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
 
     fun restart() = synchronized(lock) {
         if (!isStarted) {
-            Timber.v("Resume sync...")
+            Timber.tag(loggerTag.value).d("Resume sync...")
             isStarted = true
             // Check again server availability and the token validity
             canReachServer = true
@@ -94,7 +97,7 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
 
     fun pause() = synchronized(lock) {
         if (isStarted) {
-            Timber.v("Pause sync...")
+            Timber.tag(loggerTag.value).d("Pause sync...")
             isStarted = false
             retryNoNetworkTask?.cancel()
             syncScope.coroutineContext.cancelChildren()
@@ -102,7 +105,7 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
     }
 
     fun kill() = synchronized(lock) {
-        Timber.v("Kill sync...")
+        Timber.tag(loggerTag.value).d("Kill sync...")
         updateStateTo(SyncState.Killing)
         retryNoNetworkTask?.cancel()
         syncScope.coroutineContext.cancelChildren()
@@ -124,21 +127,21 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
     }
 
     override fun run() {
-        Timber.v("Start syncing...")
+        Timber.tag(loggerTag.value).d("Start syncing...")
 
         isStarted = true
         networkConnectivityChecker.register(this)
         backgroundDetectionObserver.register(this)
         registerActiveCallsObserver()
         while (state != SyncState.Killing) {
-            Timber.v("Entering loop, state: $state")
+            Timber.tag(loggerTag.value).d("Entering loop, state: $state")
             if (!isStarted) {
-                Timber.v("Sync is Paused. Waiting...")
+                Timber.tag(loggerTag.value).d("Sync is Paused. Waiting...")
                 updateStateTo(SyncState.Paused)
                 synchronized(lock) { lock.wait() }
-                Timber.v("...unlocked")
+                Timber.tag(loggerTag.value).d("...unlocked")
             } else if (!canReachServer) {
-                Timber.v("No network. Waiting...")
+                Timber.tag(loggerTag.value).d("No network. Waiting...")
                 updateStateTo(SyncState.NoNetwork)
                 // We force retrying in RETRY_WAIT_TIME_MS maximum. Otherwise it will be unlocked by onConnectivityChanged() or restart()
                 retryNoNetworkTask = Timer(SyncState.NoNetwork.toString(), false).schedule(RETRY_WAIT_TIME_MS) {
@@ -148,19 +151,19 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
                     }
                 }
                 synchronized(lock) { lock.wait() }
-                Timber.v("...retry")
+                Timber.tag(loggerTag.value).d("...retry")
             } else if (!isTokenValid) {
-                Timber.v("Token is invalid. Waiting...")
+                Timber.tag(loggerTag.value).d("Token is invalid. Waiting...")
                 updateStateTo(SyncState.InvalidToken)
                 synchronized(lock) { lock.wait() }
-                Timber.v("...unlocked")
+                Timber.tag(loggerTag.value).d("...unlocked")
             } else {
                 if (state !is SyncState.Running) {
                     updateStateTo(SyncState.Running(afterPause = true))
                 }
                 // No timeout after a pause
                 val timeout = state.let { if (it is SyncState.Running && it.afterPause) 0 else DEFAULT_LONG_POOL_TIMEOUT }
-                Timber.v("Execute sync request with timeout $timeout")
+                Timber.tag(loggerTag.value).d("Execute sync request with timeout $timeout")
                 val params = SyncTask.Params(timeout, SyncPresence.Online)
                 val sync = syncScope.launch {
                     doSync(params)
@@ -168,10 +171,10 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
                 runBlocking {
                     sync.join()
                 }
-                Timber.v("...Continue")
+                Timber.tag(loggerTag.value).d("...Continue")
             }
         }
-        Timber.v("Sync killed")
+        Timber.tag(loggerTag.value).d("Sync killed")
         updateStateTo(SyncState.Killed)
         backgroundDetectionObserver.unregister(this)
         networkConnectivityChecker.unregister(this)
@@ -199,19 +202,19 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
             }
             if (failure is Failure.NetworkConnection && failure.cause is SocketTimeoutException) {
                 // Timeout are not critical
-                Timber.v("Timeout")
+                Timber.tag(loggerTag.value).d("Timeout")
             } else if (failure is CancellationException) {
-                Timber.v("Cancelled")
+                Timber.tag(loggerTag.value).d("Cancelled")
             } else if (failure.isTokenError()) {
                 // No token or invalid token, stop the thread
-                Timber.w(failure, "Token error")
+                Timber.tag(loggerTag.value).w(failure, "Token error")
                 isStarted = false
                 isTokenValid = false
             } else {
-                Timber.e(failure)
+                Timber.tag(loggerTag.value).e(failure)
                 if (failure !is Failure.NetworkConnection || failure.cause is JsonEncodingException) {
                     // Wait 10s before retrying
-                    Timber.v("Wait 10s")
+                    Timber.tag(loggerTag.value).d("Wait 10s")
                     delay(RETRY_WAIT_TIME_MS)
                 }
             }
@@ -225,7 +228,7 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
     }
 
     private fun updateStateTo(newState: SyncState) {
-        Timber.v("Update state from $state to $newState")
+        Timber.tag(loggerTag.value).d("Update state from $state to $newState")
         if (newState == state) {
             return
         }
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 4656856b..6fd907d3 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
@@ -17,6 +17,7 @@
 package org.matrix.android.sdk.internal.util
 
 import org.matrix.android.sdk.BuildConfig
+import org.matrix.android.sdk.api.logger.LoggerTag
 import timber.log.Timber
 
 internal fun <T> Collection<T>.logLimit(maxQuantity: Int = 5): String {
@@ -32,14 +33,15 @@ internal fun <T> Collection<T>.logLimit(maxQuantity: Int = 5): String {
 }
 
 internal suspend fun <T> logDuration(message: String,
+                                     loggerTag: LoggerTag,
                                      block: suspend () -> T): T {
-    Timber.d("$message -- BEGIN")
+    Timber.tag(loggerTag.value).d("$message -- BEGIN")
     val start = System.currentTimeMillis()
     val result = logRamUsage(message) {
         block()
     }
     val duration = System.currentTimeMillis() - start
-    Timber.d("$message -- END duration: $duration ms")
+    Timber.tag(loggerTag.value).d("$message -- END duration: $duration ms")
 
     return result
 }
-- 
GitLab