UnifiedPush: emit error when registration fails.
Note that I did not manage to have the method `onRegistrationFailed` invoked. If the network is not available for instance, unregistering the previous pusher will fail first.
This commit is contained in:
parent
6b7e8f72f5
commit
f9c0b9e8bb
16 changed files with 208 additions and 23 deletions
|
|
@ -14,12 +14,14 @@ import dev.zacsweers.metro.SingleIn
|
|||
import dev.zacsweers.metro.binding
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.push.api.GetCurrentPushProvider
|
||||
import io.element.android.libraries.push.api.PushService
|
||||
import io.element.android.libraries.push.api.history.PushHistoryItem
|
||||
import io.element.android.libraries.push.impl.push.MutableBatteryOptimizationStore
|
||||
import io.element.android.libraries.push.impl.store.PushDataStore
|
||||
import io.element.android.libraries.push.impl.test.TestPush
|
||||
import io.element.android.libraries.push.impl.unregistration.ServiceUnregisteredHandler
|
||||
import io.element.android.libraries.pushproviders.api.Distributor
|
||||
import io.element.android.libraries.pushproviders.api.PushProvider
|
||||
import io.element.android.libraries.pushstore.api.UserPushStoreFactory
|
||||
|
|
@ -40,6 +42,7 @@ class DefaultPushService(
|
|||
private val pushClientSecretStore: PushClientSecretStore,
|
||||
private val pushDataStore: PushDataStore,
|
||||
private val mutableBatteryOptimizationStore: MutableBatteryOptimizationStore,
|
||||
private val serviceUnregisteredHandler: ServiceUnregisteredHandler,
|
||||
) : PushService, SessionListener {
|
||||
init {
|
||||
observeSessions()
|
||||
|
|
@ -141,4 +144,8 @@ class DefaultPushService(
|
|||
override suspend fun resetBatteryOptimizationState() {
|
||||
mutableBatteryOptimizationStore.reset()
|
||||
}
|
||||
|
||||
override suspend fun onServiceUnregistered(userId: UserId) {
|
||||
serviceUnregisteredHandler.handle(userId)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ interface NotificationDisplayer {
|
|||
fun cancelNotification(tag: String?, id: Int)
|
||||
fun displayDiagnosticNotification(notification: Notification): Boolean
|
||||
fun dismissDiagnosticNotification()
|
||||
fun displayUnregistrationNotification(notification: Notification): Boolean
|
||||
}
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
|
|
@ -60,6 +61,14 @@ class DefaultNotificationDisplayer(
|
|||
)
|
||||
}
|
||||
|
||||
override fun displayUnregistrationNotification(notification: Notification): Boolean {
|
||||
return showNotification(
|
||||
tag = TAG_DIAGNOSTIC,
|
||||
id = NOTIFICATION_ID_UNREGISTRATION,
|
||||
notification = notification,
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG_DIAGNOSTIC = "DIAGNOSTIC"
|
||||
|
||||
|
|
@ -67,5 +76,6 @@ class DefaultNotificationDisplayer(
|
|||
* IDs for notifications
|
||||
* ========================================================================================== */
|
||||
private const val NOTIFICATION_ID_DIAGNOSTIC = 888
|
||||
private const val NOTIFICATION_ID_UNREGISTRATION = 889
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,6 +91,10 @@ interface NotificationCreator {
|
|||
@ColorInt color: Int,
|
||||
): Notification
|
||||
|
||||
fun createUnregistrationNotification(
|
||||
notificationAccountParams: NotificationAccountParams,
|
||||
): Notification
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Creates a tag for a message notification given its [roomId] and optional [threadId].
|
||||
|
|
@ -352,6 +356,28 @@ class DefaultNotificationCreator(
|
|||
.build()
|
||||
}
|
||||
|
||||
override fun createUnregistrationNotification(
|
||||
notificationAccountParams: NotificationAccountParams,
|
||||
): Notification {
|
||||
val userId = notificationAccountParams.user.userId
|
||||
val text = if (notificationAccountParams.showSessionId) {
|
||||
// TODO i18n
|
||||
"$userId will not receive notifications anymore."
|
||||
} else {
|
||||
// TODO i18n
|
||||
"You will not receive notifications anymore."
|
||||
}
|
||||
return NotificationCompat.Builder(context, notificationChannels.getChannelIdForTest())
|
||||
.setContentTitle(stringProvider.getString(CommonStrings.dialog_title_warning))
|
||||
.setContentText(text)
|
||||
.configureWith(notificationAccountParams)
|
||||
.setPriority(NotificationCompat.PRIORITY_MAX)
|
||||
.setCategory(NotificationCompat.CATEGORY_ERROR)
|
||||
.setAutoCancel(true)
|
||||
.setContentIntent(pendingIntentFactory.createOpenSessionPendingIntent(userId))
|
||||
.build()
|
||||
}
|
||||
|
||||
private suspend fun MessagingStyle.addMessagesFromEvents(
|
||||
events: List<NotifiableMessageEvent>,
|
||||
imageLoader: ImageLoader,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.push.impl.unregistration
|
||||
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.appconfig.NotificationConfig
|
||||
import io.element.android.features.enterprise.api.EnterpriseService
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.push.impl.notifications.NotificationDisplayer
|
||||
import io.element.android.libraries.push.impl.notifications.factories.NotificationAccountParams
|
||||
import io.element.android.libraries.push.impl.notifications.factories.NotificationCreator
|
||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||
import kotlinx.coroutines.flow.first
|
||||
|
||||
interface ServiceUnregisteredHandler {
|
||||
suspend fun handle(userId: UserId)
|
||||
}
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultServiceUnregisteredHandler(
|
||||
private val enterpriseService: EnterpriseService,
|
||||
private val notificationCreator: NotificationCreator,
|
||||
private val notificationDisplayer: NotificationDisplayer,
|
||||
private val sessionStore: SessionStore,
|
||||
) : ServiceUnregisteredHandler {
|
||||
override suspend fun handle(userId: UserId) {
|
||||
val color = enterpriseService.brandColorsFlow(userId).first()?.toArgb()
|
||||
?: NotificationConfig.NOTIFICATION_ACCENT_COLOR
|
||||
val hasMultipleAccounts = sessionStore.numberOfSessions() > 1
|
||||
val notification = notificationCreator.createUnregistrationNotification(
|
||||
NotificationAccountParams(
|
||||
user = MatrixUser(userId),
|
||||
color = color,
|
||||
showSessionId = hasMultipleAccounts,
|
||||
)
|
||||
)
|
||||
notificationDisplayer.displayDiagnosticNotification(notification)
|
||||
}
|
||||
}
|
||||
|
|
@ -25,6 +25,8 @@ import io.element.android.libraries.push.impl.store.InMemoryPushDataStore
|
|||
import io.element.android.libraries.push.impl.store.PushDataStore
|
||||
import io.element.android.libraries.push.impl.test.FakeTestPush
|
||||
import io.element.android.libraries.push.impl.test.TestPush
|
||||
import io.element.android.libraries.push.impl.unregistration.FakeServiceUnregisteredHandler
|
||||
import io.element.android.libraries.push.impl.unregistration.ServiceUnregisteredHandler
|
||||
import io.element.android.libraries.push.test.FakeGetCurrentPushProvider
|
||||
import io.element.android.libraries.pushproviders.api.Config
|
||||
import io.element.android.libraries.pushproviders.api.Distributor
|
||||
|
|
@ -346,6 +348,7 @@ class DefaultPushServiceTest {
|
|||
pushClientSecretStore: PushClientSecretStore = InMemoryPushClientSecretStore(),
|
||||
pushDataStore: PushDataStore = InMemoryPushDataStore(),
|
||||
mutableBatteryOptimizationStore: MutableBatteryOptimizationStore = FakeMutableBatteryOptimizationStore(),
|
||||
serviceUnregisteredHandler: ServiceUnregisteredHandler = FakeServiceUnregisteredHandler(),
|
||||
): DefaultPushService {
|
||||
return DefaultPushService(
|
||||
testPush = testPush,
|
||||
|
|
@ -356,6 +359,7 @@ class DefaultPushServiceTest {
|
|||
pushClientSecretStore = pushClientSecretStore,
|
||||
pushDataStore = pushDataStore,
|
||||
mutableBatteryOptimizationStore = mutableBatteryOptimizationStore,
|
||||
serviceUnregisteredHandler = serviceUnregisteredHandler,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,8 @@ class FakeNotificationCreator(
|
|||
> = lambdaRecorder { _, _, _, _, _ -> A_NOTIFICATION },
|
||||
var createDiagnosticNotificationResult: LambdaOneParamRecorder<Int, Notification> =
|
||||
lambdaRecorder<Int, Notification> { _ -> A_NOTIFICATION },
|
||||
val createUnregistrationNotificationResult: LambdaOneParamRecorder<NotificationAccountParams, Notification> =
|
||||
lambdaRecorder { _ -> A_NOTIFICATION },
|
||||
) : NotificationCreator {
|
||||
override suspend fun createMessagesListNotification(
|
||||
notificationAccountParams: NotificationAccountParams,
|
||||
|
|
@ -93,4 +95,8 @@ class FakeNotificationCreator(
|
|||
): Notification {
|
||||
return createDiagnosticNotificationResult(color)
|
||||
}
|
||||
|
||||
override fun createUnregistrationNotification(notificationAccountParams: NotificationAccountParams): Notification {
|
||||
return createUnregistrationNotificationResult(notificationAccountParams)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ class FakeNotificationDisplayer(
|
|||
var cancelNotificationResult: LambdaTwoParamsRecorder<String?, Int, Unit> = lambdaRecorder { _, _ -> },
|
||||
var displayDiagnosticNotificationResult: LambdaOneParamRecorder<Notification, Boolean> = lambdaRecorder { _ -> true },
|
||||
var dismissDiagnosticNotificationResult: LambdaNoParamRecorder<Unit> = lambdaRecorder { -> },
|
||||
var displayUnregistrationNotificationResult: LambdaOneParamRecorder<Notification, Boolean> = lambdaRecorder { _ -> true },
|
||||
) : NotificationDisplayer {
|
||||
override fun showNotification(tag: String?, id: Int, notification: Notification): Boolean {
|
||||
return showNotificationResult(tag, id, notification)
|
||||
|
|
@ -41,6 +42,10 @@ class FakeNotificationDisplayer(
|
|||
return dismissDiagnosticNotificationResult()
|
||||
}
|
||||
|
||||
override fun displayUnregistrationNotification(notification: Notification): Boolean {
|
||||
return displayUnregistrationNotificationResult(notification)
|
||||
}
|
||||
|
||||
fun verifySummaryCancelled(times: Int = 1) {
|
||||
cancelNotificationResult.assertions().isCalledExactly(times).withSequence(
|
||||
listOf(value(null), value(NotificationIdProvider.getSummaryNotificationId(A_SESSION_ID)))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.push.impl.unregistration
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
|
||||
class FakeServiceUnregisteredHandler(
|
||||
private val handleResult: (UserId) -> Unit = { lambdaError() },
|
||||
) : ServiceUnregisteredHandler {
|
||||
override suspend fun handle(userId: UserId) {
|
||||
handleResult(userId)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue