diff --git a/changelog.d/7281.wip b/changelog.d/7281.wip
new file mode 100644
index 0000000000..c457ffbdb9
--- /dev/null
+++ b/changelog.d/7281.wip
@@ -0,0 +1 @@
+Links "Enable Notifications for this session" setting to enabled value in pusher
diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml
index 600b8461b9..f6c4553790 100644
--- a/library/ui-strings/src/main/res/values/strings.xml
+++ b/library/ui-strings/src/main/res/values/strings.xml
@@ -1666,6 +1666,7 @@
Create New Room
Create New Space
No network. Please check your Internet connection.
+ Something went wrong. Please check your network connection and try again.
"Change network"
"Please wait…"
Updating your data…
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 2693ca474c..aef482ae2e 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
@@ -54,6 +54,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo034
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo035
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo036
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo037
+import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo038
import org.matrix.android.sdk.internal.util.Normalizer
import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
import javax.inject.Inject
@@ -62,7 +63,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
private val normalizer: Normalizer
) : MatrixRealmMigration(
dbName = "Session",
- schemaVersion = 37L,
+ schemaVersion = 38L,
) {
/**
* Forces all RealmSessionStoreMigration instances to be equal.
@@ -109,5 +110,6 @@ internal class RealmSessionStoreMigration @Inject constructor(
if (oldVersion < 35) MigrateSessionTo035(realm).perform()
if (oldVersion < 36) MigrateSessionTo036(realm).perform()
if (oldVersion < 37) MigrateSessionTo037(realm).perform()
+ if (oldVersion < 38) MigrateSessionTo038(realm).perform()
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo038.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo038.kt
new file mode 100644
index 0000000000..b5848f04cd
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo038.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.database.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.database.model.PusherEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+internal class MigrateSessionTo038(realm: DynamicRealm) : RealmMigrator(realm, 38) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ realm.schema.get("PusherEntity")
+ ?.addField(PusherEntityFields.ENABLED, Boolean::class.java)
+ ?.addField(PusherEntityFields.DEVICE_ID, String::class.java)
+ ?.transform { obj -> obj.set(PusherEntityFields.ENABLED, true) }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt b/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt
index 44cccbd3f5..cda6f5bae8 100644
--- a/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt
+++ b/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt
@@ -23,6 +23,7 @@ import im.vector.app.core.resources.AppNameProvider
import im.vector.app.core.resources.LocaleProvider
import im.vector.app.core.resources.StringProvider
import org.matrix.android.sdk.api.session.pushers.HttpPusher
+import org.matrix.android.sdk.api.session.pushers.Pusher
import java.util.UUID
import javax.inject.Inject
import kotlin.math.abs
@@ -90,6 +91,18 @@ class PushersManager @Inject constructor(
)
}
+ fun getPusherForCurrentSession(): Pusher? {
+ val session = activeSessionHolder.getSafeActiveSession() ?: return null
+ val deviceId = session.sessionParams.deviceId
+ return session.pushersService().getPushers().firstOrNull { it.deviceId == deviceId }
+ }
+
+ suspend fun togglePusherForCurrentSession(enable: Boolean) {
+ val session = activeSessionHolder.getSafeActiveSession() ?: return
+ val pusher = getPusherForCurrentSession() ?: return
+ session.pushersService().togglePusher(pusher, enable)
+ }
+
suspend fun unregisterEmailPusher(email: String) {
val currentSession = activeSessionHolder.getSafeActiveSession() ?: return
currentSession.pushersService().removeEmailPusher(email)
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
index bbefd31dfe..24c7725f29 100644
--- 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
@@ -49,6 +49,7 @@ class SessionOverviewEntrySwitchView @JvmOverloads constructor(
setTitle(it)
setDescription(it)
setSwitchedEnabled(it)
+ setClickListener()
}
}
@@ -67,10 +68,16 @@ class SessionOverviewEntrySwitchView @JvmOverloads constructor(
}
private fun setSwitchedEnabled(typedArray: TypedArray) {
- val enabled = typedArray.getBoolean(R.styleable.SessionOverviewEntrySwitchView_sessionOverviewEntrySwitchEnabled, true)
+ val enabled = typedArray.getBoolean(R.styleable.SessionOverviewEntrySwitchView_sessionOverviewEntrySwitchEnabled, false)
binding.sessionsOverviewEntrySwitch.isChecked = enabled
}
+ private fun setClickListener() {
+ binding.root.setOnClickListener {
+ setChecked(!binding.sessionsOverviewEntrySwitch.isChecked)
+ }
+ }
+
fun setChecked(checked: Boolean) {
binding.sessionsOverviewEntrySwitch.isChecked = checked
}
diff --git a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt
index 37d09d02c9..0202acecc7 100644
--- a/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/notifications/VectorSettingsNotificationPreferenceFragment.kt
@@ -54,6 +54,7 @@ import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.settings.VectorSettingsBaseFragment
import im.vector.app.features.settings.VectorSettingsFragmentInteractionListener
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.Session
@@ -104,6 +105,10 @@ class VectorSettingsNotificationPreferenceFragment :
}
findPreference(VectorPreferences.SETTINGS_ENABLE_THIS_DEVICE_PREFERENCE_KEY)?.let {
+ pushersManager.getPusherForCurrentSession()?.let { pusher ->
+ it.isChecked = pusher.enabled
+ }
+
it.setTransactionalSwitchChangeListener(lifecycleScope) { isChecked ->
if (isChecked) {
unifiedPushHelper.register(requireActivity()) {
@@ -117,6 +122,16 @@ class VectorSettingsNotificationPreferenceFragment :
}
findPreference(VectorPreferences.SETTINGS_NOTIFICATION_METHOD_KEY)
?.summary = unifiedPushHelper.getCurrentDistributorName()
+ lifecycleScope.launch {
+ val result = runCatching {
+ pushersManager.togglePusherForCurrentSession(true)
+ }
+
+ result.exceptionOrNull()?.let { _ ->
+ Toast.makeText(context, R.string.error_check_network, Toast.LENGTH_SHORT).show()
+ it.isChecked = false
+ }
+ }
}
} else {
unifiedPushHelper.unregister(pushersManager)
diff --git a/vector/src/test/java/im/vector/app/core/pushers/PushersManagerTest.kt b/vector/src/test/java/im/vector/app/core/pushers/PushersManagerTest.kt
index 50c9024e86..750e50d578 100644
--- a/vector/src/test/java/im/vector/app/core/pushers/PushersManagerTest.kt
+++ b/vector/src/test/java/im/vector/app/core/pushers/PushersManagerTest.kt
@@ -24,8 +24,12 @@ import im.vector.app.test.fakes.FakeLocaleProvider
import im.vector.app.test.fakes.FakePushersService
import im.vector.app.test.fakes.FakeSession
import im.vector.app.test.fakes.FakeStringProvider
+import im.vector.app.test.fixtures.CredentialsFixture
import im.vector.app.test.fixtures.CryptoDeviceInfoFixture.aCryptoDeviceInfo
+import im.vector.app.test.fixtures.PusherFixture
+import im.vector.app.test.fixtures.SessionParamsFixture
import io.mockk.mockk
+import kotlinx.coroutines.test.runTest
import org.amshove.kluent.shouldBeEqualTo
import org.junit.Test
import org.matrix.android.sdk.api.session.crypto.model.UnsignedDeviceInfo
@@ -82,4 +86,34 @@ class PushersManagerTest {
val httpPusher = pushersService.verifyEnqueueAddHttpPusher()
httpPusher shouldBeEqualTo expectedHttpPusher
}
+
+ @Test
+ fun `when getPusherForCurrentSession, then return pusher`() {
+ val deviceId = "device_id"
+ val sessionParams = SessionParamsFixture.aSessionParams(
+ credentials = CredentialsFixture.aCredentials(deviceId = deviceId)
+ )
+ session.givenSessionParams(sessionParams)
+ val expectedPusher = PusherFixture.aPusher(deviceId = deviceId)
+ pushersService.givenGetPushers(listOf(expectedPusher))
+
+ val pusher = pushersManager.getPusherForCurrentSession()
+
+ pusher shouldBeEqualTo expectedPusher
+ }
+
+ @Test
+ fun `when togglePusherForCurrentSession, then do service toggle pusher`() = runTest {
+ val deviceId = "device_id"
+ val sessionParams = SessionParamsFixture.aSessionParams(
+ credentials = CredentialsFixture.aCredentials(deviceId = deviceId)
+ )
+ session.givenSessionParams(sessionParams)
+ val pusher = PusherFixture.aPusher(deviceId = deviceId)
+ pushersService.givenGetPushers(listOf(pusher))
+
+ pushersManager.togglePusherForCurrentSession(true)
+
+ pushersService.verifyOnlyGetPushersAndTogglePusherCalled(pusher, true)
+ }
}
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 da602ae227..73c9c5aa95 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
@@ -21,6 +21,7 @@ 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
@@ -55,8 +56,10 @@ 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"
@@ -203,6 +206,113 @@ 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()
+ 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,
+ pushers = Loading(),
+ )
+
+ // 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()
+ 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,
+ pushers = Loading(),
+ )
+ 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()
+ 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,
+ pushers = Loading(),
+ )
+ 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
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 60d50eab03..d506f53e60 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
@@ -29,10 +29,21 @@ import org.matrix.android.sdk.api.session.pushers.PushersService
class FakePushersService : PushersService by mockk(relaxed = true) {
+ fun givenGetPushers(pushers: List) {
+ every { getPushers() } returns pushers
+ }
+
fun givenPushersLive(pushers: List) {
every { getPushersLive() } returns liveData { emit(pushers) }
}
+ fun verifyOnlyGetPushersAndTogglePusherCalled(pusher: Pusher, enable: Boolean) {
+ coVerify(ordering = Ordering.ALL) {
+ getPushers()
+ togglePusher(pusher, enable)
+ }
+ }
+
fun verifyOnlyTogglePusherCalled(pusher: Pusher, enable: Boolean) {
coVerify(ordering = Ordering.ALL) {
getPushersLive() // verifies only getPushersLive and the following togglePusher was called
diff --git a/vector/src/test/java/im/vector/app/test/fixtures/CredentialsFixture.kt b/vector/src/test/java/im/vector/app/test/fixtures/CredentialsFixture.kt
new file mode 100644
index 0000000000..1a70806e76
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/test/fixtures/CredentialsFixture.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.test.fixtures
+
+import org.matrix.android.sdk.api.auth.data.Credentials
+import org.matrix.android.sdk.api.auth.data.DiscoveryInformation
+
+object CredentialsFixture {
+ fun aCredentials(
+ userId: String = "",
+ accessToken: String = "",
+ refreshToken: String? = null,
+ homeServer: String? = null,
+ deviceId: String? = null,
+ discoveryInformation: DiscoveryInformation? = null,
+ ) = Credentials(
+ userId,
+ accessToken,
+ refreshToken,
+ homeServer,
+ deviceId,
+ discoveryInformation,
+ )
+}
diff --git a/vector/src/test/java/im/vector/app/test/fixtures/SessionParamsFixture.kt b/vector/src/test/java/im/vector/app/test/fixtures/SessionParamsFixture.kt
new file mode 100644
index 0000000000..598a95707b
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/test/fixtures/SessionParamsFixture.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.test.fixtures
+
+import im.vector.app.test.fixtures.CredentialsFixture.aCredentials
+import io.mockk.mockk
+import org.matrix.android.sdk.api.auth.LoginType
+import org.matrix.android.sdk.api.auth.data.Credentials
+import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
+import org.matrix.android.sdk.api.auth.data.SessionParams
+
+object SessionParamsFixture {
+ fun aSessionParams(
+ credentials: Credentials = aCredentials(),
+ homeServerConnectionConfig: HomeServerConnectionConfig = mockk(relaxed = true),
+ isTokenValid: Boolean = false,
+ loginType: LoginType = LoginType.UNKNOWN,
+ ) = SessionParams(
+ credentials,
+ homeServerConnectionConfig,
+ isTokenValid,
+ loginType,
+ )
+}