diff --git a/changelog.d/4090.feature b/changelog.d/4090.feature
new file mode 100644
index 0000000000..be8aa317a6
--- /dev/null
+++ b/changelog.d/4090.feature
@@ -0,0 +1 @@
+Handle Presence support, for Direct Message room
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
index 98d561815b..23538c5285 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -23,3 +23,6 @@ vector.debugPrivateData=false
 # httpLogLevel values: NONE, BASIC, HEADERS, BODY
 vector.httpLogLevel=BASIC
 
+# Note: to debug, you can put and uncomment the following lines in the file ~/.gradle/gradle.properties to override the value above
+#vector.debugPrivateData=true
+#vector.httpLogLevel=BODY
\ No newline at end of file
diff --git a/library/ui-styles/src/main/res/values/colors.xml b/library/ui-styles/src/main/res/values/colors.xml
index b58829bb81..03d1ff69db 100644
--- a/library/ui-styles/src/main/res/values/colors.xml
+++ b/library/ui-styles/src/main/res/values/colors.xml
@@ -133,4 +133,8 @@
     <color name="vctr_voice_message_toast_background_light">@color/palette_black_900</color>
     <color name="vctr_voice_message_toast_background_dark">@color/palette_gray_400</color>
 
+    <!-- Presence Indicator colors -->
+    <attr name="vctr_presence_indicator_offline" format="color" />
+    <color name="vctr_presence_indicator_offline_light">@color/palette_gray_100</color>
+    <color name="vctr_presence_indicator_offline_dark">@color/palette_gray_450</color>
 </resources>
diff --git a/library/ui-styles/src/main/res/values/theme_dark.xml b/library/ui-styles/src/main/res/values/theme_dark.xml
index f83953a527..d07e3c5297 100644
--- a/library/ui-styles/src/main/res/values/theme_dark.xml
+++ b/library/ui-styles/src/main/res/values/theme_dark.xml
@@ -42,6 +42,9 @@
         <item name="vctr_markdown_block_background_color">@android:color/black</item>
         <item name="vctr_spoiler_background_color">#FFFFFFFF</item>
 
+        <!-- Presence Indicator colors -->
+        <item name="vctr_presence_indicator_offline">@color/vctr_presence_indicator_offline_dark</item>
+
         <!-- Some alias -->
         <item name="vctr_header_background">?vctr_system</item>
         <item name="vctr_list_separator">?vctr_content_quinary</item>
diff --git a/library/ui-styles/src/main/res/values/theme_light.xml b/library/ui-styles/src/main/res/values/theme_light.xml
index cd5e17d607..14ec372f28 100644
--- a/library/ui-styles/src/main/res/values/theme_light.xml
+++ b/library/ui-styles/src/main/res/values/theme_light.xml
@@ -42,6 +42,9 @@
         <item name="vctr_markdown_block_background_color">#FFEEEEEE</item>
         <item name="vctr_spoiler_background_color">#FF000000</item>
 
+        <!-- Presence Indicator colors -->
+        <item name="vctr_presence_indicator_offline">@color/vctr_presence_indicator_offline_light</item>
+
         <!-- Some alias -->
         <item name="vctr_header_background">?vctr_system</item>
         <item name="vctr_list_separator">?vctr_content_quinary</item>
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 124d7d9dae..d4bfd4ee8c 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
@@ -43,6 +43,7 @@ import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerS
 import org.matrix.android.sdk.api.session.media.MediaService
 import org.matrix.android.sdk.api.session.openid.OpenIdService
 import org.matrix.android.sdk.api.session.permalinks.PermalinkService
+import org.matrix.android.sdk.api.session.presence.PresenceService
 import org.matrix.android.sdk.api.session.profile.ProfileService
 import org.matrix.android.sdk.api.session.pushers.PushersService
 import org.matrix.android.sdk.api.session.room.RoomDirectoryService
@@ -76,6 +77,7 @@ interface Session :
         TermsService,
         EventService,
         ProfileService,
+        PresenceService,
         PushRuleService,
         PushersService,
         SyncStatusService,
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 96b44ce8c9..169f90dbca 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
@@ -30,6 +30,7 @@ import org.matrix.android.sdk.api.util.JsonDict
 import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult
 import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
 import org.matrix.android.sdk.internal.di.MoshiProvider
+import org.matrix.android.sdk.internal.session.presence.model.PresenceContent
 import timber.log.Timber
 
 typealias Content = JsonDict
@@ -305,3 +306,7 @@ fun Event.isReply(): Boolean {
 fun Event.isEdition(): Boolean {
     return getRelationContent()?.takeIf { it.type == RelationType.REPLACE }?.eventId != null
 }
+
+fun Event.getPresenceContent(): PresenceContent? {
+    return content.toModel<PresenceContent>()
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/PresenceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/PresenceService.kt
new file mode 100644
index 0000000000..82a81f4b64
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/PresenceService.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.presence
+
+import org.matrix.android.sdk.api.session.presence.model.PresenceEnum
+import org.matrix.android.sdk.api.session.presence.model.UserPresence
+
+/**
+ * This interface defines methods for handling user presence information.
+ */
+interface PresenceService {
+    /**
+     * Update the presence status for the current user
+     * @param presence the new presence state
+     * @param statusMsg the status message to attach to this state
+     */
+    suspend fun setMyPresence(presence: PresenceEnum, statusMsg: String? = null)
+
+    /**
+     * Fetch the given user's presence state.
+     * @param userId the userId whose presence state to get.
+     */
+    suspend fun fetchPresence(userId: String): UserPresence
+
+    // TODO Add live data (of Flow) of the presence of a userId
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/model/PresenceEnum.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/model/PresenceEnum.kt
new file mode 100644
index 0000000000..6d9994ef1c
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/model/PresenceEnum.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.presence.model
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = false)
+enum class PresenceEnum(val value: String) {
+    @Json(name = "online")
+    ONLINE("online"),
+
+    @Json(name = "offline")
+    OFFLINE("offline"),
+
+    @Json(name = "unavailable")
+    UNAVAILABLE("unavailable");
+
+    companion object {
+        fun from(s: String): PresenceEnum? = values().find { it.value == s }
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/model/UserPresence.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/model/UserPresence.kt
new file mode 100644
index 0000000000..6b33ff07d5
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/model/UserPresence.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.presence.model
+
+data class UserPresence(
+        val lastActiveAgo: Long? = null,
+        val statusMessage: String? = null,
+        val isCurrentlyActive: Boolean? = null,
+        val presence: PresenceEnum = PresenceEnum.OFFLINE
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomMemberSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomMemberSummary.kt
index fba3a1dd71..39177a4296 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomMemberSummary.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomMemberSummary.kt
@@ -16,12 +16,15 @@
 
 package org.matrix.android.sdk.api.session.room.model
 
+import org.matrix.android.sdk.api.session.presence.model.UserPresence
+
 /**
  * Class representing a simplified version of EventType.STATE_ROOM_MEMBER state event content
  */
 data class RoomMemberSummary constructor(
         val membership: Membership,
         val userId: String,
+        val userPresence: UserPresence? = null,
         val displayName: String? = null,
         val avatarUrl: String? = null
 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt
index cae4775e71..10cad026bc 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt
@@ -17,6 +17,7 @@
 package org.matrix.android.sdk.api.session.room.model
 
 import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
+import org.matrix.android.sdk.api.session.presence.model.UserPresence
 import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
 import org.matrix.android.sdk.api.session.room.send.UserDraft
 import org.matrix.android.sdk.api.session.room.sender.SenderInfo
@@ -38,6 +39,7 @@ data class RoomSummary(
         val joinRules: RoomJoinRules? = null,
         val isDirect: Boolean = false,
         val directUserId: String? = null,
+        val directUserPresence: UserPresence? = null,
         val joinedMembersCount: Int? = 0,
         val invitedMembersCount: Int? = 0,
         val latestPreviewableEvent: TimelineEvent? = null,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
index aa96ca5e1a..05137f8105 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
@@ -35,19 +35,21 @@ import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityField
 import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntityFields
 import org.matrix.android.sdk.internal.database.model.RoomAccountDataEntityFields
 import org.matrix.android.sdk.internal.database.model.RoomEntityFields
+import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields
 import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType
 import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
 import org.matrix.android.sdk.internal.database.model.RoomTagEntityFields
 import org.matrix.android.sdk.internal.database.model.SpaceChildSummaryEntityFields
 import org.matrix.android.sdk.internal.database.model.SpaceParentSummaryEntityFields
 import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
+import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntityFields
 import org.matrix.android.sdk.internal.di.MoshiProvider
 import org.matrix.android.sdk.internal.query.process
 import timber.log.Timber
 
 internal object RealmSessionStoreMigration : RealmMigration {
 
-    const val SESSION_STORE_SCHEMA_VERSION = 17L
+    const val SESSION_STORE_SCHEMA_VERSION = 18L
 
     override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
         Timber.v("Migrating Realm Session from $oldVersion to $newVersion")
@@ -69,6 +71,7 @@ internal object RealmSessionStoreMigration : RealmMigration {
         if (oldVersion <= 14) migrateTo15(realm)
         if (oldVersion <= 15) migrateTo16(realm)
         if (oldVersion <= 16) migrateTo17(realm)
+        if (oldVersion <= 17) migrateTo18(realm)
     }
 
     private fun migrateTo1(realm: DynamicRealm) {
@@ -338,4 +341,27 @@ internal object RealmSessionStoreMigration : RealmMigration {
         realm.schema.get("EventInsertEntity")
                 ?.addField(EventInsertEntityFields.CAN_BE_PROCESSED, Boolean::class.java)
     }
+
+    private fun migrateTo18(realm: DynamicRealm) {
+        Timber.d("Step 17 -> 18")
+        realm.schema.create("UserPresenceEntity")
+                ?.addField(UserPresenceEntityFields.USER_ID, String::class.java)
+                ?.addPrimaryKey(UserPresenceEntityFields.USER_ID)
+                ?.setRequired(UserPresenceEntityFields.USER_ID, true)
+                ?.addField(UserPresenceEntityFields.PRESENCE_STR, String::class.java)
+                ?.addField(UserPresenceEntityFields.LAST_ACTIVE_AGO, Long::class.java)
+                ?.setNullable(UserPresenceEntityFields.LAST_ACTIVE_AGO, true)
+                ?.addField(UserPresenceEntityFields.STATUS_MESSAGE, String::class.java)
+                ?.addField(UserPresenceEntityFields.IS_CURRENTLY_ACTIVE, Boolean::class.java)
+                ?.setNullable(UserPresenceEntityFields.IS_CURRENTLY_ACTIVE, true)
+                ?.addField(UserPresenceEntityFields.AVATAR_URL, String::class.java)
+                ?.addField(UserPresenceEntityFields.DISPLAY_NAME, String::class.java)
+
+        val userPresenceEntity = realm.schema.get("UserPresenceEntity") ?: return
+        realm.schema.get("RoomSummaryEntity")
+                ?.addRealmObjectField(RoomSummaryEntityFields.DIRECT_USER_PRESENCE.`$`, userPresenceEntity)
+
+        realm.schema.get("RoomMemberSummaryEntity")
+                ?.addRealmObjectField(RoomMemberSummaryEntityFields.USER_PRESENCE_ENTITY.`$`, userPresenceEntity)
+    }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomMemberSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomMemberSummaryMapper.kt
index 2365a39567..efd9b68011 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomMemberSummaryMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomMemberSummaryMapper.kt
@@ -18,12 +18,14 @@ package org.matrix.android.sdk.internal.database.mapper
 
 import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
 import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity
+import org.matrix.android.sdk.internal.database.model.presence.toUserPresence
 
 internal object RoomMemberSummaryMapper {
 
     fun map(roomMemberSummaryEntity: RoomMemberSummaryEntity): RoomMemberSummary {
         return RoomMemberSummary(
                 userId = roomMemberSummaryEntity.userId,
+                userPresence = roomMemberSummaryEntity.userPresenceEntity?.toUserPresence(),
                 avatarUrl = roomMemberSummaryEntity.avatarUrl,
                 displayName = roomMemberSummaryEntity.displayName,
                 membership = roomMemberSummaryEntity.membership
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt
index 0cf431c340..5900ef6b76 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt
@@ -22,6 +22,7 @@ import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
 import org.matrix.android.sdk.api.session.room.model.SpaceParentInfo
 import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
 import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
+import org.matrix.android.sdk.internal.database.model.presence.toUserPresence
 import org.matrix.android.sdk.internal.session.typing.DefaultTypingUsersTracker
 import javax.inject.Inject
 
@@ -48,6 +49,7 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa
                 joinRules = roomSummaryEntity.joinRules,
                 isDirect = roomSummaryEntity.isDirect,
                 directUserId = roomSummaryEntity.directUserId,
+                directUserPresence = roomSummaryEntity.directUserPresence?.toUserPresence(),
                 latestPreviewableEvent = latestEvent,
                 joinedMembersCount = roomSummaryEntity.joinedMembersCount,
                 invitedMembersCount = roomSummaryEntity.invitedMembersCount,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMemberSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMemberSummaryEntity.kt
index 75771ff12c..a8a76d1681 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMemberSummaryEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMemberSummaryEntity.kt
@@ -21,6 +21,7 @@ import io.realm.annotations.Index
 import io.realm.annotations.PrimaryKey
 import org.matrix.android.sdk.api.session.room.model.Membership
 import org.matrix.android.sdk.api.util.MatrixItem
+import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity
 
 internal open class RoomMemberSummaryEntity(@PrimaryKey var primaryKey: String = "",
                                             @Index var userId: String = "",
@@ -40,6 +41,11 @@ internal open class RoomMemberSummaryEntity(@PrimaryKey var primaryKey: String =
             membershipStr = value.name
         }
 
+    var userPresenceEntity: UserPresenceEntity? = null
+        set(value) {
+            if (value != field) field = value
+        }
+
     fun toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl)
 
     companion object
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt
index 64dc08e827..88b8886936 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt
@@ -27,6 +27,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
 import org.matrix.android.sdk.api.session.room.model.VersioningState
 import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
+import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity
 
 internal open class RoomSummaryEntity(
         @PrimaryKey var roomId: String = "",
@@ -204,6 +205,11 @@ internal open class RoomSummaryEntity(
             if (value != field) field = value
         }
 
+    var directUserPresence: UserPresenceEntity? = null
+        set(value) {
+            if (value != field) field = value
+        }
+
     var hasFailedSending: Boolean = false
         set(value) {
             if (value != field) field = value
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt
index 19472e21d9..c090777972 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt
@@ -17,6 +17,7 @@
 package org.matrix.android.sdk.internal.database.model
 
 import io.realm.annotations.RealmModule
+import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity
 
 /**
  * Realm module for Session
@@ -64,6 +65,7 @@ import io.realm.annotations.RealmModule
             WellknownIntegrationManagerConfigEntity::class,
             RoomAccountDataEntity::class,
             SpaceChildSummaryEntity::class,
-            SpaceParentSummaryEntity::class
+            SpaceParentSummaryEntity::class,
+            UserPresenceEntity::class
         ])
 internal class SessionRealmModule
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/presence/UserPresenceEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/presence/UserPresenceEntity.kt
new file mode 100644
index 0000000000..5713337ec5
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/presence/UserPresenceEntity.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.database.model.presence
+
+import io.realm.RealmObject
+import io.realm.annotations.PrimaryKey
+import org.matrix.android.sdk.api.session.presence.model.PresenceEnum
+import org.matrix.android.sdk.api.session.presence.model.UserPresence
+
+internal open class UserPresenceEntity(@PrimaryKey var userId: String = "",
+                                       var lastActiveAgo: Long? = null,
+                                       var statusMessage: String? = null,
+                                       var isCurrentlyActive: Boolean? = null,
+                                       var avatarUrl: String? = null,
+                                       var displayName: String? = null
+) : RealmObject() {
+
+    var presence: PresenceEnum
+        get() {
+            return PresenceEnum.valueOf(presenceStr)
+        }
+        set(value) {
+            presenceStr = value.name
+        }
+
+    private var presenceStr: String = PresenceEnum.UNAVAILABLE.name
+
+    companion object
+}
+
+internal fun UserPresenceEntity.toUserPresence() =
+        UserPresence(
+                lastActiveAgo,
+                statusMessage,
+                isCurrentlyActive,
+                presence
+        )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomMemberEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomMemberEntityQueries.kt
index a19a9cf725..1ea06b9dfb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomMemberEntityQueries.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomMemberEntityQueries.kt
@@ -21,6 +21,7 @@ import io.realm.RealmQuery
 import io.realm.kotlin.where
 import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity
 import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields
+import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity
 
 internal fun RoomMemberSummaryEntity.Companion.where(realm: Realm, roomId: String, userId: String? = null): RealmQuery<RoomMemberSummaryEntity> {
     val query = realm
@@ -32,3 +33,13 @@ internal fun RoomMemberSummaryEntity.Companion.where(realm: Realm, roomId: Strin
     }
     return query
 }
+
+internal fun RoomMemberSummaryEntity.Companion.updateUserPresence(realm: Realm, userId: String, userPresenceEntity: UserPresenceEntity) {
+    realm.where<RoomMemberSummaryEntity>()
+            .equalTo(RoomMemberSummaryEntityFields.USER_ID, userId)
+            .isNull(RoomMemberSummaryEntityFields.USER_PRESENCE_ENTITY.`$`)
+            .findAll()
+            .map {
+                it.userPresenceEntity = userPresenceEntity
+            }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomSummaryEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomSummaryEntityQueries.kt
index 5294f849af..d1b05a4932 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomSummaryEntityQueries.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomSummaryEntityQueries.kt
@@ -23,6 +23,7 @@ import io.realm.kotlin.createObject
 import io.realm.kotlin.where
 import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
 import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
+import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity
 
 internal fun RoomSummaryEntity.Companion.where(realm: Realm, roomId: String? = null): RealmQuery<RoomSummaryEntity> {
     val query = realm.where<RoomSummaryEntity>()
@@ -67,3 +68,11 @@ internal fun RoomSummaryEntity.Companion.isDirect(realm: Realm, roomId: String):
             .findAll()
             .isNotEmpty()
 }
+
+internal fun RoomSummaryEntity.Companion.updateDirectUserPresence(realm: Realm, directUserId: String, userPresenceEntity: UserPresenceEntity) {
+    RoomSummaryEntity.where(realm)
+            .equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
+            .equalTo(RoomSummaryEntityFields.DIRECT_USER_ID, directUserId)
+            .findFirst()
+            ?.directUserPresence = userPresenceEntity
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/UserPresenceEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/UserPresenceEntityQueries.kt
new file mode 100644
index 0000000000..22790b6f4f
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/UserPresenceEntityQueries.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.database.query
+
+import io.realm.Realm
+import io.realm.RealmQuery
+import io.realm.kotlin.where
+import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity
+import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntityFields
+
+internal fun UserPresenceEntity.Companion.where(realm: Realm, userId: String): RealmQuery<UserPresenceEntity> {
+    return realm
+            .where<UserPresenceEntity>()
+            .equalTo(UserPresenceEntityFields.USER_ID, userId)
+}
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 7260517ec5..6a6bce5ce2 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
@@ -46,6 +46,7 @@ import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerS
 import org.matrix.android.sdk.api.session.media.MediaService
 import org.matrix.android.sdk.api.session.openid.OpenIdService
 import org.matrix.android.sdk.api.session.permalinks.PermalinkService
+import org.matrix.android.sdk.api.session.presence.PresenceService
 import org.matrix.android.sdk.api.session.profile.ProfileService
 import org.matrix.android.sdk.api.session.pushers.PushersService
 import org.matrix.android.sdk.api.session.room.RoomDirectoryService
@@ -129,6 +130,7 @@ internal class DefaultSession @Inject constructor(
         private val callSignalingService: Lazy<CallSignalingService>,
         private val spaceService: Lazy<SpaceService>,
         private val openIdService: Lazy<OpenIdService>,
+        private val presenceService: Lazy<PresenceService>,
         @UnauthenticatedWithCertificate
         private val unauthenticatedWithCertificateOkHttpClient: Lazy<OkHttpClient>
 ) : Session,
@@ -147,6 +149,7 @@ internal class DefaultSession @Inject constructor(
         SecureStorageService by secureStorageService.get(),
         HomeServerCapabilitiesService by homeServerCapabilitiesService.get(),
         ProfileService by profileService.get(),
+        PresenceService by presenceService.get(),
         AccountService by accountService.get() {
 
     override val sharedSecretStorageService: SharedSecretStorageService
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 4c2498ab8b..bc8a707530 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
@@ -44,6 +44,7 @@ import org.matrix.android.sdk.internal.session.identity.IdentityModule
 import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManagerModule
 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.presence.di.PresenceModule
 import org.matrix.android.sdk.internal.session.profile.ProfileModule
 import org.matrix.android.sdk.internal.session.pushers.AddPusherWorker
 import org.matrix.android.sdk.internal.session.pushers.PushersModule
@@ -96,6 +97,7 @@ import org.matrix.android.sdk.internal.util.system.SystemModule
             SearchModule::class,
             ThirdPartyModule::class,
             SpaceModule::class,
+            PresenceModule::class,
             RequestModule::class
         ]
 )
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/PresenceAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/PresenceAPI.kt
new file mode 100644
index 0000000000..53d0d5e963
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/PresenceAPI.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.presence
+
+import org.matrix.android.sdk.internal.network.NetworkConstants
+import org.matrix.android.sdk.internal.session.presence.model.GetPresenceResponse
+import org.matrix.android.sdk.internal.session.presence.model.SetPresenceBody
+import retrofit2.http.Body
+import retrofit2.http.GET
+import retrofit2.http.PUT
+import retrofit2.http.Path
+
+internal interface PresenceAPI {
+
+    /**
+     * Set the presence status of the current user
+     * Ref: https://matrix.org/docs/spec/client_server/latest#put-matrix-client-r0-presence-userid-status
+     */
+    @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "presence/{userId}/status")
+    suspend fun setPresence(@Path("userId") userId: String,
+                            @Body body: SetPresenceBody)
+
+    /**
+     * Get the given user's presence state.
+     * Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-presence-userid-status
+     */
+    @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "presence/{userId}/status")
+    suspend fun getPresence(@Path("userId") userId: String): GetPresenceResponse
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/di/PresenceModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/di/PresenceModule.kt
new file mode 100644
index 0000000000..6b2ee76046
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/di/PresenceModule.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.presence.di
+
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import org.matrix.android.sdk.api.session.presence.PresenceService
+import org.matrix.android.sdk.internal.session.SessionScope
+import org.matrix.android.sdk.internal.session.presence.PresenceAPI
+import org.matrix.android.sdk.internal.session.presence.service.DefaultPresenceService
+import org.matrix.android.sdk.internal.session.presence.service.task.DefaultGetPresenceTask
+import org.matrix.android.sdk.internal.session.presence.service.task.DefaultSetPresenceTask
+import org.matrix.android.sdk.internal.session.presence.service.task.GetPresenceTask
+import org.matrix.android.sdk.internal.session.presence.service.task.SetPresenceTask
+import retrofit2.Retrofit
+
+@Module
+internal abstract class PresenceModule {
+
+    @Module
+    companion object {
+        @Provides
+        @JvmStatic
+        @SessionScope
+        fun providesPresenceAPI(retrofit: Retrofit): PresenceAPI {
+            return retrofit.create(PresenceAPI::class.java)
+        }
+    }
+
+    @Binds
+    abstract fun bindPresenceService(service: DefaultPresenceService): PresenceService
+
+    @Binds
+    abstract fun bindSetPresenceTask(task: DefaultSetPresenceTask): SetPresenceTask
+
+    @Binds
+    abstract fun bindGetPresenceTask(task: DefaultGetPresenceTask): GetPresenceTask
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/GetPresenceResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/GetPresenceResponse.kt
new file mode 100644
index 0000000000..a7552f7b02
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/GetPresenceResponse.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.presence.model
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+import org.matrix.android.sdk.api.session.presence.model.PresenceEnum
+
+@JsonClass(generateAdapter = true)
+data class GetPresenceResponse(
+        @Json(name = "presence")
+        val presence: PresenceEnum,
+        @Json(name = "last_active_ago")
+        val lastActiveAgo: Long? = null,
+        @Json(name = "status_msg")
+        val message: String? = null,
+        @Json(name = "currently_active")
+        val isCurrentlyActive: Boolean? = null
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/PresenceContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/PresenceContent.kt
new file mode 100644
index 0000000000..45e0fcf06e
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/PresenceContent.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.presence.model
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+import org.matrix.android.sdk.api.session.presence.model.PresenceEnum
+
+/**
+ * Class representing the EventType.PRESENCE event content
+ */
+@JsonClass(generateAdapter = true)
+data class PresenceContent(
+        /**
+         * Required. The presence state for this user. One of: ["online", "offline", "unavailable"]
+         */
+        @Json(name = "presence") val presence: PresenceEnum,
+        /**
+         * The last time since this used performed some action, in milliseconds.
+         */
+        @Json(name = "last_active_ago") val lastActiveAgo: Long? = null,
+        /**
+         * An optional description to accompany the presence.
+         */
+        @Json(name = "status_msg") val statusMessage: String? = null,
+        /**
+         * Whether the user is currently active
+         */
+        @Json(name = "currently_active") val isCurrentlyActive: Boolean = false,
+        /**
+         * The current avatar URL for this user, if any.
+         */
+        @Json(name = "avatar_url") val avatarUrl: String? = null,
+        /**
+         * The current display name for this user, if any.
+         */
+        @Json(name = "displayname") val displayName: String? = null
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/SetPresenceBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/SetPresenceBody.kt
new file mode 100644
index 0000000000..0c81791ec0
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/SetPresenceBody.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.presence.model
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+import org.matrix.android.sdk.api.session.presence.model.PresenceEnum
+
+@JsonClass(generateAdapter = true)
+internal data class SetPresenceBody(
+        @Json(name = "presence")
+        val presence: PresenceEnum,
+        @Json(name = "status_msg")
+        val statusMsg: String?
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/DefaultPresenceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/DefaultPresenceService.kt
new file mode 100644
index 0000000000..1083d5b4c2
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/DefaultPresenceService.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.presence.service
+
+import org.matrix.android.sdk.api.session.presence.PresenceService
+import org.matrix.android.sdk.api.session.presence.model.PresenceEnum
+import org.matrix.android.sdk.api.session.presence.model.UserPresence
+import org.matrix.android.sdk.internal.di.UserId
+import org.matrix.android.sdk.internal.session.presence.service.task.GetPresenceTask
+import org.matrix.android.sdk.internal.session.presence.service.task.SetPresenceTask
+import javax.inject.Inject
+
+internal class DefaultPresenceService @Inject constructor(
+        @UserId private val userId: String,
+        private val setPresenceTask: SetPresenceTask,
+        private val getPresenceTask: GetPresenceTask
+) : PresenceService {
+
+    override suspend fun setMyPresence(presence: PresenceEnum, statusMsg: String?) {
+        setPresenceTask.execute(SetPresenceTask.Params(userId, presence, statusMsg))
+    }
+
+    override suspend fun fetchPresence(userId: String): UserPresence {
+        val result = getPresenceTask.execute(GetPresenceTask.Params(userId))
+
+        return UserPresence(
+                lastActiveAgo = result.lastActiveAgo,
+                statusMessage = result.message,
+                isCurrentlyActive = result.isCurrentlyActive,
+                presence = result.presence
+        )
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/task/GetPresenceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/task/GetPresenceTask.kt
new file mode 100644
index 0000000000..bb628dbab4
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/task/GetPresenceTask.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.presence.service.task
+
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
+import org.matrix.android.sdk.internal.network.executeRequest
+import org.matrix.android.sdk.internal.session.presence.PresenceAPI
+import org.matrix.android.sdk.internal.session.presence.model.GetPresenceResponse
+import org.matrix.android.sdk.internal.task.Task
+import javax.inject.Inject
+
+internal abstract class GetPresenceTask : Task<GetPresenceTask.Params, GetPresenceResponse> {
+    data class Params(
+            val userId: String
+    )
+}
+
+internal class DefaultGetPresenceTask @Inject constructor(
+        private val presenceAPI: PresenceAPI,
+        private val globalErrorReceiver: GlobalErrorReceiver
+) : GetPresenceTask() {
+    override suspend fun execute(params: Params): GetPresenceResponse {
+        return executeRequest(globalErrorReceiver) {
+            presenceAPI.getPresence(params.userId)
+        }
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/task/SetPresenceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/task/SetPresenceTask.kt
new file mode 100644
index 0000000000..1b3bdabd30
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/task/SetPresenceTask.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.matrix.android.sdk.internal.session.presence.service.task
+
+import org.matrix.android.sdk.api.session.presence.model.PresenceEnum
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
+import org.matrix.android.sdk.internal.network.executeRequest
+import org.matrix.android.sdk.internal.session.presence.PresenceAPI
+import org.matrix.android.sdk.internal.session.presence.model.SetPresenceBody
+import org.matrix.android.sdk.internal.task.Task
+import javax.inject.Inject
+
+internal abstract class SetPresenceTask : Task<SetPresenceTask.Params, Any> {
+    data class Params(
+            val userId: String,
+            val presence: PresenceEnum,
+            val statusMsg: String?
+    )
+}
+
+internal class DefaultSetPresenceTask @Inject constructor(
+        private val presenceAPI: PresenceAPI,
+        private val globalErrorReceiver: GlobalErrorReceiver
+) : SetPresenceTask() {
+
+    override suspend fun execute(params: Params): Any {
+        return executeRequest(globalErrorReceiver) {
+            val setPresenceBody = SetPresenceBody(params.presence, params.statusMsg)
+            presenceAPI.setPresence(params.userId, setPresenceBody)
+        }
+    }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEntityFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEntityFactory.kt
index f78b5d7992..d6a04de5e7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEntityFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEntityFactory.kt
@@ -18,10 +18,11 @@ package org.matrix.android.sdk.internal.session.room.membership
 
 import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
 import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity
+import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity
 
 internal object RoomMemberEntityFactory {
 
-    fun create(roomId: String, userId: String, roomMember: RoomMemberContent): RoomMemberSummaryEntity {
+    fun create(roomId: String, userId: String, roomMember: RoomMemberContent, presence: UserPresenceEntity?): RoomMemberSummaryEntity {
         val primaryKey = "${roomId}_$userId"
         return RoomMemberSummaryEntity(
                 primaryKey = primaryKey,
@@ -31,6 +32,7 @@ internal object RoomMemberEntityFactory {
                 avatarUrl = roomMember.avatarUrl
         ).apply {
             membership = roomMember.membership
+            userPresenceEntity = presence
         }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt
index 7528f80cc2..25c124bd6b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt
@@ -20,6 +20,9 @@ import io.realm.Realm
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.EventType
 import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
+import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity
+import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity
+import org.matrix.android.sdk.internal.database.query.where
 import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.session.events.getFixedRoomMemberContent
 import org.matrix.android.sdk.internal.session.sync.SyncResponsePostTreatmentAggregator
@@ -47,7 +50,13 @@ internal class RoomMemberEventHandler @Inject constructor(
         if (roomMember == null) {
             return false
         }
-        val roomMemberEntity = RoomMemberEntityFactory.create(roomId, userId, roomMember)
+        val roomMemberEntity = RoomMemberEntityFactory.create(
+                roomId,
+                userId,
+                roomMember,
+                // When an update is happening, insertOrUpdate replace existing values with null if they are not provided,
+                // but we want to preserve presence record value and not replace it with null
+                getExistingPresenceState(realm, roomId, userId))
         realm.insertOrUpdate(roomMemberEntity)
         if (roomMember.membership.isActive()) {
             val userEntity = UserEntityFactory.create(userId, roomMember)
@@ -60,7 +69,15 @@ internal class RoomMemberEventHandler @Inject constructor(
         if (mxId != null && mxId != myUserId) {
             aggregator?.directChatsToCheck?.put(roomId, mxId)
         }
-
         return true
     }
+
+    /**
+     * Get the already existing presence state for a specific user & room in order NOT to be replaced in RoomMemberSummaryEntity
+     * by NULL value.
+     */
+
+    private fun getExistingPresenceState(realm: Realm, roomId: String, userId: String): UserPresenceEntity? {
+        return RoomMemberSummaryEntity.where(realm, roomId, userId).findFirst()?.userPresenceEntity
+    }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt
index eb48958afb..64f1cc34f1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt
@@ -30,8 +30,8 @@ import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.executeRequest
 import org.matrix.android.sdk.internal.session.room.RoomAPI
-import org.matrix.android.sdk.internal.session.sync.ReadReceiptHandler
-import org.matrix.android.sdk.internal.session.sync.RoomFullyReadHandler
+import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler
+import org.matrix.android.sdk.internal.session.sync.handler.room.RoomFullyReadHandler
 import org.matrix.android.sdk.internal.task.Task
 import org.matrix.android.sdk.internal.util.awaitTransaction
 import timber.log.Timber
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
index c0e428ec85..0c917448cc 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt
@@ -42,7 +42,7 @@ import org.matrix.android.sdk.internal.database.query.findAllInRoomWithSendState
 import org.matrix.android.sdk.internal.database.query.where
 import org.matrix.android.sdk.internal.database.query.whereRoomId
 import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
-import org.matrix.android.sdk.internal.session.sync.ReadReceiptHandler
+import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler
 import org.matrix.android.sdk.internal.task.TaskExecutor
 import org.matrix.android.sdk.internal.task.configureWith
 import org.matrix.android.sdk.internal.util.Debouncer
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt
index 8de36d0427..47e8f7e3a3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt
@@ -37,7 +37,7 @@ import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
 import org.matrix.android.sdk.internal.database.query.where
 import org.matrix.android.sdk.internal.di.SessionDatabase
 import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
-import org.matrix.android.sdk.internal.session.sync.ReadReceiptHandler
+import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler
 import org.matrix.android.sdk.internal.task.TaskExecutor
 
 internal class DefaultTimelineService @AssistedInject constructor(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt
index 8c7401ab47..335f619623 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt
@@ -33,6 +33,12 @@ import org.matrix.android.sdk.internal.session.group.GetGroupDataWorker
 import org.matrix.android.sdk.internal.session.initsync.ProgressReporter
 import org.matrix.android.sdk.internal.session.initsync.reportSubtask
 import org.matrix.android.sdk.internal.session.notification.ProcessEventForPushTask
+import org.matrix.android.sdk.internal.session.sync.handler.CryptoSyncHandler
+import org.matrix.android.sdk.internal.session.sync.handler.GroupSyncHandler
+import org.matrix.android.sdk.internal.session.sync.handler.PresenceSyncHandler
+import org.matrix.android.sdk.internal.session.sync.handler.SyncResponsePostTreatmentAggregatorHandler
+import org.matrix.android.sdk.internal.session.sync.handler.UserAccountDataSyncHandler
+import org.matrix.android.sdk.internal.session.sync.handler.room.RoomSyncHandler
 import org.matrix.android.sdk.internal.util.awaitTransaction
 import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
 import timber.log.Timber
@@ -55,7 +61,9 @@ internal class SyncResponseHandler @Inject constructor(
         private val cryptoService: DefaultCryptoService,
         private val tokenStore: SyncTokenStore,
         private val processEventForPushTask: ProcessEventForPushTask,
-        private val pushRuleService: PushRuleService) {
+        private val pushRuleService: PushRuleService,
+        private val presenceSyncHandler: PresenceSyncHandler
+) {
 
     suspend fun handleResponse(syncResponse: SyncResponse,
                                fromToken: String?,
@@ -118,6 +126,13 @@ internal class SyncResponseHandler @Inject constructor(
             }.also {
                 Timber.v("Finish handling accountData in $it ms")
             }
+
+            measureTimeMillis {
+                Timber.v("Handle Presence")
+                presenceSyncHandler.handle(realm, syncResponse.presence)
+            }.also {
+                Timber.v("Finish handling Presence in $it ms")
+            }
             tokenStore.saveToken(realm, syncResponse.nextBatch)
         }
 
@@ -145,7 +160,8 @@ internal class SyncResponseHandler @Inject constructor(
     private fun dispatchInvitedRoom(roomsSyncResponse: RoomsSyncResponse) {
         roomsSyncResponse.invite.keys.forEach { roomId ->
             sessionListeners.dispatch { session, listener ->
-                listener.onNewInvitedRoom(session, roomId) }
+                listener.onNewInvitedRoom(session, roomId)
+            }
         }
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt
index cc4ccc2e46..fe44531390 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt
@@ -5,7 +5,7 @@
  * 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
+ *    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,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/CryptoSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt
similarity index 98%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/CryptoSyncHandler.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt
index cec5689a82..c5ec34176c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/CryptoSyncHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.internal.session.sync
+package org.matrix.android.sdk.internal.session.sync.handler
 
 import org.matrix.android.sdk.api.session.crypto.MXCryptoError
 import org.matrix.android.sdk.api.session.events.model.Event
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/GroupSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/GroupSyncHandler.kt
similarity index 98%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/GroupSyncHandler.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/GroupSyncHandler.kt
index 2b054e578f..552462e25e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/GroupSyncHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/GroupSyncHandler.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.internal.session.sync
+package org.matrix.android.sdk.internal.session.sync.handler
 
 import io.realm.Realm
 import org.matrix.android.sdk.api.session.initsync.InitSyncStep
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/PresenceSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/PresenceSyncHandler.kt
new file mode 100644
index 0000000000..fe173a35c3
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/PresenceSyncHandler.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.sync.handler
+
+import io.realm.Realm
+import org.matrix.android.sdk.api.session.events.model.EventType
+import org.matrix.android.sdk.api.session.events.model.getPresenceContent
+import org.matrix.android.sdk.api.session.sync.model.PresenceSyncResponse
+import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity
+import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
+import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity
+import org.matrix.android.sdk.internal.database.query.updateDirectUserPresence
+import org.matrix.android.sdk.internal.database.query.updateUserPresence
+import javax.inject.Inject
+
+internal class PresenceSyncHandler @Inject constructor() {
+
+    fun handle(realm: Realm, presenceSyncResponse: PresenceSyncResponse?) {
+        presenceSyncResponse?.events
+                ?.filter { event -> event.type == EventType.PRESENCE }
+                ?.forEach { event ->
+                    val content = event.getPresenceContent() ?: return@forEach
+                    val userId = event.senderId ?: return@forEach
+                    val userPresenceEntity = UserPresenceEntity(
+                            userId = userId,
+                            lastActiveAgo = content.lastActiveAgo,
+                            statusMessage = content.statusMessage,
+                            isCurrentlyActive = content.isCurrentlyActive,
+                            avatarUrl = content.avatarUrl,
+                            displayName = content.displayName
+                    ).also {
+                        it.presence = content.presence
+                    }
+
+                    storePresenceToDB(realm, userPresenceEntity)
+                }
+    }
+
+    /**
+     * Store user presence to DB and update Direct Rooms and Room Member Summaries accordingly
+     */
+    private fun storePresenceToDB(realm: Realm, userPresenceEntity: UserPresenceEntity) =
+            realm.copyToRealmOrUpdate(userPresenceEntity)?.apply {
+                RoomSummaryEntity.updateDirectUserPresence(realm, userPresenceEntity.userId, this)
+                RoomMemberSummaryEntity.updateUserPresence(realm, userPresenceEntity.userId, this)
+            }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregatorHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt
similarity index 93%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregatorHandler.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt
index db1100d76c..1e0e87a450 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregatorHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt
@@ -14,9 +14,11 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.internal.session.sync
+package org.matrix.android.sdk.internal.session.sync.handler
 
 import org.matrix.android.sdk.api.MatrixPatterns
+import org.matrix.android.sdk.internal.session.sync.RoomSyncEphemeralTemporaryStore
+import org.matrix.android.sdk.internal.session.sync.SyncResponsePostTreatmentAggregator
 import org.matrix.android.sdk.internal.session.sync.model.accountdata.toMutable
 import org.matrix.android.sdk.internal.session.user.accountdata.DirectChatsHelper
 import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/UserAccountDataSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UserAccountDataSyncHandler.kt
similarity index 99%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/UserAccountDataSyncHandler.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UserAccountDataSyncHandler.kt
index a9926c70aa..3e38cd7839 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/UserAccountDataSyncHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UserAccountDataSyncHandler.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.internal.session.sync
+package org.matrix.android.sdk.internal.session.sync.handler
 
 import com.zhuinden.monarchy.Monarchy
 import io.realm.Realm
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ReadReceiptHandler.kt
similarity index 96%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ReadReceiptHandler.kt
index fc1a2c3870..025ee329f8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ReadReceiptHandler.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.internal.session.sync
+package org.matrix.android.sdk.internal.session.sync.handler.room
 
 import io.realm.Realm
 import org.matrix.android.sdk.api.session.events.model.EventType
@@ -23,6 +23,8 @@ import org.matrix.android.sdk.internal.database.model.ReadReceiptsSummaryEntity
 import org.matrix.android.sdk.internal.database.query.createUnmanaged
 import org.matrix.android.sdk.internal.database.query.getOrCreate
 import org.matrix.android.sdk.internal.database.query.where
+import org.matrix.android.sdk.internal.session.sync.RoomSyncEphemeralTemporaryStore
+import org.matrix.android.sdk.internal.session.sync.SyncResponsePostTreatmentAggregator
 import timber.log.Timber
 import javax.inject.Inject
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomFullyReadHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomFullyReadHandler.kt
similarity index 95%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomFullyReadHandler.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomFullyReadHandler.kt
index 3d0db212c2..b5c8a099d3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomFullyReadHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomFullyReadHandler.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.internal.session.sync
+package org.matrix.android.sdk.internal.session.sync.handler.room
 
 import io.realm.Realm
 import org.matrix.android.sdk.internal.database.model.ReadMarkerEntity
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
similarity index 98%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
index 52e5b6b58d..8c4af81c99 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.internal.session.sync
+package org.matrix.android.sdk.internal.session.sync.handler.room
 
 import io.realm.Realm
 import io.realm.kotlin.createObject
@@ -62,6 +62,9 @@ import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater
 import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection
 import org.matrix.android.sdk.internal.session.room.timeline.TimelineInput
 import org.matrix.android.sdk.internal.session.room.typing.TypingEventContent
+import org.matrix.android.sdk.internal.session.sync.InitialSyncStrategy
+import org.matrix.android.sdk.internal.session.sync.SyncResponsePostTreatmentAggregator
+import org.matrix.android.sdk.internal.session.sync.initialSyncStrategy
 import org.matrix.android.sdk.internal.session.sync.parsing.RoomSyncAccountDataHandler
 import org.matrix.android.sdk.internal.util.computeBestChunkSize
 import timber.log.Timber
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTagHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomTagHandler.kt
similarity index 95%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTagHandler.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomTagHandler.kt
index 8997435be4..55b15624bc 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTagHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomTagHandler.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.internal.session.sync
+package org.matrix.android.sdk.internal.session.sync.handler.room
 
 import io.realm.Realm
 import org.matrix.android.sdk.api.session.room.model.tag.RoomTagContent
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTypingUsersHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomTypingUsersHandler.kt
similarity index 96%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTypingUsersHandler.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomTypingUsersHandler.kt
index 1433d89143..63db13a5b8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTypingUsersHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomTypingUsersHandler.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.matrix.android.sdk.internal.session.sync
+package org.matrix.android.sdk.internal.session.sync.handler.room
 
 import io.realm.Realm
 import org.matrix.android.sdk.api.session.room.sender.SenderInfo
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/RoomSyncAccountDataHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/RoomSyncAccountDataHandler.kt
index 6ca008c5b1..5e7bde87e7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/RoomSyncAccountDataHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/RoomSyncAccountDataHandler.kt
@@ -28,8 +28,8 @@ import org.matrix.android.sdk.internal.database.model.RoomAccountDataEntityField
 import org.matrix.android.sdk.internal.database.model.RoomEntity
 import org.matrix.android.sdk.internal.database.query.getOrCreate
 import org.matrix.android.sdk.internal.session.room.read.FullyReadContent
-import org.matrix.android.sdk.internal.session.sync.RoomFullyReadHandler
-import org.matrix.android.sdk.internal.session.sync.RoomTagHandler
+import org.matrix.android.sdk.internal.session.sync.handler.room.RoomFullyReadHandler
+import org.matrix.android.sdk.internal.session.sync.handler.room.RoomTagHandler
 import javax.inject.Inject
 
 internal class RoomSyncAccountDataHandler @Inject constructor(private val roomTagHandler: RoomTagHandler,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt
index e5c338d511..ddcac475ee 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt
@@ -25,7 +25,7 @@ import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataEvent
 import org.matrix.android.sdk.api.util.Optional
 import org.matrix.android.sdk.internal.di.SessionDatabase
 import org.matrix.android.sdk.internal.session.room.accountdata.RoomAccountDataDataSource
-import org.matrix.android.sdk.internal.session.sync.UserAccountDataSyncHandler
+import org.matrix.android.sdk.internal.session.sync.handler.UserAccountDataSyncHandler
 import org.matrix.android.sdk.internal.task.TaskExecutor
 import org.matrix.android.sdk.internal.task.configureWith
 import org.matrix.android.sdk.internal.util.awaitCallback
diff --git a/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItem.kt
index 6eb06722b9..e6451b34ef 100644
--- a/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItem.kt
+++ b/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItem.kt
@@ -23,6 +23,7 @@ import android.widget.TextView
 import com.airbnb.epoxy.EpoxyModelClass
 import im.vector.app.R
 import im.vector.app.core.epoxy.VectorEpoxyHolder
+import im.vector.app.core.ui.views.PresenceStateImageView
 import im.vector.app.core.ui.views.ShieldImageView
 
 @EpoxyModelClass(layout = R.layout.item_profile_matrix_item)
@@ -31,6 +32,7 @@ abstract class ProfileMatrixItem : BaseProfileMatrixItem<ProfileMatrixItem.Holde
     open class Holder : VectorEpoxyHolder() {
         val titleView by bind<TextView>(R.id.matrixItemTitle)
         val subtitleView by bind<TextView>(R.id.matrixItemSubtitle)
+        val presenceImageView by bind<PresenceStateImageView>(R.id.matrixItemPresenceImageView)
         val avatarImageView by bind<ImageView>(R.id.matrixItemAvatar)
         val avatarDecorationImageView by bind<ShieldImageView>(R.id.matrixItemAvatarDecoration)
         val editableView by bind<View>(R.id.matrixItemEditable)
diff --git a/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItemWithPowerLevelWithPresence.kt b/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItemWithPowerLevelWithPresence.kt
new file mode 100644
index 0000000000..92216cbb38
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/core/epoxy/profiles/ProfileMatrixItemWithPowerLevelWithPresence.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2021 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package im.vector.app.core.epoxy.profiles
+
+import com.airbnb.epoxy.EpoxyAttribute
+import com.airbnb.epoxy.EpoxyModelClass
+import im.vector.app.R
+import org.matrix.android.sdk.api.session.presence.model.UserPresence
+
+@EpoxyModelClass(layout = R.layout.item_profile_matrix_item)
+abstract class ProfileMatrixItemWithPowerLevelWithPresence : ProfileMatrixItemWithPowerLevel() {
+
+    @EpoxyAttribute var userPresence: UserPresence? = null
+
+    override fun bind(holder: Holder) {
+        super.bind(holder)
+        holder.presenceImageView.render(userPresence = userPresence)
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/core/ui/views/PresenceStateImageView.kt b/vector/src/main/java/im/vector/app/core/ui/views/PresenceStateImageView.kt
new file mode 100644
index 0000000000..301f8afdc9
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/core/ui/views/PresenceStateImageView.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.core.ui.views
+
+import android.content.Context
+import android.util.AttributeSet
+import androidx.appcompat.widget.AppCompatImageView
+import androidx.core.view.isVisible
+import im.vector.app.R
+import org.matrix.android.sdk.api.session.presence.model.PresenceEnum
+import org.matrix.android.sdk.api.session.presence.model.UserPresence
+
+/**
+ * Custom ImageView to dynamically render Presence state in multiple screens
+ */
+class PresenceStateImageView @JvmOverloads constructor(
+        context: Context,
+        attrs: AttributeSet? = null,
+        defStyleAttr: Int = 0
+) : AppCompatImageView(context, attrs, defStyleAttr) {
+
+    fun render(showPresence: Boolean = true, userPresence: UserPresence?) {
+        isVisible = showPresence && userPresence != null
+
+        when (userPresence?.presence) {
+            PresenceEnum.ONLINE      -> {
+                setImageResource(R.drawable.ic_presence_online)
+                contentDescription = context.getString(R.string.a11y_presence_online)
+            }
+            PresenceEnum.UNAVAILABLE -> {
+                setImageResource(R.drawable.ic_presence_offline)
+                contentDescription = context.getString(R.string.a11y_presence_unavailable)
+            }
+            PresenceEnum.OFFLINE     -> {
+                setImageResource(R.drawable.ic_presence_offline)
+                contentDescription = context.getString(R.string.a11y_presence_offline)
+            }
+        }
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt
index 26364a55ec..e9948e6cf4 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt
@@ -1427,9 +1427,10 @@ class RoomDetailFragment @Inject constructor(
             views.roomToolbarContentView.isClickable = roomSummary.membership == Membership.JOIN
             views.roomToolbarTitleView.text = roomSummary.displayName
             avatarRenderer.render(roomSummary.toMatrixItem(), views.roomToolbarAvatarImageView)
-
             renderSubTitle(typingMessage, roomSummary.topic)
             views.roomToolbarDecorationImageView.render(roomSummary.roomEncryptionTrustLevel)
+            views.roomToolbarPresenceImageView.render(roomSummary.isDirect, roomSummary.directUserPresence)
+            views.roomToolbarPublicImageView.isVisible = roomSummary.isPublic && !roomSummary.isDirect
         }
     }
 
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItem.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItem.kt
index e4abbeb1ff..fd0ccd17e9 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItem.kt
@@ -32,11 +32,13 @@ import im.vector.app.core.epoxy.VectorEpoxyHolder
 import im.vector.app.core.epoxy.VectorEpoxyModel
 import im.vector.app.core.epoxy.onClick
 import im.vector.app.core.extensions.setTextOrHide
+import im.vector.app.core.ui.views.PresenceStateImageView
 import im.vector.app.core.ui.views.ShieldImageView
 import im.vector.app.features.displayname.getBestName
 import im.vector.app.features.home.AvatarRenderer
 import im.vector.app.features.themes.ThemeUtils
 import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
+import org.matrix.android.sdk.api.session.presence.model.UserPresence
 import org.matrix.android.sdk.api.util.MatrixItem
 
 @EpoxyModelClass(layout = R.layout.item_room)
@@ -53,6 +55,8 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() {
     @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) lateinit var lastFormattedEvent: CharSequence
     @EpoxyAttribute lateinit var lastEventTime: CharSequence
     @EpoxyAttribute var encryptionTrustLevel: RoomEncryptionTrustLevel? = null
+    @EpoxyAttribute var userPresence: UserPresence? = null
+    @EpoxyAttribute var showPresence: Boolean = false
     @EpoxyAttribute var izPublic: Boolean = false
     @EpoxyAttribute var unreadNotificationCount: Int = 0
     @EpoxyAttribute var hasUnreadMessage: Boolean = false
@@ -83,6 +87,7 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() {
         renderSelection(holder, showSelected)
         holder.typingView.setTextOrHide(typingMessage)
         holder.lastEventView.isInvisible = holder.typingView.isVisible
+        holder.roomAvatarPresenceImageView.render(showPresence, userPresence)
     }
 
     override fun unbind(holder: Holder) {
@@ -117,6 +122,7 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() {
         val roomAvatarDecorationImageView by bind<ShieldImageView>(R.id.roomAvatarDecorationImageView)
         val roomAvatarPublicDecorationImageView by bind<ImageView>(R.id.roomAvatarPublicDecorationImageView)
         val roomAvatarFailSendingImageView by bind<ImageView>(R.id.roomAvatarFailSendingImageView)
+        val roomAvatarPresenceImageView by bind<PresenceStateImageView>(R.id.roomAvatarPresenceImageView)
         val rootView by bind<ViewGroup>(R.id.itemRoomLayout)
     }
 }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt
index c06ab33a54..fdb7d3a323 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt
@@ -124,6 +124,8 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
                 // We do not display shield in the room list anymore
                 // .encryptionTrustLevel(roomSummary.roomEncryptionTrustLevel)
                 .izPublic(roomSummary.isPublic)
+                .showPresence(roomSummary.isDirect)
+                .userPresence(roomSummary.directUserPresence)
                 .matrixItem(roomSummary.toMatrixItem())
                 .lastEventTime(latestEventTime)
                 .typingMessage(typingMessage)
diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt
index 4b37d038b5..e07746af85 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt
@@ -219,6 +219,8 @@ class RoomProfileFragment @Inject constructor(
                 avatarRenderer.render(matrixItem, views.matrixProfileToolbarAvatarImageView)
                 headerViews.roomProfileDecorationImageView.render(it.roomEncryptionTrustLevel)
                 views.matrixProfileDecorationToolbarAvatarImageView.render(it.roomEncryptionTrustLevel)
+                headerViews.roomProfilePresenceImageView.render(it.isDirect, it.directUserPresence)
+                headerViews.roomProfilePublicImageView.isVisible = it.isPublic && !it.isDirect
             }
         }
         roomProfileController.setData(state)
diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt
index 8399c9e238..ed0b88c8d3 100644
--- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt
+++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListController.kt
@@ -17,14 +17,19 @@
 package im.vector.app.features.roomprofile.members
 
 import com.airbnb.epoxy.TypedEpoxyController
+import im.vector.app.R
 import im.vector.app.core.epoxy.dividerItem
 import im.vector.app.core.epoxy.profiles.buildProfileSection
 import im.vector.app.core.epoxy.profiles.profileMatrixItem
+import im.vector.app.core.epoxy.profiles.profileMatrixItemWithPowerLevelWithPresence
 import im.vector.app.core.extensions.join
+import im.vector.app.core.resources.ColorProvider
 import im.vector.app.core.resources.StringProvider
 import im.vector.app.features.home.AvatarRenderer
+import me.gujun.android.span.span
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.presence.model.UserPresence
 import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
 import org.matrix.android.sdk.api.session.room.model.RoomThirdPartyInviteContent
 import org.matrix.android.sdk.api.util.MatrixItem
@@ -34,6 +39,7 @@ import javax.inject.Inject
 class RoomMemberListController @Inject constructor(
         private val avatarRenderer: AvatarRenderer,
         private val stringProvider: StringProvider,
+        private val colorProvider: ColorProvider,
         private val roomMemberSummaryFilter: RoomMemberSummaryFilter
 ) : TypedEpoxyController<RoomMemberListViewState>() {
 
@@ -84,17 +90,10 @@ class RoomMemberListController @Inject constructor(
             buildProfileSection(
                     stringProvider.getString(powerLevelCategory.titleRes)
             )
+
             filteredRoomMemberList.join(
                     each = { _, roomMember ->
-                        profileMatrixItem {
-                            id(roomMember.userId)
-                            matrixItem(roomMember.toMatrixItem())
-                            avatarRenderer(host.avatarRenderer)
-                            userEncryptionTrustLevel(data.trustLevelMap.invoke()?.get(roomMember.userId))
-                            clickListener {
-                                host.callback?.onRoomMemberClicked(roomMember)
-                            }
-                        }
+                        buildPresence(roomMember, powerLevelCategory, host, data, roomMember.userPresence)
                     },
                     between = { _, roomMemberBefore ->
                         dividerItem {
@@ -123,6 +122,33 @@ class RoomMemberListController @Inject constructor(
         }
     }
 
+    private fun buildPresence(roomMember: RoomMemberSummary,
+                              powerLevelCategory: RoomMemberListCategories,
+                              host: RoomMemberListController,
+                              data: RoomMemberListViewState,
+                              userPresence: UserPresence?
+    ) {
+        val powerLabel = stringProvider.getString(powerLevelCategory.titleRes)
+
+        profileMatrixItemWithPowerLevelWithPresence {
+            id(roomMember.userId)
+            matrixItem(roomMember.toMatrixItem())
+            avatarRenderer(host.avatarRenderer)
+            userEncryptionTrustLevel(data.trustLevelMap.invoke()?.get(roomMember.userId))
+            clickListener {
+                host.callback?.onRoomMemberClicked(roomMember)
+            }
+            userPresence(userPresence)
+            powerLevelLabel(
+                    span {
+                        span(powerLabel) {
+                            textColor = host.colorProvider.getColorFromAttribute(R.attr.vctr_content_secondary)
+                        }
+                    }
+            )
+        }
+    }
+
     private fun buildThreePidInvites(data: RoomMemberListViewState) {
         val host = this
         data.threePidInvites()
diff --git a/vector/src/main/res/drawable/ic_presence_offline.xml b/vector/src/main/res/drawable/ic_presence_offline.xml
new file mode 100644
index 0000000000..3f0dc251ce
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_presence_offline.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt"
+    android:width="11.89dp"
+    android:height="12dp"
+    android:viewportWidth="11.89"
+    android:viewportHeight="12"
+    >
+
+    <group>
+
+        <clip-path
+            android:pathData="M11.8857 6C11.8857 9.31371 9.225 12 5.94286 12C2.66071 12 0 9.31371 0 6C0 2.68629 2.66071 0 5.94286 0C9.225 0 11.8857 2.68629 11.8857 6Z"
+            />
+
+        <path
+            android:pathData="M0 0V12H11.8857V0"
+            android:fillColor="?vctr_presence_indicator_offline"
+            />
+
+    </group>
+
+</vector>
\ No newline at end of file
diff --git a/vector/src/main/res/drawable/ic_presence_online.xml b/vector/src/main/res/drawable/ic_presence_online.xml
new file mode 100644
index 0000000000..2184f359b2
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_presence_online.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt"
+    android:width="11.89dp"
+    android:height="12dp"
+    android:viewportWidth="11.89"
+    android:viewportHeight="12"
+    >
+
+    <group>
+
+        <clip-path
+            android:pathData="M11.8857 6C11.8857 9.31371 9.225 12 5.94286 12C2.66071 12 0 9.31371 0 6C0 2.68629 2.66071 0 5.94286 0C9.225 0 11.8857 2.68629 11.8857 6Z"
+            />
+
+        <path
+            android:pathData="M0 0V12H11.8857V0"
+            android:fillColor="#0DBD8B"
+            />
+
+    </group>
+
+</vector>
diff --git a/vector/src/main/res/layout/fragment_room_detail.xml b/vector/src/main/res/layout/fragment_room_detail.xml
index 6c58f40bad..c0ac3170e5 100644
--- a/vector/src/main/res/layout/fragment_room_detail.xml
+++ b/vector/src/main/res/layout/fragment_room_detail.xml
@@ -45,19 +45,50 @@
 
                 <im.vector.app.core.ui.views.ShieldImageView
                     android:id="@+id/roomToolbarDecorationImageView"
-                    android:layout_width="20dp"
-                    android:layout_height="20dp"
+                    android:layout_width="17dp"
+                    android:layout_height="17dp"
+                    android:layout_marginStart="5dp"
+                    android:layout_marginTop="2dp"
+                    app:layout_constraintBottom_toBottomOf="@+id/roomToolbarTitleView"
+                    app:layout_constraintStart_toEndOf="@+id/roomToolbarAvatarImageView"
+                    app:layout_constraintTop_toTopOf="@+id/roomToolbarTitleView" />
+
+                <im.vector.app.core.ui.views.PresenceStateImageView
+                    android:id="@+id/roomToolbarPresenceImageView"
+                    android:layout_width="12dp"
+                    android:layout_height="12dp"
+                    android:background="@drawable/background_circle"
+                    android:padding="2dp"
+                    android:visibility="gone"
                     app:layout_constraintCircle="@+id/roomToolbarAvatarImageView"
                     app:layout_constraintCircleAngle="135"
                     app:layout_constraintCircleRadius="20dp"
-                    tools:ignore="MissingConstraints" />
+                    tools:ignore="MissingConstraints"
+                    tools:layout_constraintCircleRadius="8dp"
+                    tools:src="@drawable/ic_presence_offline"
+                    tools:visibility="visible" />
+
+                <ImageView
+                    android:id="@+id/roomToolbarPublicImageView"
+                    android:layout_width="13dp"
+                    android:layout_height="13dp"
+                    android:background="@drawable/background_circle"
+                    android:contentDescription="@string/a11y_public_room"
+                    android:padding="1dp"
+                    android:src="@drawable/ic_public_room"
+                    android:visibility="gone"
+                    app:layout_constraintCircle="@+id/roomToolbarAvatarImageView"
+                    app:layout_constraintCircleAngle="135"
+                    app:layout_constraintCircleRadius="20dp"
+                    tools:ignore="MissingConstraints"
+                    tools:visibility="visible" />
 
                 <TextView
                     android:id="@+id/roomToolbarTitleView"
                     style="@style/Widget.Vector.TextView.HeadlineMedium"
                     android:layout_width="0dp"
                     android:layout_height="wrap_content"
-                    android:layout_marginStart="8dp"
+                    android:layout_marginStart="4dp"
                     android:layout_marginEnd="8dp"
                     android:ellipsize="end"
                     android:maxLines="1"
@@ -65,8 +96,10 @@
                     app:layout_constraintBottom_toTopOf="@+id/roomToolbarSubtitleView"
                     app:layout_constraintEnd_toEndOf="parent"
                     app:layout_constraintHorizontal_bias="0.0"
-                    app:layout_constraintStart_toEndOf="@+id/roomToolbarAvatarImageView"
+                    app:layout_constraintStart_toEndOf="@+id/roomToolbarDecorationImageView"
                     app:layout_constraintTop_toTopOf="parent"
+                    app:layout_constraintVertical_chainStyle="packed"
+                    app:layout_goneMarginStart="7dp"
                     tools:text="@sample/rooms.json/data/name" />
 
                 <TextView
@@ -74,9 +107,8 @@
                     style="@style/Widget.Vector.TextView.Body"
                     android:layout_width="0dp"
                     android:layout_height="wrap_content"
-                    android:layout_marginStart="8dp"
+                    android:layout_marginStart="7dp"
                     android:layout_marginEnd="8dp"
-                    android:layout_marginBottom="8dp"
                     android:ellipsize="end"
                     android:maxLines="1"
                     android:textColor="?vctr_content_primary"
@@ -85,7 +117,8 @@
                     app:layout_constraintHorizontal_bias="0.0"
                     app:layout_constraintStart_toEndOf="@+id/roomToolbarAvatarImageView"
                     app:layout_constraintTop_toBottomOf="@+id/roomToolbarTitleView"
-                    tools:text="@sample/rooms.json/data/topic" />
+                    tools:text="@sample/rooms.json/data/topic"
+                    tools:visibility="visible" />
 
             </androidx.constraintlayout.widget.ConstraintLayout>
 
diff --git a/vector/src/main/res/layout/item_profile_matrix_item.xml b/vector/src/main/res/layout/item_profile_matrix_item.xml
index 636113752b..cea2f62968 100644
--- a/vector/src/main/res/layout/item_profile_matrix_item.xml
+++ b/vector/src/main/res/layout/item_profile_matrix_item.xml
@@ -1,4 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!-- https://tinyurl.com/PresenceListInRooms -->
 <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
@@ -28,19 +29,35 @@
 
     <im.vector.app.core.ui.views.ShieldImageView
         android:id="@+id/matrixItemAvatarDecoration"
-        android:layout_width="20dp"
-        android:layout_height="20dp"
+        android:layout_width="16dp"
+        android:layout_height="16dp"
+        android:layout_marginStart="5dp"
+        android:layout_marginTop="2dp"
+        app:layout_constraintBottom_toBottomOf="@+id/matrixItemTitle"
+        app:layout_constraintStart_toEndOf="@+id/matrixItemAvatar"
+        app:layout_constraintTop_toTopOf="@+id/matrixItemTitle" />
+
+    <im.vector.app.core.ui.views.PresenceStateImageView
+        android:id="@+id/matrixItemPresenceImageView"
+        android:layout_width="12dp"
+        android:layout_height="12dp"
+        android:background="@drawable/background_circle"
+        android:importantForAccessibility="no"
+        android:padding="2dp"
+        android:visibility="gone"
         app:layout_constraintCircle="@+id/matrixItemAvatar"
         app:layout_constraintCircleAngle="135"
         app:layout_constraintCircleRadius="16dp"
-        tools:ignore="MissingConstraints" />
+        tools:ignore="MissingConstraints"
+        tools:src="@drawable/ic_presence_offline"
+        tools:visibility="visible" />
 
     <TextView
         android:id="@+id/matrixItemTitle"
         style="@style/Widget.Vector.TextView.Subtitle"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
-        android:layout_marginStart="16dp"
+        android:layout_marginStart="4dp"
         android:layout_marginEnd="8dp"
         android:ellipsize="end"
         android:maxLines="1"
@@ -48,9 +65,10 @@
         app:layout_constrainedWidth="true"
         app:layout_constraintBottom_toTopOf="@+id/matrixItemSubtitle"
         app:layout_constraintEnd_toStartOf="@+id/matrixItemPowerLevelLabel"
-        app:layout_constraintStart_toEndOf="@id/matrixItemAvatar"
+        app:layout_constraintStart_toEndOf="@+id/matrixItemAvatarDecoration"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_goneMarginEnd="80dp"
+        app:layout_goneMarginStart="7dp"
         tools:text="@sample/users.json/data/displayName" />
 
     <TextView
@@ -58,7 +76,7 @@
         style="@style/Widget.Vector.TextView.Body"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
-        android:layout_marginStart="16dp"
+        android:layout_marginStart="7dp"
         android:layout_marginEnd="8dp"
         android:ellipsize="end"
         android:maxLines="1"
diff --git a/vector/src/main/res/layout/item_room.xml b/vector/src/main/res/layout/item_room.xml
index 601ee14799..e690dd122c 100644
--- a/vector/src/main/res/layout/item_room.xml
+++ b/vector/src/main/res/layout/item_room.xml
@@ -86,6 +86,22 @@
         tools:ignore="MissingConstraints"
         tools:visibility="visible" />
 
+    <im.vector.app.core.ui.views.PresenceStateImageView
+        android:id="@+id/roomAvatarPresenceImageView"
+        android:layout_width="16dp"
+        android:layout_height="16dp"
+        android:background="@drawable/background_circle"
+        android:importantForAccessibility="no"
+        android:padding="2dp"
+        android:visibility="gone"
+        app:layout_constraintCircle="@id/roomAvatarContainer"
+        app:layout_constraintCircleAngle="135"
+        app:layout_constraintCircleRadius="28dp"
+        tools:ignore="MissingConstraints"
+        tools:layout_constraintCircleRadius="8dp"
+        tools:src="@drawable/ic_presence_offline"
+        tools:visibility="visible" />
+
     <!-- Margin bottom does not work, so I use space -->
     <Space
         android:id="@+id/roomAvatarBottomSpace"
diff --git a/vector/src/main/res/layout/view_stub_room_member_profile_header.xml b/vector/src/main/res/layout/view_stub_room_member_profile_header.xml
index e7d930b070..0b3e21a348 100644
--- a/vector/src/main/res/layout/view_stub_room_member_profile_header.xml
+++ b/vector/src/main/res/layout/view_stub_room_member_profile_header.xml
@@ -30,14 +30,33 @@
             app:layout_constraintVertical_chainStyle="spread_inside"
             tools:src="@sample/user_round_avatars" />
 
-        <im.vector.app.core.ui.views.ShieldImageView
-            android:id="@+id/memberProfileDecorationImageView"
-            android:layout_width="48dp"
-            android:layout_height="48dp"
+
+        <im.vector.app.core.ui.views.PresenceStateImageView
+            android:id="@+id/memberProfilePresenceImageView"
+            android:layout_width="26dp"
+            android:layout_height="26dp"
+            android:background="@drawable/background_circle"
+            android:importantForAccessibility="no"
+            android:padding="3dp"
+            android:visibility="gone"
             app:layout_constraintCircle="@+id/memberProfileAvatarView"
             app:layout_constraintCircleAngle="135"
             app:layout_constraintCircleRadius="64dp"
-            tools:ignore="MissingConstraints" />
+            tools:ignore="MissingConstraints"
+            tools:src="@drawable/ic_presence_offline"
+            tools:visibility="visible" />
+
+        <im.vector.app.core.ui.views.ShieldImageView
+            android:id="@+id/memberProfileDecorationImageView"
+            android:layout_width="30dp"
+            android:layout_height="30dp"
+            android:layout_marginTop="2dp"
+            android:layout_marginEnd="8dp"
+            app:layout_constraintBottom_toBottomOf="@+id/memberProfileNameView"
+            app:layout_constraintEnd_toStartOf="@+id/memberProfileNameView"
+            app:layout_constraintHorizontal_chainStyle="packed"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="@+id/memberProfileNameView" />
 
         <TextView
             android:id="@+id/memberProfileNameView"
@@ -48,7 +67,7 @@
             android:textStyle="bold"
             app:layout_constraintBottom_toTopOf="@+id/memberProfileIdView"
             app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintStart_toEndOf="@+id/memberProfileDecorationImageView"
             app:layout_constraintTop_toBottomOf="@+id/memberProfileAvatarView"
             tools:text="@sample/users.json/data/displayName" />
 
diff --git a/vector/src/main/res/layout/view_stub_room_profile_header.xml b/vector/src/main/res/layout/view_stub_room_profile_header.xml
index 7308e8a207..b6fc16e606 100644
--- a/vector/src/main/res/layout/view_stub_room_profile_header.xml
+++ b/vector/src/main/res/layout/view_stub_room_profile_header.xml
@@ -20,25 +20,58 @@
         app:layout_constraintTop_toTopOf="parent"
         tools:src="@sample/room_round_avatars" />
 
-    <im.vector.app.core.ui.views.ShieldImageView
-        android:id="@+id/roomProfileDecorationImageView"
-        android:layout_width="48dp"
-        android:layout_height="48dp"
+    <im.vector.app.core.ui.views.PresenceStateImageView
+        android:id="@+id/roomProfilePresenceImageView"
+        android:layout_width="26dp"
+        android:layout_height="26dp"
+        android:background="@drawable/background_circle"
+        android:importantForAccessibility="no"
+        android:padding="3dp"
+        android:visibility="gone"
         app:layout_constraintCircle="@+id/roomProfileAvatarView"
         app:layout_constraintCircleAngle="135"
         app:layout_constraintCircleRadius="64dp"
-        tools:ignore="MissingConstraints" />
+        tools:ignore="MissingConstraints"
+        tools:src="@drawable/ic_presence_offline"
+        tools:visibility="visible" />
+
+    <ImageView
+        android:id="@+id/roomProfilePublicImageView"
+        android:layout_width="28dp"
+        android:layout_height="28dp"
+        android:background="@drawable/background_circle"
+        android:contentDescription="@string/a11y_public_room"
+        android:padding="2dp"
+        android:src="@drawable/ic_public_room"
+        android:visibility="gone"
+        app:layout_constraintCircle="@+id/roomProfileAvatarView"
+        app:layout_constraintCircleAngle="135"
+        app:layout_constraintCircleRadius="64dp"
+        tools:ignore="MissingConstraints"
+        tools:visibility="visible" />
+
+    <im.vector.app.core.ui.views.ShieldImageView
+        android:id="@+id/roomProfileDecorationImageView"
+        android:layout_width="30dp"
+        android:layout_height="30dp"
+        android:layout_marginTop="2dp"
+        android:layout_marginEnd="8dp"
+        app:layout_constraintBottom_toBottomOf="@+id/roomProfileNameView"
+        app:layout_constraintEnd_toStartOf="@+id/roomProfileNameView"
+        app:layout_constraintHorizontal_chainStyle="packed"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="@+id/roomProfileNameView"/>
 
     <TextView
         android:id="@+id/roomProfileNameView"
         style="@style/Widget.Vector.TextView.Title"
-        android:layout_width="0dp"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:gravity="center"
         android:textStyle="bold"
         app:layout_constraintBottom_toTopOf="@+id/roomProfileAliasView"
         app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/roomProfileDecorationImageView"
         app:layout_constraintTop_toBottomOf="@+id/roomProfileAvatarView"
         tools:text="@sample/rooms.json/data/name" />
 
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index a61ba52a79..7fa4918266 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -3406,6 +3406,9 @@
     <string name="a11y_view_read_receipts">View read receipts</string>
     <string name="a11y_public_room">Public room</string>
     <string name="a11y_public_space">Public space</string>
+    <string name="a11y_presence_online">Online</string>
+    <string name="a11y_presence_offline">Offline</string>
+    <string name="a11y_presence_unavailable">Unavailable</string>
 
     <string name="dev_tools_menu_name">Dev Tools</string>
     <string name="dev_tools_explore_room_state">Explore Room State</string>