Hide message notification content when the app is locked (#6902)

* Hide message notification content by default when the app is locked

* Make this the default without an option

* Fix test

* Update DefaultNotificationDrawerManager.kt

* Fix
This commit is contained in:
bxdxnn
2026-06-19 16:34:38 +03:00
committed by GitHub
parent d4aa49f3d3
commit 790b9cff63
5 changed files with 66 additions and 4 deletions
@@ -11,6 +11,7 @@ package io.element.android.libraries.push.impl.notifications
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesBinding
import dev.zacsweers.metro.SingleIn
import io.element.android.features.lockscreen.api.LockScreenService
import io.element.android.libraries.di.annotations.AppCoroutineScope
import io.element.android.libraries.matrix.api.MatrixClientProvider
import io.element.android.libraries.matrix.api.core.EventId
@@ -37,6 +38,7 @@ import io.element.android.services.appnavstate.api.currentRoomId
import io.element.android.services.appnavstate.api.currentSessionId
import io.element.android.services.appnavstate.api.currentThreadId
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
/**
@@ -55,6 +57,7 @@ class DefaultNotificationDrawerManager(
private val matrixClientProvider: MatrixClientProvider,
private val imageLoaderHolder: ImageLoaderHolder,
private val activeNotificationsProvider: ActiveNotificationsProvider,
private val lockScreenService: LockScreenService,
sessionObserver: SessionObserver,
) : NotificationCleaner {
// TODO EAx add a setting per user for this
@@ -197,6 +200,8 @@ class DefaultNotificationDrawerManager(
it.sessionId
}
val isAppLocked = lockScreenService.isPinSetup().first()
for ((sessionId, notifiableEvents) in eventsForSessions) {
val client = matrixClientProvider.getOrRestore(sessionId).getOrThrow()
val imageLoader = imageLoaderHolder.get(client)
@@ -207,7 +212,25 @@ class DefaultNotificationDrawerManager(
} else {
client.getUserProfile().getOrNull() ?: MatrixUser(sessionId)
}
notificationRenderer.render(currentUser, useCompleteNotificationFormat, notifiableEvents, imageLoader)
if (isAppLocked) {
// When the app is locked, show a single fallback notification with event count
// instead of per-room notifications with message content/sender info.
clearAllMessagesEvents(sessionId)
val fallbackEvents = notifiableEvents.mapNotNull { it.toFallbackNotifiableEvent() }
notificationRenderer.render(
currentUser = currentUser,
useCompleteNotificationFormat = false,
eventsToProcess = fallbackEvents,
imageLoader = imageLoader,
)
} else {
notificationRenderer.render(
currentUser = currentUser,
useCompleteNotificationFormat = useCompleteNotificationFormat,
eventsToProcess = notifiableEvents,
imageLoader = imageLoader,
)
}
}
}
}
@@ -238,3 +261,29 @@ private fun AppNavigationState.shouldIgnoreEvent(event: NotifiableEvent): Boolea
}
}
}
/**
* Convert a [NotifiableEvent] into a [FallbackNotifiableEvent], stripping all content and sender info.
* Used when notification content should be hidden (app locked with PIN).
*/
private fun NotifiableEvent.toFallbackNotifiableEvent(): FallbackNotifiableEvent? {
val timestamp = when (this) {
is NotifiableMessageEvent -> timestamp
is InviteNotifiableEvent -> timestamp
is SimpleNotifiableEvent -> timestamp
is FallbackNotifiableEvent -> timestamp
is NotifiableRingingCallEvent -> return null
}
return FallbackNotifiableEvent(
sessionId = sessionId,
roomId = roomId,
eventId = eventId,
editedEventId = null,
description = null,
canBeReplaced = false,
isRedacted = false,
isUpdated = false,
timestamp = timestamp,
cause = null,
)
}
@@ -67,7 +67,7 @@ class DefaultRoomGroupMessageCreator(
val roomIsDm = !roomIsGroup
return notificationCreator.createMessagesListNotification(
notificationAccountParams = notificationAccountParams,
RoomEventGroupInfo(
roomInfo = RoomEventGroupInfo(
sessionId = notificationAccountParams.user.userId,
roomId = roomId,
roomDisplayName = roomName,
@@ -13,6 +13,8 @@ import androidx.compose.ui.graphics.Color
import com.google.common.truth.Truth.assertThat
import io.element.android.features.enterprise.api.EnterpriseService
import io.element.android.features.enterprise.test.FakeEnterpriseService
import io.element.android.features.lockscreen.api.LockScreenService
import io.element.android.features.lockscreen.test.FakeLockScreenService
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_ROOM_ID_2
@@ -510,6 +512,7 @@ fun TestScope.createDefaultNotificationDrawerManager(
enterpriseService: EnterpriseService = FakeEnterpriseService(),
sessionObserver: SessionObserver = FakeSessionObserver(),
analyticsService: FakeAnalyticsService = FakeAnalyticsService(),
lockScreenService: LockScreenService = FakeLockScreenService(),
): DefaultNotificationDrawerManager {
return DefaultNotificationDrawerManager(
notificationDisplayer = notificationDisplayer,
@@ -530,6 +533,7 @@ fun TestScope.createDefaultNotificationDrawerManager(
matrixClientProvider = matrixClientProvider,
imageLoaderHolder = FakeImageLoaderHolder(),
activeNotificationsProvider = activeNotificationsProvider,
lockScreenService = lockScreenService,
sessionObserver = sessionObserver,
)
}
@@ -57,7 +57,17 @@ class FakeNotificationCreator(
events: List<NotifiableMessageEvent>,
): Notification {
return createMessagesListNotificationResult(
listOf(notificationAccountParams, roomInfo, threadId, largeIcon, lastMessageTimestamp, tickerText, existingNotification, imageLoader, events)
listOf(
notificationAccountParams,
roomInfo,
threadId,
largeIcon,
lastMessageTimestamp,
tickerText,
existingNotification,
imageLoader,
events,
)
)
}
@@ -56,7 +56,6 @@ class UserPushStoreDataStore(
private val currentPushKey = stringPreferencesKey("currentPushKey")
private val notificationEnabled = booleanPreferencesKey("notificationEnabled")
private val ignoreRegistrationError = booleanPreferencesKey("ignoreRegistrationError")
override suspend fun getPushProviderName(): String? {
return store.data.first()[pushProviderName]
}