Troubleshoot notifications screen

This commit is contained in:
Benoit Marty 2024-03-26 11:36:31 +01:00
parent 6c9ea2b920
commit 2bfe125a77
80 changed files with 3086 additions and 99 deletions

View file

@ -0,0 +1,47 @@
/*
* Copyright (c) 2024 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 io.element.android.libraries.pushproviders.unifiedpush
import android.content.Context
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.androidutils.system.getApplicationLabel
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.ApplicationContext
import io.element.android.libraries.pushproviders.api.Distributor
import org.unifiedpush.android.connector.UnifiedPush
import javax.inject.Inject
interface UnifiedPushDistributorProvider {
fun getDistributors(): List<Distributor>
}
@ContributesBinding(AppScope::class)
class DefaultUnifiedPushDistributorProvider @Inject constructor(
@ApplicationContext private val context: Context,
) : UnifiedPushDistributorProvider {
override fun getDistributors(): List<Distributor> {
val distributors = UnifiedPush.getDistributors(context)
return distributors.mapNotNull {
if (it == context.packageName) {
// Exclude self
null
} else {
Distributor(it, context.getApplicationLabel(it))
}
}
}
}

View file

@ -16,17 +16,16 @@
package io.element.android.libraries.pushproviders.unifiedpush
import android.content.Context
import com.squareup.anvil.annotations.ContributesMultibinding
import io.element.android.libraries.androidutils.system.getApplicationLabel
import io.element.android.libraries.core.log.logger.LoggerTag
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.ApplicationContext
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.pushproviders.api.CurrentUserPushConfig
import io.element.android.libraries.pushproviders.api.Distributor
import io.element.android.libraries.pushproviders.api.PushProvider
import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret
import org.unifiedpush.android.connector.UnifiedPush
import io.element.android.services.appnavstate.api.AppNavigationStateService
import io.element.android.services.appnavstate.api.currentSessionId
import timber.log.Timber
import javax.inject.Inject
@ -34,10 +33,12 @@ private val loggerTag = LoggerTag("UnifiedPushProvider", LoggerTag.PushLoggerTag
@ContributesMultibinding(AppScope::class)
class UnifiedPushProvider @Inject constructor(
@ApplicationContext private val context: Context,
private val unifiedPushDistributorProvider: UnifiedPushDistributorProvider,
private val registerUnifiedPushUseCase: RegisterUnifiedPushUseCase,
private val unRegisterUnifiedPushUseCase: UnregisterUnifiedPushUseCase,
private val pushClientSecret: PushClientSecret,
private val unifiedPushStore: UnifiedPushStore,
private val appNavigationStateService: AppNavigationStateService,
) : PushProvider {
override val index = UnifiedPushConfig.INDEX
override val name = UnifiedPushConfig.NAME
@ -54,15 +55,7 @@ class UnifiedPushProvider @Inject constructor(
}
override fun getDistributors(): List<Distributor> {
val distributors = UnifiedPush.getDistributors(context)
return distributors.mapNotNull {
if (it == context.packageName) {
// Exclude self
null
} else {
Distributor(it, context.getApplicationLabel(it))
}
}
return unifiedPushDistributorProvider.getDistributors()
}
override suspend fun registerWith(matrixClient: MatrixClient, distributor: Distributor) {
@ -75,7 +68,14 @@ class UnifiedPushProvider @Inject constructor(
unRegisterUnifiedPushUseCase.execute(clientSecret)
}
override suspend fun troubleshoot(): Result<Unit> {
TODO("Not yet implemented")
override suspend fun getCurrentUserPushConfig(): CurrentUserPushConfig? {
val currentSession = appNavigationStateService.appNavigationState.value.navigationState.currentSessionId() ?: return null
val clientSecret = pushClientSecret.getSecretForUser(currentSession)
val url = unifiedPushStore.getPushGateway(clientSecret) ?: return null
val pushKey = unifiedPushStore.getEndpoint(clientSecret) ?: return null
return CurrentUserPushConfig(
url = url,
pushKey = pushKey,
)
}
}

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2024 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 io.element.android.libraries.pushproviders.unifiedpush.troubleshoot
import android.content.Context
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.androidutils.system.openUrlInExternalApp
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.ApplicationContext
import javax.inject.Inject
interface OpenDistributorWebPageAction {
fun execute()
}
@ContributesBinding(AppScope::class)
class DefaultOpenDistributorWebPageAction @Inject constructor(
@ApplicationContext private val context: Context,
) : OpenDistributorWebPageAction {
override fun execute() {
// Open the distributor download page
context.openUrlInExternalApp(
url = "https://unifiedpush.org/users/distributors/",
inNewTask = true
)
}
}

View file

@ -0,0 +1,70 @@
/*
* Copyright (c) 2024 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 io.element.android.libraries.pushproviders.unifiedpush.troubleshoot
import com.squareup.anvil.annotations.ContributesMultibinding
import io.element.android.libraries.core.notifications.NotificationTroubleshootTest
import io.element.android.libraries.core.notifications.NotificationTroubleshootTestDelegate
import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState
import io.element.android.libraries.core.notifications.TestFilterData
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.pushproviders.unifiedpush.UnifiedPushConfig
import io.element.android.libraries.pushproviders.unifiedpush.UnifiedPushDistributorProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.StateFlow
import javax.inject.Inject
@ContributesMultibinding(AppScope::class)
class UnifiedPushTest @Inject constructor(
private val unifiedPushDistributorProvider: UnifiedPushDistributorProvider,
private val openDistributorWebPageAction: OpenDistributorWebPageAction,
) : NotificationTroubleshootTest {
override val order = 400
private val delegate = NotificationTroubleshootTestDelegate(
defaultName = "Check UnifiedPush",
defaultDescription = "Ensure that UnifiedPush distributors are available.",
visibleWhenIdle = false,
fakeDelay = NotificationTroubleshootTestDelegate.SHORT_DELAY,
)
override val state: StateFlow<NotificationTroubleshootTestState> = delegate.state
override fun isRelevant(data: TestFilterData): Boolean {
return data.currentPushProviderName == UnifiedPushConfig.NAME
}
override suspend fun run(coroutineScope: CoroutineScope) {
delegate.start()
val distributors = unifiedPushDistributorProvider.getDistributors()
if (distributors.isNotEmpty()) {
delegate.updateState(
description = "Distributors found: ${distributors.joinToString { it.name }}",
status = NotificationTroubleshootTestState.Status.Success
)
} else {
delegate.updateState(
description = "No push distributors found",
status = NotificationTroubleshootTestState.Status.Failure(true)
)
}
}
override fun reset() = delegate.reset()
override suspend fun quickFix(coroutineScope: CoroutineScope) {
openDistributorWebPageAction.execute()
}
}

View file

@ -0,0 +1,23 @@
/*
* Copyright (c) 2024 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 io.element.android.libraries.pushproviders.unifiedpush.troubleshoot
class FakeOpenDistributorWebPageAction(
private val executeAction: () -> Unit = {}
) : OpenDistributorWebPageAction {
override fun execute() = executeAction()
}

View file

@ -0,0 +1,32 @@
/*
* Copyright (c) 2024 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 io.element.android.libraries.pushproviders.unifiedpush.troubleshoot
import io.element.android.libraries.pushproviders.api.Distributor
import io.element.android.libraries.pushproviders.unifiedpush.UnifiedPushDistributorProvider
class FakeUnifiedPushDistributorProvider(
private var getDistributorsResult: List<Distributor> = emptyList()
) : UnifiedPushDistributorProvider {
override fun getDistributors(): List<Distributor> {
return getDistributorsResult
}
fun setDistributorsResult(list: List<Distributor>) {
getDistributorsResult = list
}
}

View file

@ -0,0 +1,81 @@
/*
* Copyright (c) 2024 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 io.element.android.libraries.pushproviders.unifiedpush.troubleshoot
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.core.notifications.NotificationTroubleshootTestState
import io.element.android.libraries.pushproviders.api.Distributor
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest
import org.junit.Test
class UnifiedPushTestTest {
@Test
fun `test UnifiedPushTest success`() = runTest {
val sut = UnifiedPushTest(
unifiedPushDistributorProvider = FakeUnifiedPushDistributorProvider(
getDistributorsResult = listOf(
Distributor("value", "Name"),
)
),
openDistributorWebPageAction = FakeOpenDistributorWebPageAction(),
)
launch {
sut.run(this)
}
sut.state.test {
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false))
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
val lastItem = awaitItem()
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Success)
}
}
@Test
fun `test UnifiedPushTest error`() = runTest {
val providers = FakeUnifiedPushDistributorProvider()
val sut = UnifiedPushTest(
unifiedPushDistributorProvider = providers,
openDistributorWebPageAction = FakeOpenDistributorWebPageAction(
executeAction = {
providers.setDistributorsResult(
listOf(
Distributor("value", "Name"),
)
)
}
),
)
launch {
sut.run(this)
}
sut.state.test {
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(false))
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
val lastItem = awaitItem()
assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(true))
// Quick fix
launch {
sut.quickFix(this)
sut.run(this)
}
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress)
assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Success)
}
}
}