Skip to content

Commit

Permalink
Merge pull request #7476 from vector-im/fix/mna/push-toggle-check-sup…
Browse files Browse the repository at this point in the history
…port

[Session manager] Hide push notification toggle when there is no server support (PSG-970)
  • Loading branch information
mnaturel authored Nov 4, 2022
2 parents f34758c + e9daef9 commit 4621488
Show file tree
Hide file tree
Showing 24 changed files with 572 additions and 65 deletions.
1 change: 1 addition & 0 deletions changelog.d/7457.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[Session manager] Hide push notification toggle when there is no server support
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ data class HomeServerCapabilities(
* True if the home server supports threaded read receipts and unread notifications.
*/
val canUseThreadReadReceiptsAndNotifications: Boolean = false,

/**
* True if the home server supports remote toggle of Pusher for a given device.
*/
val canRemotelyTogglePushNotificationsOfDevices: Boolean = false,
) {

enum class RoomCapabilitySupport {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.auth.version

import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.extensions.orFalse

/**
* Model for https://matrix.org/docs/spec/client_server/latest#get-matrix-client-versions.
Expand Down Expand Up @@ -56,6 +57,7 @@ private const val FEATURE_THREADS_MSC3440_STABLE = "org.matrix.msc3440.stable"
private const val FEATURE_QR_CODE_LOGIN = "org.matrix.msc3882"
private const val FEATURE_THREADS_MSC3771 = "org.matrix.msc3771"
private const val FEATURE_THREADS_MSC3773 = "org.matrix.msc3773"
private const val FEATURE_REMOTE_TOGGLE_PUSH_NOTIFICATIONS_MSC3881 = "org.matrix.msc3881"

/**
* Return true if the SDK supports this homeserver version.
Expand Down Expand Up @@ -142,3 +144,12 @@ private fun Versions.getMaxVersion(): HomeServerVersion {
?.maxOrNull()
?: HomeServerVersion.r0_0_0
}

/**
* Indicate if the server supports MSC3881: https://github.com/matrix-org/matrix-spec-proposals/pull/3881.
*
* @return true if remote toggle of push notifications is supported
*/
internal fun Versions.doesServerSupportRemoteToggleOfPushNotifications(): Boolean {
return unstableFeatures?.get(FEATURE_REMOTE_TOGGLE_PUSH_NOTIFICATIONS_MSC3881).orFalse()
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo038
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo039
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo040
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo041
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo042
import org.matrix.android.sdk.internal.util.Normalizer
import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
import javax.inject.Inject
Expand All @@ -66,7 +67,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
private val normalizer: Normalizer
) : MatrixRealmMigration(
dbName = "Session",
schemaVersion = 41L,
schemaVersion = 42L,
) {
/**
* Forces all RealmSessionStoreMigration instances to be equal.
Expand Down Expand Up @@ -117,5 +118,6 @@ internal class RealmSessionStoreMigration @Inject constructor(
if (oldVersion < 39) MigrateSessionTo039(realm).perform()
if (oldVersion < 40) MigrateSessionTo040(realm).perform()
if (oldVersion < 41) MigrateSessionTo041(realm).perform()
if (oldVersion < 42) MigrateSessionTo042(realm).perform()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ internal object HomeServerCapabilitiesMapper {
canUseThreading = entity.canUseThreading,
canControlLogoutDevices = entity.canControlLogoutDevices,
canLoginWithQrCode = entity.canLoginWithQrCode,
canUseThreadReadReceiptsAndNotifications = entity.canUseThreadReadReceiptsAndNotifications
canUseThreadReadReceiptsAndNotifications = entity.canUseThreadReadReceiptsAndNotifications,
canRemotelyTogglePushNotificationsOfDevices = entity.canRemotelyTogglePushNotificationsOfDevices,
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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.HomeServerCapabilitiesEntityFields
import org.matrix.android.sdk.internal.extensions.forceRefreshOfHomeServerCapabilities
import org.matrix.android.sdk.internal.util.database.RealmMigrator

internal class MigrateSessionTo042(realm: DynamicRealm) : RealmMigrator(realm, 42) {

override fun doMigrate(realm: DynamicRealm) {
realm.schema.get("HomeServerCapabilitiesEntity")
?.addField(HomeServerCapabilitiesEntityFields.CAN_REMOTELY_TOGGLE_PUSH_NOTIFICATIONS_OF_DEVICES, Boolean::class.java)
?.forceRefreshOfHomeServerCapabilities()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ internal open class HomeServerCapabilitiesEntity(
var canControlLogoutDevices: Boolean = false,
var canLoginWithQrCode: Boolean = false,
var canUseThreadReadReceiptsAndNotifications: Boolean = false,
var canRemotelyTogglePushNotificationsOfDevices: Boolean = false,
) : RealmObject() {

companion object
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
import org.matrix.android.sdk.internal.auth.version.Versions
import org.matrix.android.sdk.internal.auth.version.doesServerSupportLogoutDevices
import org.matrix.android.sdk.internal.auth.version.doesServerSupportQrCodeLogin
import org.matrix.android.sdk.internal.auth.version.doesServerSupportRemoteToggleOfPushNotifications
import org.matrix.android.sdk.internal.auth.version.doesServerSupportThreadUnreadNotifications
import org.matrix.android.sdk.internal.auth.version.doesServerSupportThreads
import org.matrix.android.sdk.internal.auth.version.isLoginAndRegistrationSupportedBySdk
Expand Down Expand Up @@ -141,13 +142,18 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
}

if (getVersionResult != null) {
homeServerCapabilitiesEntity.lastVersionIdentityServerSupported = getVersionResult.isLoginAndRegistrationSupportedBySdk()
homeServerCapabilitiesEntity.canControlLogoutDevices = getVersionResult.doesServerSupportLogoutDevices()
homeServerCapabilitiesEntity.lastVersionIdentityServerSupported =
getVersionResult.isLoginAndRegistrationSupportedBySdk()
homeServerCapabilitiesEntity.canControlLogoutDevices =
getVersionResult.doesServerSupportLogoutDevices()
homeServerCapabilitiesEntity.canUseThreading = /* capabilities?.threads?.enabled.orFalse() || */
getVersionResult.doesServerSupportThreads()
homeServerCapabilitiesEntity.canUseThreadReadReceiptsAndNotifications =
getVersionResult.doesServerSupportThreadUnreadNotifications()
homeServerCapabilitiesEntity.canLoginWithQrCode = getVersionResult.doesServerSupportQrCodeLogin()
homeServerCapabilitiesEntity.canLoginWithQrCode =
getVersionResult.doesServerSupportQrCodeLogin()
homeServerCapabilitiesEntity.canRemotelyTogglePushNotificationsOfDevices =
getVersionResult.doesServerSupportRemoteToggleOfPushNotifications()
}

if (getWellknownResult != null && getWellknownResult is WellknownResult.Prompt) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* 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.notification

import im.vector.app.core.di.ActiveSessionHolder
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
import javax.inject.Inject

class CheckIfCanTogglePushNotificationsViaAccountDataUseCase @Inject constructor(
private val activeSessionHolder: ActiveSessionHolder,
) {

fun execute(deviceId: String): Boolean {
return activeSessionHolder
.getSafeActiveSession()
?.accountDataService()
?.getUserAccountDataEvent(UserAccountDataTypes.TYPE_LOCAL_NOTIFICATION_SETTINGS + deviceId) != null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* 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.notification

import im.vector.app.core.di.ActiveSessionHolder
import org.matrix.android.sdk.api.extensions.orFalse
import javax.inject.Inject

class CheckIfCanTogglePushNotificationsViaPusherUseCase @Inject constructor(
private val activeSessionHolder: ActiveSessionHolder,
) {

fun execute(): Boolean {
return activeSessionHolder
.getSafeActiveSession()
?.homeServerCapabilitiesService()
?.getHomeServerCapabilities()
?.canRemotelyTogglePushNotificationsOfDevices
.orFalse()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* 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.notification

import im.vector.app.core.di.ActiveSessionHolder
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import org.matrix.android.sdk.api.account.LocalNotificationSettingsContent
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.flow.flow
import org.matrix.android.sdk.flow.unwrap
import javax.inject.Inject

class GetNotificationsStatusUseCase @Inject constructor(
private val activeSessionHolder: ActiveSessionHolder,
private val checkIfCanTogglePushNotificationsViaPusherUseCase: CheckIfCanTogglePushNotificationsViaPusherUseCase,
private val checkIfCanTogglePushNotificationsViaAccountDataUseCase: CheckIfCanTogglePushNotificationsViaAccountDataUseCase,
) {

fun execute(deviceId: String): Flow<NotificationsStatus> {
val session = activeSessionHolder.getSafeActiveSession()
return when {
session == null -> flowOf(NotificationsStatus.NOT_SUPPORTED)
checkIfCanTogglePushNotificationsViaAccountDataUseCase.execute(deviceId) -> {
session.flow()
.liveUserAccountData(UserAccountDataTypes.TYPE_LOCAL_NOTIFICATION_SETTINGS + deviceId)
.unwrap()
.map { it.content.toModel<LocalNotificationSettingsContent>()?.isSilenced?.not() }
.map { if (it == true) NotificationsStatus.ENABLED else NotificationsStatus.DISABLED }
.distinctUntilChanged()
}
checkIfCanTogglePushNotificationsViaPusherUseCase.execute() -> {
session.flow()
.livePushers()
.map { it.filter { pusher -> pusher.deviceId == deviceId } }
.map { it.takeIf { it.isNotEmpty() }?.any { pusher -> pusher.enabled } }
.map { if (it == true) NotificationsStatus.ENABLED else NotificationsStatus.DISABLED }
.distinctUntilChanged()
}
else -> flowOf(NotificationsStatus.NOT_SUPPORTED)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* 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.notification

enum class NotificationsStatus {
ENABLED,
DISABLED,
NOT_SUPPORTED,
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

package im.vector.app.features.settings.devices.v2.overview
package im.vector.app.features.settings.devices.v2.notification

import im.vector.app.core.di.ActiveSessionHolder
import org.matrix.android.sdk.api.account.LocalNotificationSettingsContent
Expand All @@ -24,17 +24,21 @@ import javax.inject.Inject

class TogglePushNotificationUseCase @Inject constructor(
private val activeSessionHolder: ActiveSessionHolder,
private val checkIfCanTogglePushNotificationsViaPusherUseCase: CheckIfCanTogglePushNotificationsViaPusherUseCase,
private val checkIfCanTogglePushNotificationsViaAccountDataUseCase: CheckIfCanTogglePushNotificationsViaAccountDataUseCase,
) {

suspend fun execute(deviceId: String, enabled: Boolean) {
val session = activeSessionHolder.getSafeActiveSession() ?: return
val devicePusher = session.pushersService().getPushers().firstOrNull { it.deviceId == deviceId }
devicePusher?.let { pusher ->
session.pushersService().togglePusher(pusher, enabled)

if (checkIfCanTogglePushNotificationsViaPusherUseCase.execute()) {
val devicePusher = session.pushersService().getPushers().firstOrNull { it.deviceId == deviceId }
devicePusher?.let { pusher ->
session.pushersService().togglePusher(pusher, enabled)
}
}

val accountData = session.accountDataService().getUserAccountDataEvent(UserAccountDataTypes.TYPE_LOCAL_NOTIFICATION_SETTINGS + deviceId)
if (accountData != null) {
if (checkIfCanTogglePushNotificationsViaAccountDataUseCase.execute(deviceId)) {
val newNotificationSettingsContent = LocalNotificationSettingsContent(isSilenced = !enabled)
session.accountDataService().updateUserAccountData(
UserAccountDataTypes.TYPE_LOCAL_NOTIFICATION_SETTINGS + deviceId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -43,6 +44,7 @@ import im.vector.app.features.auth.ReAuthActivity
import im.vector.app.features.crypto.recover.SetupMode
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.settings.devices.v2.notification.NotificationsStatus
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
Expand Down Expand Up @@ -177,7 +179,7 @@ class SessionOverviewFragment :
updateEntryDetails(state.deviceId)
updateSessionInfo(state)
updateLoading(state.isLoading)
updatePushNotificationToggle(state.deviceId, state.notificationsEnabled)
updatePushNotificationToggle(state.deviceId, state.notificationsStatus)
}

private fun updateToolbar(viewState: SessionOverviewViewState) {
Expand Down Expand Up @@ -218,15 +220,19 @@ class SessionOverviewFragment :
}
}

private fun updatePushNotificationToggle(deviceId: String, enabled: Boolean) {
views.sessionOverviewPushNotifications.apply {
setOnCheckedChangeListener(null)
setChecked(enabled)
post {
setOnCheckedChangeListener { _, isChecked ->
viewModel.handle(SessionOverviewAction.TogglePushNotifications(deviceId, isChecked))
private fun updatePushNotificationToggle(deviceId: String, notificationsStatus: NotificationsStatus) {
views.sessionOverviewPushNotifications.isGone = notificationsStatus == NotificationsStatus.NOT_SUPPORTED
when (notificationsStatus) {
NotificationsStatus.ENABLED, NotificationsStatus.DISABLED -> {
views.sessionOverviewPushNotifications.setOnCheckedChangeListener(null)
views.sessionOverviewPushNotifications.setChecked(notificationsStatus == NotificationsStatus.ENABLED)
views.sessionOverviewPushNotifications.post {
views.sessionOverviewPushNotifications.setOnCheckedChangeListener { _, isChecked ->
viewModel.handle(SessionOverviewAction.TogglePushNotifications(deviceId, isChecked))
}
}
}
else -> Unit
}
}

Expand Down
Loading

0 comments on commit 4621488

Please sign in to comment.