diff --git a/changelog.d/7261.wip b/changelog.d/7261.wip
new file mode 100644
index 0000000000..f7063fcc1b
--- /dev/null
+++ b/changelog.d/7261.wip
@@ -0,0 +1 @@
+Adds pusher toggle setting to device manager v2
diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml
index 63a469a8da..63c1f8a8bb 100644
--- a/library/ui-strings/src/main/res/values/strings.xml
+++ b/library/ui-strings/src/main/res/values/strings.xml
@@ -3304,6 +3304,8 @@
     <string name="device_manager_session_overview_signout">Sign out of this session</string>
     <string name="device_manager_session_details_title">Session details</string>
     <string name="device_manager_session_details_description">Application, device, and activity information.</string>
+    <string name="device_manager_push_notifications_title">Push notifications</string>
+    <string name="device_manager_push_notifications_description">Receive push notifications on this session.</string>
     <string name="device_manager_session_details_session_name">Session name</string>
     <string name="device_manager_session_details_session_id">Session ID</string>
     <string name="device_manager_session_details_session_last_activity">Last activity</string>
diff --git a/library/ui-styles/src/main/res/values/stylable_session_overview_entry_view.xml b/library/ui-styles/src/main/res/values/stylable_session_overview_entry_view.xml
index d3884f247d..6428cd6eac 100644
--- a/library/ui-styles/src/main/res/values/stylable_session_overview_entry_view.xml
+++ b/library/ui-styles/src/main/res/values/stylable_session_overview_entry_view.xml
@@ -6,4 +6,10 @@
         <attr name="sessionOverviewEntryDescription" format="string" />
     </declare-styleable>
 
+    <declare-styleable name="SessionOverviewEntrySwitchView">
+        <attr name="sessionOverviewEntrySwitchTitle" format="string" />
+        <attr name="sessionOverviewEntrySwitchDescription" format="string" />
+        <attr name="sessionOverviewEntrySwitchEnabled" format="boolean" />
+    </declare-styleable>
+
 </resources>
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt
index d7958ea3cd..6a27f7af61 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt
@@ -67,6 +67,14 @@ interface PushersService {
             append: Boolean = true
     )
 
