Repository: element-hq/element-x-android (language: kotlin)

Issue:
Selecting ntfy as notification provider does not work
### Steps to reproduce

1. In "Advanced settings", tap "Push notification provider"
2. From the selection of "Firebase" and "ntfy", pick "ntfy"
3. "Push notification provider" setting now shows "Error" as current value, going back and then into "Advanced settings" again makes it switch back to "Firebase"

### Outcome

#### What did you expect?

It is possible to use ntfy as a push notification provider.

#### What happened instead?

Changing the provider to ntfy fails. Other apps like FediLab can register their ntfy topics without issue.

### Your phone model

Samsung S10

### Operating system version

LineageOS for microG 20

### Application version and app store

EXA 0.4.13

### Homeserver

xiretza.xyz, Synapse v1.105.1

### Will you send logs?

Yes

### Are you willing to provide a PR?

No


Discussion hints:
OK, thanks for the issue and the log. If when you come back to the Advanced setting screen and see again "Firebase", it means that the unregistration of Firebase has failed. And indeed, I see in your log:

> java.lang.IllegalStateException: Unable to unregister pusher, Firebase token is not known.

The application should be lenient in this case I guess, I will create a PR.

Pull request: Fix unregistering pusher local error
<!-- Please read [CONTRIBUTING.md](https://github.com/element-hq/element-x-android/blob/develop/CONTRIBUTING.md) before submitting your pull request -->
 
## Type of change

- [ ] Feature
- [x] Bugfix
- [ ] Technical
- [ ] Other :

## Content

<!-- Describe shortly what has been changed -->
Fixes #2895 by ignoring local error when unregistering a pusher (Firebase and UnifiedPush cases)

Base on the `unitTestPush` branch to be able to update the test, but the other PR will be merged first.

## Motivation and context

<!-- Provide link to the corresponding issue if applicable or explain the context -->
Fixes #2895 

## Screenshots / GIFs

<!--
We have screenshot tests in the project, so attaching screenshots to a PR is not mandatory, as far as there
is a Composable Preview covering the changes. In this case, the change will appear in the file diff.
Note that all the UI composables should be covered by a Composable Preview.

Providing a video of the change is still very useful for the reviewer and for the history of the project.

You can use a table like this to show screenshots comparison.
Uncomment this markdown table below and edit the last line `|||`:
|copy screenshot of before here|copy screenshot of after here|

|Before|After|
|-|-|
|||
 -->
NA

## Tests

<!-- Explain how you tested your development -->

- Hard to test without hacking the code, but IRL this seems to happen.

Maybe @Xiretza may want to test the patch.

## Tested devices

- [ ] Physical
- [x] Emulator
- OS version(s):

## Checklist

<!-- Depending on the Pull Request content, it can be acceptable if some of the following checkboxes stay unchecked. -->

- [ ] Changes have been tested on an Android device or Android emulator with API 23
- [ ] UI change has been tested on both light and dark themes
- [ ] Accessibility has been taken into account. See https://github.com/element-hq/element-x-android/blob/develop/CONTRIBUTING.md#accessibility
- [ ] Pull request is based on the develop branch
- [ ] Pull request includes a new file under ./changelog.d. See https://github.com/element-hq/element-x-android/blob/develop/CONTRIBUTING.md#changelog
- [ ] Pull request includes screenshots or videos if containing UI changes
- [ ] Pull request includes a [sign off](https://matrix-org.github.io/synapse/latest/development/contributing_guide.html#sign-off)
- [ ] You've made a self review of your PR


Test command: ./gradlew :libraries:pushproviders:firebase:testDebugUnitTest --tests "io.element.android.libraries.pushproviders.firebase.FirebasePushProviderTest" :libraries:pushproviders:unifiedpush:testDebugUnitTest --tests "io.element.android.libraries.pushproviders.unifiedpush.DefaultUnregisterUnifiedPushUseCaseTest"

Fix patch:
diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultPushService.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultPushService.kt
index bf2b1ed5422..47e26fb9207 100644
--- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultPushService.kt
+++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultPushService.kt
@@ -51,13 +51,16 @@ class DefaultPushService @Inject constructor(
         pushProvider: PushProvider,
         distributor: Distributor,
     ): Result<Unit> {
+        Timber.d("Registering with ${pushProvider.name}/${distributor.name}}")
         val userPushStore = userPushStoreFactory.getOrCreate(matrixClient.sessionId)
         val currentPushProviderName = userPushStore.getPushProviderName()
         val currentPushProvider = pushProviders.find { it.name == currentPushProviderName }
         val currentDistributorValue = currentPushProvider?.getCurrentDistributor(matrixClient)?.value
         if (currentPushProviderName != pushProvider.name || currentDistributorValue != distributor.value) {
             // Unregister previous one if any
-            currentPushProvider?.unregister(matrixClient)
+            currentPushProvider
+                ?.also { Timber.d("Unregistering previous push provider $currentPushProviderName/$currentDistributorValue") }
+                ?.unregister(matrixClient)
                 ?.onFailure {
                     Timber.w(it, "Failed to unregister previous push provider")
                     return Result.failure(it)
diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushProvider.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushProvider.kt
index b88d70b1572..0228f8f74ba 100644
--- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushProvider.kt
+++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushProvider.kt
@@ -64,14 +64,13 @@ class FirebasePushProvider @Inject constructor(
     override suspend fun getCurrentDistributor(matrixClient: MatrixClient) = firebaseDistributor
 
     override suspend fun unregister(matrixClient: MatrixClient): Result<Unit> {
-        val pushKey = firebaseStore.getFcmToken() ?: return Result.failure<Unit>(
-            IllegalStateException(
-                "Unable to unregister pusher, Firebase token is not known."
-            )
-        ).also {
+        val pushKey = firebaseStore.getFcmToken()
+        return if (pushKey == null) {
             Timber.tag(loggerTag.value).w("Unable to unregister pusher, Firebase token is not known.")
+            Result.success(Unit)
+        } else {
+            pusherSubscriber.unregisterPusher(matrixClient, pushKey, FirebaseConfig.PUSHER_HTTP_URL)
         }
-        return pusherSubscriber.unregisterPusher(matrixClient, pushKey, FirebaseConfig.PUSHER_HTTP_URL)
     }
 
     override suspend fun getCurrentUserPushConfig(): CurrentUserPushConfig? {
diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnregisterUnifiedPushUseCase.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnregisterUnifiedPushUseCase.kt
index 65d924740de..769f6507d5b 100644
--- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnregisterUnifiedPushUseCase.kt
+++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnregisterUnifiedPushUseCase.kt
@@ -23,6 +23,7 @@ import io.element.android.libraries.di.ApplicationContext
 import io.element.android.libraries.matrix.api.MatrixClient
 import io.element.android.libraries.pushproviders.api.PusherSubscriber
 import org.unifiedpush.android.connector.UnifiedPush
+import timber.log.Timber
 import javax.inject.Inject
 
 interface UnregisterUnifiedPushUseCase {
@@ -39,7 +40,11 @@ class DefaultUnregisterUnifiedPushUseCase @Inject constructor(
         val endpoint = unifiedPushStore.getEndpoint(clientSecret)
         val gateway = unifiedPushStore.getPushGateway(clientSecret)
         if (endpoint == null || gateway == null) {
-            return Result.failure(IllegalStateException("No endpoint or gateway found for client secret"))
+            Timber.w("No endpoint or gateway found for client secret")
+            // Ensure we don't have any remaining data, but ignore this error
+            unifiedPushStore.storeUpEndpoint(clientSecret, null)
+            unifiedPushStore.storePushGateway(clientSecret, null)
+            return Result.success(Unit)
         }
         return pusherSubscriber.unregisterPusher(matrixClient, endpoint, gateway)
             .onSuccess {


Test patch:
diff --git a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushProviderTest.kt b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushProviderTest.kt
index 3c826827673..880be2f053f 100644
--- a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushProviderTest.kt
+++ b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FirebasePushProviderTest.kt
@@ -134,17 +134,14 @@ class FirebasePushProviderTest {
     }
 
     @Test
-    fun `unregister ko no token`() = runTest {
+    fun `unregister no token - in this case, the error is ignored`() = runTest {
         val firebasePushProvider = createFirebasePushProvider(
             firebaseStore = InMemoryFirebaseStore(
                 token = null
             ),
-            pusherSubscriber = FakePusherSubscriber(
-                unregisterPusherResult = { _, _, _ -> Result.success(Unit) }
-            )
         )
         val result = firebasePushProvider.unregister(FakeMatrixClient())
-        assertThat(result.isFailure).isTrue()
+        assertThat(result.isSuccess).isTrue()
     }
 
     @Test
diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/DefaultUnregisterUnifiedPushUseCaseTest.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/DefaultUnregisterUnifiedPushUseCaseTest.kt
index 1d04a07651f..dfa03707cc0 100644
--- a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/DefaultUnregisterUnifiedPushUseCaseTest.kt
+++ b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/DefaultUnregisterUnifiedPushUseCaseTest.kt
@@ -64,29 +64,49 @@ class DefaultUnregisterUnifiedPushUseCaseTest {
     }
 
     @Test
-    fun `test un registration error - no endpoint`() = runTest {
+    fun `test un registration error - no endpoint - will not unregister but return success`() = runTest {
         val matrixClient = FakeMatrixClient()
+        val storeUpEndpointResult = lambdaRecorder { _: String, _: String? -> }
+        val storePushGatewayResult = lambdaRecorder { _: String, _: String? -> }
         val useCase = createDefaultUnregisterUnifiedPushUseCase(
             unifiedPushStore = FakeUnifiedPushStore(
                 getEndpointResult = { null },
                 getPushGatewayResult = { "aGateway" },
+                storeUpEndpointResult = storeUpEndpointResult,
+                storePushGatewayResult = storePushGatewayResult,
             ),
         )
         val result = useCase.execute(matrixClient, A_SECRET)
-        assertThat(result.isFailure).isTrue()
+        assertThat(result.isSuccess).isTrue()
+        storeUpEndpointResult.assertions()
+            .isCalledOnce()
+            .with(value(A_SECRET), value(null))
+        storePushGatewayResult.assertions()
+            .isCalledOnce()
+            .with(value(A_SECRET), value(null))
     }
 
     @Test
-    fun `test un registration error - no gateway`() = runTest {
+    fun `test un registration error - no gateway - will not unregister but return success`() = runTest {
         val matrixClient = FakeMatrixClient()
+        val storeUpEndpointResult = lambdaRecorder { _: String, _: String? -> }
+        val storePushGatewayResult = lambdaRecorder { _: String, _: String? -> }
         val useCase = createDefaultUnregisterUnifiedPushUseCase(
             unifiedPushStore = FakeUnifiedPushStore(
                 getEndpointResult = { "aEndpoint" },
                 getPushGatewayResult = { null },
+                storeUpEndpointResult = storeUpEndpointResult,
+                storePushGatewayResult = storePushGatewayResult,
             ),
         )
         val result = useCase.execute(matrixClient, A_SECRET)
-        assertThat(result.isFailure).isTrue()
+        assertThat(result.isSuccess).isTrue()
+        storeUpEndpointResult.assertions()
+            .isCalledOnce()
+            .with(value(A_SECRET), value(null))
+        storePushGatewayResult.assertions()
+            .isCalledOnce()
+            .with(value(A_SECRET), value(null))
     }
 
     @Test