+    /**
+     * Enables or disables a registered pusher.
+     *
+     * @param pusher The pusher being toggled
+     * @param enable Whether the pusher should be enabled or disabled
+     */
+    suspend fun togglePusher(pusher: Pusher, enable: Boolean)
+
     /**
      * Directly ask the push gateway to send a push to this device.
      * If successful, the push gateway has accepted the request. In this case, the app should receive a Push with the provided eventId.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt
index 082b5b63eb..e89cfa508c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt
@@ -42,6 +42,7 @@ internal class DefaultPushersService @Inject constructor(
         private val getPusherTask: GetPushersTask,
         private val pushGatewayNotifyTask: PushGatewayNotifyTask,
         private val addPusherTask: AddPusherTask,
+        private val togglePusherTask: TogglePusherTask,
         private val removePusherTask: RemovePusherTask,
         private val taskExecutor: TaskExecutor
 ) : PushersService {
@@ -108,6 +109,24 @@ internal class DefaultPushersService @Inject constructor(
         )
     }
 
+    override suspend fun togglePusher(pusher: Pusher, enable: Boolean) {
+        togglePusherTask.execute(TogglePusherTask.Params(pusher.toJsonPusher(), enable))
+    }
+
+    private fun Pusher.toJsonPusher() = JsonPusher(
+            pushKey = pushKey,
+            kind = kind,
+            appId = appId,
+            appDisplayName = appDisplayName,
+            deviceDisplayName = deviceDisplayName,
+            profileTag = profileTag,
+            lang = lang,
+            data = JsonPusherData(data.url, data.format),
+            append = false,
+            enabled = enabled,
+            deviceId = deviceId,
+    )
+
     private fun enqueueAddPusher(pusher: JsonPusher): UUID {
         val params = AddPusherWorker.Params(sessionId, pusher)
         val request = workManagerProvider.matrixOneTimeWorkRequestBuilder<AddPusherWorker>()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt
index 4528c95e69..37c1c0c3ad 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt
@@ -68,6 +68,9 @@ internal abstract class PushersModule {
     @Binds
     abstract fun bindAddPusherTask(task: DefaultAddPusherTask): AddPusherTask
 
+    @Binds
+    abstract fun bindTogglePusherTask(task: DefaultTogglePusherTask): TogglePusherTask
+
     @Binds
     abstract fun bindRemovePusherTask(task: DefaultRemovePusherTask): RemovePusherTask
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/TogglePusherTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/TogglePusherTask.kt
new file mode 100644
index 0000000000..87836e1c76
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/TogglePusherTask.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 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.pushers
+
+import com.zhuinden.monarchy.Monarchy
+import org.matrix.android.sdk.internal.database.model.PusherEntity
+import org.matrix.android.sdk.internal.database.query.where
+import org.matrix.android.sdk.internal.di.SessionDatabase
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
+import org.matrix.android.sdk.internal.network.RequestExecutor
+import org.matrix.android.sdk.internal.task.Task
+import org.matrix.android.sdk.internal.util.awaitTransaction
+import javax.inject.Inject
+
+internal interface TogglePusherTask : Task<TogglePusherTask.Params, Unit> {
+    data class Params(val pusher: JsonPusher, val enable: Boolean)
+}
+
+internal class DefaultTogglePusherTask @Inject constructor(
+        private val pushersAPI: PushersAPI,
+        @SessionDatabase private val monarchy: Monarchy,
+        private val requestExecutor: RequestExecutor,
+        private val globalErrorReceiver: GlobalErrorReceiver
+) : TogglePusherTask {
+
+    override suspend fun execute(params: TogglePusherTask.Params) {
+        val pusher = params.pusher.copy(enabled = params.enable)
+
+        requestExecutor.executeRequest(globalErrorReceiver) {
+            pushersAPI.setPusher(pusher)
+        }
+
+        monarchy.awaitTransaction { realm ->
+            val entity = PusherEntity.where(realm, params.pusher.pushKey).findFirst()
+            entity?.apply { enabled = params.enable }?.let { realm.insertOrUpdate(it) }
+        }
+    }
+}
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersServiceTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersServiceTest.kt
new file mode 100644
index 0000000000..a00ac3a17d
--- /dev/null
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersServiceTest.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.pushers
+
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.matrix.android.sdk.test.fakes.FakeAddPusherTask
+import org.matrix.android.sdk.test.fakes.FakeGetPushersTask
+import org.matrix.android.sdk.test.fakes.FakeMonarchy
+import org.matrix.android.sdk.test.fakes.FakeRemovePusherTask
+import org.matrix.android.sdk.test.fakes.FakeTaskExecutor
+import org.matrix.android.sdk.test.fakes.FakeTogglePusherTask
+import org.matrix.android.sdk.test.fakes.FakeWorkManagerProvider
+import org.matrix.android.sdk.test.fakes.internal.FakePushGatewayNotifyTask
+import org.matrix.android.sdk.test.fixtures.PusherFixture
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class DefaultPushersServiceTest {
+
+    private val workManagerProvider = FakeWorkManagerProvider()
+    private val monarchy = FakeMonarchy()
+    private val sessionId = ""
+    private val getPushersTask = FakeGetPushersTask()
+    private val pushGatewayNotifyTask = FakePushGatewayNotifyTask()
+    private val addPusherTask = FakeAddPusherTask()
+    private val togglePusherTask = FakeTogglePusherTask()
+    private val removePusherTask = FakeRemovePusherTask()
+    private val taskExecutor = FakeTaskExecutor()
+
+    private val pushersService = DefaultPushersService(
+            workManagerProvider.instance,
+            monarchy.instance,
+            sessionId,
+            getPushersTask,
+            pushGatewayNotifyTask,
+            addPusherTask,
+            togglePusherTask,
+            removePusherTask,
+            taskExecutor.instance,
+    )
+
+    @Test
+    fun `when togglePusher, then execute task`() = runTest {
+        val pusher = PusherFixture.aPusher()
+        val enable = true
+
+        pushersService.togglePusher(pusher, enable)
+
+        togglePusherTask.verifyExecution(pusher, enable)
+    }
+}
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultTogglePusherTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultTogglePusherTaskTest.kt
new file mode 100644
index 0000000000..3c54f6f1e1
--- /dev/null
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultTogglePusherTaskTest.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.pushers
+
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.amshove.kluent.shouldBeEqualTo
+import org.junit.Test
+import org.matrix.android.sdk.internal.database.model.PusherEntity
+import org.matrix.android.sdk.internal.database.model.PusherEntityFields
+import org.matrix.android.sdk.test.fakes.FakeGlobalErrorReceiver
+import org.matrix.android.sdk.test.fakes.FakeMonarchy
+import org.matrix.android.sdk.test.fakes.FakePushersAPI
+import org.matrix.android.sdk.test.fakes.FakeRequestExecutor
+import org.matrix.android.sdk.test.fakes.givenEqualTo
+import org.matrix.android.sdk.test.fakes.givenFindFirst
+import org.matrix.android.sdk.test.fixtures.JsonPusherFixture.aJsonPusher
+import org.matrix.android.sdk.test.fixtures.PusherEntityFixture.aPusherEntity
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class DefaultTogglePusherTaskTest {
+
+    private val pushersAPI = FakePushersAPI()
+    private val monarchy = FakeMonarchy()
+    private val requestExecutor = FakeRequestExecutor()
+    private val globalErrorReceiver = FakeGlobalErrorReceiver()
+
+    private val togglePusherTask = DefaultTogglePusherTask(pushersAPI, monarchy.instance, requestExecutor, globalErrorReceiver)
+
+    @Test
+    fun `execution toggles enable on both local and remote`() = runTest {
+        val jsonPusher = aJsonPusher(enabled = false)
+        val params = TogglePusherTask.Params(aJsonPusher(), true)
+
+        val pusherEntity = aPusherEntity(enabled = false)
+        monarchy.givenWhere<PusherEntity>()
+                .givenEqualTo(PusherEntityFields.PUSH_KEY, jsonPusher.pushKey)
+                .givenFindFirst(pusherEntity)
+
+        togglePusherTask.execute(params)
+
+        val expectedPayload = jsonPusher.copy(enabled = true)
+        pushersAPI.verifySetPusher(expectedPayload)
+        monarchy.verifyInsertOrUpdate<PusherEntity> {
+            withArg { actual ->
+                actual.enabled shouldBeEqualTo true
+            }
+        }
+    }
+}
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeAddPusherTask.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeAddPusherTask.kt
new file mode 100644
index 0000000000..16cdd7a626
--- /dev/null
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeAddPusherTask.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.test.fakes
+
+import io.mockk.mockk
+import org.matrix.android.sdk.internal.session.pushers.AddPusherTask
+
+class FakeAddPusherTask : AddPusherTask by mockk()
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeGetPushersTask.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeGetPushersTask.kt
new file mode 100644
index 0000000000..d5a41bb0e0
--- /dev/null
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeGetPushersTask.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.test.fakes
+
+import io.mockk.mockk
+import org.matrix.android.sdk.internal.session.pushers.GetPushersTask
+
+class FakeGetPushersTask : GetPushersTask by mockk()
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRemovePusherTask.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRemovePusherTask.kt
new file mode 100644
index 0000000000..55a7607a03
--- /dev/null
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRemovePusherTask.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.test.fakes
+
+import io.mockk.mockk
+import org.matrix.android.sdk.internal.session.pushers.RemovePusherTask
+
+class FakeRemovePusherTask : RemovePusherTask by mockk()
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeTaskExecutor.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeTaskExecutor.kt
new file mode 100644
index 0000000000..543dda8a4f
--- /dev/null
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeTaskExecutor.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.test.fakes
+
+import io.mockk.mockk
+import org.matrix.android.sdk.internal.task.TaskExecutor
+
+internal class FakeTaskExecutor {
+
+    val instance: TaskExecutor = mockk()
+}
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeTogglePusherTask.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeTogglePusherTask.kt
new file mode 100644
index 0000000000..b1e059a40e
--- /dev/null
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeTogglePusherTask.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.test.fakes
+
+import io.mockk.coVerify
+import io.mockk.mockk
+import io.mockk.slot
+import org.amshove.kluent.shouldBeEqualTo
+import org.matrix.android.sdk.api.session.pushers.Pusher
+import org.matrix.android.sdk.internal.session.pushers.TogglePusherTask
+
+class FakeTogglePusherTask : TogglePusherTask by mockk(relaxed = true) {
+
+    fun verifyExecution(pusher: Pusher, enable: Boolean) {
+        val slot = slot<TogglePusherTask.Params>()
+        coVerify { execute(capture(slot)) }
+        val params = slot.captured
+        params.pusher.pushKey shouldBeEqualTo pusher.pushKey
+        params.enable shouldBeEqualTo enable
+    }
+}
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/FakePushGatewayNotifyTask.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/FakePushGatewayNotifyTask.kt
new file mode 100644
index 0000000000..46a106dcb2
--- /dev/null
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/FakePushGatewayNotifyTask.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.test.fakes.internal
+
+import io.mockk.mockk
+import org.matrix.android.sdk.internal.session.pushers.gateway.PushGatewayNotifyTask
+
+class FakePushGatewayNotifyTask : PushGatewayNotifyTask by mockk()
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/PusherFixture.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/PusherFixture.kt
new file mode 100644
index 0000000000..0ac7885062
--- /dev/null
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fixtures/PusherFixture.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.test.fixtures
+
+import org.matrix.android.sdk.api.session.pushers.Pusher
+import org.matrix.android.sdk.api.session.pushers.PusherData
+import org.matrix.android.sdk.api.session.pushers.PusherState
+
+object PusherFixture {
+
+    fun aPusher(
+            pushKey: String = "",
+            kind: String = "",
+            appId: String = "",
+            appDisplayName: String? = "",
+            deviceDisplayName: String? = "",
+            profileTag: String? = null,
+            lang: String? = "",
+            data: PusherData = PusherData("f.o/_matrix/push/v1/notify", ""),
+            enabled: Boolean = true,
+            deviceId: String? = "",
+            state: PusherState = PusherState.REGISTERED,
+    ) = Pusher(
+            pushKey,
+            kind,
+            appId,
+            appDisplayName,
+            deviceDisplayName,
+            profileTag,
+            lang,
+            data,
+            enabled,
+            deviceId,
+            state,
+    )
+}
diff --git a/vector/build.gradle b/vector/build.gradle
index 76f32a34db..37a98d8242 100644
--- a/vector/build.gradle
+++ b/vector/build.gradle
@@ -296,6 +296,7 @@ dependencies {
     // Plant Timber tree for test
     testImplementation libs.tests.timberJunitRule
     testImplementation libs.airbnb.mavericksTesting
+    testImplementation libs.androidx.coreTesting
     testImplementation(libs.jetbrains.coroutinesTest) {
         exclude group: "org.jetbrains.kotlinx", module: "kotlinx-coroutines-debug"
     }
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewAction.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewAction.kt
index 42e79ac89f..9a92d5b629 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewAction.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewAction.kt
@@ -19,9 +19,14 @@ package im.vector.app.features.settings.devices.v2.overview
 import im.vector.app.core.platform.VectorViewModelAction
 
 sealed class SessionOverviewAction : VectorViewModelAction {
+
     object VerifySession : SessionOverviewAction()
     object SignoutOtherSession : SessionOverviewAction()
     object SsoAuthDone : SessionOverviewAction()
     data class PasswordAuthDone(val password: String) : SessionOverviewAction()
     object ReAuthCancelled : SessionOverviewAction()
+    data class TogglePushNotifications(
+            val deviceId: String,
+            val enabled: Boolean,
+    ) : SessionOverviewAction()
 }
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewEntrySwitchView.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewEntrySwitchView.kt
new file mode 100644
index 0000000000..bbefd31dfe
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewEntrySwitchView.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.settings.devices.v2.overview
+
+import android.content.Context
+import android.content.res.TypedArray
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.widget.CompoundButton
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.content.res.use
+import im.vector.app.R
+import im.vector.app.core.extensions.setAttributeBackground
+import im.vector.app.databinding.ViewSessionOverviewEntrySwitchBinding
+
+class SessionOverviewEntrySwitchView @JvmOverloads constructor(
+        context: Context,
+        attrs: AttributeSet? = null,
+        defStyleAttr: Int = 0
+) : ConstraintLayout(context, attrs, defStyleAttr) {
+
+    private val binding = ViewSessionOverviewEntrySwitchBinding.inflate(
+            LayoutInflater.from(context),
+            this
+    )
+
+    init {
+        initBackground()
+        context.obtainStyledAttributes(
+                attrs,
+                R.styleable.SessionOverviewEntrySwitchView,
+                0,
+                0
+        ).use {
+            setTitle(it)
+            setDescription(it)
+            setSwitchedEnabled(it)
+        }
+    }
+
+    private fun initBackground() {
+        binding.root.setAttributeBackground(android.R.attr.selectableItemBackground)
+    }
+
+    private fun setTitle(typedArray: TypedArray) {
+        val title = typedArray.getString(R.styleable.SessionOverviewEntrySwitchView_sessionOverviewEntrySwitchTitle)
+        binding.sessionsOverviewEntryTitle.text = title
+    }
+
+    private fun setDescription(typedArray: TypedArray) {
+        val description = typedArray.getString(R.styleable.SessionOverviewEntrySwitchView_sessionOverviewEntrySwitchDescription)
+        binding.sessionsOverviewEntryDescription.text = description
+    }
+
+    private fun setSwitchedEnabled(typedArray: TypedArray) {
+        val enabled = typedArray.getBoolean(R.styleable.SessionOverviewEntrySwitchView_sessionOverviewEntrySwitchEnabled, true)
+        binding.sessionsOverviewEntrySwitch.isChecked = enabled
+    }
+
+    fun setChecked(checked: Boolean) {
+        binding.sessionsOverviewEntrySwitch.isChecked = checked
+    }
+
+    fun setOnCheckedChangeListener(listener: CompoundButton.OnCheckedChangeListener?) {
+        binding.sessionsOverviewEntrySwitch.setOnCheckedChangeListener(listener)
+    }
+
+    override fun onDetachedFromWindow() {
+        super.onDetachedFromWindow()
+        binding.sessionsOverviewEntrySwitch.setOnCheckedChangeListener(null)
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt
index 58b0a13706..7510880087 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewFragment.kt
@@ -24,6 +24,7 @@ import android.view.View
 import android.view.ViewGroup
 import android.widget.Toast
 import androidx.appcompat.app.AppCompatActivity
+import androidx.core.view.isGone
 import androidx.core.view.isVisible
 import com.airbnb.mvrx.Success
 import com.airbnb.mvrx.fragmentViewModel
@@ -41,12 +42,14 @@ import im.vector.app.core.resources.StringProvider
 import im.vector.app.databinding.FragmentSessionOverviewBinding
 import im.vector.app.features.auth.ReAuthActivity
 import im.vector.app.features.crypto.recover.SetupMode
+import im.vector.app.features.settings.devices.v2.DeviceFullInfo
 import im.vector.app.features.settings.devices.v2.list.SessionInfoViewState
 import im.vector.app.features.settings.devices.v2.more.SessionLearnMoreBottomSheet
 import im.vector.app.features.workers.signout.SignOutUiWorker
 import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
 import org.matrix.android.sdk.api.extensions.orFalse
 import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
+import org.matrix.android.sdk.api.session.pushers.Pusher
 import javax.inject.Inject
 
 /**
@@ -174,9 +177,14 @@ class SessionOverviewFragment :
 
     override fun invalidate() = withState(viewModel) { state ->
         updateToolbar(state)
-        updateEntryDetails(state.deviceId)
         updateSessionInfo(state)
         updateLoading(state.isLoading)
+        updatePushNotificationToggle(state.deviceId, state.pushers.invoke().orEmpty())
+        if (state.deviceInfo is Success) {
+            renderSessionInfo(state.isCurrentSessionTrusted, state.deviceInfo.invoke())
+        } else {
+            hideSessionInfo()
+        }
     }
 
     private fun updateToolbar(viewState: SessionOverviewViewState) {
@@ -189,12 +197,6 @@ class SessionOverviewFragment :
         }
     }
 
-    private fun updateEntryDetails(deviceId: String) {
-        views.sessionOverviewEntryDetails.setOnClickListener {
-            viewNavigator.goToSessionDetails(requireContext(), deviceId)
-        }
-    }
-
     private fun updateSessionInfo(viewState: SessionOverviewViewState) {
         if (viewState.deviceInfo is Success) {
             views.sessionOverviewInfo.isVisible = true
@@ -217,6 +219,35 @@ class SessionOverviewFragment :
         }
     }
 
+    private fun updatePushNotificationToggle(deviceId: String, pushers: List<Pusher>) {
+        views.sessionOverviewPushNotifications.apply {
+            if (pushers.isEmpty()) {
+                isVisible = false
+            } else {
+                val allPushersAreEnabled = pushers.all { it.enabled }
+                setOnCheckedChangeListener(null)
+                setChecked(allPushersAreEnabled)
+                post {
+                    setOnCheckedChangeListener { _, isChecked ->
+                        viewModel.handle(SessionOverviewAction.TogglePushNotifications(deviceId, isChecked))
+                    }
+                }
+            }
+        }
+    }
+
+    private fun renderSessionInfo(isCurrentSession: Boolean, deviceFullInfo: DeviceFullInfo) {
+        views.sessionOverviewInfo.isVisible = true
+        val viewState = SessionInfoViewState(
+                isCurrentSession = isCurrentSession,
+                deviceFullInfo = deviceFullInfo,
+                isDetailsButtonVisible = false,
+                isLearnMoreLinkVisible = true,
+                isLastSeenDetailsVisible = true,
+        )
+        views.sessionOverviewInfo.render(viewState, dateFormatter, drawableProvider, colorProvider, stringProvider)
+    }
+
     private fun updateLoading(isLoading: Boolean) {
         if (isLoading) {
             showLoading(null)
@@ -275,4 +306,8 @@ class SessionOverviewFragment :
         )
         SessionLearnMoreBottomSheet.show(childFragmentManager, args)
     }
+
+    private fun hideSessionInfo() {
+        views.sessionOverviewInfo.isGone = true
+    }
 }
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt
index bd5c7725eb..a7b0435e29 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt
@@ -43,14 +43,17 @@ import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
 import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
 import org.matrix.android.sdk.api.extensions.orFalse
 import org.matrix.android.sdk.api.failure.Failure
+import org.matrix.android.sdk.api.session.Session
 import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
 import org.matrix.android.sdk.api.session.uia.DefaultBaseAuth
+import org.matrix.android.sdk.flow.flow
 import timber.log.Timber
 import javax.net.ssl.HttpsURLConnection
 import kotlin.coroutines.Continuation
 
 class SessionOverviewViewModel @AssistedInject constructor(
         @Assisted val initialState: SessionOverviewViewState,
+        private val session: Session,
         private val stringProvider: StringProvider,
         private val getDeviceFullInfoUseCase: GetDeviceFullInfoUseCase,
         private val checkIfCurrentSessionCanBeVerifiedUseCase: CheckIfCurrentSessionCanBeVerifiedUseCase,
@@ -73,6 +76,7 @@ class SessionOverviewViewModel @AssistedInject constructor(
     init {
         observeSessionInfo(initialState.deviceId)
         observeCurrentSessionInfo()
+        observePushers(initialState.deviceId)
     }
 
     private fun observeSessionInfo(deviceId: String) {
@@ -94,6 +98,13 @@ class SessionOverviewViewModel @AssistedInject constructor(
                 }
     }
 
+    private fun observePushers(deviceId: String) {
+        session.flow()
+                .livePushers()
+                .map { it.filter { pusher -> pusher.deviceId == deviceId } }
+                .execute { copy(pushers = it) }
+    }
+
     override fun handle(action: SessionOverviewAction) {
         when (action) {
             is SessionOverviewAction.VerifySession -> handleVerifySessionAction()
@@ -101,6 +112,7 @@ class SessionOverviewViewModel @AssistedInject constructor(
             SessionOverviewAction.SsoAuthDone -> handleSsoAuthDone()
             is SessionOverviewAction.PasswordAuthDone -> handlePasswordAuthDone(action)
             SessionOverviewAction.ReAuthCancelled -> handleReAuthCancelled()
+            is SessionOverviewAction.TogglePushNotifications -> handleTogglePusherAction(action)
         }
     }
 
@@ -198,4 +210,13 @@ class SessionOverviewViewModel @AssistedInject constructor(
     private fun handleReAuthCancelled() {
         pendingAuthHandler.reAuthCancelled()
     }
+
+    private fun handleTogglePusherAction(action: SessionOverviewAction.TogglePushNotifications) {
+        viewModelScope.launch {
+            val devicePushers = awaitState().pushers.invoke()?.filter { it.deviceId == action.deviceId }
+            devicePushers?.forEach { pusher ->
+                session.pushersService().togglePusher(pusher, action.enabled)
+            }
+        }
+    }
 }
diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt
index 07423888b5..c2d4a858b3 100644
--- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewState.kt
@@ -20,12 +20,14 @@ import com.airbnb.mvrx.Async
 import com.airbnb.mvrx.MavericksState
 import com.airbnb.mvrx.Uninitialized
 import im.vector.app.features.settings.devices.v2.DeviceFullInfo
+import org.matrix.android.sdk.api.session.pushers.Pusher
 
 data class SessionOverviewViewState(
         val deviceId: String,
         val isCurrentSessionTrusted: Boolean = false,
         val deviceInfo: Async<DeviceFullInfo> = Uninitialized,
         val isLoading: Boolean = false,
+        val pushers: Async<List<Pusher>> = Uninitialized,
 ) : MavericksState {
     constructor(args: SessionOverviewArgs) : this(
             deviceId = args.deviceId
diff --git a/vector/src/main/res/layout/fragment_session_overview.xml b/vector/src/main/res/layout/fragment_session_overview.xml
index 0a9dd61fe0..80ad744d01 100644
--- a/vector/src/main/res/layout/fragment_session_overview.xml
+++ b/vector/src/main/res/layout/fragment_session_overview.xml
@@ -31,6 +31,16 @@
             app:sessionOverviewEntryDescription="@string/device_manager_session_details_description"
             app:sessionOverviewEntryTitle="@string/device_manager_session_details_title" />
 
+        <im.vector.app.features.settings.devices.v2.overview.SessionOverviewEntrySwitchView
+            android:id="@+id/sessionOverviewPushNotifications"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/sessionOverviewEntryDetails"
+            app:sessionOverviewEntrySwitchDescription="@string/device_manager_push_notifications_description"
+            app:sessionOverviewEntrySwitchTitle="@string/device_manager_push_notifications_title" />
+
         <Button
             android:id="@+id/sessionOverviewSignout"
             style="@style/Widget.Vector.Button.Text.Destructive"
diff --git a/vector/src/main/res/layout/view_session_overview_entry_switch.xml b/vector/src/main/res/layout/view_session_overview_entry_switch.xml
new file mode 100644
index 0000000000..34d68abf6e
--- /dev/null
+++ b/vector/src/main/res/layout/view_session_overview_entry_switch.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merge 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"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
+
+    <TextView
+        android:id="@+id/sessionsOverviewEntryTitle"
+        style="@style/TextAppearance.Vector.Subtitle.DevicesManagement"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginHorizontal="@dimen/layout_horizontal_margin"
+        android:layout_marginTop="20dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:text="Push notifications" />
+
+    <TextView
+        android:id="@+id/sessionsOverviewEntryDescription"
+        style="@style/TextAppearance.Vector.Body.DevicesManagement"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="4dp"
+        app:layout_constraintEnd_toEndOf="@id/sessionsOverviewEntryTitle"
+        app:layout_constraintStart_toStartOf="@id/sessionsOverviewEntryTitle"
+        app:layout_constraintTop_toBottomOf="@id/sessionsOverviewEntryTitle"
+        tools:text="Receive push notifications on this session." />
+
+    <androidx.appcompat.widget.SwitchCompat
+        android:id="@+id/sessionsOverviewEntrySwitch"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="16dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent" />
+
+    <View
+        android:id="@+id/sessionsOverviewEntryDivider"
+        android:layout_width="0dp"
+        android:layout_height="1dp"
+        android:layout_marginTop="20dp"
+        android:background="@drawable/divider_horizontal"
+        app:layout_constraintEnd_toEndOf="@id/sessionsOverviewEntryTitle"
+        app:layout_constraintStart_toStartOf="@id/sessionsOverviewEntryTitle"
+        app:layout_constraintTop_toBottomOf="@id/sessionsOverviewEntryDescription" />
+
+</merge>
diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt
index 3454b41ee0..da602ae227 100644
--- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt
+++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt
@@ -17,9 +17,10 @@
 package im.vector.app.features.settings.devices.v2.overview
 
 import android.os.SystemClock
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import com.airbnb.mvrx.Loading
 import com.airbnb.mvrx.Success
 import com.airbnb.mvrx.test.MavericksTestRule
-import im.vector.app.R
 import im.vector.app.features.settings.devices.v2.DeviceFullInfo
 import im.vector.app.features.settings.devices.v2.RefreshDevicesUseCase
 import im.vector.app.features.settings.devices.v2.signout.InterceptSignoutFlowResponseUseCase
@@ -28,8 +29,10 @@ import im.vector.app.features.settings.devices.v2.signout.SignoutSessionUseCase
 import im.vector.app.features.settings.devices.v2.verification.CheckIfCurrentSessionCanBeVerifiedUseCase
 import im.vector.app.test.fakes.FakeActiveSessionHolder
 import im.vector.app.test.fakes.FakePendingAuthHandler
+import im.vector.app.test.fakes.FakeSession
 import im.vector.app.test.fakes.FakeStringProvider
 import im.vector.app.test.fakes.FakeVerificationService
+import im.vector.app.test.fixtures.PusherFixture.aPusher
 import im.vector.app.test.test
 import im.vector.app.test.testDispatcher
 import io.mockk.coEvery
@@ -52,10 +55,8 @@ import org.junit.Test
 import org.matrix.android.sdk.api.auth.UIABaseAuth
 import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
 import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
-import org.matrix.android.sdk.api.failure.Failure
 import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
 import org.matrix.android.sdk.api.session.uia.DefaultBaseAuth
-import javax.net.ssl.HttpsURLConnection
 import kotlin.coroutines.Continuation
 
 private const val A_SESSION_ID_1 = "session-id-1"
@@ -69,12 +70,16 @@ class SessionOverviewViewModelTest {
     @get:Rule
     val mavericksTestRule = MavericksTestRule(testDispatcher = testDispatcher)
 
+    @get:Rule
+    val instantTaskExecutorRule = InstantTaskExecutorRule()
+
     private val args = SessionOverviewArgs(
             deviceId = A_SESSION_ID_1
     )
+    private val fakeSession = FakeSession()
+    private val getDeviceFullInfoUseCase = mockk<GetDeviceFullInfoUseCase>(relaxed = true)
     private val fakeActiveSessionHolder = FakeActiveSessionHolder()
     private val fakeStringProvider = FakeStringProvider()
-    private val getDeviceFullInfoUseCase = mockk<GetDeviceFullInfoUseCase>()
     private val checkIfCurrentSessionCanBeVerifiedUseCase = mockk<CheckIfCurrentSessionCanBeVerifiedUseCase>()
     private val signoutSessionUseCase = mockk<SignoutSessionUseCase>()
     private val interceptSignoutFlowResponseUseCase = mockk<InterceptSignoutFlowResponseUseCase>()
@@ -83,6 +88,7 @@ class SessionOverviewViewModelTest {
 
     private fun createViewModel() = SessionOverviewViewModel(
             initialState = SessionOverviewViewState(args),
+            session = fakeSession,
             stringProvider = fakeStringProvider.instance,
             getDeviceFullInfoUseCase = getDeviceFullInfoUseCase,
             checkIfCurrentSessionCanBeVerifiedUseCase = checkIfCurrentSessionCanBeVerifiedUseCase,
@@ -108,8 +114,7 @@ class SessionOverviewViewModelTest {
     }
 
     @Test
-    fun `given the viewModel has been initialized then viewState is updated with session info and current session verification status`() {
-        // Given
+    fun `given the viewModel has been initialized then viewState is updated with session info`() {
         val deviceFullInfo = mockk<DeviceFullInfo>()
         every { getDeviceFullInfoUseCase.execute(A_SESSION_ID_1) } returns flowOf(deviceFullInfo)
         givenCurrentSessionIsTrusted()
@@ -117,12 +122,11 @@ class SessionOverviewViewModelTest {
                 deviceId = A_SESSION_ID_1,
                 deviceInfo = Success(deviceFullInfo),
                 isCurrentSessionTrusted = true,
+                pushers = Loading(),
         )
 
-        // When
         val viewModel = createViewModel()
 
-        // Then
         viewModel.test()
                 .assertLatestState { state -> state == expectedState }
                 .finish()
@@ -199,110 +203,6 @@ class SessionOverviewViewModelTest {
                 .finish()
     }
 
-    @Test
-    fun `given another session and no reAuth is needed when handling signout action then signout process is performed`() {
-        // Given
-        val deviceFullInfo = mockk<DeviceFullInfo>()
-        every { deviceFullInfo.isCurrentDevice } returns false
-        every { getDeviceFullInfoUseCase.execute(A_SESSION_ID_1) } returns flowOf(deviceFullInfo)
-        givenSignoutSuccess(A_SESSION_ID_1)
-        every { refreshDevicesUseCase.execute() } just runs
-        val signoutAction = SessionOverviewAction.SignoutOtherSession
-        givenCurrentSessionIsTrusted()
-        val expectedViewState = SessionOverviewViewState(
-                deviceId = A_SESSION_ID_1,
-                isCurrentSessionTrusted = true,
-                deviceInfo = Success(deviceFullInfo),
-                isLoading = false,
-        )
-
-        // When
-        val viewModel = createViewModel()
-        val viewModelTest = viewModel.test()
-        viewModel.handle(signoutAction)
-
-        // Then
-        viewModelTest
-                .assertStatesChanges(
-                        expectedViewState,
-                        { copy(isLoading = true) },
-                        { copy(isLoading = false) }
-                )
-                .assertEvent { it is SessionOverviewViewEvent.SignoutSuccess }
-                .finish()
-        verify {
-            refreshDevicesUseCase.execute()
-        }
-    }
-
-    @Test
-    fun `given another session and server error during signout when handling signout action then signout process is performed`() {
-        // Given
-        val deviceFullInfo = mockk<DeviceFullInfo>()
-        every { deviceFullInfo.isCurrentDevice } returns false
-        every { getDeviceFullInfoUseCase.execute(A_SESSION_ID_1) } returns flowOf(deviceFullInfo)
-        val serverError = Failure.OtherServerError(errorBody = "", httpCode = HttpsURLConnection.HTTP_UNAUTHORIZED)
-        givenSignoutError(A_SESSION_ID_1, serverError)
-        val signoutAction = SessionOverviewAction.SignoutOtherSession
-        givenCurrentSessionIsTrusted()
-        val expectedViewState = SessionOverviewViewState(
-                deviceId = A_SESSION_ID_1,
-                isCurrentSessionTrusted = true,
-                deviceInfo = Success(deviceFullInfo),
-                isLoading = false,
-        )
-        fakeStringProvider.given(R.string.authentication_error, AUTH_ERROR_MESSAGE)
-
-        // When
-        val viewModel = createViewModel()
-        val viewModelTest = viewModel.test()
-        viewModel.handle(signoutAction)
-
-        // Then
-        viewModelTest
-                .assertStatesChanges(
-                        expectedViewState,
-                        { copy(isLoading = true) },
-                        { copy(isLoading = false) }
-                )
-                .assertEvent { it is SessionOverviewViewEvent.SignoutError && it.error.message == AUTH_ERROR_MESSAGE }
-                .finish()
-    }
-
-    @Test
-    fun `given another session and unexpected error during signout when handling signout action then signout process is performed`() {
-        // Given
-        val deviceFullInfo = mockk<DeviceFullInfo>()
-        every { deviceFullInfo.isCurrentDevice } returns false
-        every { getDeviceFullInfoUseCase.execute(A_SESSION_ID_1) } returns flowOf(deviceFullInfo)
-        val error = Exception()
-        givenSignoutError(A_SESSION_ID_1, error)
-        val signoutAction = SessionOverviewAction.SignoutOtherSession
-        givenCurrentSessionIsTrusted()
-        val expectedViewState = SessionOverviewViewState(
-                deviceId = A_SESSION_ID_1,
-                isCurrentSessionTrusted = true,
-                deviceInfo = Success(deviceFullInfo),
-                isLoading = false,
-        )
-        fakeStringProvider.given(R.string.matrix_error, AN_ERROR_MESSAGE)
-
-        // When
-        val viewModel = createViewModel()
-        val viewModelTest = viewModel.test()
-        viewModel.handle(signoutAction)
-
-        // Then
-        viewModelTest
-                .assertStatesChanges(
-                        expectedViewState,
-                        { copy(isLoading = true) },
-                        { copy(isLoading = false) }
-                )
-                .assertEvent { it is SessionOverviewViewEvent.SignoutError && it.error.message == AN_ERROR_MESSAGE }
-                .finish()
-    }
-
     @Test
     fun `given another session and reAuth is needed during signout when handling signout action then requestReAuth is sent and pending auth is stored`() {
         // Given
@@ -447,4 +347,30 @@ class SessionOverviewViewModelTest {
         every { deviceFullInfo.roomEncryptionTrustLevel } returns RoomEncryptionTrustLevel.Trusted
         every { getDeviceFullInfoUseCase.execute(A_SESSION_ID_2) } returns flowOf(deviceFullInfo)
     }
+
+    @Test
+    fun `when viewModel init, then observe pushers and emit to state`() {
+        val pushers = listOf(aPusher(deviceId = A_SESSION_ID_1))
+        fakeSession.pushersService().givenPushersLive(pushers)
+
+        val viewModel = createViewModel()
+
+        viewModel.test()
+                .assertLatestState { state -> state.pushers.invoke() == pushers }
+                .finish()
+    }
+
+    @Test
+    fun `when handle TogglePushNotifications, then toggle enabled for device pushers`() {
+        val pushers = listOf(
+                aPusher(deviceId = A_SESSION_ID_1, enabled = false),
+                aPusher(deviceId = "another id", enabled = false)
+        )
+        fakeSession.pushersService().givenPushersLive(pushers)
+
+        val viewModel = createViewModel()
+        viewModel.handle(SessionOverviewAction.TogglePushNotifications(A_SESSION_ID_1, true))
+
+        fakeSession.pushersService().verifyOnlyTogglePusherCalled(pushers.first(), true)
+    }
 }
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakePushersService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakePushersService.kt
index 9e11b86871..60d50eab03 100644
--- a/vector/src/test/java/im/vector/app/test/fakes/FakePushersService.kt
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakePushersService.kt
@@ -16,14 +16,30 @@
 
 package im.vector.app.test.fakes
 
+import androidx.lifecycle.liveData
+import io.mockk.Ordering
+import io.mockk.coVerify
+import io.mockk.every
 import io.mockk.mockk
 import io.mockk.slot
 import io.mockk.verify
 import org.matrix.android.sdk.api.session.pushers.HttpPusher
+import org.matrix.android.sdk.api.session.pushers.Pusher
 import org.matrix.android.sdk.api.session.pushers.PushersService
 
 class FakePushersService : PushersService by mockk(relaxed = true) {
 
+    fun givenPushersLive(pushers: List<Pusher>) {
+        every { getPushersLive() } returns liveData { emit(pushers) }
+    }
+
+    fun verifyOnlyTogglePusherCalled(pusher: Pusher, enable: Boolean) {
+        coVerify(ordering = Ordering.ALL) {
+            getPushersLive() // verifies only getPushersLive and the following togglePusher was called
+            togglePusher(pusher, enable)
+        }
+    }
+
     fun verifyEnqueueAddHttpPusher(): HttpPusher {
         val httpPusherSlot = slot<HttpPusher>()
         verify { enqueueAddHttpPusher(capture(httpPusherSlot)) }
diff --git a/vector/src/test/java/im/vector/app/test/fixtures/PusherFixture.kt b/vector/src/test/java/im/vector/app/test/fixtures/PusherFixture.kt
new file mode 100644
index 0000000000..300c66eca3
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/test/fixtures/PusherFixture.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2022 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.test.fixtures
+
+import org.matrix.android.sdk.api.session.pushers.Pusher
+import org.matrix.android.sdk.api.session.pushers.PusherData
+import org.matrix.android.sdk.api.session.pushers.PusherState
+
+object PusherFixture {
+
+    fun aPusher(
+            pushKey: String = "",
+            kind: String = "",
+            appId: String = "",
+            appDisplayName: String? = "",
+            deviceDisplayName: String? = "",
+            profileTag: String? = null,
+            lang: String? = "",
+            data: PusherData = PusherData("f.o/_matrix/push/v1/notify", ""),
+            enabled: Boolean = true,
+            deviceId: String? = "",
+            state: PusherState = PusherState.REGISTERED,
+    ) = Pusher(
+            pushKey,
+            kind,
+            appId,
+            appDisplayName,
+            deviceDisplayName,
+            profileTag,
+            lang,
+            data,
+            enabled,
+            deviceId,
+            state,
+    )
+}