diff --git a/appnav/build.gradle.kts b/appnav/build.gradle.kts index 4e436ec718..ab215f8394 100644 --- a/appnav/build.gradle.kts +++ b/appnav/build.gradle.kts @@ -65,6 +65,7 @@ dependencies { testImplementation(libs.test.truth) testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) + testImplementation(projects.libraries.push.test) testImplementation(projects.features.networkmonitor.test) testImplementation(projects.features.login.impl) testImplementation(projects.tests.testutils) diff --git a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt index 079ccab17a..0da579aa76 100644 --- a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt +++ b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt @@ -22,13 +22,10 @@ import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.networkmonitor.api.NetworkStatus import io.element.android.features.networkmonitor.test.FakeNetworkMonitor -import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService -import io.element.android.libraries.push.api.PushService -import io.element.android.libraries.pushproviders.api.Distributor -import io.element.android.libraries.pushproviders.api.PushProvider +import io.element.android.libraries.push.test.FakePushService import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.consumeItemsUntilPredicate import kotlinx.coroutines.test.runTest @@ -73,20 +70,7 @@ class LoggedInPresenterTest { return LoggedInPresenter( matrixClient = FakeMatrixClient(roomListService = roomListService), networkMonitor = FakeNetworkMonitor(networkStatus), - pushService = object : PushService { - override fun notificationStyleChanged() { - } - - override fun getAvailablePushProviders(): List { - return emptyList() - } - - override suspend fun registerWith(matrixClient: MatrixClient, pushProvider: PushProvider, distributor: Distributor) { - } - - override suspend fun testPush() { - } - } + pushService = FakePushService(), ) } } diff --git a/changelog.d/2601.feature b/changelog.d/2601.feature new file mode 100644 index 0000000000..ecfc26061d --- /dev/null +++ b/changelog.d/2601.feature @@ -0,0 +1 @@ + Add a notification troubleshoot screen diff --git a/features/invitelist/impl/src/main/res/values-be/translations.xml b/features/invitelist/impl/src/main/res/values-be/translations.xml index 36c895dcb9..9a37d82357 100644 --- a/features/invitelist/impl/src/main/res/values-be/translations.xml +++ b/features/invitelist/impl/src/main/res/values-be/translations.xml @@ -1,8 +1,8 @@ - "Вы ўпэўненыя, што жадаеце адхіліць запрашэнне ў %1$s?" + "Вы ўпэўненыя, што хочаце адхіліць запрашэнне ў %1$s?" "Адхіліць запрашэнне" - "Вы ўпэўненыя, што жадаеце адмовіцца ад прыватных зносін з %1$s?" + "Вы ўпэўненыя, што хочаце адмовіцца ад прыватных зносін з %1$s?" "Адхіліць чат" "Няма запрашэнняў" "%1$s (%2$s) запрасіў вас" diff --git a/features/leaveroom/api/src/main/res/values-be/translations.xml b/features/leaveroom/api/src/main/res/values-be/translations.xml index 0e80bcd433..c8bb068fa7 100644 --- a/features/leaveroom/api/src/main/res/values-be/translations.xml +++ b/features/leaveroom/api/src/main/res/values-be/translations.xml @@ -1,7 +1,7 @@ "Вы ўпэўнены, што хочаце пакінуць гэту размову? Гэта размова не з\'яўляецца публічнай, і вы не зможаце далучыцца зноў без запрашэння." - "Вы ўпэўнены, што жадаеце пакінуць гэты пакой? Вы тут адзіны карыстальнік. Калі вы выйдзеце, ніхто не зможа далучыцца ў будучыні, у тым ліку і вы." - "Вы ўпэўнены, што жадаеце пакінуць гэты пакой? Гэты пакой не агульнадаступны, і вы не зможаце далучыцца да яго зноў без запрашэння." + "Вы ўпэўнены, што хочаце пакінуць гэты пакой? Вы тут адзіны карыстальнік. Калі вы выйдзеце, ніхто не зможа далучыцца ў будучыні, у тым ліку і вы." + "Вы ўпэўнены, што жхочаце пакінуць гэты пакой? Гэты пакой не агульнадаступны, і вы не зможаце далучыцца да яго зноў без запрашэння." "Вы ўпэўнены, што хочаце пакінуць пакой?" diff --git a/features/lockscreen/impl/src/main/res/values-be/translations.xml b/features/lockscreen/impl/src/main/res/values-be/translations.xml index f786884ed0..2c5bd554b0 100644 --- a/features/lockscreen/impl/src/main/res/values-be/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-be/translations.xml @@ -7,7 +7,7 @@ "Змяніць PIN-код" "Дазволіць біяметрычную разблакіроўку" "Выдаліць PIN-код" - "Вы ўпэўнены, што жадаеце выдаліць PIN-код?" + "Вы ўпэўнены, што хочаце выдаліць PIN-код?" "Выдаліць PIN-код?" "Дазволіць %1$s" "Я хацеў бы выкарыстоўваць PIN-код" diff --git a/features/lockscreen/impl/src/main/res/values-sv/translations.xml b/features/lockscreen/impl/src/main/res/values-sv/translations.xml index b89f26bae3..c0ffcd9cdf 100644 --- a/features/lockscreen/impl/src/main/res/values-sv/translations.xml +++ b/features/lockscreen/impl/src/main/res/values-sv/translations.xml @@ -1,4 +1,8 @@ + "Byt PIN-kod" + "Tillåt biometrisk upplåsning" + "Ta bort PIN-kod" + "Ta bort PIN-koden?" "Loggar ut …" diff --git a/features/messages/impl/src/main/res/values-be/translations.xml b/features/messages/impl/src/main/res/values-be/translations.xml index ee875ab37a..f2ea6cfd09 100644 --- a/features/messages/impl/src/main/res/values-be/translations.xml +++ b/features/messages/impl/src/main/res/values-be/translations.xml @@ -9,7 +9,7 @@ "Падарожжы & Месцы" "Сімвалы" "Заблакіраваць карыстальніка" - "Адзначце, ці жадаеце вы схаваць усе бягучыя і будучыя паведамленні ад гэтага карыстальніка" + "Адзначце, ці хочаце вы схаваць усе бягучыя і будучыя паведамленні ад гэтага карыстальніка" "Гэтае паведамленне будзе перададзена адміністратару вашага хатняга сервера. Яны не змогуць прачытаць зашыфраваныя паведамленні." "Прычына, па якой вы паскардзіліся на гэты змест" "Камера" diff --git a/features/poll/impl/src/main/res/values-be/translations.xml b/features/poll/impl/src/main/res/values-be/translations.xml index 4526df8572..1d0238bca3 100644 --- a/features/poll/impl/src/main/res/values-be/translations.xml +++ b/features/poll/impl/src/main/res/values-be/translations.xml @@ -4,7 +4,7 @@ "Паказаць вынікі толькі пасля заканчэння апытання" "Схаваць галасы" "Варыянт %1$d" - "Вашы змены не былі захаваны. Вы ўпэўнены, што жадаеце вярнуцца?" + "Вашы змены не былі захаваны. Вы ўпэўнены, што хочаце вярнуцца?" "Пытанне або тэма" "Пра што апытанне?" "Стварэнне апытання" diff --git a/features/preferences/impl/build.gradle.kts b/features/preferences/impl/build.gradle.kts index ee0658ddc6..edfb275f17 100644 --- a/features/preferences/impl/build.gradle.kts +++ b/features/preferences/impl/build.gradle.kts @@ -23,6 +23,11 @@ plugins { android { namespace = "io.element.android.features.preferences.impl" + testOptions { + unitTests { + isIncludeAndroidResources = true + } + } } anvil { @@ -44,12 +49,14 @@ dependencies { implementation(projects.libraries.pushstore.api) implementation(projects.libraries.indicator.api) implementation(projects.libraries.preferences.api) + implementation(projects.libraries.troubleshoot.api) implementation(projects.libraries.testtags) implementation(projects.libraries.uiStrings) implementation(projects.libraries.matrixui) implementation(projects.libraries.mediapickers.api) implementation(projects.libraries.mediaupload.api) implementation(projects.libraries.permissions.api) + implementation(projects.libraries.push.api) implementation(projects.features.rageshake.api) implementation(projects.features.lockscreen.api) implementation(projects.features.analytics.api) @@ -71,12 +78,14 @@ dependencies { testImplementation(libs.test.truth) testImplementation(libs.test.turbine) testImplementation(libs.test.mockk) + testImplementation(libs.test.robolectric) testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.featureflag.test) testImplementation(projects.libraries.mediapickers.test) testImplementation(projects.libraries.mediaupload.test) testImplementation(projects.libraries.permissions.test) testImplementation(projects.libraries.preferences.test) + testImplementation(projects.libraries.push.test) testImplementation(projects.libraries.pushstore.test) testImplementation(projects.features.rageshake.test) testImplementation(projects.features.rageshake.impl) @@ -86,4 +95,6 @@ dependencies { testImplementation(projects.services.toolbox.test) testImplementation(projects.features.analytics.impl) testImplementation(projects.tests.testutils) + testImplementation(libs.androidx.compose.ui.test.junit) + testReleaseImplementation(libs.androidx.compose.ui.test.manifest) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt index c27a5ec14e..1dc5f650fd 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt @@ -24,6 +24,7 @@ import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.core.plugin.plugins import com.bumble.appyx.navmodel.backstack.BackStack +import com.bumble.appyx.navmodel.backstack.operation.pop import com.bumble.appyx.navmodel.backstack.operation.push import dagger.assisted.Assisted import dagger.assisted.AssistedInject @@ -47,6 +48,7 @@ import io.element.android.libraries.architecture.createNode import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.user.MatrixUser +import io.element.android.libraries.troubleshoot.api.NotificationTroubleShootEntryPoint import kotlinx.parcelize.Parcelize @ContributesNode(SessionScope::class) @@ -54,6 +56,7 @@ class PreferencesFlowNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, private val lockScreenEntryPoint: LockScreenEntryPoint, + private val notificationTroubleShootEntryPoint: NotificationTroubleShootEntryPoint, private val logoutEntryPoint: LogoutEntryPoint, ) : BaseFlowNode( backstack = BackStack( @@ -85,6 +88,9 @@ class PreferencesFlowNode @AssistedInject constructor( @Parcelize data object NotificationSettings : NavTarget + @Parcelize + data object TroubleshootNotifications : NavTarget + @Parcelize data object LockScreenSettings : NavTarget @@ -177,9 +183,22 @@ class PreferencesFlowNode @AssistedInject constructor( override fun editDefaultNotificationMode(isOneToOne: Boolean) { backstack.push(NavTarget.EditDefaultNotificationSetting(isOneToOne)) } + + override fun onTroubleshootNotificationsClicked() { + backstack.push(NavTarget.TroubleshootNotifications) + } } createNode(buildContext, listOf(notificationSettingsCallback)) } + NavTarget.TroubleshootNotifications -> { + notificationTroubleShootEntryPoint.nodeBuilder(this, buildContext) + .callback(object : NotificationTroubleShootEntryPoint.Callback { + override fun onDone() { + backstack.pop() + } + }) + .build() + } is NavTarget.EditDefaultNotificationSetting -> { val callback = object : EditDefaultNotificationSettingNode.Callback { override fun openRoomNotificationSettings(roomId: RoomId) { diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsNode.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsNode.kt index 122d13a817..621b0ed8b1 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsNode.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsNode.kt @@ -35,6 +35,7 @@ class NotificationSettingsNode @AssistedInject constructor( ) : Node(buildContext, plugins = plugins) { interface Callback : Plugin { fun editDefaultNotificationMode(isOneToOne: Boolean) + fun onTroubleshootNotificationsClicked() } private val callbacks = plugins() @@ -43,6 +44,10 @@ class NotificationSettingsNode @AssistedInject constructor( callbacks.forEach { it.editDefaultNotificationMode(isOneToOne) } } + private fun onTroubleshootNotificationsClicked() { + callbacks.forEach { it.onTroubleshootNotificationsClicked() } + } + @Composable override fun View(modifier: Modifier) { val state = presenter.present() @@ -50,6 +55,7 @@ class NotificationSettingsNode @AssistedInject constructor( state = state, onOpenEditDefault = { openEditDefault(isOneToOne = it) }, onBackPressed = ::navigateUp, + onTroubleshootNotificationsClicked = ::onTroubleshootNotificationsClicked, modifier = modifier, ) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenter.kt index 7083b9f88b..9aa9cafb81 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenter.kt @@ -48,7 +48,7 @@ class NotificationSettingsPresenter @Inject constructor( ) : Presenter { @Composable override fun present(): NotificationSettingsState { - val userPushStore = remember { userPushStoreFactory.create(matrixClient.sessionId) } + val userPushStore = remember { userPushStoreFactory.getOrCreate(matrixClient.sessionId) } val systemNotificationsEnabled: MutableState = remember { mutableStateOf(systemNotificationsEnabledProvider.notificationsEnabled()) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsStateProvider.kt index 2dc0c02145..dc1e972aa6 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsStateProvider.kt @@ -23,26 +23,48 @@ import io.element.android.libraries.matrix.api.room.RoomNotificationMode open class NotificationSettingsStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( - aNotificationSettingsState(), - aNotificationSettingsState(changeNotificationSettingAction = AsyncAction.Loading), - aNotificationSettingsState(changeNotificationSettingAction = AsyncAction.Failure(Throwable("error"))), + aValidNotificationSettingsState(), + aValidNotificationSettingsState(changeNotificationSettingAction = AsyncAction.Loading), + aValidNotificationSettingsState(changeNotificationSettingAction = AsyncAction.Failure(Throwable("error"))), + aInvalidNotificationSettingsState(), + aInvalidNotificationSettingsState(fixFailed = true), ) } -fun aNotificationSettingsState( +fun aValidNotificationSettingsState( changeNotificationSettingAction: AsyncAction = AsyncAction.Uninitialized, + atRoomNotificationsEnabled: Boolean = true, + callNotificationsEnabled: Boolean = true, + inviteForMeNotificationsEnabled: Boolean = true, + appNotificationEnabled: Boolean = true, + eventSink: (NotificationSettingsEvents) -> Unit = {}, ) = NotificationSettingsState( matrixSettings = NotificationSettingsState.MatrixSettings.Valid( - atRoomNotificationsEnabled = true, - callNotificationsEnabled = true, - inviteForMeNotificationsEnabled = true, + atRoomNotificationsEnabled = atRoomNotificationsEnabled, + callNotificationsEnabled = callNotificationsEnabled, + inviteForMeNotificationsEnabled = inviteForMeNotificationsEnabled, defaultGroupNotificationMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY, defaultOneToOneNotificationMode = RoomNotificationMode.ALL_MESSAGES, ), appSettings = NotificationSettingsState.AppSettings( systemNotificationsEnabled = false, - appNotificationsEnabled = true, + appNotificationsEnabled = appNotificationEnabled, ), changeNotificationSettingAction = changeNotificationSettingAction, - eventSink = {} + eventSink = eventSink, +) + +fun aInvalidNotificationSettingsState( + fixFailed: Boolean = false, + eventSink: (NotificationSettingsEvents) -> Unit = {}, +) = NotificationSettingsState( + matrixSettings = NotificationSettingsState.MatrixSettings.Invalid( + fixFailed = fixFailed, + ), + appSettings = NotificationSettingsState.AppSettings( + systemNotificationsEnabled = false, + appNotificationsEnabled = true, + ), + changeNotificationSettingAction = AsyncAction.Uninitialized, + eventSink = eventSink, ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt index e68b9fc8b5..d62f972d71 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsView.kt @@ -46,6 +46,7 @@ import io.element.android.libraries.ui.strings.CommonStrings fun NotificationSettingsView( state: NotificationSettingsState, onOpenEditDefault: (isOneToOne: Boolean) -> Unit, + onTroubleshootNotificationsClicked: () -> Unit, onBackPressed: () -> Unit, modifier: Modifier = Modifier, ) { @@ -77,6 +78,7 @@ fun NotificationSettingsView( // TODO We are removing the call notification toggle until support for call notifications has been added // onCallsNotificationsChanged = { state.eventSink(NotificationSettingsEvents.SetCallNotificationsEnabled(it)) }, onInviteForMeNotificationsChanged = { state.eventSink(NotificationSettingsEvents.SetInviteForMeNotificationsEnabled(it)) }, + onTroubleshootNotificationsClicked = onTroubleshootNotificationsClicked, ) } AsyncActionView( @@ -99,6 +101,7 @@ private fun NotificationSettingsContentView( // TODO We are removing the call notification toggle until support for call notifications has been added // onCallsNotificationsChanged: (Boolean) -> Unit, onInviteForMeNotificationsChanged: (Boolean) -> Unit, + onTroubleshootNotificationsClicked: () -> Unit, ) { val context = LocalContext.current if (systemSettings.appNotificationsEnabled && !systemSettings.systemNotificationsEnabled) { @@ -163,6 +166,13 @@ private fun NotificationSettingsContentView( onCheckedChange = onInviteForMeNotificationsChanged ) } + PreferenceCategory(title = stringResource(id = R.string.troubleshoot_notifications_entry_point_section)) { + PreferenceText( + modifier = Modifier, + title = stringResource(id = R.string.troubleshoot_notifications_entry_point_title), + onClick = onTroubleshootNotificationsClicked + ) + } } } @@ -204,15 +214,6 @@ internal fun NotificationSettingsViewPreview(@PreviewParameter(NotificationSetti state = state, onBackPressed = {}, onOpenEditDefault = {}, - ) -} - -@PreviewsDayNight -@Composable -internal fun InvalidNotificationSettingsViewPreview() = ElementPreview { - InvalidNotificationSettingsView( - showError = false, - onContinueClicked = {}, - onDismissError = {}, + onTroubleshootNotificationsClicked = {}, ) } diff --git a/features/preferences/impl/src/main/res/values-be/translations.xml b/features/preferences/impl/src/main/res/values-be/translations.xml index 8f20b79a09..450a47ce61 100644 --- a/features/preferences/impl/src/main/res/values-be/translations.xml +++ b/features/preferences/impl/src/main/res/values-be/translations.xml @@ -49,4 +49,6 @@ "налады сістэмы" "Сістэмныя апавяшчэнні выключаны" "Апавяшчэнні" + "Выпраўленне непаладак" + "Выпраўленне непаладак з апавяшчэннямі" diff --git a/features/preferences/impl/src/main/res/values-cs/translations.xml b/features/preferences/impl/src/main/res/values-cs/translations.xml index ed209124ec..60bc4b2b3e 100644 --- a/features/preferences/impl/src/main/res/values-cs/translations.xml +++ b/features/preferences/impl/src/main/res/values-cs/translations.xml @@ -51,4 +51,6 @@ Pokud budete pokračovat, některá nastavení se mohou změnit." "systémová nastavení" "Systémová oznámení byla vypnuta" "Oznámení" + "Odstraňování problémů" + "Odstraňování problémů s upozorněními" diff --git a/features/preferences/impl/src/main/res/values-de/translations.xml b/features/preferences/impl/src/main/res/values-de/translations.xml index 6d47e4817d..65b4923452 100644 --- a/features/preferences/impl/src/main/res/values-de/translations.xml +++ b/features/preferences/impl/src/main/res/values-de/translations.xml @@ -49,4 +49,6 @@ Wenn du fortfährst, können sich einige deiner Einstellungen ändern." "Systemeinstellungen" "Systembenachrichtigungen deaktiviert" "Benachrichtigungen" + "Fehlerbehebung" + "Fehlerbehebung für Benachrichtigungen" diff --git a/features/preferences/impl/src/main/res/values-fr/translations.xml b/features/preferences/impl/src/main/res/values-fr/translations.xml index ddfd6eec5c..9c415fea2d 100644 --- a/features/preferences/impl/src/main/res/values-fr/translations.xml +++ b/features/preferences/impl/src/main/res/values-fr/translations.xml @@ -49,4 +49,6 @@ Si vous continuez, il est possible que certains de vos paramètres soient modifi "paramètres du système" "Les notifications du système sont désactivées" "Notifications" + "Dépannage" + "Résoudre les problèmes liés aux notifications" diff --git a/features/preferences/impl/src/main/res/values-hu/translations.xml b/features/preferences/impl/src/main/res/values-hu/translations.xml index 739722f07b..841695edcf 100644 --- a/features/preferences/impl/src/main/res/values-hu/translations.xml +++ b/features/preferences/impl/src/main/res/values-hu/translations.xml @@ -49,4 +49,6 @@ Ha folytatja, egyes beállítások megváltozhatnak." "rendszerbeállításokat" "A rendszerértesítések ki vannak kapcsolva" "Értesítések" + "Hibaelhárítás" + "Értesítések hibaelhárítása" diff --git a/features/preferences/impl/src/main/res/values-in/translations.xml b/features/preferences/impl/src/main/res/values-in/translations.xml index f9fb4c0848..8bca603eeb 100644 --- a/features/preferences/impl/src/main/res/values-in/translations.xml +++ b/features/preferences/impl/src/main/res/values-in/translations.xml @@ -51,4 +51,6 @@ Jika Anda melanjutkan, beberapa pengaturan Anda dapat berubah." "pengaturan sistem" "Pemberitahuan sistem dimatikan" "Notifikasi" + "Pemecahan masalah" + "Pecahkan masalah notifikasi" diff --git a/features/preferences/impl/src/main/res/values-ru/translations.xml b/features/preferences/impl/src/main/res/values-ru/translations.xml index 06b2cddad5..3b24cf8c80 100644 --- a/features/preferences/impl/src/main/res/values-ru/translations.xml +++ b/features/preferences/impl/src/main/res/values-ru/translations.xml @@ -49,4 +49,6 @@ "настройки системы" "Системные уведомления выключены" "Уведомления" + "Устранение неполадок" + "Уведомления об устранении неполадок" diff --git a/features/preferences/impl/src/main/res/values-sk/translations.xml b/features/preferences/impl/src/main/res/values-sk/translations.xml index ddc71f8c67..f382d0ab6f 100644 --- a/features/preferences/impl/src/main/res/values-sk/translations.xml +++ b/features/preferences/impl/src/main/res/values-sk/translations.xml @@ -51,4 +51,6 @@ Ak budete pokračovať, niektoré z vašich nastavení sa môžu zmeniť.""nastavenia systému" "Systémové oznámenia sú vypnuté" "Oznámenia" + "Riešenie problémov" + "Oznámenia riešení problémov" diff --git a/features/preferences/impl/src/main/res/values/localazy.xml b/features/preferences/impl/src/main/res/values/localazy.xml index 492c75295a..56a5c0ba03 100644 --- a/features/preferences/impl/src/main/res/values/localazy.xml +++ b/features/preferences/impl/src/main/res/values/localazy.xml @@ -49,4 +49,6 @@ If you proceed, some of your settings may change." "system settings" "System notifications turned off" "Notifications" + "Troubleshoot" + "Troubleshoot notifications" diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsViewTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsViewTest.kt new file mode 100644 index 0000000000..8397d5aef6 --- /dev/null +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsViewTest.kt @@ -0,0 +1,267 @@ +/* + * 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.features.preferences.impl.notifications + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.features.preferences.impl.R +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.matrix.test.AN_EXCEPTION +import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.tests.testutils.EnsureNeverCalled +import io.element.android.tests.testutils.EnsureNeverCalledWithParam +import io.element.android.tests.testutils.EventsRecorder +import io.element.android.tests.testutils.clickOn +import io.element.android.tests.testutils.ensureCalledOnce +import io.element.android.tests.testutils.ensureCalledOnceWithParam +import io.element.android.tests.testutils.pressBack +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestRule +import org.junit.runner.RunWith +import org.robolectric.annotation.Config + +@RunWith(AndroidJUnit4::class) +class NotificationSettingsViewTest { + @get:Rule + val rule = createAndroidComposeRule() + + @Test + fun `clicking on back invokes the expected callback`() { + val eventsRecorder = EventsRecorder() + ensureCalledOnce { + rule.setNotificationSettingsView( + state = aValidNotificationSettingsState( + eventSink = eventsRecorder + ), + onBackPressed = it + ) + rule.pressBack() + } + eventsRecorder.assertSingle(NotificationSettingsEvents.RefreshSystemNotificationsEnabled) + } + + @Config(qualifiers = "h1024dp") + @Test + fun `clicking on troubleshoot notification invokes the expected callback`() { + val eventsRecorder = EventsRecorder() + ensureCalledOnce { + rule.setNotificationSettingsView( + state = aValidNotificationSettingsState( + eventSink = eventsRecorder + ), + onTroubleshootNotificationsClicked = it + ) + rule.clickOn(R.string.troubleshoot_notifications_entry_point_title) + } + eventsRecorder.assertSingle(NotificationSettingsEvents.RefreshSystemNotificationsEnabled) + } + + @Config(qualifiers = "h1024dp") + @Test + fun `clicking on group chats invokes the expected callback`() { + val eventsRecorder = EventsRecorder() + ensureCalledOnceWithParam(false) { + rule.setNotificationSettingsView( + state = aValidNotificationSettingsState( + eventSink = eventsRecorder + ), + onOpenEditDefault = it + ) + rule.clickOn(R.string.screen_notification_settings_group_chats) + } + eventsRecorder.assertSingle(NotificationSettingsEvents.RefreshSystemNotificationsEnabled) + } + + @Config(qualifiers = "h1024dp") + @Test + fun `clicking on direct chats invokes the expected callback`() { + val eventsRecorder = EventsRecorder() + ensureCalledOnceWithParam(true) { + rule.setNotificationSettingsView( + state = aValidNotificationSettingsState( + eventSink = eventsRecorder + ), + onOpenEditDefault = it + ) + rule.clickOn(R.string.screen_notification_settings_direct_chats) + } + eventsRecorder.assertSingle(NotificationSettingsEvents.RefreshSystemNotificationsEnabled) + } + + @Config(qualifiers = "h1024dp") + @Test + fun `clicking on disable notifications emits the expected events`() { + testNotificationToggle(true) + } + + @Config(qualifiers = "h1024dp") + @Test + fun `clicking on enable notifications emits the expected events`() { + testNotificationToggle(false) + } + + private fun testNotificationToggle(initialState: Boolean) { + val eventsRecorder = EventsRecorder() + rule.setNotificationSettingsView( + state = aValidNotificationSettingsState( + appNotificationEnabled = initialState, + eventSink = eventsRecorder + ), + ) + rule.clickOn(R.string.screen_notification_settings_enable_notifications) + eventsRecorder.assertList( + listOf( + NotificationSettingsEvents.RefreshSystemNotificationsEnabled, + NotificationSettingsEvents.SetNotificationsEnabled(!initialState) + ) + ) + } + + @Config(qualifiers = "h1024dp") + @Test + fun `clicking on disable notify me on at room emits the expected events`() { + testAtRoomToggle(true) + } + + @Config(qualifiers = "h1024dp") + @Test + fun `clicking on enable notify me on at room emits the expected events`() { + testAtRoomToggle(false) + } + + private fun testAtRoomToggle(initialState: Boolean) { + val eventsRecorder = EventsRecorder() + rule.setNotificationSettingsView( + state = aValidNotificationSettingsState( + atRoomNotificationsEnabled = initialState, + eventSink = eventsRecorder + ), + ) + rule.clickOn(R.string.screen_notification_settings_room_mention_label) + eventsRecorder.assertList( + listOf( + NotificationSettingsEvents.RefreshSystemNotificationsEnabled, + NotificationSettingsEvents.SetAtRoomNotificationsEnabled(!initialState) + ) + ) + } + + @Config(qualifiers = "h1024dp") + @Test + fun `clicking on disable notify me on invitation emits the expected events`() { + testInvitationToggle(true) + } + + @Config(qualifiers = "h1024dp") + @Test + fun `clicking on enable notify me on invitation emits the expected events`() { + testInvitationToggle(false) + } + + private fun testInvitationToggle(initialState: Boolean) { + val eventsRecorder = EventsRecorder() + rule.setNotificationSettingsView( + state = aValidNotificationSettingsState( + inviteForMeNotificationsEnabled = initialState, + eventSink = eventsRecorder + ), + ) + rule.clickOn(R.string.screen_notification_settings_invite_for_me_label) + eventsRecorder.assertList( + listOf( + NotificationSettingsEvents.RefreshSystemNotificationsEnabled, + NotificationSettingsEvents.SetInviteForMeNotificationsEnabled(!initialState) + ) + ) + } + + @Config(qualifiers = "h1024dp") + @Test + fun `with an error configuration, clicking on continue emits the expected events`() { + val eventsRecorder = EventsRecorder() + rule.setNotificationSettingsView( + state = aValidNotificationSettingsState( + changeNotificationSettingAction = AsyncAction.Failure(AN_EXCEPTION), + eventSink = eventsRecorder + ), + ) + rule.clickOn(CommonStrings.action_ok) + eventsRecorder.assertList( + listOf( + NotificationSettingsEvents.RefreshSystemNotificationsEnabled, + NotificationSettingsEvents.ClearNotificationChangeError + ) + ) + } + + @Config(qualifiers = "h1024dp") + @Test + fun `with invalid configuration, clicking on continue emits the expected events`() { + val eventsRecorder = EventsRecorder() + rule.setNotificationSettingsView( + state = aInvalidNotificationSettingsState( + fixFailed = false, + eventSink = eventsRecorder + ), + ) + rule.clickOn(CommonStrings.action_continue) + eventsRecorder.assertList( + listOf( + NotificationSettingsEvents.RefreshSystemNotificationsEnabled, + NotificationSettingsEvents.FixConfigurationMismatch + ) + ) + } + + @Config(qualifiers = "h1024dp") + @Test + fun `with invalid configuration and error, clicking on OK emits the expected events`() { + val eventsRecorder = EventsRecorder() + rule.setNotificationSettingsView( + state = aInvalidNotificationSettingsState( + fixFailed = true, + eventSink = eventsRecorder + ), + ) + rule.clickOn(CommonStrings.action_ok) + eventsRecorder.assertList( + listOf( + NotificationSettingsEvents.RefreshSystemNotificationsEnabled, + NotificationSettingsEvents.ClearConfigurationMismatchError + ) + ) + } +} + +private fun AndroidComposeTestRule.setNotificationSettingsView( + state: NotificationSettingsState, + onOpenEditDefault: (isOneToOne: Boolean) -> Unit = EnsureNeverCalledWithParam(), + onTroubleshootNotificationsClicked: () -> Unit = EnsureNeverCalled(), + onBackPressed: () -> Unit = EnsureNeverCalled(), +) { + setContent { + NotificationSettingsView( + state = state, + onOpenEditDefault = onOpenEditDefault, + onTroubleshootNotificationsClicked = onTroubleshootNotificationsClicked, + onBackPressed = onBackPressed, + ) + } +} diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/VersionFormatterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/VersionFormatterTest.kt index ebab2960e9..e41895440c 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/VersionFormatterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/VersionFormatterTest.kt @@ -23,25 +23,31 @@ import kotlinx.coroutines.test.runTest import org.junit.Test class VersionFormatterTest { - @Test - fun `version formatter should return simplified version for other branch`() = runTest { - val sut = DefaultVersionFormatter( - stringProvider = FakeStringProvider(defaultResult = VERSION), - buildMeta = aBuildMeta(gitBranchName = "main") - ) - assertThat(sut.get()).isEqualTo(VERSION) - } - @Test fun `version formatter should return simplified version for main branch`() = runTest { val sut = DefaultVersionFormatter( stringProvider = FakeStringProvider(defaultResult = VERSION), buildMeta = aBuildMeta( + gitBranchName = "main", + versionName = "versionName", + versionCode = 123 + ) + ) + assertThat(sut.get()).isEqualTo("${VERSION}versionName, 123") + } + + @Test + fun `version formatter should return simplified version for other branch`() = runTest { + val sut = DefaultVersionFormatter( + stringProvider = FakeStringProvider(defaultResult = VERSION), + buildMeta = aBuildMeta( + versionName = "versionName", + versionCode = 123, gitBranchName = "branch", gitRevision = "1234567890", ) ) - assertThat(sut.get()).isEqualTo("$VERSION\nbranch (1234567890)") + assertThat(sut.get()).isEqualTo("${VERSION}versionName, 123\nbranch (1234567890)") } companion object { diff --git a/features/rageshake/api/src/main/res/values-be/translations.xml b/features/rageshake/api/src/main/res/values-be/translations.xml index a73c7eedd5..3d9eec29d2 100644 --- a/features/rageshake/api/src/main/res/values-be/translations.xml +++ b/features/rageshake/api/src/main/res/values-be/translations.xml @@ -1,7 +1,7 @@ - "Пры апошнім выкарыстанні %1$s адбыўся збой. Жадаеце падзяліцца справаздачай аб збоі?" - "Падобна, што вы трасеце тэлефон. Жадаеце адкрыць экран паведамлення пра памылку?" + "Пры апошнім выкарыстанні %1$s адбыўся збой. Хочаце падзяліцца справаздачай аб збоі?" + "Падобна, што вы трасеце тэлефон. Хочаце адкрыць экран паведамлення пра памылку?" "Rageshake" "Парог выяўлення" diff --git a/features/rageshake/impl/src/main/res/values-be/translations.xml b/features/rageshake/impl/src/main/res/values-be/translations.xml index 8bf98b9c85..f927ba3c5e 100644 --- a/features/rageshake/impl/src/main/res/values-be/translations.xml +++ b/features/rageshake/impl/src/main/res/values-be/translations.xml @@ -12,6 +12,6 @@ "Дазволіць журналы" "Адправіць здымак экрана" "Каб пераканацца, што ўсё працуе правільна, у паведамленне будуць уключаны часопісы. Каб адправіць паведамленне без часопісаў, адключыце гэтую наладу." - "Пры апошнім выкарыстанні %1$s адбыўся збой. Жадаеце падзяліцца справаздачай аб збоі?" + "Пры апошнім выкарыстанні %1$s адбыўся збой. Хочаце падзяліцца справаздачай аб збоі?" "Прагляд журналаў" diff --git a/features/roomdetails/impl/src/main/res/values-be/translations.xml b/features/roomdetails/impl/src/main/res/values-be/translations.xml index 68ef4d8903..be2ac558fd 100644 --- a/features/roomdetails/impl/src/main/res/values-be/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-be/translations.xml @@ -30,6 +30,7 @@ "Вы не зможаце адмяніць гэтае змяненне, бо паніжаеце сябе. Калі вы апошні адміністратар у пакоі, вярнуць права будзе немагчыма." "Панізіць сябе?" "%1$s (У чаканні)" + "(У чаканні)" "Рэдагаваць мадэратараў" "Адміністратары" "Мадэратары" diff --git a/features/roomdetails/impl/src/main/res/values-fr/translations.xml b/features/roomdetails/impl/src/main/res/values-fr/translations.xml index 8479044057..005531b3db 100644 --- a/features/roomdetails/impl/src/main/res/values-fr/translations.xml +++ b/features/roomdetails/impl/src/main/res/values-fr/translations.xml @@ -30,6 +30,7 @@ "Vous ne pourrez pas annuler ce changement car vous vous rétrogradez, si vous êtes le dernier utilisateur privilégié du salon il sera impossible de retrouver les privilèges." "Vous rétrograder ?" "%1$s (En attente)" + "(En attente)" "Modifier les modérateurs" "Administrateurs" "Modérateurs" diff --git a/features/roomdetails/impl/src/main/res/values/localazy.xml b/features/roomdetails/impl/src/main/res/values/localazy.xml index b6bf76c440..04afc61ad6 100644 --- a/features/roomdetails/impl/src/main/res/values/localazy.xml +++ b/features/roomdetails/impl/src/main/res/values/localazy.xml @@ -30,6 +30,7 @@ "You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges." "Demote yourself?" "%1$s (Pending)" + "(Pending)" "Edit Moderators" "Admins" "Moderators" diff --git a/features/securebackup/impl/src/main/res/values-be/translations.xml b/features/securebackup/impl/src/main/res/values-be/translations.xml index 38215dfd20..63a2a9aae8 100644 --- a/features/securebackup/impl/src/main/res/values-be/translations.xml +++ b/features/securebackup/impl/src/main/res/values-be/translations.xml @@ -15,7 +15,7 @@ "Адключэнне рэзервовага капіравання прывядзе да выдалення бягучай рэзервовай копіі ключа шыфравання і адключэння іншых функцый бяспекі. У гэтым выпадку вы:" "Няма зашыфраванай гісторыі паведамленняў на новых прыладах" "Калі вы выходзіце з сістэмы, то губляеце доступ да зашыфраваных паведамленняў %1$s усюды" - "Вы ўпэўнены, што жадаеце адключыць рэзервовае капіраванне?" + "Вы ўпэўнены, што хочаце адключыць рэзервовае капіраванне?" "Атрымайце новы ключ аднаўлення, калі вы страцілі існуючы. Пасля змены ключа аднаўлення ваш стары больш не будзе працаваць." "Стварыць новы ключ аднаўлення" "Пераканайцеся, што вы можаце захаваць ключ аднаўлення ў бяспечным месцы" diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 73bd8725c8..599cb7ee2d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -177,7 +177,9 @@ kotlinpoet = "com.squareup:kotlinpoet:1.16.0" # Analytics posthog = "com.posthog:posthog-android:3.1.16" sentry = "io.sentry:sentry-android:7.6.0" -matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.14.0" +# Note: only 0.19.0 will compile properly +# main branch can be tested replacing the version with main-SNAPSHOT +matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.15.0" # Emojibase matrix_emojibase_bindings = "io.element.android:emojibase-bindings:1.1.3" diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/SystemUtils.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/SystemUtils.kt index 422307ffd9..737eab7ac7 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/SystemUtils.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/system/SystemUtils.kt @@ -16,6 +16,7 @@ package io.element.android.libraries.androidutils.system +import android.app.Activity import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent @@ -73,6 +74,9 @@ fun Context.startNotificationSettingsIntent(activityResultLauncher: ActivityResu val intent = Intent() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS + if (this !is Activity && activityResultLauncher == null) { + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName) } else { intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS @@ -154,6 +158,9 @@ fun Context.openUrlInExternalApp( errorMessage: String = getString(R.string.error_no_compatible_app_found), ) { val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) + if (this !is Activity) { + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } try { startActivity(intent) } catch (activityNotFoundException: ActivityNotFoundException) { diff --git a/libraries/permissions/api/build.gradle.kts b/libraries/permissions/api/build.gradle.kts index 32d3776419..cb60c6c0a7 100644 --- a/libraries/permissions/api/build.gradle.kts +++ b/libraries/permissions/api/build.gradle.kts @@ -24,7 +24,6 @@ android { dependencies { implementation(projects.libraries.architecture) - implementation(projects.libraries.designsystem) implementation(projects.libraries.uiStrings) diff --git a/libraries/permissions/impl/build.gradle.kts b/libraries/permissions/impl/build.gradle.kts index 7d05d9a1d7..7b80ef0d4f 100644 --- a/libraries/permissions/impl/build.gradle.kts +++ b/libraries/permissions/impl/build.gradle.kts @@ -46,8 +46,10 @@ dependencies { implementation(projects.libraries.architecture) implementation(projects.libraries.matrix.api) implementation(projects.libraries.matrixui) + implementation(projects.libraries.troubleshoot.api) implementation(projects.libraries.designsystem) implementation(projects.libraries.uiStrings) + implementation(projects.services.toolbox.api) api(projects.libraries.permissions.api) testImplementation(libs.test.junit) @@ -57,6 +59,7 @@ dependencies { testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.permissions.test) + testImplementation(projects.services.toolbox.test) testImplementation(projects.tests.testutils) ksp(libs.showkase.processor) diff --git a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionStateProvider.kt b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionStateProvider.kt index c05df3de46..61a82aafff 100644 --- a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionStateProvider.kt +++ b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionStateProvider.kt @@ -17,6 +17,8 @@ package io.element.android.libraries.permissions.impl import android.content.Context +import android.content.pm.PackageManager +import androidx.core.content.ContextCompat import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext @@ -33,7 +35,10 @@ class DefaultPermissionStateProvider @Inject constructor( private val permissionsStore: PermissionsStore, ) : PermissionStateProvider { override fun isPermissionGranted(permission: String): Boolean { - return context.checkSelfPermission(permission) == android.content.pm.PackageManager.PERMISSION_GRANTED + return ContextCompat.checkSelfPermission( + context, + permission, + ) == PackageManager.PERMISSION_GRANTED } override suspend fun setPermissionDenied(permission: String, value: Boolean) = permissionsStore.setPermissionDenied(permission, value) diff --git a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/action/AndroidPermissionActions.kt b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/action/AndroidPermissionActions.kt index 6370e839a3..405b61a809 100644 --- a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/action/AndroidPermissionActions.kt +++ b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/action/AndroidPermissionActions.kt @@ -18,7 +18,7 @@ package io.element.android.libraries.permissions.impl.action import android.content.Context import com.squareup.anvil.annotations.ContributesBinding -import io.element.android.libraries.androidutils.system.openAppSettingsPage +import io.element.android.libraries.androidutils.system.startNotificationSettingsIntent import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext import javax.inject.Inject @@ -28,6 +28,6 @@ class AndroidPermissionActions @Inject constructor( @ApplicationContext private val context: Context ) : PermissionActions { override fun openSettings() { - context.openAppSettingsPage() + context.startNotificationSettingsIntent() } } diff --git a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTest.kt b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTest.kt new file mode 100644 index 0000000000..7e916c37b5 --- /dev/null +++ b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTest.kt @@ -0,0 +1,69 @@ +/* + * 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.permissions.impl.troubleshoot + +import android.Manifest +import android.os.Build +import com.squareup.anvil.annotations.ContributesMultibinding +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.permissions.api.PermissionStateProvider +import io.element.android.libraries.permissions.impl.R +import io.element.android.libraries.permissions.impl.action.PermissionActions +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestDelegate +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState +import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider +import io.element.android.services.toolbox.api.strings.StringProvider +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.StateFlow +import javax.inject.Inject + +@ContributesMultibinding(AppScope::class) +class NotificationTroubleshootCheckPermissionTest @Inject constructor( + private val permissionStateProvider: PermissionStateProvider, + private val sdkVersionProvider: BuildVersionSdkIntProvider, + private val permissionActions: PermissionActions, + private val stringProvider: StringProvider, +) : NotificationTroubleshootTest { + override val order: Int = 0 + + private val delegate = NotificationTroubleshootTestDelegate( + defaultName = stringProvider.getString(R.string.troubleshoot_notifications_test_check_permission_title), + defaultDescription = stringProvider.getString(R.string.troubleshoot_notifications_test_check_permission_description), + hasQuickFix = true, + fakeDelay = NotificationTroubleshootTestDelegate.SHORT_DELAY, + ) + + override val state: StateFlow = delegate.state + + override suspend fun run(coroutineScope: CoroutineScope) { + delegate.start() + val result = if (sdkVersionProvider.isAtLeast(Build.VERSION_CODES.TIRAMISU)) { + permissionStateProvider.isPermissionGranted(Manifest.permission.POST_NOTIFICATIONS) + } else { + true + } + delegate.done(result) + } + + override suspend fun reset() = delegate.reset() + + override suspend fun quickFix(coroutineScope: CoroutineScope) { + // Do not bother about asking the permission inline, just lead the user to the settings + permissionActions.openSettings() + } +} diff --git a/libraries/permissions/impl/src/main/res/values-be/translations.xml b/libraries/permissions/impl/src/main/res/values-be/translations.xml new file mode 100644 index 0000000000..44c9d8376a --- /dev/null +++ b/libraries/permissions/impl/src/main/res/values-be/translations.xml @@ -0,0 +1,5 @@ + + + "Пераканайцеся, што праграма можа паказваць апавяшчэнні." + "Праверце дазволы" + diff --git a/libraries/permissions/impl/src/main/res/values-cs/translations.xml b/libraries/permissions/impl/src/main/res/values-cs/translations.xml new file mode 100644 index 0000000000..038561d93b --- /dev/null +++ b/libraries/permissions/impl/src/main/res/values-cs/translations.xml @@ -0,0 +1,5 @@ + + + "Ujistěte se, že aplikace může zobrazovat oznámení." + "Kontrola oprávnění" + diff --git a/libraries/permissions/impl/src/main/res/values-de/translations.xml b/libraries/permissions/impl/src/main/res/values-de/translations.xml new file mode 100644 index 0000000000..a0ff0ff417 --- /dev/null +++ b/libraries/permissions/impl/src/main/res/values-de/translations.xml @@ -0,0 +1,5 @@ + + + "Stelle sicher, dass die Anwendung Benachrichtigungen anzeigen kann." + "Berechtigungen überprüfen" + diff --git a/libraries/permissions/impl/src/main/res/values-fr/translations.xml b/libraries/permissions/impl/src/main/res/values-fr/translations.xml new file mode 100644 index 0000000000..b2cd018579 --- /dev/null +++ b/libraries/permissions/impl/src/main/res/values-fr/translations.xml @@ -0,0 +1,5 @@ + + + "Vérifie que l’application peut afficher des notifications." + "Vérifier les autorisations" + diff --git a/libraries/permissions/impl/src/main/res/values-hu/translations.xml b/libraries/permissions/impl/src/main/res/values-hu/translations.xml new file mode 100644 index 0000000000..4afe7f181b --- /dev/null +++ b/libraries/permissions/impl/src/main/res/values-hu/translations.xml @@ -0,0 +1,5 @@ + + + "Ellenőrizze, hogy az alkalmazás képes-e értesítéseket megjeleníteni." + "Engedélyek ellenőrzése" + diff --git a/libraries/permissions/impl/src/main/res/values-ru/translations.xml b/libraries/permissions/impl/src/main/res/values-ru/translations.xml new file mode 100644 index 0000000000..d1acff7ebd --- /dev/null +++ b/libraries/permissions/impl/src/main/res/values-ru/translations.xml @@ -0,0 +1,5 @@ + + + "Убедитесь, что приложение может показывать уведомления." + "Проверка разрешений" + diff --git a/libraries/permissions/impl/src/main/res/values-sk/translations.xml b/libraries/permissions/impl/src/main/res/values-sk/translations.xml new file mode 100644 index 0000000000..995754907e --- /dev/null +++ b/libraries/permissions/impl/src/main/res/values-sk/translations.xml @@ -0,0 +1,5 @@ + + + "Uistite sa, že aplikácia dokáže zobrazovať upozornenia." + "Skontrolovať povolenia" + diff --git a/libraries/permissions/impl/src/main/res/values/localazy.xml b/libraries/permissions/impl/src/main/res/values/localazy.xml new file mode 100644 index 0000000000..948b026970 --- /dev/null +++ b/libraries/permissions/impl/src/main/res/values/localazy.xml @@ -0,0 +1,5 @@ + + + "Check that the application can show notifications." + "Check permissions" + diff --git a/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/action/FakePermissionActions.kt b/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/action/FakePermissionActions.kt index fa17329900..f6709a7f3d 100644 --- a/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/action/FakePermissionActions.kt +++ b/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/action/FakePermissionActions.kt @@ -16,11 +16,14 @@ package io.element.android.libraries.permissions.impl.action -class FakePermissionActions : PermissionActions { +class FakePermissionActions( + val openSettingsAction: () -> Unit = {} +) : PermissionActions { var openSettingsCalled = false private set override fun openSettings() { + openSettingsAction() openSettingsCalled = true } } diff --git a/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTestTest.kt b/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTestTest.kt new file mode 100644 index 0000000000..b38d00ee22 --- /dev/null +++ b/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/troubleshoot/NotificationTroubleshootCheckPermissionTestTest.kt @@ -0,0 +1,104 @@ +/* + * 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.permissions.impl.troubleshoot + +import android.os.Build +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.permissions.impl.FakePermissionStateProvider +import io.element.android.libraries.permissions.impl.action.FakePermissionActions +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState +import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider +import io.element.android.services.toolbox.test.strings.FakeStringProvider +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class NotificationTroubleshootCheckPermissionTestTest { + @Test + fun `test NotificationTroubleshootCheckPermissionTest below TIRAMISU success`() = runTest { + val sut = NotificationTroubleshootCheckPermissionTest( + permissionStateProvider = FakePermissionStateProvider(), + sdkVersionProvider = FakeBuildVersionSdkIntProvider(sdkInt = Build.VERSION_CODES.TIRAMISU - 1), + permissionActions = FakePermissionActions(), + stringProvider = FakeStringProvider(), + ) + launch { + sut.run(this) + } + sut.state.test { + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true)) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + val lastItem = awaitItem() + assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Success) + } + } + + @Test + fun `test NotificationTroubleshootCheckPermissionTest TIRAMISU success`() = runTest { + val sut = NotificationTroubleshootCheckPermissionTest( + permissionStateProvider = FakePermissionStateProvider(), + sdkVersionProvider = FakeBuildVersionSdkIntProvider(sdkInt = Build.VERSION_CODES.TIRAMISU), + permissionActions = FakePermissionActions(), + stringProvider = FakeStringProvider(), + ) + launch { + sut.run(this) + } + sut.state.test { + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true)) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + val lastItem = awaitItem() + assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Success) + } + } + + @Test + fun `test NotificationTroubleshootCheckPermissionTest TIRAMISU error`() = runTest { + val permissionStateProvider = FakePermissionStateProvider( + permissionGranted = false + ) + val actions = FakePermissionActions( + openSettingsAction = { + permissionStateProvider.setPermissionGranted() + } + ) + val sut = NotificationTroubleshootCheckPermissionTest( + permissionStateProvider = permissionStateProvider, + sdkVersionProvider = FakeBuildVersionSdkIntProvider(sdkInt = Build.VERSION_CODES.TIRAMISU), + permissionActions = actions, + stringProvider = FakeStringProvider(), + ) + launch { + sut.run(this) + } + sut.state.test { + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true)) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + val lastItem = awaitItem() + assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(true)) + // Quick fix + launch { + sut.quickFix(this) + // Run the test again (IRL it will be done thanks to the resuming of the application) + sut.run(this) + } + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Success) + } + } +} diff --git a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/GetCurrentPushProvider.kt b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/GetCurrentPushProvider.kt new file mode 100644 index 0000000000..0c6ed41929 --- /dev/null +++ b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/GetCurrentPushProvider.kt @@ -0,0 +1,21 @@ +/* + * 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.push.api + +interface GetCurrentPushProvider { + suspend fun getCurrentPushProvider(): String? +} diff --git a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/PushService.kt b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/PushService.kt index 5f4736e5ab..abfc328e9f 100644 --- a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/PushService.kt +++ b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/PushService.kt @@ -37,6 +37,8 @@ interface PushService { */ suspend fun registerWith(matrixClient: MatrixClient, pushProvider: PushProvider, distributor: Distributor) - // TODO Move away - suspend fun testPush() + /** + * Return false in case of early error. + */ + suspend fun testPush(): Boolean } diff --git a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/gateway/PushGatewayFailure.kt b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/gateway/PushGatewayFailure.kt index c7814a1796..07cf9acf52 100644 --- a/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/gateway/PushGatewayFailure.kt +++ b/libraries/push/api/src/main/kotlin/io/element/android/libraries/push/api/gateway/PushGatewayFailure.kt @@ -16,6 +16,6 @@ package io.element.android.libraries.push.api.gateway -sealed class PushGatewayFailure : Throwable(cause = null) { - data object PusherRejected : PushGatewayFailure() +sealed class PushGatewayFailure : Exception() { + class PusherRejected : PushGatewayFailure() } diff --git a/libraries/push/impl/build.gradle.kts b/libraries/push/impl/build.gradle.kts index 7df972c3c3..ee528a4ae7 100644 --- a/libraries/push/impl/build.gradle.kts +++ b/libraries/push/impl/build.gradle.kts @@ -53,6 +53,7 @@ dependencies { implementation(projects.libraries.matrix.api) implementation(projects.libraries.matrixui) implementation(projects.libraries.uiStrings) + implementation(projects.libraries.troubleshoot.api) api(projects.libraries.pushproviders.api) api(projects.libraries.pushstore.api) api(projects.libraries.push.api) @@ -69,6 +70,8 @@ dependencies { testImplementation(libs.coil.test) testImplementation(libs.coroutines.test) testImplementation(projects.libraries.matrix.test) + testImplementation(projects.libraries.push.test) + testImplementation(projects.libraries.pushproviders.test) testImplementation(projects.tests.testutils) testImplementation(projects.services.appnavstate.test) testImplementation(projects.services.toolbox.impl) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultGetCurrentPushProvider.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultGetCurrentPushProvider.kt new file mode 100644 index 0000000000..977d41caca --- /dev/null +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/DefaultGetCurrentPushProvider.kt @@ -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.push.impl + +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.push.api.GetCurrentPushProvider +import io.element.android.libraries.pushstore.api.UserPushStoreFactory +import io.element.android.services.appnavstate.api.AppNavigationStateService +import io.element.android.services.appnavstate.api.currentSessionId +import javax.inject.Inject + +@ContributesBinding(AppScope::class) +class DefaultGetCurrentPushProvider @Inject constructor( + private val pushStoreFactory: UserPushStoreFactory, + private val appNavigationStateService: AppNavigationStateService, +) : GetCurrentPushProvider { + override suspend fun getCurrentPushProvider(): String? { + return appNavigationStateService + .appNavigationState + .value + .navigationState + .currentSessionId() + ?.let { pushStoreFactory.getOrCreate(it) } + ?.getPushProviderName() + } +} 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 e60cd8b014..cff18cfb3d 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 @@ -19,6 +19,7 @@ package io.element.android.libraries.push.impl import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.di.AppScope import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.push.api.GetCurrentPushProvider import io.element.android.libraries.push.api.PushService import io.element.android.libraries.push.impl.notifications.DefaultNotificationDrawerManager import io.element.android.libraries.pushproviders.api.Distributor @@ -32,6 +33,7 @@ class DefaultPushService @Inject constructor( private val pushersManager: PushersManager, private val userPushStoreFactory: UserPushStoreFactory, private val pushProviders: Set<@JvmSuppressWildcards PushProvider>, + private val getCurrentPushProvider: GetCurrentPushProvider, ) : PushService { override fun notificationStyleChanged() { defaultNotificationDrawerManager.notificationStyleChanged() @@ -47,7 +49,7 @@ class DefaultPushService @Inject constructor( * Get current push provider, compare with provided one, then unregister and register if different, and store change. */ override suspend fun registerWith(matrixClient: MatrixClient, pushProvider: PushProvider, distributor: Distributor) { - val userPushStore = userPushStoreFactory.create(matrixClient.sessionId) + val userPushStore = userPushStoreFactory.getOrCreate(matrixClient.sessionId) val currentPushProviderName = userPushStore.getPushProviderName() if (currentPushProviderName != pushProvider.name) { // Unregister previous one if any @@ -58,7 +60,11 @@ class DefaultPushService @Inject constructor( userPushStore.setPushProviderName(pushProvider.name) } - override suspend fun testPush() { - pushersManager.testPush() + override suspend fun testPush(): Boolean { + val currentPushProvider = getCurrentPushProvider.getCurrentPushProvider() + val pushProvider = pushProviders.find { it.name == currentPushProvider } ?: return false + val config = pushProvider.getCurrentUserPushConfig() ?: return false + pushersManager.testPush(config) + return true } } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/PushersManager.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/PushersManager.kt index d4424da492..4306072e48 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/PushersManager.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/PushersManager.kt @@ -23,9 +23,11 @@ import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.di.AppScope import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.pusher.SetHttpPusherData import io.element.android.libraries.push.impl.pushgateway.PushGatewayNotifyRequest +import io.element.android.libraries.pushproviders.api.CurrentUserPushConfig import io.element.android.libraries.pushproviders.api.PusherSubscriber import io.element.android.libraries.pushstore.api.UserPushStoreFactory import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret @@ -45,16 +47,14 @@ class PushersManager @Inject constructor( private val pushClientSecret: PushClientSecret, private val userPushStoreFactory: UserPushStoreFactory, ) : PusherSubscriber { - // TODO Move this to the PushProvider API - suspend fun testPush() { + suspend fun testPush(config: CurrentUserPushConfig) { pushGatewayNotifyRequest.execute( PushGatewayNotifyRequest.Params( - // unifiedPushHelper.getPushGateway() ?: return - url = "TODO", + url = config.url, appId = PushConfig.PUSHER_APP_ID, - // unifiedPushHelper.getEndpointOrToken().orEmpty() - pushKey = "TODO", - eventId = TEST_EVENT_ID + pushKey = config.pushKey, + eventId = TEST_EVENT_ID, + roomId = TEST_ROOM_ID, ) ) } @@ -63,7 +63,7 @@ class PushersManager @Inject constructor( * Register a pusher to the server if not done yet. */ override suspend fun registerPusher(matrixClient: MatrixClient, pushKey: String, gateway: String) { - val userDataStore = userPushStoreFactory.create(matrixClient.sessionId) + val userDataStore = userPushStoreFactory.getOrCreate(matrixClient.sessionId) if (userDataStore.getCurrentRegisteredPushKey() == pushKey) { Timber.tag(loggerTag.value) .d("Unnecessary to register again the same pusher, but do it in case the pusher has been removed from the server") @@ -112,5 +112,6 @@ class PushersManager @Inject constructor( companion object { val TEST_EVENT_ID = EventId("\$THIS_IS_A_FAKE_EVENT_ID") + val TEST_ROOM_ID = RoomId("!room:domain") } } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDisplayer.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDisplayer.kt index 2cb01ba2f7..04202bbb2f 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDisplayer.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationDisplayer.kt @@ -17,7 +17,6 @@ package io.element.android.libraries.push.impl.notifications import android.Manifest -import android.annotation.SuppressLint import android.app.Notification import android.content.Context import android.content.pm.PackageManager @@ -32,12 +31,13 @@ class NotificationDisplayer @Inject constructor( ) { private val notificationManager = NotificationManagerCompat.from(context) - fun showNotificationMessage(tag: String?, id: Int, notification: Notification) { + fun showNotificationMessage(tag: String?, id: Int, notification: Notification): Boolean { if (ActivityCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { Timber.w("Not allowed to notify.") - return + return false } notificationManager.notify(tag, id, notification) + return true } fun cancelNotificationMessage(tag: String?, id: Int) { @@ -53,15 +53,21 @@ class NotificationDisplayer @Inject constructor( } } - @SuppressLint("LaunchActivityFromNotification") - fun displayDiagnosticNotification(notification: Notification) { - showNotificationMessage( + fun displayDiagnosticNotification(notification: Notification): Boolean { + return showNotificationMessage( tag = "DIAGNOSTIC", id = NOTIFICATION_ID_DIAGNOSTIC, notification = notification ) } + fun dismissDiagnosticNotification() { + cancelNotificationMessage( + tag = "DIAGNOSTIC", + id = NOTIFICATION_ID_DIAGNOSTIC + ) + } + /** * Cancel the foreground notification service. */ diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/TestNotificationReceiver.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/TestNotificationReceiver.kt index 152ed0a03e..0bc3e69bfb 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/TestNotificationReceiver.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/TestNotificationReceiver.kt @@ -19,9 +19,15 @@ package io.element.android.libraries.push.impl.notifications import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import io.element.android.libraries.architecture.bindings +import io.element.android.libraries.push.impl.troubleshoot.NotificationClickHandler +import javax.inject.Inject class TestNotificationReceiver : BroadcastReceiver() { + @Inject lateinit var notificationClickHandler: NotificationClickHandler + override fun onReceive(context: Context, intent: Intent) { - // TODO The test notification has been clicked, notify the ui + context.bindings().inject(this) + notificationClickHandler.handleNotificationClick() } } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/TestNotificationReceiverBinding.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/TestNotificationReceiverBinding.kt new file mode 100644 index 0000000000..6390bff885 --- /dev/null +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/TestNotificationReceiverBinding.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2023 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.push.impl.notifications + +import com.squareup.anvil.annotations.ContributesTo +import io.element.android.libraries.di.AppScope + +@ContributesTo(AppScope::class) +interface TestNotificationReceiverBinding { + fun inject(service: TestNotificationReceiver) +} diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt index 2beddc53f9..67c8973d13 100755 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/factories/NotificationCreator.kt @@ -299,6 +299,7 @@ class NotificationCreator @Inject constructor( } fun createDiagnosticNotification(): Notification { + val intent = pendingIntentFactory.createTestPendingIntent() return NotificationCompat.Builder(context, notificationChannels.getChannelIdForTest()) .setContentTitle(buildMeta.applicationName) .setContentText(stringProvider.getString(R.string.notification_test_push_notification_content)) @@ -308,7 +309,8 @@ class NotificationCreator @Inject constructor( .setPriority(NotificationCompat.PRIORITY_MAX) .setCategory(NotificationCompat.CATEGORY_STATUS) .setAutoCancel(true) - .setContentIntent(pendingIntentFactory.createTestPendingIntent()) + .setContentIntent(intent) + .setDeleteIntent(intent) .build() } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandler.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandler.kt index a930db2708..2c9abb77b3 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandler.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandler.kt @@ -27,6 +27,7 @@ import io.element.android.libraries.push.impl.PushersManager import io.element.android.libraries.push.impl.notifications.DefaultNotificationDrawerManager import io.element.android.libraries.push.impl.notifications.NotifiableEventResolver import io.element.android.libraries.push.impl.store.DefaultPushDataStore +import io.element.android.libraries.push.impl.troubleshoot.DiagnosticPushHandler import io.element.android.libraries.pushproviders.api.PushData import io.element.android.libraries.pushproviders.api.PushHandler import io.element.android.libraries.pushstore.api.UserPushStoreFactory @@ -51,6 +52,7 @@ class DefaultPushHandler @Inject constructor( // private val actionIds: NotificationActionIds, private val buildMeta: BuildMeta, private val matrixAuthenticationService: MatrixAuthenticationService, + private val diagnosticPushHandler: DiagnosticPushHandler, ) : PushHandler { private val coroutineScope = CoroutineScope(SupervisorJob()) @@ -75,8 +77,7 @@ class DefaultPushHandler @Inject constructor( // Diagnostic Push if (pushData.eventId == PushersManager.TEST_EVENT_ID) { - // val intent = Intent(actionIds.push) - // TODO The test push has been received, notify the ui + diagnosticPushHandler.handlePush() return } @@ -121,7 +122,7 @@ class DefaultPushHandler @Inject constructor( return } - val userPushStore = userPushStoreFactory.create(userId) + val userPushStore = userPushStoreFactory.getOrCreate(userId) if (!userPushStore.getNotificationEnabledForDevice().first()) { // TODO We need to check if this is an incoming call Timber.tag(loggerTag.value).i("Notification are disabled for this device, ignore push.") diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayNotification.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayNotification.kt index 9e52d94049..5e341e3286 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayNotification.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayNotification.kt @@ -23,6 +23,8 @@ import kotlinx.serialization.Serializable internal data class PushGatewayNotification( @SerialName("event_id") val eventId: String, + @SerialName("room_id") + val roomId: String, /** * Required. This is an array of devices that the notification should be sent to. */ diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayNotifyRequest.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayNotifyRequest.kt index 7130e38d6e..e8c01493ab 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayNotifyRequest.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/pushgateway/PushGatewayNotifyRequest.kt @@ -16,6 +16,7 @@ package io.element.android.libraries.push.impl.pushgateway import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.network.RetrofitFactory import io.element.android.libraries.push.api.gateway.PushGatewayFailure import javax.inject.Inject @@ -27,7 +28,8 @@ class PushGatewayNotifyRequest @Inject constructor( val url: String, val appId: String, val pushKey: String, - val eventId: EventId + val eventId: EventId, + val roomId: RoomId, ) suspend fun execute(params: Params) { @@ -40,6 +42,7 @@ class PushGatewayNotifyRequest @Inject constructor( PushGatewayNotifyBody( PushGatewayNotification( eventId = params.eventId.value, + roomId = params.roomId.value, devices = listOf( PushGatewayDevice( params.appId, @@ -51,7 +54,7 @@ class PushGatewayNotifyRequest @Inject constructor( ) if (response.rejectedPushKeys.contains(params.pushKey)) { - throw PushGatewayFailure.PusherRejected + throw PushGatewayFailure.PusherRejected() } } } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/CurrentPushProviderTest.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/CurrentPushProviderTest.kt new file mode 100644 index 0000000000..3e7fe1ae59 --- /dev/null +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/CurrentPushProviderTest.kt @@ -0,0 +1,61 @@ +/* + * 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.push.impl.troubleshoot + +import com.squareup.anvil.annotations.ContributesMultibinding +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.push.api.GetCurrentPushProvider +import io.element.android.libraries.push.impl.R +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestDelegate +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState +import io.element.android.services.toolbox.api.strings.StringProvider +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.StateFlow +import javax.inject.Inject + +@ContributesMultibinding(AppScope::class) +class CurrentPushProviderTest @Inject constructor( + private val getCurrentPushProvider: GetCurrentPushProvider, + private val stringProvider: StringProvider, +) : NotificationTroubleshootTest { + override val order = 110 + private val delegate = NotificationTroubleshootTestDelegate( + defaultName = stringProvider.getString(R.string.troubleshoot_notifications_test_current_push_provider_title), + defaultDescription = stringProvider.getString(R.string.troubleshoot_notifications_test_current_push_provider_description), + fakeDelay = NotificationTroubleshootTestDelegate.SHORT_DELAY, + ) + override val state: StateFlow = delegate.state + + override suspend fun run(coroutineScope: CoroutineScope) { + delegate.start() + val provider = getCurrentPushProvider.getCurrentPushProvider() + if (provider != null) { + delegate.updateState( + description = stringProvider.getString(R.string.troubleshoot_notifications_test_current_push_provider_success, provider), + status = NotificationTroubleshootTestState.Status.Success + ) + } else { + delegate.updateState( + description = stringProvider.getString(R.string.troubleshoot_notifications_test_current_push_provider_failure), + status = NotificationTroubleshootTestState.Status.Failure(false) + ) + } + } + + override suspend fun reset() = delegate.reset() +} diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/DiagnosticPushHandler.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/DiagnosticPushHandler.kt new file mode 100644 index 0000000000..21b78161d9 --- /dev/null +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/DiagnosticPushHandler.kt @@ -0,0 +1,33 @@ +/* + * 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.push.impl.troubleshoot + +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.SingleIn +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import javax.inject.Inject + +@SingleIn(AppScope::class) +class DiagnosticPushHandler @Inject constructor() { + private val _state = MutableSharedFlow() + val state: SharedFlow = _state + + suspend fun handlePush() { + _state.emit(Unit) + } +} diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationClickHandler.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationClickHandler.kt new file mode 100644 index 0000000000..29f5fe0b9f --- /dev/null +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationClickHandler.kt @@ -0,0 +1,33 @@ +/* + * 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.push.impl.troubleshoot + +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.SingleIn +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import javax.inject.Inject + +@SingleIn(AppScope::class) +class NotificationClickHandler @Inject constructor() { + private val _state = MutableSharedFlow(extraBufferCapacity = 1) + val state: SharedFlow = _state + + fun handleNotificationClick() { + _state.tryEmit(Unit) + } +} diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTest.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTest.kt new file mode 100644 index 0000000000..8de8304c2d --- /dev/null +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTest.kt @@ -0,0 +1,101 @@ +/* + * 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.push.impl.troubleshoot + +import com.squareup.anvil.annotations.ContributesMultibinding +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.push.impl.R +import io.element.android.libraries.push.impl.notifications.NotificationDisplayer +import io.element.android.libraries.push.impl.notifications.factories.NotificationCreator +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestDelegate +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState +import io.element.android.services.toolbox.api.strings.StringProvider +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import kotlinx.coroutines.withTimeout +import timber.log.Timber +import javax.inject.Inject +import kotlin.time.Duration.Companion.seconds + +@ContributesMultibinding(AppScope::class) +class NotificationTest @Inject constructor( + private val notificationCreator: NotificationCreator, + private val notificationDisplayer: NotificationDisplayer, + private val notificationClickHandler: NotificationClickHandler, + private val stringProvider: StringProvider, +) : NotificationTroubleshootTest { + override val order = 50 + private val delegate = NotificationTroubleshootTestDelegate( + defaultName = stringProvider.getString(R.string.troubleshoot_notifications_test_display_notification_title), + defaultDescription = stringProvider.getString(R.string.troubleshoot_notifications_test_display_notification_description), + fakeDelay = NotificationTroubleshootTestDelegate.SHORT_DELAY, + ) + override val state: StateFlow = delegate.state + + override suspend fun run(coroutineScope: CoroutineScope) { + delegate.start() + val notification = notificationCreator.createDiagnosticNotification() + val result = notificationDisplayer.displayDiagnosticNotification(notification) + if (result) { + coroutineScope.listenToNotificationClick() + delegate.updateState( + description = stringProvider.getString(R.string.troubleshoot_notifications_test_display_notification_waiting), + status = NotificationTroubleshootTestState.Status.WaitingForUser + ) + } else { + delegate.updateState( + description = stringProvider.getString(R.string.troubleshoot_notifications_test_display_notification_permission_failure), + status = NotificationTroubleshootTestState.Status.Failure(false) + ) + } + } + + private fun CoroutineScope.listenToNotificationClick() = launch { + val job = launch { + notificationClickHandler.state.first() + Timber.d("Notification clicked!") + } + runCatching { + withTimeout(30.seconds) { + job.join() + } + }.fold( + onSuccess = { + delegate.updateState( + description = stringProvider.getString(R.string.troubleshoot_notifications_test_display_notification_success), + status = NotificationTroubleshootTestState.Status.Success + ) + }, + onFailure = { + job.cancel() + notificationDisplayer.dismissDiagnosticNotification() + delegate.updateState( + description = stringProvider.getString(R.string.troubleshoot_notifications_test_display_notification_failure), + status = NotificationTroubleshootTestState.Status.Failure(false) + ) + } + ) + }.invokeOnCompletion { + // Ensure that the notification is cancelled when the screen is left + notificationDisplayer.dismissDiagnosticNotification() + } + + override suspend fun reset() = delegate.reset() +} diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushLoopbackTest.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushLoopbackTest.kt new file mode 100644 index 0000000000..42a6394fc9 --- /dev/null +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushLoopbackTest.kt @@ -0,0 +1,109 @@ +/* + * 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.push.impl.troubleshoot + +import com.squareup.anvil.annotations.ContributesMultibinding +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.push.api.PushService +import io.element.android.libraries.push.api.gateway.PushGatewayFailure +import io.element.android.libraries.push.impl.R +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestDelegate +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState +import io.element.android.services.toolbox.api.strings.StringProvider +import io.element.android.services.toolbox.api.systemclock.SystemClock +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import kotlinx.coroutines.withTimeout +import timber.log.Timber +import javax.inject.Inject +import kotlin.time.Duration.Companion.seconds + +@ContributesMultibinding(AppScope::class) +class PushLoopbackTest @Inject constructor( + private val pushService: PushService, + private val diagnosticPushHandler: DiagnosticPushHandler, + private val clock: SystemClock, + private val stringProvider: StringProvider, +) : NotificationTroubleshootTest { + override val order = 500 + private val delegate = NotificationTroubleshootTestDelegate( + defaultName = stringProvider.getString(R.string.troubleshoot_notifications_test_push_loop_back_title), + defaultDescription = stringProvider.getString(R.string.troubleshoot_notifications_test_push_loop_back_description), + ) + override val state: StateFlow = delegate.state + + override suspend fun run(coroutineScope: CoroutineScope) { + delegate.start() + val startTime = clock.epochMillis() + val completable = CompletableDeferred() + val job = coroutineScope.launch { + diagnosticPushHandler.state.first() + completable.complete(clock.epochMillis() - startTime) + } + val testPushResult = try { + pushService.testPush() + } catch (pusherRejected: PushGatewayFailure.PusherRejected) { + delegate.updateState( + description = stringProvider.getString(R.string.troubleshoot_notifications_test_push_loop_back_failure_1), + status = NotificationTroubleshootTestState.Status.Failure(false) + ) + job.cancel() + return + } catch (e: Exception) { + Timber.e(e, "Failed to test push") + delegate.updateState( + description = stringProvider.getString(R.string.troubleshoot_notifications_test_push_loop_back_failure_2, e.message), + status = NotificationTroubleshootTestState.Status.Failure(false) + ) + job.cancel() + return + } + if (!testPushResult) { + delegate.updateState( + description = stringProvider.getString(R.string.troubleshoot_notifications_test_push_loop_back_failure_3), + status = NotificationTroubleshootTestState.Status.Failure(false) + ) + job.cancel() + return + } + runCatching { + withTimeout(10.seconds) { + completable.await() + } + }.fold( + onSuccess = { duration -> + delegate.updateState( + description = stringProvider.getString(R.string.troubleshoot_notifications_test_push_loop_back_success, duration), + status = NotificationTroubleshootTestState.Status.Success + ) + }, + onFailure = { + job.cancel() + delegate.updateState( + description = stringProvider.getString(R.string.troubleshoot_notifications_test_push_loop_back_failure_4), + status = NotificationTroubleshootTestState.Status.Failure(false) + ) + } + ) + } + + override suspend fun reset() = delegate.reset() +} diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushProvidersTest.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushProvidersTest.kt new file mode 100644 index 0000000000..190cf8fe98 --- /dev/null +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushProvidersTest.kt @@ -0,0 +1,67 @@ +/* + * 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.push.impl.troubleshoot + +import com.squareup.anvil.annotations.ContributesMultibinding +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.push.impl.R +import io.element.android.libraries.pushproviders.api.PushProvider +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestDelegate +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState +import io.element.android.services.toolbox.api.strings.StringProvider +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.StateFlow +import javax.inject.Inject + +@ContributesMultibinding(AppScope::class) +class PushProvidersTest @Inject constructor( + pushProviders: Set<@JvmSuppressWildcards PushProvider>, + private val stringProvider: StringProvider, +) : NotificationTroubleshootTest { + private val sortedPushProvider = pushProviders.sortedBy { it.index } + override val order = 100 + private val delegate = NotificationTroubleshootTestDelegate( + defaultName = stringProvider.getString(R.string.troubleshoot_notifications_test_detect_push_provider_title), + defaultDescription = stringProvider.getString(R.string.troubleshoot_notifications_test_detect_push_provider_description), + fakeDelay = NotificationTroubleshootTestDelegate.SHORT_DELAY, + ) + override val state: StateFlow = delegate.state + + override suspend fun run(coroutineScope: CoroutineScope) { + delegate.start() + val result = sortedPushProvider.isNotEmpty() + if (result) { + delegate.updateState( + description = stringProvider.getQuantityString( + resId = R.plurals.troubleshoot_notifications_test_detect_push_provider_success, + quantity = sortedPushProvider.size, + sortedPushProvider.size, + sortedPushProvider.joinToString { it.name } + ), + status = NotificationTroubleshootTestState.Status.Success + ) + } else { + delegate.updateState( + description = stringProvider.getString(R.string.troubleshoot_notifications_test_detect_push_provider_failure), + status = NotificationTroubleshootTestState.Status.Failure(false) + ) + } + } + + override suspend fun reset() = delegate.reset() +} diff --git a/libraries/push/impl/src/main/res/values-be/translations.xml b/libraries/push/impl/src/main/res/values-be/translations.xml index f947eb3a00..686d10a281 100644 --- a/libraries/push/impl/src/main/res/values-be/translations.xml +++ b/libraries/push/impl/src/main/res/values-be/translations.xml @@ -56,4 +56,29 @@ "Фонавая сінхранізацыя" "Сэрвісы Google" "Службы Google Play не знойдзены. Апавяшчэнні могуць не працаваць належным чынам." + "Атрымаць назву бягучага пастаўшчыка." + "Пастаўшчыкі push-апавяшчэнняў не выбраны." + "Бягучы пастаўшчык push-апавяшчэнняў: %1$s." + "Бягучы пастаўшчык push-апавяшчэнняў" + "Пераканайцеся, што ў праграме ёсць хаця б адзін пастаўшчык push-апавяшчэнняў." + "Пастаўшчыкі push-апавяшчэнняў не знойдзены." + + "Знайшлі %1$d пастаўшчыка push-апавяшчэнняў: %2$s" + "Знайшлі %1$d пастаўшчыкоў push-апавяшчэнняў: %2$s" + "Знайшлі %1$d пастаўшчыкоў push-апавяшчэнняў: %2$s" + + "Выяўленне пастаўшчыкоў push-паслуг" + "Праверце, ці можа праграма паказваць апавяшчэнні." + "Апавяшчэнне не было націснута." + "Немагчыма паказаць апавяшчэнне." + "Апавяшчэнне было націснута!" + "Паказаць апавяшчэнне" + "Націсніце на апавяшчэнне, каб працягнуць тэст." + "Пераканайцеся, што праграма атрымлівае push-апавяшчэнні." + "Памылка: pusher адхіліў запыт." + "Памылка: %1$s." + "Памылка, немагчыма праверыць push-апавяшчэнне." + "Памылка, тайм-аўт у чаканні push-апавяшчэння." + "Зварот цыклу назад заняў %1$d мс." + "Тэст Націсніце кнопку вярнуцца" diff --git a/libraries/push/impl/src/main/res/values-bg/translations.xml b/libraries/push/impl/src/main/res/values-bg/translations.xml index 5a9f8d2a59..821ca2f5d8 100644 --- a/libraries/push/impl/src/main/res/values-bg/translations.xml +++ b/libraries/push/impl/src/main/res/values-bg/translations.xml @@ -36,4 +36,5 @@ "%d стая" "%d стаи" + "Грешка: %1$s" diff --git a/libraries/push/impl/src/main/res/values-cs/translations.xml b/libraries/push/impl/src/main/res/values-cs/translations.xml index db4e6e4c6a..fac2a69784 100644 --- a/libraries/push/impl/src/main/res/values-cs/translations.xml +++ b/libraries/push/impl/src/main/res/values-cs/translations.xml @@ -56,4 +56,29 @@ "Synchronizace na pozadí" "Služby Google" "Nebyly nalezeny žádné funkční služby Google Play. Oznámení nemusí fungovat správně." + "Získat název aktuálního poskytovatele." + "Nebyli vybráni žádní push poskytovatelé." + "Aktuální push poskytovatel: %1$s." + "Aktuální push poskytovatel" + "Ujistěte se, že aplikace má alespoň jednoho push poskytovatele." + "Nebyli nalezeni žádní push poskytovatelé." + + "Nalezen %1$d push poskytovatel: %2$s" + "Nalezeni %1$d push poskytovatelé: %2$s" + "Nalezeno %1$d push poskytovatelů: %2$s" + + "Zjistit push poskytovatele" + "Zkontrolujte, zda aplikace může zobrazit oznámení." + "Na oznámení nebylo kliknuto." + "Oznámení nelze zobrazit." + "Na oznámení bylo kliknuto!" + "Zobrazit oznámení" + "Kliknutím na oznámení pokračujte v testu." + "Ujistěte se, že aplikace přijímá push." + "Chyba: pusher odmítl požadavek." + "Chyba: %1$s." + "Chyba, nelze otestovat push." + "Chyba, časový limit čekání na push." + "Push zpětná smyčka trvala %1$d ms." + "Otestovat push zpětnou smyčku" diff --git a/libraries/push/impl/src/main/res/values-de/translations.xml b/libraries/push/impl/src/main/res/values-de/translations.xml index 5476ad2eea..2757e89dea 100644 --- a/libraries/push/impl/src/main/res/values-de/translations.xml +++ b/libraries/push/impl/src/main/res/values-de/translations.xml @@ -50,4 +50,28 @@ "Hintergrundsynchronisation" "Google-Dienste" "Keine gültigen Google Play-Dienste gefunden. Benachrichtigungen funktionieren möglicherweise nicht richtig." + "Ermittele den Namen des aktuellen Anbieters." + "Kein Push-Anbieter ausgewählt." + "Aktueller Push-Anbieter:%1$s." + "Aktueller Push-Anbieter" + "Stelle sicher, dass die Anwendung mindestens einen Push-Anbieter hat." + "Keine Push-Anbieter gefunden." + + "%1$d Push-Anbieter gefunden: %2$s" + "%1$d Push-Anbieter gefunden: %2$s" + + "Push-Anbieter erkennen" + "Prüfe, ob die Anwendung Benachrichtigungen anzeigen kann." + "Die Benachrichtigung wurde nicht angeklickt." + "Die Benachrichtigung kann nicht angezeigt werden." + "Die Benachrichtigung wurde angeklickt!" + "Benachrichtigung anzeigen" + "Bitte klicke auf die Benachrichtigung, um den Test fortzusetzen." + "Stelle sicher, dass die Anwendung Push-Nachrichten empfängt." + "Fehler: Der Pusher hat die Anfrage abgelehnt." + "Fehler:%1$s." + "Fehler: Push kann nicht getestet werden." + "Fehler: Timeout beim Warten auf Push." + "Push-Loop-Back Dauer: %1$d ms." + "Teste Push-Loop-Back" diff --git a/libraries/push/impl/src/main/res/values-fr/translations.xml b/libraries/push/impl/src/main/res/values-fr/translations.xml index eb6e4eabbc..8501b8ad76 100644 --- a/libraries/push/impl/src/main/res/values-fr/translations.xml +++ b/libraries/push/impl/src/main/res/values-fr/translations.xml @@ -50,4 +50,28 @@ "Synchronisation en arrière-plan" "Services Google" "Aucun service Google Play valide n’a été trouvé. Les notifications peuvent ne pas fonctionner correctement." + "Obtenir le nom du fournisseur de Push actuel." + "Aucun fournisseur de Push n’est sélectionné." + "Fournisseur de Push actuel : %1$s." + "Fournisseur de Push actuel" + "Vérifier que l’application possède au moins un fournisseur de Push." + "Aucun fournisseur de Push n’a été trouvé." + + "%1$d fournisseur de Push détecté : %2$s" + "%1$d fournisseurs de Push détectés : %2$s" + + "Détecter les fournisseurs de Push" + "Vérifier que l’application peut afficher des notifications." + "Vous n’avez pas cliqué sur la notification." + "Impossible d’afficher la notification." + "Vous avez cliqué sur la notification!" + "Affichage des notifications" + "Veuillez cliquer sur la notification pour continuer le test." + "Vérifier que l’application reçoit les Push." + "Erreur : le Pusher a rejeté la demande." + "Erreur :%1$s." + "Erreur, impossible de tester les Push." + "Erreur, le délai d’attente du Push est dépassé." + "La demande d’envoi de Push et sa réception ont pris %1$d ms." + "Tester la réception des Push" diff --git a/libraries/push/impl/src/main/res/values-hu/translations.xml b/libraries/push/impl/src/main/res/values-hu/translations.xml index de719fc0c6..7b0c66aa65 100644 --- a/libraries/push/impl/src/main/res/values-hu/translations.xml +++ b/libraries/push/impl/src/main/res/values-hu/translations.xml @@ -50,4 +50,28 @@ "Háttérszinkronizálás" "Google szolgáltatások" "A Google Play szolgáltatások nem találhatók. Előfordulhat, hogy az értesítések nem működnek megfelelően." + "A jelenlegi szolgáltató nevének lekérdezése." + "Nincs kiválasztva leküldéses értesítési szolgáltató." + "Jelenlegi leküldéses értesítési szolgáltató: %1$s." + "Jelenlegi leküldéses értesítési szolgáltató" + "Győződjön meg arról, hogy az alkalmazás legalább egy leküldéses értesítési szolgáltatóval rendelkezik." + "Nem található leküldéses értesítési szolgáltató." + + "%1$d leküldéses szolgáltató találva: %2$s" + "%1$d leküldéses szolgáltató találva: %2$s" + + "Leküldéses értesítési szolgáltatók észlelése" + "Ellenőrizze, hogy az alkalmazás képes-e megjeleníteni az értesítést." + "Az értesítésre nem kattintottak rá." + "Az értesítés nem jeleníthető meg." + "Az értesítésre rákattintottak!" + "Értesítés megjelenítése" + "A teszt folytatásához kattintson az értesítésre." + "Győződjön meg arról, hogy az alkalmazás megkapja-e a leküldéses értesítést." + "Hiba: a leküldő elutasította a kérést." + "Hiba: %1$s." + "Hiba, nem lehet tesztelni a leküldéses értesítést." + "Hiba, időtúllépés a leküldéses értesítésre való várakozás során." + "A leküldéses értesítés folyamata %1$d ezredmásodpercig tartott." + "Tesztelje a leküldéses értesítés folyamatát" diff --git a/libraries/push/impl/src/main/res/values-in/translations.xml b/libraries/push/impl/src/main/res/values-in/translations.xml index 1a81220eeb..b933330be1 100644 --- a/libraries/push/impl/src/main/res/values-in/translations.xml +++ b/libraries/push/impl/src/main/res/values-in/translations.xml @@ -44,4 +44,27 @@ "Sinkronisasi latar belakang" "Layanan Google" "Tidak ditemukan Layanan Google Play yang valid. Pemberitahuan mungkin tidak berfungsi dengan baik." + "Dapatkan nama penyedia saat ini." + "Tidak ada penyedia notifikasi dorongan yang dipilih." + "Penyedia notifikasi dorongan saat ini: %1$s." + "Penyedia notifikasi dorongan saat ini" + "Pastikan aplikasi memiliki setidaknya satu penyedia notifikasi dorongan." + "Tidak ada penyedia notifikasi dorongan yang ditemukan." + + "Ditemukan %1$d penyedia notifikasi dorongan: %2$s" + + "Deteksi penyedia notifikasi dorongan" + "Periksa apakah aplikasi dapat menampilkan notifikasi." + "Notifikasi belum diklik." + "Tidak dapat menampilkan notifikasi." + "Notifikasi telah diklik!" + "Tampilan notifikasi" + "Silakan klik pada notifikasi untuk melanjutkan tes." + "Pastikan aplikasi menerima notifikasi dorongan." + "Kesalahan: pendorong telah menolak permintaan." + "Kesalahan: %1$s." + "Terjadi kesalahan, tidak dapat menguji notifikasi dorongan." + "Terjadi kesalahan, melebihi batas waktu menunggu notifikasi dorongan." + "Ulangan notifikasi dorongan membutuhkan %1$d ms." + "Uji ulangan notifikasi dorongan lagi" diff --git a/libraries/push/impl/src/main/res/values-ru/translations.xml b/libraries/push/impl/src/main/res/values-ru/translations.xml index 8881d488ba..65242651b2 100644 --- a/libraries/push/impl/src/main/res/values-ru/translations.xml +++ b/libraries/push/impl/src/main/res/values-ru/translations.xml @@ -56,4 +56,29 @@ "Фоновая синхронизация" "Сервисы Google" "Не найдены действующие службы Google Play. Уведомления могут работать некорректно." + "Получение имени текущего поставщика." + "Поставщики push-уведомлений не выбраны." + "Текущий поставщик push-уведомлений: %1$s." + "Текущий поставщик push-уведомлений" + "Убедитесь, что у приложения есть хотя бы один поставщик push-сообщений." + "Поставщики push-уведомлений не найдены." + + "Найден %1$d push-провайдер: %2$s" + "Найдено %1$d push-провайдеров: %2$s" + "Найдено %1$d push-провайдеров: %2$s" + + "Обнаружение поставщиков push-уведомлений" + "Убедитесь, что приложение может отображать уведомление." + "Уведомление не было нажато." + "Невозможно отобразить уведомление." + "Уведомление было нажато!" + "Отобразить уведомление" + "Нажмите на уведомление, чтобы продолжить тест." + "Убедитесь, что приложение получает push-сообщение." + "Ошибка: pusher отклонил запрос." + "Ошибка: %1$s." + "Ошибка, невозможно протестировать отправку." + "Ошибка, тайм-аут ожидания push-уведомления." + "Обратная отправка push-уведомления, заняла %1$d мс." + "Тест обратной отправки push-уведомления" diff --git a/libraries/push/impl/src/main/res/values-sk/translations.xml b/libraries/push/impl/src/main/res/values-sk/translations.xml index 4517697945..95aed49bd3 100644 --- a/libraries/push/impl/src/main/res/values-sk/translations.xml +++ b/libraries/push/impl/src/main/res/values-sk/translations.xml @@ -56,4 +56,29 @@ "Synchronizácia na pozadí" "Služby Google" "Nenašli sa žiadne platné služby Google Play. Oznámenia nemusia fungovať správne." + "Získaťe názov aktuálneho poskytovateľa." + "Nie sú vybraní žiadni poskytovatelia push." + "Aktuálny poskytovateľ push: %1$s." + "Aktuálny poskytovateľ push" + "Uistite sa, že aplikácia má aspoň jedného poskytovateľa push." + "Nenašli sa žiadni poskytovatelia push." + + "Nájdený %1$d poskytovateľ služby push: %2$s" + "Nájdení %1$d poskytovatelia služby push: %2$s" + "Nájdených %1$d poskytovateľov služby push: %2$s" + + "Zistiť poskytovateľov push" + "Skontrolujte, či aplikácia dokáže zobraziť upozornenie." + "Na oznámenie nebolo kliknuté." + "Nie je možné zobraziť upozornenie." + "Na oznámenie bolo kliknuté!" + "Zobraziť upozornenie" + "Kliknite na upozornenie a pokračujte v teste." + "Uistite sa, že aplikácia prijíma push oznámenia." + "Chyba: pusher odmietol požiadavku." + "Chyba: %1$s." + "Chyba, nie je možné testovať push." + "Chyba, časový limit na push vypršal." + "Push loop back trvalo %1$d ms." + "Testovať Push loop back" diff --git a/libraries/push/impl/src/main/res/values/localazy.xml b/libraries/push/impl/src/main/res/values/localazy.xml index ed49f5edd8..c9d7627d07 100644 --- a/libraries/push/impl/src/main/res/values/localazy.xml +++ b/libraries/push/impl/src/main/res/values/localazy.xml @@ -50,4 +50,28 @@ "Background synchronization" "Google Services" "No valid Google Play Services found. Notifications may not work properly." + "Get the name of the current provider." + "No push providers selected." + "Current push provider: %1$s." + "Current push provider" + "Ensure that the application has at least one push provider." + "No push providers found." + + "Found %1$d push provider: %2$s" + "Found %1$d push providers: %2$s" + + "Detect push providers" + "Check that the application can display notification." + "The notification has not been clicked." + "Cannot display the notification." + "The notification has been clicked!" + "Display notification" + "Please click on the notification to continue the test." + "Ensure that the application is receiving push." + "Error: pusher has rejected the request." + "Error: %1$s." + "Error, cannot test push." + "Error, timeout waiting for push." + "Push loop back took %1$d ms." + "Test Push loop back" diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManagerTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManagerTest.kt index e641029c0d..b84e7b4be3 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManagerTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManagerTest.kt @@ -23,10 +23,10 @@ import io.element.android.libraries.matrix.test.A_SPACE_ID import io.element.android.libraries.matrix.test.A_THREAD_ID import io.element.android.libraries.matrix.test.FakeMatrixClientProvider import io.element.android.libraries.matrix.test.core.aBuildMeta -import io.element.android.libraries.push.impl.notifications.fake.FakeAndroidNotificationFactory import io.element.android.libraries.push.impl.notifications.fake.FakeImageLoaderHolder -import io.element.android.libraries.push.impl.notifications.fake.FakeRoomGroupMessageCreator -import io.element.android.libraries.push.impl.notifications.fake.FakeSummaryGroupMessageCreator +import io.element.android.libraries.push.impl.notifications.fake.MockkNotificationCreator +import io.element.android.libraries.push.impl.notifications.fake.MockkRoomGroupMessageCreator +import io.element.android.libraries.push.impl.notifications.fake.MockkSummaryGroupMessageCreator import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent import io.element.android.services.appnavstate.api.AppNavigationState @@ -115,12 +115,12 @@ class DefaultNotificationDrawerManagerTest { appNavigationStateService = appNavigationStateService ), notificationRenderer = NotificationRenderer( - NotificationIdProvider(), - NotificationDisplayer(context), - NotificationFactory( - FakeAndroidNotificationFactory().instance, - FakeRoomGroupMessageCreator().instance, - FakeSummaryGroupMessageCreator().instance, + notificationIdProvider = NotificationIdProvider(), + notificationDisplayer = NotificationDisplayer(context), + notificationFactory = NotificationFactory( + notificationCreator = MockkNotificationCreator().instance, + roomGroupMessageCreator = MockkRoomGroupMessageCreator().instance, + summaryGroupMessageCreator = MockkSummaryGroupMessageCreator().instance, ) ), notificationEventPersistence = InMemoryNotificationEventPersistence(initialData = initialData), diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventProcessorTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventProcessorTest.kt index 02da10a351..a8626766e5 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventProcessorTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventProcessorTest.kt @@ -25,7 +25,7 @@ import io.element.android.libraries.matrix.test.A_ROOM_ID_2 import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_SPACE_ID import io.element.android.libraries.matrix.test.A_THREAD_ID -import io.element.android.libraries.push.impl.notifications.fake.FakeOutdatedEventDetector +import io.element.android.libraries.push.impl.notifications.fake.MockkOutdatedEventDetector import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent import io.element.android.libraries.push.impl.notifications.fixtures.aSimpleNotifiableEvent import io.element.android.libraries.push.impl.notifications.fixtures.anInviteNotifiableEvent @@ -42,7 +42,7 @@ private val VIEWING_A_ROOM = aNavigationState(A_SESSION_ID, A_SPACE_ID, A_ROOM_I private val VIEWING_A_THREAD = aNavigationState(A_SESSION_ID, A_SPACE_ID, A_ROOM_ID, A_THREAD_ID) class NotifiableEventProcessorTest { - private val outdatedDetector = FakeOutdatedEventDetector() + private val mockkOutdatedDetector = MockkOutdatedEventDetector() @Test fun `given simple events when processing then keep simple events`() { @@ -97,7 +97,7 @@ class NotifiableEventProcessorTest { @Test fun `given out of date message event when processing then removes message event`() { val events = listOf(aNotifiableMessageEvent(eventId = AN_EVENT_ID, roomId = A_ROOM_ID)) - outdatedDetector.givenEventIsOutOfDate(events[0]) + mockkOutdatedDetector.givenEventIsOutOfDate(events[0]) val eventProcessor = createProcessor(navigationState = NOT_VIEWING_A_ROOM) @@ -113,7 +113,7 @@ class NotifiableEventProcessorTest { @Test fun `given in date message event when processing then keep message event`() { val events = listOf(aNotifiableMessageEvent(eventId = AN_EVENT_ID, roomId = A_ROOM_ID)) - outdatedDetector.givenEventIsInDate(events[0]) + mockkOutdatedDetector.givenEventIsInDate(events[0]) val eventProcessor = createProcessor(navigationState = NOT_VIEWING_A_ROOM) val result = eventProcessor.process(events, renderedEvents = emptyList()) @@ -128,7 +128,7 @@ class NotifiableEventProcessorTest { @Test fun `given viewing the same room main timeline when processing main timeline message event then removes message`() { val events = listOf(aNotifiableMessageEvent(eventId = AN_EVENT_ID, roomId = A_ROOM_ID, threadId = null)) - events.forEach { outdatedDetector.givenEventIsOutOfDate(it) } + events.forEach { mockkOutdatedDetector.givenEventIsOutOfDate(it) } val eventProcessor = createProcessor(isInForeground = true, navigationState = VIEWING_A_ROOM) val result = eventProcessor.process(events, renderedEvents = emptyList()) @@ -143,7 +143,7 @@ class NotifiableEventProcessorTest { @Test fun `given viewing the same thread timeline when processing thread message event then removes message`() { val events = listOf(aNotifiableMessageEvent(eventId = AN_EVENT_ID, roomId = A_ROOM_ID, threadId = A_THREAD_ID)) - events.forEach { outdatedDetector.givenEventIsOutOfDate(it) } + events.forEach { mockkOutdatedDetector.givenEventIsOutOfDate(it) } val eventProcessor = createProcessor(isInForeground = true, navigationState = VIEWING_A_THREAD) val result = eventProcessor.process(events, renderedEvents = emptyList()) @@ -158,7 +158,7 @@ class NotifiableEventProcessorTest { @Test fun `given viewing main timeline of the same room when processing thread timeline message event then keep message`() { val events = listOf(aNotifiableMessageEvent(eventId = AN_EVENT_ID, roomId = A_ROOM_ID, threadId = A_THREAD_ID)) - outdatedDetector.givenEventIsInDate(events[0]) + mockkOutdatedDetector.givenEventIsInDate(events[0]) val eventProcessor = createProcessor(isInForeground = true, navigationState = VIEWING_A_ROOM) val result = eventProcessor.process(events, renderedEvents = emptyList()) @@ -173,7 +173,7 @@ class NotifiableEventProcessorTest { @Test fun `given viewing thread timeline of the same room when processing main timeline message event then keep message`() { val events = listOf(aNotifiableMessageEvent(eventId = AN_EVENT_ID, roomId = A_ROOM_ID)) - outdatedDetector.givenEventIsInDate(events[0]) + mockkOutdatedDetector.givenEventIsInDate(events[0]) val eventProcessor = createProcessor(isInForeground = true, navigationState = VIEWING_A_THREAD) val result = eventProcessor.process(events, renderedEvents = emptyList()) @@ -213,8 +213,8 @@ class NotifiableEventProcessorTest { navigationState: NavigationState ): NotifiableEventProcessor { return NotifiableEventProcessor( - outdatedDetector.instance, - FakeAppNavigationStateService(MutableStateFlow(AppNavigationState(navigationState, isInForeground))), + outdatedDetector = mockkOutdatedDetector.instance, + appNavigationStateService = FakeAppNavigationStateService(MutableStateFlow(AppNavigationState(navigationState, isInForeground))), ) } } diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationFactoryTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationFactoryTest.kt index 50ee91f448..6a211fc446 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationFactoryTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationFactoryTest.kt @@ -22,10 +22,10 @@ import io.element.android.libraries.matrix.api.user.MatrixUser 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_SESSION_ID -import io.element.android.libraries.push.impl.notifications.fake.FakeAndroidNotificationFactory import io.element.android.libraries.push.impl.notifications.fake.FakeImageLoader -import io.element.android.libraries.push.impl.notifications.fake.FakeRoomGroupMessageCreator -import io.element.android.libraries.push.impl.notifications.fake.FakeSummaryGroupMessageCreator +import io.element.android.libraries.push.impl.notifications.fake.MockkNotificationCreator +import io.element.android.libraries.push.impl.notifications.fake.MockkRoomGroupMessageCreator +import io.element.android.libraries.push.impl.notifications.fake.MockkSummaryGroupMessageCreator import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent import io.element.android.libraries.push.impl.notifications.fixtures.aSimpleNotifiableEvent import io.element.android.libraries.push.impl.notifications.fixtures.anInviteNotifiableEvent @@ -41,19 +41,19 @@ private val A_MESSAGE_EVENT = aNotifiableMessageEvent(eventId = AN_EVENT_ID, roo @RunWith(RobolectricTestRunner::class) class NotificationFactoryTest { - private val androidNotificationFactory = FakeAndroidNotificationFactory() - private val roomGroupMessageCreator = FakeRoomGroupMessageCreator() - private val summaryGroupMessageCreator = FakeSummaryGroupMessageCreator() + private val mockkNotificationCreator = MockkNotificationCreator() + private val mockkRoomGroupMessageCreator = MockkRoomGroupMessageCreator() + private val mockkSummaryGroupMessageCreator = MockkSummaryGroupMessageCreator() private val notificationFactory = NotificationFactory( - androidNotificationFactory.instance, - roomGroupMessageCreator.instance, - summaryGroupMessageCreator.instance + notificationCreator = mockkNotificationCreator.instance, + roomGroupMessageCreator = mockkRoomGroupMessageCreator.instance, + summaryGroupMessageCreator = mockkSummaryGroupMessageCreator.instance ) @Test fun `given a room invitation when mapping to notification then is Append`() = testWith(notificationFactory) { - val expectedNotification = androidNotificationFactory.givenCreateRoomInvitationNotificationFor(AN_INVITATION_EVENT) + val expectedNotification = mockkNotificationCreator.givenCreateRoomInvitationNotificationFor(AN_INVITATION_EVENT) val roomInvitation = listOf(ProcessedEvent(ProcessedEvent.Type.KEEP, AN_INVITATION_EVENT)) val result = roomInvitation.toNotifications() @@ -90,7 +90,7 @@ class NotificationFactoryTest { @Test fun `given a simple event when mapping to notification then is Append`() = testWith(notificationFactory) { - val expectedNotification = androidNotificationFactory.givenCreateSimpleInvitationNotificationFor(A_SIMPLE_EVENT) + val expectedNotification = mockkNotificationCreator.givenCreateSimpleInvitationNotificationFor(A_SIMPLE_EVENT) val roomInvitation = listOf(ProcessedEvent(ProcessedEvent.Type.KEEP, A_SIMPLE_EVENT)) val result = roomInvitation.toNotifications() @@ -128,7 +128,7 @@ class NotificationFactoryTest { @Test fun `given room with message when mapping to notification then delegates to room group message creator`() = testWith(notificationFactory) { val events = listOf(A_MESSAGE_EVENT) - val expectedNotification = roomGroupMessageCreator.givenCreatesRoomMessageFor( + val expectedNotification = mockkRoomGroupMessageCreator.givenCreatesRoomMessageFor( MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL), events, A_ROOM_ID @@ -197,7 +197,7 @@ class NotificationFactoryTest { ) ) val withRedactedRemoved = listOf(A_MESSAGE_EVENT.copy(eventId = EventId("\$not-redacted"))) - val expectedNotification = roomGroupMessageCreator.givenCreatesRoomMessageFor( + val expectedNotification = mockkRoomGroupMessageCreator.givenCreatesRoomMessageFor( MatrixUser(A_SESSION_ID, A_SESSION_ID.value, MY_AVATAR_URL), withRedactedRemoved, A_ROOM_ID, diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRendererTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRendererTest.kt index 4780ae3914..21fc1b4fca 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRendererTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationRendererTest.kt @@ -22,8 +22,8 @@ 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_SESSION_ID import io.element.android.libraries.push.impl.notifications.fake.FakeImageLoader -import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationDisplayer -import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationFactory +import io.element.android.libraries.push.impl.notifications.fake.MockkNotificationDisplayer +import io.element.android.libraries.push.impl.notifications.fake.MockkNotificationFactory import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent import io.mockk.mockk import kotlinx.coroutines.test.runTest @@ -51,14 +51,14 @@ private val ONE_SHOT_META = OneShotNotification.Append.Meta(key = "ignored", sum @RunWith(RobolectricTestRunner::class) class NotificationRendererTest { - private val notificationDisplayer = FakeNotificationDisplayer() - private val notificationFactory = FakeNotificationFactory() + private val mockkNotificationDisplayer = MockkNotificationDisplayer() + private val mockkNotificationFactory = MockkNotificationFactory() private val notificationIdProvider = NotificationIdProvider() private val notificationRenderer = NotificationRenderer( notificationIdProvider = notificationIdProvider, - notificationDisplayer = notificationDisplayer.instance, - notificationFactory = notificationFactory.instance, + notificationDisplayer = mockkNotificationDisplayer.instance, + notificationFactory = mockkNotificationFactory.instance, ) @Test @@ -67,8 +67,8 @@ class NotificationRendererTest { renderEventsAsNotifications() - notificationDisplayer.verifySummaryCancelled() - notificationDisplayer.verifyNoOtherInteractions() + mockkNotificationDisplayer.verifySummaryCancelled() + mockkNotificationDisplayer.verifyNoOtherInteractions() } @Test @@ -77,7 +77,7 @@ class NotificationRendererTest { renderEventsAsNotifications() - notificationDisplayer.verifyInOrder { + mockkNotificationDisplayer.verifyInOrder { cancelNotificationMessage(tag = null, notificationIdProvider.getSummaryNotificationId(A_SESSION_ID)) cancelNotificationMessage(tag = A_ROOM_ID.value, notificationIdProvider.getRoomMessagesNotificationId(A_SESSION_ID)) } @@ -89,7 +89,7 @@ class NotificationRendererTest { renderEventsAsNotifications() - notificationDisplayer.verifyInOrder { + mockkNotificationDisplayer.verifyInOrder { cancelNotificationMessage(tag = A_ROOM_ID.value, notificationIdProvider.getRoomMessagesNotificationId(A_SESSION_ID)) showNotificationMessage(tag = null, notificationIdProvider.getSummaryNotificationId(A_SESSION_ID), A_SUMMARY_NOTIFICATION.notification) } @@ -108,7 +108,7 @@ class NotificationRendererTest { renderEventsAsNotifications() - notificationDisplayer.verifyInOrder { + mockkNotificationDisplayer.verifyInOrder { showNotificationMessage(tag = A_ROOM_ID.value, notificationIdProvider.getRoomMessagesNotificationId(A_SESSION_ID), A_NOTIFICATION) showNotificationMessage(tag = null, notificationIdProvider.getSummaryNotificationId(A_SESSION_ID), A_SUMMARY_NOTIFICATION.notification) } @@ -120,7 +120,7 @@ class NotificationRendererTest { renderEventsAsNotifications() - notificationDisplayer.verifyInOrder { + mockkNotificationDisplayer.verifyInOrder { cancelNotificationMessage(tag = null, notificationIdProvider.getSummaryNotificationId(A_SESSION_ID)) cancelNotificationMessage(tag = AN_EVENT_ID.value, notificationIdProvider.getRoomEventNotificationId(A_SESSION_ID)) } @@ -132,7 +132,7 @@ class NotificationRendererTest { renderEventsAsNotifications() - notificationDisplayer.verifyInOrder { + mockkNotificationDisplayer.verifyInOrder { cancelNotificationMessage(tag = AN_EVENT_ID.value, notificationIdProvider.getRoomEventNotificationId(A_SESSION_ID)) showNotificationMessage(tag = null, notificationIdProvider.getSummaryNotificationId(A_SESSION_ID), A_SUMMARY_NOTIFICATION.notification) } @@ -151,7 +151,7 @@ class NotificationRendererTest { renderEventsAsNotifications() - notificationDisplayer.verifyInOrder { + mockkNotificationDisplayer.verifyInOrder { showNotificationMessage(tag = AN_EVENT_ID.value, notificationIdProvider.getRoomEventNotificationId(A_SESSION_ID), A_NOTIFICATION) showNotificationMessage(tag = null, notificationIdProvider.getSummaryNotificationId(A_SESSION_ID), A_SUMMARY_NOTIFICATION.notification) } @@ -163,7 +163,7 @@ class NotificationRendererTest { renderEventsAsNotifications() - notificationDisplayer.verifyInOrder { + mockkNotificationDisplayer.verifyInOrder { cancelNotificationMessage(tag = null, notificationIdProvider.getSummaryNotificationId(A_SESSION_ID)) cancelNotificationMessage(tag = A_ROOM_ID.value, notificationIdProvider.getRoomInvitationNotificationId(A_SESSION_ID)) } @@ -175,7 +175,7 @@ class NotificationRendererTest { renderEventsAsNotifications() - notificationDisplayer.verifyInOrder { + mockkNotificationDisplayer.verifyInOrder { cancelNotificationMessage(tag = A_ROOM_ID.value, notificationIdProvider.getRoomInvitationNotificationId(A_SESSION_ID)) showNotificationMessage(tag = null, notificationIdProvider.getSummaryNotificationId(A_SESSION_ID), A_SUMMARY_NOTIFICATION.notification) } @@ -194,7 +194,7 @@ class NotificationRendererTest { renderEventsAsNotifications() - notificationDisplayer.verifyInOrder { + mockkNotificationDisplayer.verifyInOrder { showNotificationMessage(tag = A_ROOM_ID.value, notificationIdProvider.getRoomEventNotificationId(A_SESSION_ID), A_NOTIFICATION) showNotificationMessage(tag = null, notificationIdProvider.getSummaryNotificationId(A_SESSION_ID), A_SUMMARY_NOTIFICATION.notification) } @@ -221,7 +221,7 @@ class NotificationRendererTest { useCompleteNotificationFormat: Boolean = USE_COMPLETE_NOTIFICATION_FORMAT, summaryNotification: SummaryNotification = A_SUMMARY_NOTIFICATION ) { - notificationFactory.givenNotificationsFor( + mockkNotificationFactory.givenNotificationsFor( groupedEvents = A_PROCESSED_EVENTS, matrixUser = MatrixUser(A_SESSION_ID, MY_USER_DISPLAY_NAME, MY_USER_AVATAR_URL), useCompleteNotificationFormat = useCompleteNotificationFormat, diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeAndroidNotificationFactory.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/MockkNotificationCreator.kt similarity index 85% rename from libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeAndroidNotificationFactory.kt rename to libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/MockkNotificationCreator.kt index 6637b64979..205ba058e6 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeAndroidNotificationFactory.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/MockkNotificationCreator.kt @@ -23,7 +23,7 @@ import io.element.android.libraries.push.impl.notifications.model.SimpleNotifiab import io.mockk.every import io.mockk.mockk -class FakeAndroidNotificationFactory { +class MockkNotificationCreator { val instance = mockk() fun givenCreateRoomInvitationNotificationFor(event: InviteNotifiableEvent): Notification { @@ -37,4 +37,10 @@ class FakeAndroidNotificationFactory { every { instance.createSimpleEventNotification(event) } returns mockNotification return mockNotification } + + fun givenCreateDiagnosticNotification(): Notification { + val mockNotification = mockk() + every { instance.createDiagnosticNotification() } returns mockNotification + return mockNotification + } } diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationDisplayer.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/MockkNotificationDisplayer.kt similarity index 87% rename from libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationDisplayer.kt rename to libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/MockkNotificationDisplayer.kt index 9af681490a..dc55cecfac 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationDisplayer.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/MockkNotificationDisplayer.kt @@ -20,13 +20,18 @@ import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.push.impl.notifications.NotificationDisplayer import io.element.android.libraries.push.impl.notifications.NotificationIdProvider import io.mockk.confirmVerified +import io.mockk.every import io.mockk.mockk import io.mockk.verify import io.mockk.verifyOrder -class FakeNotificationDisplayer { +class MockkNotificationDisplayer { val instance = mockk(relaxed = true) + fun givenDisplayDiagnosticNotificationResult(result: Boolean) { + every { instance.displayDiagnosticNotification(any()) } returns result + } + fun verifySummaryCancelled() { verify { instance.cancelNotificationMessage(tag = null, NotificationIdProvider().getSummaryNotificationId(A_SESSION_ID)) } } diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationFactory.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/MockkNotificationFactory.kt similarity index 98% rename from libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationFactory.kt rename to libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/MockkNotificationFactory.kt index 9c7755aa9d..6a8410d2cb 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeNotificationFactory.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/MockkNotificationFactory.kt @@ -26,7 +26,7 @@ import io.mockk.coEvery import io.mockk.every import io.mockk.mockk -class FakeNotificationFactory { +class MockkNotificationFactory { val instance = mockk() fun givenNotificationsFor( diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeOutdatedEventDetector.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/MockkOutdatedEventDetector.kt similarity index 97% rename from libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeOutdatedEventDetector.kt rename to libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/MockkOutdatedEventDetector.kt index 03bf7e8491..414f7ae652 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeOutdatedEventDetector.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/MockkOutdatedEventDetector.kt @@ -21,7 +21,7 @@ import io.element.android.libraries.push.impl.notifications.model.NotifiableEven import io.mockk.every import io.mockk.mockk -class FakeOutdatedEventDetector { +class MockkOutdatedEventDetector { val instance = mockk() fun givenEventIsOutOfDate(notifiableEvent: NotifiableEvent) { diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeRoomGroupMessageCreator.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/MockkRoomGroupMessageCreator.kt similarity index 97% rename from libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeRoomGroupMessageCreator.kt rename to libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/MockkRoomGroupMessageCreator.kt index a41a4aadc2..389a4f441d 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeRoomGroupMessageCreator.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/MockkRoomGroupMessageCreator.kt @@ -24,7 +24,7 @@ import io.element.android.libraries.push.impl.notifications.model.NotifiableMess import io.mockk.coEvery import io.mockk.mockk -class FakeRoomGroupMessageCreator { +class MockkRoomGroupMessageCreator { val instance = mockk() fun givenCreatesRoomMessageFor( diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeSummaryGroupMessageCreator.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/MockkSummaryGroupMessageCreator.kt similarity index 95% rename from libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeSummaryGroupMessageCreator.kt rename to libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/MockkSummaryGroupMessageCreator.kt index 546cb1e054..8f99651c89 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/FakeSummaryGroupMessageCreator.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/fake/MockkSummaryGroupMessageCreator.kt @@ -19,6 +19,6 @@ package io.element.android.libraries.push.impl.notifications.fake import io.element.android.libraries.push.impl.notifications.SummaryGroupMessageCreator import io.mockk.mockk -class FakeSummaryGroupMessageCreator { +class MockkSummaryGroupMessageCreator { val instance = mockk() } diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/CurrentPushProviderTestTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/CurrentPushProviderTestTest.kt new file mode 100644 index 0000000000..7f7beb9d9b --- /dev/null +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/CurrentPushProviderTestTest.kt @@ -0,0 +1,63 @@ +/* + * 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.push.impl.troubleshoot + +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.push.test.FakeGetCurrentPushProvider +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState +import io.element.android.services.toolbox.test.strings.FakeStringProvider +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class CurrentPushProviderTestTest { + @Test + fun `test CurrentPushProviderTest with a push provider`() = runTest { + val sut = CurrentPushProviderTest( + getCurrentPushProvider = FakeGetCurrentPushProvider("foo"), + stringProvider = FakeStringProvider(), + ) + launch { + sut.run(this) + } + sut.state.test { + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true)) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + val lastItem = awaitItem() + assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Success) + assertThat(lastItem.description).contains("foo") + } + } + + @Test + fun `test CurrentPushProviderTest without push provider`() = runTest { + val sut = CurrentPushProviderTest( + getCurrentPushProvider = FakeGetCurrentPushProvider(null), + stringProvider = FakeStringProvider(), + ) + launch { + sut.run(this) + } + sut.state.test { + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true)) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + val lastItem = awaitItem() + assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(false)) + } + } +} diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTestTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTestTest.kt new file mode 100644 index 0000000000..1351117527 --- /dev/null +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/NotificationTestTest.kt @@ -0,0 +1,90 @@ +/* + * 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.push.impl.troubleshoot + +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.push.impl.notifications.fake.MockkNotificationCreator +import io.element.android.libraries.push.impl.notifications.fake.MockkNotificationDisplayer +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState +import io.element.android.services.toolbox.test.strings.FakeStringProvider +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class NotificationTestTest { + private val mockkNotificationCreator = MockkNotificationCreator().apply { + givenCreateDiagnosticNotification() + } + private val mockkNotificationDisplayer = MockkNotificationDisplayer().apply { + givenDisplayDiagnosticNotificationResult(true) + } + + private val notificationClickHandler = NotificationClickHandler() + + @Test + fun `test NotificationTest notification cannot be displayed`() = runTest { + mockkNotificationDisplayer.givenDisplayDiagnosticNotificationResult(false) + val sut = createNotificationTest() + launch { + sut.run(this) + } + sut.state.test { + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true)) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + assertThat(awaitItem().status).isInstanceOf(NotificationTroubleshootTestState.Status.Failure::class.java) + } + } + + @Test + fun `test NotificationTest user does not click on notification`() = runTest { + val sut = createNotificationTest() + launch { + sut.run(this) + } + sut.state.test { + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true)) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.WaitingForUser) + assertThat(awaitItem().status).isInstanceOf(NotificationTroubleshootTestState.Status.Failure::class.java) + } + } + + @Test + fun `test NotificationTest user clicks on notification`() = runTest { + val sut = createNotificationTest() + launch { + sut.run(this) + } + sut.state.test { + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true)) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.WaitingForUser) + notificationClickHandler.handleNotificationClick() + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Success) + } + } + + private fun createNotificationTest(): NotificationTest { + return NotificationTest( + notificationCreator = mockkNotificationCreator.instance, + notificationDisplayer = mockkNotificationDisplayer.instance, + notificationClickHandler = notificationClickHandler, + stringProvider = FakeStringProvider(), + ) + } +} diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushLoopbackTestTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushLoopbackTestTest.kt new file mode 100644 index 0000000000..2c1363af78 --- /dev/null +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushLoopbackTestTest.kt @@ -0,0 +1,146 @@ +/* + * 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.push.impl.troubleshoot + +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.matrix.test.AN_EXCEPTION +import io.element.android.libraries.matrix.test.A_FAILURE_REASON +import io.element.android.libraries.push.api.gateway.PushGatewayFailure +import io.element.android.libraries.push.test.FakePushService +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState +import io.element.android.services.toolbox.test.strings.FakeStringProvider +import io.element.android.services.toolbox.test.systemclock.FakeSystemClock +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class PushLoopbackTestTest { + @Test + fun `test PushLoopbackTest timeout - push is not received`() = runTest { + val diagnosticPushHandler = DiagnosticPushHandler() + val sut = PushLoopbackTest( + pushService = FakePushService(), + diagnosticPushHandler = diagnosticPushHandler, + clock = FakeSystemClock(), + stringProvider = FakeStringProvider(), + ) + launch { + sut.run(this) + } + sut.state.test { + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true)) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + val lastItem = awaitItem() + assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(false)) + } + } + + @Test + fun `test PushLoopbackTest PusherRejected error`() = runTest { + val diagnosticPushHandler = DiagnosticPushHandler() + val sut = PushLoopbackTest( + pushService = FakePushService( + testPushBlock = { + throw PushGatewayFailure.PusherRejected() + } + ), + diagnosticPushHandler = diagnosticPushHandler, + clock = FakeSystemClock(), + stringProvider = FakeStringProvider(), + ) + launch { + sut.run(this) + } + sut.state.test { + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true)) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + val lastItem = awaitItem() + assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(false)) + } + } + + @Test + fun `test PushLoopbackTest setup error`() = runTest { + val diagnosticPushHandler = DiagnosticPushHandler() + val sut = PushLoopbackTest( + pushService = FakePushService( + testPushBlock = { false } + ), + diagnosticPushHandler = diagnosticPushHandler, + clock = FakeSystemClock(), + stringProvider = FakeStringProvider(), + ) + launch { + sut.run(this) + } + sut.state.test { + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true)) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + val lastItem = awaitItem() + assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(false)) + } + } + + @Test + fun `test PushLoopbackTest other error`() = runTest { + val diagnosticPushHandler = DiagnosticPushHandler() + val sut = PushLoopbackTest( + pushService = FakePushService( + testPushBlock = { + throw AN_EXCEPTION + } + ), + diagnosticPushHandler = diagnosticPushHandler, + clock = FakeSystemClock(), + stringProvider = FakeStringProvider(), + ) + launch { + sut.run(this) + } + sut.state.test { + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true)) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + val lastItem = awaitItem() + assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(false)) + assertThat(lastItem.description).contains(A_FAILURE_REASON) + } + } + + @Test + fun `test PushLoopbackTest push is received`() = runTest { + val diagnosticPushHandler = DiagnosticPushHandler() + val sut = PushLoopbackTest( + pushService = FakePushService(testPushBlock = { + diagnosticPushHandler.handlePush() + true + }), + diagnosticPushHandler = diagnosticPushHandler, + clock = FakeSystemClock(), + stringProvider = FakeStringProvider(), + ) + launch { + sut.run(this) + } + sut.state.test { + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true)) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + val lastItem = awaitItem() + assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Success) + } + } +} diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushProvidersTestTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushProvidersTestTest.kt new file mode 100644 index 0000000000..e58a490715 --- /dev/null +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/troubleshoot/PushProvidersTestTest.kt @@ -0,0 +1,68 @@ +/* + * 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.push.impl.troubleshoot + +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.pushproviders.test.FakePushProvider +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState +import io.element.android.services.toolbox.test.strings.FakeStringProvider +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class PushProvidersTestTest { + @Test + fun `test PushProvidersTest with empty list`() = runTest { + val sut = PushProvidersTest( + pushProviders = emptySet(), + stringProvider = FakeStringProvider(), + ) + launch { + sut.run(this) + } + sut.state.test { + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true)) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + val lastItem = awaitItem() + assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Failure(false)) + } + } + + @Test + fun `test PushProvidersTest with 2 push providers`() = runTest { + val sut = PushProvidersTest( + pushProviders = setOf( + FakePushProvider(name = "foo"), + FakePushProvider(name = "bar"), + ), + stringProvider = FakeStringProvider(), + ) + launch { + sut.run(this) + } + sut.state.test { + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Idle(true)) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + val lastItem = awaitItem() + assertThat(lastItem.status).isEqualTo(NotificationTroubleshootTestState.Status.Success) + assertThat(lastItem.description).contains("2") + assertThat(lastItem.description).contains("foo") + assertThat(lastItem.description).contains("bar") + } + } +} diff --git a/libraries/push/test/build.gradle.kts b/libraries/push/test/build.gradle.kts index 9fccadb9be..7826c3072b 100644 --- a/libraries/push/test/build.gradle.kts +++ b/libraries/push/test/build.gradle.kts @@ -25,5 +25,6 @@ android { dependencies { api(projects.libraries.push.api) implementation(projects.libraries.matrix.api) + implementation(projects.libraries.pushproviders.api) implementation(projects.tests.testutils) } diff --git a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/FakeGetCurrentPushProvider.kt b/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/FakeGetCurrentPushProvider.kt new file mode 100644 index 0000000000..76363c9d99 --- /dev/null +++ b/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/FakeGetCurrentPushProvider.kt @@ -0,0 +1,25 @@ +/* + * 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.push.test + +import io.element.android.libraries.push.api.GetCurrentPushProvider + +class FakeGetCurrentPushProvider( + private val currentPushProvider: String? +) : GetCurrentPushProvider { + override suspend fun getCurrentPushProvider(): String? = currentPushProvider +} diff --git a/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/FakePushService.kt b/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/FakePushService.kt new file mode 100644 index 0000000000..969815ec66 --- /dev/null +++ b/libraries/push/test/src/main/kotlin/io/element/android/libraries/push/test/FakePushService.kt @@ -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.push.test + +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.push.api.PushService +import io.element.android.libraries.pushproviders.api.Distributor +import io.element.android.libraries.pushproviders.api.PushProvider +import io.element.android.tests.testutils.simulateLongTask + +class FakePushService( + private val testPushBlock: suspend () -> Boolean = { true } +) : PushService { + override fun notificationStyleChanged() { + } + + override fun getAvailablePushProviders(): List { + return emptyList() + } + + override suspend fun registerWith(matrixClient: MatrixClient, pushProvider: PushProvider, distributor: Distributor) { + } + + override suspend fun testPush(): Boolean = simulateLongTask { + testPushBlock() + } +} diff --git a/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/CurrentUserPushConfig.kt b/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/CurrentUserPushConfig.kt new file mode 100644 index 0000000000..bfd6488904 --- /dev/null +++ b/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/CurrentUserPushConfig.kt @@ -0,0 +1,22 @@ +/* + * 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.api + +data class CurrentUserPushConfig( + val url: String, + val pushKey: String, +) diff --git a/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PushProvider.kt b/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PushProvider.kt index 3d8349a117..4e9b818dd4 100644 --- a/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PushProvider.kt +++ b/libraries/pushproviders/api/src/main/kotlin/io/element/android/libraries/pushproviders/api/PushProvider.kt @@ -49,8 +49,5 @@ interface PushProvider { */ suspend fun unregister(matrixClient: MatrixClient) - /** - * Attempt to troubleshoot the push provider. - */ - suspend fun troubleshoot(): Result + suspend fun getCurrentUserPushConfig(): CurrentUserPushConfig? } diff --git a/libraries/pushproviders/firebase/build.gradle.kts b/libraries/pushproviders/firebase/build.gradle.kts index 186ab121fd..6e36b92a09 100644 --- a/libraries/pushproviders/firebase/build.gradle.kts +++ b/libraries/pushproviders/firebase/build.gradle.kts @@ -40,6 +40,9 @@ dependencies { implementation(projects.libraries.core) implementation(projects.libraries.di) implementation(projects.libraries.matrix.api) + implementation(projects.libraries.uiStrings) + implementation(projects.libraries.troubleshoot.api) + implementation(projects.services.toolbox.api) implementation(projects.libraries.pushstore.api) implementation(projects.libraries.pushproviders.api) @@ -51,8 +54,11 @@ dependencies { exclude(group = "com.google.firebase", module = "firebase-measurement-connector") } + testImplementation(libs.coroutines.test) testImplementation(libs.test.junit) testImplementation(libs.test.truth) + testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) testImplementation(projects.tests.testutils) + testImplementation(projects.services.toolbox.test) } diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseNewTokenHandler.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseNewTokenHandler.kt index 313b9ab706..20d0de4ebf 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseNewTokenHandler.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseNewTokenHandler.kt @@ -44,7 +44,7 @@ class FirebaseNewTokenHandler @Inject constructor( sessionStore.getAllSessions().toUserList() .map { SessionId(it) } .forEach { userId -> - val userDataStore = userPushStoreFactory.create(userId) + val userDataStore = userPushStoreFactory.getOrCreate(userId) if (userDataStore.getPushProviderName() == FirebaseConfig.NAME) { matrixAuthenticationService.restoreSession(userId).getOrNull()?.use { client -> pusherSubscriber.registerPusher(client, firebaseToken, FirebaseConfig.PUSHER_HTTP_URL) 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 4c3d6d3a20..317d49f3b6 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 @@ -16,14 +16,11 @@ package io.element.android.libraries.pushproviders.firebase -import android.content.Context -import com.google.android.gms.common.ConnectionResult -import com.google.android.gms.common.GoogleApiAvailability import com.squareup.anvil.annotations.ContributesMultibinding 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.pushproviders.api.PusherSubscriber @@ -34,25 +31,15 @@ private val loggerTag = LoggerTag("FirebasePushProvider", LoggerTag.PushLoggerTa @ContributesMultibinding(AppScope::class) class FirebasePushProvider @Inject constructor( - @ApplicationContext private val context: Context, private val firebaseStore: FirebaseStore, - private val firebaseTroubleshooter: FirebaseTroubleshooter, private val pusherSubscriber: PusherSubscriber, + private val isPlayServiceAvailable: IsPlayServiceAvailable, ) : PushProvider { override val index = FirebaseConfig.INDEX override val name = FirebaseConfig.NAME override fun isAvailable(): Boolean { - // The PlayServices has to be available - val apiAvailability = GoogleApiAvailability.getInstance() - val resultCode = apiAvailability.isGooglePlayServicesAvailable(context) - return if (resultCode == ConnectionResult.SUCCESS) { - Timber.tag(loggerTag.value).d("Google Play Services is available") - true - } else { - Timber.tag(loggerTag.value).w("Google Play Services is not available") - false - } + return isPlayServiceAvailable.isAvailable() } override fun getDistributors(): List { @@ -73,7 +60,12 @@ class FirebasePushProvider @Inject constructor( pusherSubscriber.unregisterPusher(matrixClient, pushKey, FirebaseConfig.PUSHER_HTTP_URL) } - override suspend fun troubleshoot(): Result { - return firebaseTroubleshooter.troubleshoot() + override suspend fun getCurrentUserPushConfig(): CurrentUserPushConfig? { + return firebaseStore.getFcmToken()?.let { fcmToken -> + CurrentUserPushConfig( + url = FirebaseConfig.PUSHER_HTTP_URL, + pushKey = fcmToken + ) + } } } diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseStore.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseStore.kt index 0342c67462..0614e2065c 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseStore.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseStore.kt @@ -18,20 +18,28 @@ package io.element.android.libraries.pushproviders.firebase import android.content.SharedPreferences import androidx.core.content.edit +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.DefaultPreferences import javax.inject.Inject /** * This class store the Firebase token in SharedPrefs. */ -class FirebaseStore @Inject constructor( +interface FirebaseStore { + fun getFcmToken(): String? + fun storeFcmToken(token: String?) +} + +@ContributesBinding(AppScope::class) +class DefaultFirebaseStore @Inject constructor( @DefaultPreferences private val sharedPrefs: SharedPreferences, -) { - fun getFcmToken(): String? { +) : FirebaseStore { + override fun getFcmToken(): String? { return sharedPrefs.getString(PREFS_KEY_FCM_TOKEN, null) } - fun storeFcmToken(token: String?) { + override fun storeFcmToken(token: String?) { sharedPrefs.edit { putString(PREFS_KEY_FCM_TOKEN, token) } diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTroubleshooter.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTroubleshooter.kt index f3efba1a16..6d205c42ae 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTroubleshooter.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTroubleshooter.kt @@ -16,25 +16,28 @@ package io.element.android.libraries.pushproviders.firebase -import android.content.Context -import com.google.android.gms.common.ConnectionResult -import com.google.android.gms.common.GoogleApiAvailability import com.google.firebase.messaging.FirebaseMessaging -import io.element.android.libraries.di.ApplicationContext +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.di.AppScope import timber.log.Timber import javax.inject.Inject import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException import kotlin.coroutines.suspendCoroutine +interface FirebaseTroubleshooter { + suspend fun troubleshoot(): Result +} + /** * This class force retrieving and storage of the Firebase token. */ -class FirebaseTroubleshooter @Inject constructor( - @ApplicationContext private val context: Context, +@ContributesBinding(AppScope::class) +class DefaultFirebaseTroubleshooter @Inject constructor( private val newTokenHandler: FirebaseNewTokenHandler, -) { - suspend fun troubleshoot(): Result { + private val isPlayServiceAvailable: IsPlayServiceAvailable, +) : FirebaseTroubleshooter { + override suspend fun troubleshoot(): Result { return runCatching { val token = retrievedFirebaseToken() newTokenHandler.handle(token) @@ -44,7 +47,7 @@ class FirebaseTroubleshooter @Inject constructor( private suspend fun retrievedFirebaseToken(): String { return suspendCoroutine { continuation -> // 'app should always check the device for a compatible Google Play services APK before accessing Google Play services features' - if (checkPlayServices(context)) { + if (isPlayServiceAvailable.isAvailable()) { try { FirebaseMessaging.getInstance().token .addOnSuccessListener { token -> @@ -65,15 +68,4 @@ class FirebaseTroubleshooter @Inject constructor( } } } - - /** - * Check the device to make sure it has the Google Play Services APK. If - * it doesn't, display a dialog that allows users to download the APK from - * the Google Play Store or enable it in the device's system settings. - */ - private fun checkPlayServices(context: Context): Boolean { - val apiAvailability = GoogleApiAvailability.getInstance() - val resultCode = apiAvailability.isGooglePlayServicesAvailable(context) - return resultCode == ConnectionResult.SUCCESS - } } diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/IsPlayServiceAvailable.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/IsPlayServiceAvailable.kt new file mode 100644 index 0000000000..50d9d4fe6f --- /dev/null +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/IsPlayServiceAvailable.kt @@ -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.firebase + +import android.content.Context +import com.google.android.gms.common.ConnectionResult +import com.google.android.gms.common.GoogleApiAvailability +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.di.ApplicationContext +import timber.log.Timber +import javax.inject.Inject + +interface IsPlayServiceAvailable { + fun isAvailable(): Boolean +} + +@ContributesBinding(AppScope::class) +class DefaultIsPlayServiceAvailable @Inject constructor( + @ApplicationContext private val context: Context, +) : IsPlayServiceAvailable { + override fun isAvailable(): Boolean { + val apiAvailability = GoogleApiAvailability.getInstance() + val resultCode = apiAvailability.isGooglePlayServicesAvailable(context) + return if (resultCode == ConnectionResult.SUCCESS) { + Timber.d("Google Play Services is available") + true + } else { + Timber.w("Google Play Services is not available") + false + } + } +} diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseAvailabilityTest.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseAvailabilityTest.kt new file mode 100644 index 0000000000..6cca3d21af --- /dev/null +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseAvailabilityTest.kt @@ -0,0 +1,68 @@ +/* + * 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.firebase.troubleshoot + +import com.squareup.anvil.annotations.ContributesMultibinding +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.pushproviders.firebase.FirebaseConfig +import io.element.android.libraries.pushproviders.firebase.IsPlayServiceAvailable +import io.element.android.libraries.pushproviders.firebase.R +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestDelegate +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState +import io.element.android.libraries.troubleshoot.api.test.TestFilterData +import io.element.android.services.toolbox.api.strings.StringProvider +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.StateFlow +import javax.inject.Inject + +@ContributesMultibinding(AppScope::class) +class FirebaseAvailabilityTest @Inject constructor( + private val isPlayServiceAvailable: IsPlayServiceAvailable, + private val stringProvider: StringProvider, +) : NotificationTroubleshootTest { + override val order = 300 + private val delegate = NotificationTroubleshootTestDelegate( + defaultName = stringProvider.getString(R.string.troubleshoot_notifications_test_firebase_availability_title), + defaultDescription = stringProvider.getString(R.string.troubleshoot_notifications_test_firebase_availability_description), + visibleWhenIdle = false, + fakeDelay = NotificationTroubleshootTestDelegate.LONG_DELAY, + ) + override val state: StateFlow = delegate.state + + override fun isRelevant(data: TestFilterData): Boolean { + return data.currentPushProviderName == FirebaseConfig.NAME + } + + override suspend fun run(coroutineScope: CoroutineScope) { + delegate.start() + val result = isPlayServiceAvailable.isAvailable() + if (result) { + delegate.updateState( + description = stringProvider.getString(R.string.troubleshoot_notifications_test_firebase_availability_success), + status = NotificationTroubleshootTestState.Status.Success + ) + } else { + delegate.updateState( + description = stringProvider.getString(R.string.troubleshoot_notifications_test_firebase_availability_failure), + status = NotificationTroubleshootTestState.Status.Failure(false) + ) + } + } + + override suspend fun reset() = delegate.reset() +} diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTest.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTest.kt new file mode 100644 index 0000000000..a465ca3a7b --- /dev/null +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTest.kt @@ -0,0 +1,79 @@ +/* + * 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.firebase.troubleshoot + +import com.squareup.anvil.annotations.ContributesMultibinding +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.pushproviders.firebase.FirebaseConfig +import io.element.android.libraries.pushproviders.firebase.FirebaseStore +import io.element.android.libraries.pushproviders.firebase.FirebaseTroubleshooter +import io.element.android.libraries.pushproviders.firebase.R +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestDelegate +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState +import io.element.android.libraries.troubleshoot.api.test.TestFilterData +import io.element.android.services.toolbox.api.strings.StringProvider +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.StateFlow +import javax.inject.Inject + +@ContributesMultibinding(AppScope::class) +class FirebaseTokenTest @Inject constructor( + private val firebaseStore: FirebaseStore, + private val firebaseTroubleshooter: FirebaseTroubleshooter, + private val stringProvider: StringProvider, +) : NotificationTroubleshootTest { + override val order = 310 + private val delegate = NotificationTroubleshootTestDelegate( + defaultName = stringProvider.getString(R.string.troubleshoot_notifications_test_firebase_token_title), + defaultDescription = stringProvider.getString(R.string.troubleshoot_notifications_test_firebase_token_description), + visibleWhenIdle = false, + fakeDelay = NotificationTroubleshootTestDelegate.LONG_DELAY, + ) + override val state: StateFlow = delegate.state + + override fun isRelevant(data: TestFilterData): Boolean { + return data.currentPushProviderName == FirebaseConfig.NAME + } + + override suspend fun run(coroutineScope: CoroutineScope) { + delegate.start() + val token = firebaseStore.getFcmToken() + if (token != null) { + delegate.updateState( + description = stringProvider.getString( + R.string.troubleshoot_notifications_test_firebase_token_success, + "${token.take(8)}*****" + ), + status = NotificationTroubleshootTestState.Status.Success + ) + } else { + delegate.updateState( + description = stringProvider.getString(R.string.troubleshoot_notifications_test_firebase_token_failure), + status = NotificationTroubleshootTestState.Status.Failure(true) + ) + } + } + + override suspend fun reset() = delegate.reset() + + override suspend fun quickFix(coroutineScope: CoroutineScope) { + delegate.start() + firebaseTroubleshooter.troubleshoot() + run(coroutineScope) + } +} diff --git a/libraries/pushproviders/firebase/src/main/res/values-be/translations.xml b/libraries/pushproviders/firebase/src/main/res/values-be/translations.xml new file mode 100644 index 0000000000..cd9082e913 --- /dev/null +++ b/libraries/pushproviders/firebase/src/main/res/values-be/translations.xml @@ -0,0 +1,11 @@ + + + "Пераканайцеся, што Firebase даступны." + "Firebase недаступны." + "Firebase даступны." + "Праверыць Firebase" + "Пераканайцеся, што маркер Firebase даступны." + "Маркер Firebase невядомы." + "Маркер Firebase: %1$s." + "Праверце маркер Firebase" + diff --git a/libraries/pushproviders/firebase/src/main/res/values-cs/translations.xml b/libraries/pushproviders/firebase/src/main/res/values-cs/translations.xml new file mode 100644 index 0000000000..e0b7eff47f --- /dev/null +++ b/libraries/pushproviders/firebase/src/main/res/values-cs/translations.xml @@ -0,0 +1,11 @@ + + + "Ujistěte se, že je k dispozici Firebase." + "Firebase není k dispozici." + "Firebase je k dispozici." + "Zkontrolovat Firebase" + "Ujistěte se, že je k dispozici Firebase token." + "Firebase token není znám." + "Firebase token: %1$s." + "Zkontrolovat Firebase token" + diff --git a/libraries/pushproviders/firebase/src/main/res/values-de/translations.xml b/libraries/pushproviders/firebase/src/main/res/values-de/translations.xml new file mode 100644 index 0000000000..5df74b7284 --- /dev/null +++ b/libraries/pushproviders/firebase/src/main/res/values-de/translations.xml @@ -0,0 +1,11 @@ + + + "Stelle sicher, dass Firebase verfügbar ist." + "Firebase ist nicht verfügbar." + "Firebase ist verfügbar." + "Überprüfe Firebase" + "Stelle sicher, dass der Firebase Token verfügbar ist." + "Firebase Token ist nicht bekannt." + "Firebase Token: %1$s." + "Prüfe Firebase Token" + diff --git a/libraries/pushproviders/firebase/src/main/res/values-fr/translations.xml b/libraries/pushproviders/firebase/src/main/res/values-fr/translations.xml new file mode 100644 index 0000000000..75256022a2 --- /dev/null +++ b/libraries/pushproviders/firebase/src/main/res/values-fr/translations.xml @@ -0,0 +1,11 @@ + + + "Vérification que Firebase est disponible." + "Firebase n’est pas disponible." + "Firebase est disponible." + "Vérification de Firebase" + "Vérifier que le jeton Firebase est disponible." + "Le jeton Firebase n’est pas connu." + "Jeton Firebase :%1$s." + "Vérifier le jeton Firebase" + diff --git a/libraries/pushproviders/firebase/src/main/res/values-hu/translations.xml b/libraries/pushproviders/firebase/src/main/res/values-hu/translations.xml new file mode 100644 index 0000000000..10fa194159 --- /dev/null +++ b/libraries/pushproviders/firebase/src/main/res/values-hu/translations.xml @@ -0,0 +1,11 @@ + + + "Győződjön meg arról, hogy a Firebase elérhető-e." + "A Firebase nem érhető el." + "A Firebase elérhető." + "Ellenőrizze a Firebase-t" + "Győződjön meg arról, hogy a Firebase-token elérhető." + "A Firebase-token nem ismert." + "Firebase-token: %1$s." + "Ellenőrizze a Firebase-tokent" + diff --git a/libraries/pushproviders/firebase/src/main/res/values-ru/translations.xml b/libraries/pushproviders/firebase/src/main/res/values-ru/translations.xml new file mode 100644 index 0000000000..4167dd0d36 --- /dev/null +++ b/libraries/pushproviders/firebase/src/main/res/values-ru/translations.xml @@ -0,0 +1,11 @@ + + + "Убедитесь, что Firebase доступен." + "Firebase недоступен." + "Firebase доступен." + "Проверить Firebase" + "Убедитесь, что токен Firebase доступен." + "Токен Firebase неизвестен." + "Токен Firebase: %1$s." + "Проверить токен Firebase" + diff --git a/libraries/pushproviders/firebase/src/main/res/values-sk/translations.xml b/libraries/pushproviders/firebase/src/main/res/values-sk/translations.xml new file mode 100644 index 0000000000..312f751ce5 --- /dev/null +++ b/libraries/pushproviders/firebase/src/main/res/values-sk/translations.xml @@ -0,0 +1,11 @@ + + + "Uistite sa, že Firebase je k dispozícii." + "Firebase nie je k dispozícii." + "Firebase je k dispozícii." + "Skontrolovať Firebase" + "Uistite sa, že je k dispozícii token Firebase." + "Token Firebase nie je známy." + "Token Firebase: %1$s." + "Skontrolovať token Firebase" + diff --git a/libraries/pushproviders/firebase/src/main/res/values/localazy.xml b/libraries/pushproviders/firebase/src/main/res/values/localazy.xml new file mode 100644 index 0000000000..654ba04134 --- /dev/null +++ b/libraries/pushproviders/firebase/src/main/res/values/localazy.xml @@ -0,0 +1,11 @@ + + + "Ensure that Firebase is available." + "Firebase is not available." + "Firebase is available." + "Check Firebase" + "Ensure that Firebase token is available." + "Firebase token is not known." + "Firebase token: %1$s." + "Check Firebase token" + diff --git a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FakeFirebaseTroubleshooter.kt b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FakeFirebaseTroubleshooter.kt new file mode 100644 index 0000000000..b0dae793cd --- /dev/null +++ b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/FakeFirebaseTroubleshooter.kt @@ -0,0 +1,27 @@ +/* + * 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.firebase + +import io.element.android.tests.testutils.simulateLongTask + +class FakeFirebaseTroubleshooter( + private val troubleShootResult: () -> Result = { Result.success(Unit) } +) : FirebaseTroubleshooter { + override suspend fun troubleshoot(): Result = simulateLongTask { + troubleShootResult() + } +} diff --git a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/InMemoryFirebaseStore.kt b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/InMemoryFirebaseStore.kt new file mode 100644 index 0000000000..f298b9f30e --- /dev/null +++ b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/InMemoryFirebaseStore.kt @@ -0,0 +1,27 @@ +/* + * 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.firebase + +class InMemoryFirebaseStore( + private var token: String? = null +) : FirebaseStore { + override fun getFcmToken(): String? = token + + override fun storeFcmToken(token: String?) { + this.token = token + } +} diff --git a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseAvailabilityTestTest.kt b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseAvailabilityTestTest.kt new file mode 100644 index 0000000000..6f1a3da7cb --- /dev/null +++ b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseAvailabilityTestTest.kt @@ -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.firebase.troubleshoot + +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.pushproviders.firebase.IsPlayServiceAvailable +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState +import io.element.android.services.toolbox.test.strings.FakeStringProvider +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class FirebaseAvailabilityTestTest { + @Test + fun `test FirebaseAvailabilityTest success`() = runTest { + val sut = FirebaseAvailabilityTest( + isPlayServiceAvailable = object : IsPlayServiceAvailable { + override fun isAvailable(): Boolean { + return true + } + }, + stringProvider = FakeStringProvider(), + ) + 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 FirebaseAvailabilityTest failure`() = runTest { + val sut = FirebaseAvailabilityTest( + isPlayServiceAvailable = object : IsPlayServiceAvailable { + override fun isAvailable(): Boolean { + return false + } + }, + stringProvider = FakeStringProvider(), + ) + 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(false)) + } + } +} diff --git a/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTestTest.kt b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTestTest.kt new file mode 100644 index 0000000000..2d8de62ad9 --- /dev/null +++ b/libraries/pushproviders/firebase/src/test/kotlin/io/element/android/libraries/pushproviders/firebase/troubleshoot/FirebaseTokenTestTest.kt @@ -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.firebase.troubleshoot + +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.pushproviders.firebase.FakeFirebaseTroubleshooter +import io.element.android.libraries.pushproviders.firebase.InMemoryFirebaseStore +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState +import io.element.android.services.toolbox.test.strings.FakeStringProvider +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class FirebaseTokenTestTest { + @Test + fun `test FirebaseTokenTest success`() = runTest { + val sut = FirebaseTokenTest( + firebaseStore = InMemoryFirebaseStore(FAKE_TOKEN), + firebaseTroubleshooter = FakeFirebaseTroubleshooter(), + stringProvider = FakeStringProvider(), + ) + 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) + assertThat(lastItem.description).contains(FAKE_TOKEN.take(8)) + assertThat(lastItem.description).doesNotContain(FAKE_TOKEN) + } + } + + @Test + fun `test FirebaseTokenTest error`() = runTest { + val firebaseStore = InMemoryFirebaseStore(null) + val sut = FirebaseTokenTest( + firebaseStore = firebaseStore, + firebaseTroubleshooter = FakeFirebaseTroubleshooter( + troubleShootResult = { + firebaseStore.storeFcmToken(FAKE_TOKEN) + Result.success(Unit) + } + ), + stringProvider = FakeStringProvider(), + ) + 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 + sut.quickFix(this) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.InProgress) + assertThat(awaitItem().status).isEqualTo(NotificationTroubleshootTestState.Status.Success) + } + } + + companion object { + private const val FAKE_TOKEN = "abcdefghijk" + } +} diff --git a/libraries/pushproviders/test/build.gradle.kts b/libraries/pushproviders/test/build.gradle.kts new file mode 100644 index 0000000000..ddb68ed43f --- /dev/null +++ b/libraries/pushproviders/test/build.gradle.kts @@ -0,0 +1,27 @@ +/* + * 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. + */ +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.libraries.pushproviders.test" +} + +dependencies { + implementation(projects.libraries.matrix.api) + implementation(projects.libraries.pushproviders.api) +} diff --git a/libraries/pushproviders/test/src/main/kotlin/io/element/android/libraries/pushproviders/test/FakePushProvider.kt b/libraries/pushproviders/test/src/main/kotlin/io/element/android/libraries/pushproviders/test/FakePushProvider.kt new file mode 100644 index 0000000000..8d8b94ec19 --- /dev/null +++ b/libraries/pushproviders/test/src/main/kotlin/io/element/android/libraries/pushproviders/test/FakePushProvider.kt @@ -0,0 +1,45 @@ +/* + * 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.test + +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 + +class FakePushProvider( + override val index: Int = 0, + override val name: String = "aFakePushProvider", + private val isAvailable: Boolean = true, + private val distributors: List = emptyList() +) : PushProvider { + override fun isAvailable(): Boolean = isAvailable + + override fun getDistributors(): List = distributors + + override suspend fun registerWith(matrixClient: MatrixClient, distributor: Distributor) { + // No-op + } + + override suspend fun unregister(matrixClient: MatrixClient) { + // No-op + } + + override suspend fun getCurrentUserPushConfig(): CurrentUserPushConfig? { + return null + } +} diff --git a/libraries/pushproviders/unifiedpush/build.gradle.kts b/libraries/pushproviders/unifiedpush/build.gradle.kts index a3968f4ecc..d5dcc9727d 100644 --- a/libraries/pushproviders/unifiedpush/build.gradle.kts +++ b/libraries/pushproviders/unifiedpush/build.gradle.kts @@ -32,11 +32,14 @@ dependencies { implementation(projects.libraries.androidutils) implementation(projects.libraries.core) implementation(projects.libraries.matrix.api) + implementation(projects.libraries.uiStrings) + api(projects.libraries.troubleshoot.api) implementation(projects.libraries.pushstore.api) implementation(projects.libraries.pushproviders.api) implementation(projects.libraries.architecture) implementation(projects.libraries.core) + implementation(projects.services.appnavstate.api) implementation(projects.services.toolbox.api) implementation(projects.libraries.network) @@ -50,8 +53,11 @@ dependencies { // UnifiedPush library api(libs.unifiedpush) + testImplementation(libs.coroutines.test) testImplementation(libs.test.junit) testImplementation(libs.test.truth) + testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) testImplementation(projects.tests.testutils) + testImplementation(projects.services.toolbox.test) } diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushConfig.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushConfig.kt index 07c57496db..b1d321f42f 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushConfig.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushConfig.kt @@ -23,6 +23,8 @@ object UnifiedPushConfig { */ const val DEFAULT_PUSH_GATEWAY_HTTP_URL: String = "https://matrix.gateway.unifiedpush.org/_matrix/push/v1/notify" + const val UNIFIED_PUSH_DISTRIBUTORS_URL = "https://unifiedpush.org/users/distributors/" + const val INDEX = 1 const val NAME = "UnifiedPush" } diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushDistributorProvider.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushDistributorProvider.kt new file mode 100644 index 0000000000..5c9249e8f4 --- /dev/null +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushDistributorProvider.kt @@ -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 +} + +@ContributesBinding(AppScope::class) +class DefaultUnifiedPushDistributorProvider @Inject constructor( + @ApplicationContext private val context: Context, +) : UnifiedPushDistributorProvider { + override fun getDistributors(): List { + val distributors = UnifiedPush.getDistributors(context) + return distributors.mapNotNull { + if (it == context.packageName) { + // Exclude self + null + } else { + Distributor(it, context.getApplicationLabel(it)) + } + } + } +} diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushNewGatewayHandler.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushNewGatewayHandler.kt index 2ae8753a1e..4ee637a3ab 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushNewGatewayHandler.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushNewGatewayHandler.kt @@ -40,7 +40,7 @@ class UnifiedPushNewGatewayHandler @Inject constructor( val userId = pushClientSecret.getUserIdFromSecret(clientSecret) ?: return Unit.also { Timber.w("Unable to retrieve session") } - val userDataStore = userPushStoreFactory.create(userId) + val userDataStore = userPushStoreFactory.getOrCreate(userId) if (userDataStore.getPushProviderName() == UnifiedPushConfig.NAME) { matrixAuthenticationService.restoreSession(userId).getOrNull()?.use { client -> pusherSubscriber.registerPusher(client, endpoint, pushGateway) diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushProvider.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushProvider.kt index 5d0a0da7a1..e7ea1841c5 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushProvider.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushProvider.kt @@ -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 { - 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 { - 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, + ) } } diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/OpenDistributorWebPageAction.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/OpenDistributorWebPageAction.kt new file mode 100644 index 0000000000..dda4292f12 --- /dev/null +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/OpenDistributorWebPageAction.kt @@ -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 io.element.android.libraries.pushproviders.unifiedpush.UnifiedPushConfig +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 = UnifiedPushConfig.UNIFIED_PUSH_DISTRIBUTORS_URL, + ) + } +} diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushTest.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushTest.kt new file mode 100644 index 0000000000..77a2347429 --- /dev/null +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushTest.kt @@ -0,0 +1,78 @@ +/* + * 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.di.AppScope +import io.element.android.libraries.pushproviders.unifiedpush.R +import io.element.android.libraries.pushproviders.unifiedpush.UnifiedPushConfig +import io.element.android.libraries.pushproviders.unifiedpush.UnifiedPushDistributorProvider +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestDelegate +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState +import io.element.android.libraries.troubleshoot.api.test.TestFilterData +import io.element.android.services.toolbox.api.strings.StringProvider +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, + private val stringProvider: StringProvider, +) : NotificationTroubleshootTest { + override val order = 400 + private val delegate = NotificationTroubleshootTestDelegate( + defaultName = stringProvider.getString(R.string.troubleshoot_notifications_test_unified_push_title), + defaultDescription = stringProvider.getString(R.string.troubleshoot_notifications_test_unified_push_description), + visibleWhenIdle = false, + fakeDelay = NotificationTroubleshootTestDelegate.SHORT_DELAY, + ) + override val state: StateFlow = 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 = stringProvider.getQuantityString( + resId = R.plurals.troubleshoot_notifications_test_unified_push_success, + quantity = distributors.size, + distributors.size, + distributors.joinToString { it.name } + ), + status = NotificationTroubleshootTestState.Status.Success + ) + } else { + delegate.updateState( + description = stringProvider.getString(R.string.troubleshoot_notifications_test_unified_push_failure), + status = NotificationTroubleshootTestState.Status.Failure(true) + ) + } + } + + override suspend fun reset() = delegate.reset() + + override suspend fun quickFix(coroutineScope: CoroutineScope) { + openDistributorWebPageAction.execute() + } +} diff --git a/libraries/pushproviders/unifiedpush/src/main/res/values-be/translations.xml b/libraries/pushproviders/unifiedpush/src/main/res/values-be/translations.xml new file mode 100644 index 0000000000..5fcab0a3f3 --- /dev/null +++ b/libraries/pushproviders/unifiedpush/src/main/res/values-be/translations.xml @@ -0,0 +1,11 @@ + + + "Пераканайцеся, што размеркавальнікі UnifiedPush даступныя." + "Размеркавальнікі не знойдзены." + + "%1$d знойдзены размеркавальнік: %2$s." + "%1$d знойдзена размеркавальніка: %2$s." + "%1$d знойдзена размеркавальнікаў: %2$s." + + "Праверыць UnifiedPush" + diff --git a/libraries/pushproviders/unifiedpush/src/main/res/values-cs/translations.xml b/libraries/pushproviders/unifiedpush/src/main/res/values-cs/translations.xml new file mode 100644 index 0000000000..d4eeb3ef96 --- /dev/null +++ b/libraries/pushproviders/unifiedpush/src/main/res/values-cs/translations.xml @@ -0,0 +1,11 @@ + + + "Ujistěte se, že jsou k dispozici distributoři UnifiedPush." + "Nebyli nalezeni žádní push distributoři." + + "Nalezen %1$d distributor: %2$s." + "Nalezeni %1$d distributoři: %2$s." + "Nalezeno %1$d distributorů: %2$s." + + "Zkontrolovat UnifiedPush" + diff --git a/libraries/pushproviders/unifiedpush/src/main/res/values-de/translations.xml b/libraries/pushproviders/unifiedpush/src/main/res/values-de/translations.xml new file mode 100644 index 0000000000..92a632a39d --- /dev/null +++ b/libraries/pushproviders/unifiedpush/src/main/res/values-de/translations.xml @@ -0,0 +1,10 @@ + + + "Stelle sicher, dass UnifiedPush-Verteiler verfügbar sind." + "Keine Push-Verteiler gefunden." + + "%1$d Verteiler gefunden: %2$s." + "%1$d Verteiler gefunden: %2$s." + + "UnifiedPush prüfen" + diff --git a/libraries/pushproviders/unifiedpush/src/main/res/values-fr/translations.xml b/libraries/pushproviders/unifiedpush/src/main/res/values-fr/translations.xml new file mode 100644 index 0000000000..fe7769da5e --- /dev/null +++ b/libraries/pushproviders/unifiedpush/src/main/res/values-fr/translations.xml @@ -0,0 +1,10 @@ + + + "Vérifier qu’au moins un distributeur UnifiedPush est disponible." + "Aucun distributeur UnifiedPush n’a été trouvé." + + "%1$d distributeur détecté :%2$s." + "%1$d distributeurs détectés :%2$s." + + "Vérifier UnifiedPush" + diff --git a/libraries/pushproviders/unifiedpush/src/main/res/values-hu/translations.xml b/libraries/pushproviders/unifiedpush/src/main/res/values-hu/translations.xml new file mode 100644 index 0000000000..7c92f521da --- /dev/null +++ b/libraries/pushproviders/unifiedpush/src/main/res/values-hu/translations.xml @@ -0,0 +1,10 @@ + + + "Győződjön meg arról, hogy a UnifiedPush forgalmazói elérhetők." + "Nem található forgalmazó a leküldéses értesítésekhez." + + "%1$d forgalmazó található: %2$s." + "%1$d forgalmazó található: %2$s." + + "Ellenőrizze a UnifiedPush szolgáltatást" + diff --git a/libraries/pushproviders/unifiedpush/src/main/res/values-ru/translations.xml b/libraries/pushproviders/unifiedpush/src/main/res/values-ru/translations.xml new file mode 100644 index 0000000000..91a4dbc81b --- /dev/null +++ b/libraries/pushproviders/unifiedpush/src/main/res/values-ru/translations.xml @@ -0,0 +1,11 @@ + + + "Убедитесь, что дистрибьюторы UnifiedPush доступны." + "Поставщиков push-уведомлений не найдено." + + "%1$d провайдер найден: %2$s." + "%1$d провайдеров найдено: %2$s." + "%1$d провайдеров найдено: %2$s." + + "Проверка UnifiedPush" + diff --git a/libraries/pushproviders/unifiedpush/src/main/res/values-sk/translations.xml b/libraries/pushproviders/unifiedpush/src/main/res/values-sk/translations.xml new file mode 100644 index 0000000000..3dc876cb5b --- /dev/null +++ b/libraries/pushproviders/unifiedpush/src/main/res/values-sk/translations.xml @@ -0,0 +1,11 @@ + + + "Uistite sa, že sú dostupní distribútori UnifiedPush." + "Nenašli sa žiadni distribútori push." + + "%1$d nájdený distribútor: %2$s." + "%1$d nájdení distribútori: %2$s." + "%1$d nájdených distribútorov: %2$s." + + "Skontrolovať UnifiedPush" + diff --git a/libraries/pushproviders/unifiedpush/src/main/res/values/localazy.xml b/libraries/pushproviders/unifiedpush/src/main/res/values/localazy.xml new file mode 100644 index 0000000000..0e16af1f3e --- /dev/null +++ b/libraries/pushproviders/unifiedpush/src/main/res/values/localazy.xml @@ -0,0 +1,10 @@ + + + "Ensure that UnifiedPush distributors are available." + "No push distributors found." + + "%1$d distributor found: %2$s." + "%1$d distributors found: %2$s." + + "Check UnifiedPush" + diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/FakeOpenDistributorWebPageAction.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/FakeOpenDistributorWebPageAction.kt new file mode 100644 index 0000000000..ca91807fb9 --- /dev/null +++ b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/FakeOpenDistributorWebPageAction.kt @@ -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() +} diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/FakeUnifiedPushDistributorProvider.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/FakeUnifiedPushDistributorProvider.kt new file mode 100644 index 0000000000..e9734956d7 --- /dev/null +++ b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/FakeUnifiedPushDistributorProvider.kt @@ -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 = emptyList() +) : UnifiedPushDistributorProvider { + override fun getDistributors(): List { + return getDistributorsResult + } + + fun setDistributorsResult(list: List) { + getDistributorsResult = list + } +} diff --git a/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushTestTest.kt b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushTestTest.kt new file mode 100644 index 0000000000..117e8b7457 --- /dev/null +++ b/libraries/pushproviders/unifiedpush/src/test/kotlin/io/element/android/libraries/pushproviders/unifiedpush/troubleshoot/UnifiedPushTestTest.kt @@ -0,0 +1,84 @@ +/* + * 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.pushproviders.api.Distributor +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState +import io.element.android.services.toolbox.test.strings.FakeStringProvider +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(), + stringProvider = FakeStringProvider(), + ) + 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"), + ) + ) + } + ), + stringProvider = FakeStringProvider(), + ) + 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) + } + } +} diff --git a/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/UserPushStoreFactory.kt b/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/UserPushStoreFactory.kt index 52e4596ca0..95097845ab 100644 --- a/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/UserPushStoreFactory.kt +++ b/libraries/pushstore/api/src/main/kotlin/io/element/android/libraries/pushstore/api/UserPushStoreFactory.kt @@ -22,5 +22,5 @@ import io.element.android.libraries.matrix.api.core.SessionId * Store data related to push about a user. */ interface UserPushStoreFactory { - fun create(userId: SessionId): UserPushStore + fun getOrCreate(userId: SessionId): UserPushStore } diff --git a/libraries/pushstore/impl/src/androidTest/kotlin/io/element/android/libraries/pushstore/impl/DefaultUserPushStoreFactoryTest.kt b/libraries/pushstore/impl/src/androidTest/kotlin/io/element/android/libraries/pushstore/impl/DefaultUserPushStoreFactoryTest.kt index b7bbf46dc4..7f130459fa 100644 --- a/libraries/pushstore/impl/src/androidTest/kotlin/io/element/android/libraries/pushstore/impl/DefaultUserPushStoreFactoryTest.kt +++ b/libraries/pushstore/impl/src/androidTest/kotlin/io/element/android/libraries/pushstore/impl/DefaultUserPushStoreFactoryTest.kt @@ -40,11 +40,11 @@ class DefaultUserPushStoreFactoryTest { val userPushStoreFactory = DefaultUserPushStoreFactory(context, NoOpSessionObserver()) var userPushStore1: UserPushStore? = null val thread1 = thread { - userPushStore1 = userPushStoreFactory.create(sessionId) + userPushStore1 = userPushStoreFactory.getOrCreate(sessionId) } var userPushStore2: UserPushStore? = null val thread2 = thread { - userPushStore2 = userPushStoreFactory.create(sessionId) + userPushStore2 = userPushStoreFactory.getOrCreate(sessionId) } thread1.join() thread2.join() diff --git a/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/DefaultUserPushStoreFactory.kt b/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/DefaultUserPushStoreFactory.kt index 8c85dca80c..3c65c76a01 100644 --- a/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/DefaultUserPushStoreFactory.kt +++ b/libraries/pushstore/impl/src/main/kotlin/io/element/android/libraries/pushstore/impl/DefaultUserPushStoreFactory.kt @@ -41,7 +41,7 @@ class DefaultUserPushStoreFactory @Inject constructor( // We can have only one class accessing a single data store, so keep a cache of them. private val cache = ConcurrentHashMap() - override fun create(userId: SessionId): UserPushStore { + override fun getOrCreate(userId: SessionId): UserPushStore { return cache.getOrPut(userId) { UserPushStoreDataStore( context = context, @@ -60,6 +60,6 @@ class DefaultUserPushStoreFactory @Inject constructor( override suspend fun onSessionDeleted(userId: String) { // Delete the store - create(SessionId(userId)).reset() + getOrCreate(SessionId(userId)).reset() } } diff --git a/libraries/pushstore/test/src/main/kotlin/com/element/android/libraries/pushstore/test/userpushstore/FakeUserPushStoreFactory.kt b/libraries/pushstore/test/src/main/kotlin/com/element/android/libraries/pushstore/test/userpushstore/FakeUserPushStoreFactory.kt index a529e34bc1..2f4f524cc2 100644 --- a/libraries/pushstore/test/src/main/kotlin/com/element/android/libraries/pushstore/test/userpushstore/FakeUserPushStoreFactory.kt +++ b/libraries/pushstore/test/src/main/kotlin/com/element/android/libraries/pushstore/test/userpushstore/FakeUserPushStoreFactory.kt @@ -21,7 +21,7 @@ import io.element.android.libraries.pushstore.api.UserPushStore import io.element.android.libraries.pushstore.api.UserPushStoreFactory class FakeUserPushStoreFactory : UserPushStoreFactory { - override fun create(userId: SessionId): UserPushStore { + override fun getOrCreate(userId: SessionId): UserPushStore { return FakeUserPushStore() } } diff --git a/libraries/troubleshoot/api/build.gradle.kts b/libraries/troubleshoot/api/build.gradle.kts new file mode 100644 index 0000000000..5ac917fd0b --- /dev/null +++ b/libraries/troubleshoot/api/build.gradle.kts @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2023 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. + */ +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.libraries.troubleshoot.api" +} + +dependencies { + implementation(projects.libraries.architecture) + implementation(libs.androidx.corektx) + implementation(libs.coroutines.core) +} diff --git a/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/NotificationTroubleShootEntryPoint.kt b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/NotificationTroubleShootEntryPoint.kt new file mode 100644 index 0000000000..6e4e1c39e3 --- /dev/null +++ b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/NotificationTroubleShootEntryPoint.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023 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.troubleshoot.api + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import io.element.android.libraries.architecture.FeatureEntryPoint + +interface NotificationTroubleShootEntryPoint : FeatureEntryPoint { + fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder + + interface NodeBuilder { + fun callback(callback: Callback): NodeBuilder + fun build(): Node + } + + interface Callback : Plugin { + fun onDone() + } +} diff --git a/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootTest.kt b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootTest.kt new file mode 100644 index 0000000000..69073925fb --- /dev/null +++ b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootTest.kt @@ -0,0 +1,31 @@ +/* + * 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.troubleshoot.api.test + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.StateFlow + +interface NotificationTroubleshootTest { + val order: Int + val state: StateFlow + fun isRelevant(data: TestFilterData): Boolean = true + suspend fun run(coroutineScope: CoroutineScope) + suspend fun reset() + suspend fun quickFix(coroutineScope: CoroutineScope) { + error("Quick fix not implemented, you need to override this method in your test") + } +} diff --git a/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootTestDelegate.kt b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootTestDelegate.kt new file mode 100644 index 0000000000..da36de8ee7 --- /dev/null +++ b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootTestDelegate.kt @@ -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.troubleshoot.api.test + +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +/** + * A NotificationTroubleshootTest delegate, with common pattern for running and resetting. + */ +class NotificationTroubleshootTestDelegate( + private val defaultName: String, + private val defaultDescription: String, + private val visibleWhenIdle: Boolean = true, + private val hasQuickFix: Boolean = false, + private val fakeDelay: Long = 0L, +) { + private val _state: MutableStateFlow = MutableStateFlow( + NotificationTroubleshootTestState( + name = defaultName, + description = defaultDescription, + status = NotificationTroubleshootTestState.Status.Idle(visibleWhenIdle), + ) + ) + + val state: StateFlow = _state.asStateFlow() + + suspend fun updateState( + status: NotificationTroubleshootTestState.Status, + name: String = defaultName, + description: String = defaultDescription, + ) { + _state.emit( + NotificationTroubleshootTestState( + name = name, + description = description, + status = status, + ) + ) + } + + suspend fun reset() { + updateState(NotificationTroubleshootTestState.Status.Idle(visibleWhenIdle)) + } + + suspend fun start() { + updateState(NotificationTroubleshootTestState.Status.InProgress) + delay(fakeDelay) + } + + suspend fun done(isSuccess: Boolean = true) { + updateState( + if (isSuccess) { + NotificationTroubleshootTestState.Status.Success + } else { + NotificationTroubleshootTestState.Status.Failure(hasQuickFix) + } + ) + } + + companion object { + const val SHORT_DELAY = 300L + const val LONG_DELAY = 500L + } +} diff --git a/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootTestState.kt b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootTestState.kt new file mode 100644 index 0000000000..1429916bcc --- /dev/null +++ b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/NotificationTroubleshootTestState.kt @@ -0,0 +1,31 @@ +/* + * 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.troubleshoot.api.test + +data class NotificationTroubleshootTestState( + val name: String, + val description: String, + val status: Status, +) { + sealed interface Status { + data class Idle(val visible: Boolean) : Status + data object InProgress : Status + data object WaitingForUser : Status + data object Success : Status + data class Failure(val hasQuickFix: Boolean) : Status + } +} diff --git a/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/TestFilterData.kt b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/TestFilterData.kt new file mode 100644 index 0000000000..77f633363d --- /dev/null +++ b/libraries/troubleshoot/api/src/main/kotlin/io/element/android/libraries/troubleshoot/api/test/TestFilterData.kt @@ -0,0 +1,21 @@ +/* + * 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.troubleshoot.api.test + +data class TestFilterData( + val currentPushProviderName: String?, +) diff --git a/libraries/troubleshoot/impl/build.gradle.kts b/libraries/troubleshoot/impl/build.gradle.kts new file mode 100644 index 0000000000..4967a34cf3 --- /dev/null +++ b/libraries/troubleshoot/impl/build.gradle.kts @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2023 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. + */ +plugins { + id("io.element.android-compose-library") + alias(libs.plugins.anvil) + alias(libs.plugins.ksp) + alias(libs.plugins.kotlin.serialization) +} + +android { + namespace = "io.element.android.libraries.troubleshoot.impl" + + testOptions { + unitTests { + isIncludeAndroidResources = true + } + } +} + +anvil { + generateDaggerFactories.set(true) +} + +dependencies { + implementation(projects.anvilannotations) + anvil(projects.anvilcodegen) + implementation(libs.dagger) + implementation(projects.libraries.architecture) + implementation(projects.libraries.designsystem) + implementation(projects.libraries.di) + api(projects.libraries.troubleshoot.api) + api(projects.libraries.push.api) + implementation(projects.services.analytics.api) + ksp(libs.showkase.processor) + + testImplementation(libs.test.junit) + testImplementation(libs.test.robolectric) + testImplementation(libs.molecule.runtime) + testImplementation(libs.test.truth) + testImplementation(libs.test.turbine) + testImplementation(libs.coroutines.test) + testImplementation(projects.services.analytics.test) + testImplementation(projects.tests.testutils) + testImplementation(projects.libraries.push.test) + testImplementation(libs.androidx.compose.ui.test.junit) + testReleaseImplementation(libs.androidx.compose.ui.test.manifest) +} diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/DefaultNotificationTroubleShootEntryPoint.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/DefaultNotificationTroubleShootEntryPoint.kt new file mode 100644 index 0000000000..81c0cdaf00 --- /dev/null +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/DefaultNotificationTroubleShootEntryPoint.kt @@ -0,0 +1,44 @@ +/* + * 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.troubleshoot.impl + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.architecture.createNode +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.troubleshoot.api.NotificationTroubleShootEntryPoint +import javax.inject.Inject + +@ContributesBinding(AppScope::class) +class DefaultNotificationTroubleShootEntryPoint @Inject constructor() : NotificationTroubleShootEntryPoint { + override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NotificationTroubleShootEntryPoint.NodeBuilder { + val plugins = ArrayList() + + return object : NotificationTroubleShootEntryPoint.NodeBuilder { + override fun callback(callback: NotificationTroubleShootEntryPoint.Callback): NotificationTroubleShootEntryPoint.NodeBuilder { + plugins += callback + return this + } + + override fun build(): Node { + return parentNode.createNode(buildContext, plugins) + } + } + } +} diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsEvents.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsEvents.kt new file mode 100644 index 0000000000..f2398e3b78 --- /dev/null +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsEvents.kt @@ -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.troubleshoot.impl + +sealed interface TroubleshootNotificationsEvents { + data object StartTests : TroubleshootNotificationsEvents + data object RetryFailedTests : TroubleshootNotificationsEvents + data class QuickFix(val testIndex: Int) : TroubleshootNotificationsEvents +} diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsNode.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsNode.kt new file mode 100644 index 0000000000..96404d6d8c --- /dev/null +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsNode.kt @@ -0,0 +1,56 @@ +/* + * 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.troubleshoot.impl + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import com.bumble.appyx.core.plugin.plugins +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import im.vector.app.features.analytics.plan.MobileScreen +import io.element.android.anvilannotations.ContributesNode +import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.troubleshoot.api.NotificationTroubleShootEntryPoint +import io.element.android.services.analytics.api.ScreenTracker + +@ContributesNode(SessionScope::class) +class TroubleshootNotificationsNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + private val presenter: TroubleshootNotificationsPresenter, + private val screenTracker: ScreenTracker, +) : Node(buildContext, plugins = plugins) { + private fun onDone() { + plugins().forEach { + it.onDone() + } + } + + @Composable + override fun View(modifier: Modifier) { + screenTracker.TrackScreen(MobileScreen.ScreenName.NotificationTroubleshoot) + val state = presenter.present() + TroubleshootNotificationsView( + state = state, + onBackPressed = ::onDone, + modifier = modifier, + ) + } +} diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsPresenter.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsPresenter.kt new file mode 100644 index 0000000000..a730199fac --- /dev/null +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsPresenter.kt @@ -0,0 +1,58 @@ +/* + * 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.troubleshoot.impl + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope +import io.element.android.libraries.architecture.Presenter +import kotlinx.coroutines.launch +import javax.inject.Inject + +class TroubleshootNotificationsPresenter @Inject constructor( + private val troubleshootTestSuite: TroubleshootTestSuite, +) : Presenter { + @Composable + override fun present(): TroubleshootNotificationsState { + val coroutineScope = rememberCoroutineScope() + LaunchedEffect(Unit) { + troubleshootTestSuite.start(this) + } + + val testSuiteState by troubleshootTestSuite.state.collectAsState() + fun handleEvents(event: TroubleshootNotificationsEvents) { + when (event) { + TroubleshootNotificationsEvents.StartTests -> coroutineScope.launch { + troubleshootTestSuite.runTestSuite(this) + } + is TroubleshootNotificationsEvents.QuickFix -> coroutineScope.launch { + troubleshootTestSuite.quickFix(event.testIndex, this) + } + TroubleshootNotificationsEvents.RetryFailedTests -> coroutineScope.launch { + troubleshootTestSuite.retryFailedTest(this) + } + } + } + + return TroubleshootNotificationsState( + testSuiteState = testSuiteState, + eventSink = ::handleEvents + ) + } +} diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsState.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsState.kt new file mode 100644 index 0000000000..4ff7c2c04c --- /dev/null +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsState.kt @@ -0,0 +1,24 @@ +/* + * 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.troubleshoot.impl + +data class TroubleshootNotificationsState( + val testSuiteState: TroubleshootTestSuiteState, + val eventSink: (TroubleshootNotificationsEvents) -> Unit, +) { + val hasFailedTests: Boolean = testSuiteState.mainState.isFailure() +} diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsStateProvider.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsStateProvider.kt new file mode 100644 index 0000000000..f2d49a82a0 --- /dev/null +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsStateProvider.kt @@ -0,0 +1,119 @@ +/* + * 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.troubleshoot.impl + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState +import kotlinx.collections.immutable.toImmutableList + +open class TroubleshootNotificationsStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aTroubleshootNotificationsState( + listOf( + aTroubleshootTestStateIdle(), + aTroubleshootTestStateIdle(), + aTroubleshootTestStateIdle(visible = false), + ) + ), + aTroubleshootNotificationsState( + listOf( + aTroubleshootTestStateInProgress(), + aTroubleshootTestStateIdle(), + aTroubleshootTestStateIdle(), + ) + ), + aTroubleshootNotificationsState( + listOf( + aTroubleshootTestStateSuccess(), + aTroubleshootTestStateInProgress(), + aTroubleshootTestStateIdle(), + ) + ), + aTroubleshootNotificationsState( + listOf( + aTroubleshootTestStateSuccess(), + aTroubleshootTestStateWaitingForUser(), + aTroubleshootTestStateIdle(), + ) + ), + aTroubleshootNotificationsState( + listOf( + aTroubleshootTestStateSuccess(), + aTroubleshootTestStateFailure(hasQuickFix = true), + aTroubleshootTestStateInProgress(), + ) + ), + aTroubleshootNotificationsState( + listOf( + aTroubleshootTestStateSuccess(), + aTroubleshootTestStateFailure(hasQuickFix = true), + aTroubleshootTestStateFailure(hasQuickFix = false), + ) + ), + aTroubleshootNotificationsState( + listOf( + aTroubleshootTestStateSuccess(), + aTroubleshootTestStateSuccess(), + aTroubleshootTestStateSuccess(), + ) + ), + aTroubleshootNotificationsState( + listOf( + aTroubleshootTestStateWaitingForUser(), + ) + ), + ) +} + +fun aTroubleshootNotificationsState( + tests: List = emptyList(), + eventSink: (TroubleshootNotificationsEvents) -> Unit = {}, +) = TroubleshootNotificationsState( + eventSink = eventSink, + testSuiteState = TroubleshootTestSuiteState( + mainState = tests.computeMainState(), + tests = tests.toImmutableList(), + ), +) + +fun aTroubleshootTestState( + status: NotificationTroubleshootTestState.Status, + name: String = "Test", + description: String = "Description", +): NotificationTroubleshootTestState { + return NotificationTroubleshootTestState( + name = name, + description = description, + status = status, + ) +} + +fun aTroubleshootTestStateIdle(visible: Boolean = true) = + aTroubleshootTestState(status = NotificationTroubleshootTestState.Status.Idle(visible = visible)) + +fun aTroubleshootTestStateInProgress() = + aTroubleshootTestState(status = NotificationTroubleshootTestState.Status.InProgress) + +fun aTroubleshootTestStateWaitingForUser() = + aTroubleshootTestState(status = NotificationTroubleshootTestState.Status.WaitingForUser) + +fun aTroubleshootTestStateSuccess() = + aTroubleshootTestState(status = NotificationTroubleshootTestState.Status.Success) + +fun aTroubleshootTestStateFailure(hasQuickFix: Boolean) = + aTroubleshootTestState(status = NotificationTroubleshootTestState.Status.Failure(hasQuickFix = hasQuickFix)) diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsView.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsView.kt new file mode 100644 index 0000000000..8b2ba843be --- /dev/null +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsView.kt @@ -0,0 +1,223 @@ +/* + * 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.troubleshoot.impl + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.progressSemantics +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp +import androidx.lifecycle.Lifecycle +import io.element.android.compound.theme.ElementTheme +import io.element.android.compound.tokens.generated.CompoundIcons +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.designsystem.components.list.ListItemContent +import io.element.android.libraries.designsystem.components.preferences.PreferencePage +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.Button +import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator +import io.element.android.libraries.designsystem.theme.components.Icon +import io.element.android.libraries.designsystem.theme.components.ListItem +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.designsystem.utils.OnLifecycleEvent +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState.Status + +@Composable +fun TroubleshootNotificationsView( + state: TroubleshootNotificationsState, + onBackPressed: () -> Unit, + modifier: Modifier = Modifier, +) { + OnLifecycleEvent { _, event -> + when (event) { + Lifecycle.Event.ON_RESUME -> { + if (state.hasFailedTests) { + state.eventSink(TroubleshootNotificationsEvents.RetryFailedTests) + } + } + else -> Unit + } + } + + PreferencePage( + modifier = modifier, + onBackPressed = onBackPressed, + title = stringResource(id = R.string.troubleshoot_notifications_screen_title), + ) { + TroubleshootNotificationsContent(state) + } +} + +@Composable +private fun TroubleshootTestView( + testState: NotificationTroubleshootTestState, + onQuickFixClicked: () -> Unit, +) { + if ((testState.status as? Status.Idle)?.visible == false) return + ListItem( + headlineContent = { Text(text = testState.name) }, + supportingContent = { Text(text = testState.description) }, + trailingContent = when (testState.status) { + is Status.Idle -> null + Status.InProgress -> ListItemContent.Custom { + CircularProgressIndicator( + modifier = Modifier + .progressSemantics() + .size(20.dp), + strokeWidth = 2.dp + ) + } + Status.WaitingForUser -> ListItemContent.Custom { + Icon( + contentDescription = null, + modifier = Modifier.size(24.dp), + imageVector = CompoundIcons.Info(), + tint = ElementTheme.colors.iconAccentTertiary + ) + } + Status.Success -> ListItemContent.Custom { + Icon( + contentDescription = null, + modifier = Modifier.size(24.dp), + imageVector = CompoundIcons.Check(), + tint = ElementTheme.colors.iconAccentTertiary + ) + } + is Status.Failure -> ListItemContent.Custom { + Icon( + contentDescription = null, + modifier = Modifier.size(24.dp), + imageVector = CompoundIcons.Error(), + tint = ElementTheme.colors.textCriticalPrimary + ) + } + } + ) + if ((testState.status as? Status.Failure)?.hasQuickFix == true) { + ListItem( + headlineContent = { + }, + trailingContent = ListItemContent.Custom { + Button( + text = stringResource(id = R.string.troubleshoot_notifications_screen_quick_fix_action), + onClick = onQuickFixClicked + ) + } + ) + } +} + +@Composable +private fun TroubleshootNotificationsContent(state: TroubleshootNotificationsState) { + when (state.testSuiteState.mainState) { + AsyncAction.Loading, + AsyncAction.Confirming, + is AsyncAction.Success, + is AsyncAction.Failure -> { + TestSuiteView( + testSuiteState = state.testSuiteState, + onQuickFixClicked = { + state.eventSink(TroubleshootNotificationsEvents.QuickFix(it)) + } + ) + } + AsyncAction.Uninitialized -> Unit + } + when (state.testSuiteState.mainState) { + AsyncAction.Uninitialized -> { + ListItem(headlineContent = { + Text( + text = stringResource(id = R.string.troubleshoot_notifications_screen_notice) + ) + }) + RunTestButton(state = state) + } + AsyncAction.Loading -> Unit + is AsyncAction.Failure -> { + ListItem(headlineContent = { + Text(text = stringResource(id = R.string.troubleshoot_notifications_screen_failure)) + }) + RunTestButton(state = state) + } + AsyncAction.Confirming -> { + ListItem(headlineContent = { + Text( + text = stringResource(id = R.string.troubleshoot_notifications_screen_waiting) + ) + }) + } + is AsyncAction.Success -> { + ListItem(headlineContent = { + Text( + text = stringResource(id = R.string.troubleshoot_notifications_screen_success) + ) + }) + } + } +} + +@Composable +private fun RunTestButton(state: TroubleshootNotificationsState) { + ListItem( + headlineContent = { + Button( + text = stringResource( + id = if (state.testSuiteState.mainState is AsyncAction.Failure) { + R.string.troubleshoot_notifications_screen_action_again + } else { + R.string.troubleshoot_notifications_screen_action + } + ), + onClick = { + state.eventSink(TroubleshootNotificationsEvents.StartTests) + }, + modifier = Modifier.fillMaxWidth(), + ) + } + ) +} + +@Composable +private fun TestSuiteView( + testSuiteState: TroubleshootTestSuiteState, + onQuickFixClicked: (Int) -> Unit, +) { + testSuiteState.tests.forEachIndexed { index, testState -> + TroubleshootTestView( + testState = testState, + onQuickFixClicked = { + onQuickFixClicked(index) + }, + ) + } +} + +@PreviewsDayNight +@Composable +internal fun TroubleshootNotificationsViewPreview( + @PreviewParameter(TroubleshootNotificationsStateProvider::class) state: TroubleshootNotificationsState, +) = ElementPreview { + TroubleshootNotificationsView( + state = state, + onBackPressed = {}, + ) +} diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootTestSuite.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootTestSuite.kt new file mode 100644 index 0000000000..1c5fbbc3e3 --- /dev/null +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootTestSuite.kt @@ -0,0 +1,122 @@ +/* + * 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.troubleshoot.impl + +import im.vector.app.features.analytics.plan.NotificationTroubleshoot +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.push.api.GetCurrentPushProvider +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState +import io.element.android.libraries.troubleshoot.api.test.TestFilterData +import io.element.android.services.analytics.api.AnalyticsService +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import javax.inject.Inject + +class TroubleshootTestSuite @Inject constructor( + private val notificationTroubleshootTests: Set<@JvmSuppressWildcards NotificationTroubleshootTest>, + private val getCurrentPushProvider: GetCurrentPushProvider, + private val analyticsService: AnalyticsService, +) { + lateinit var tests: List + + private val _state: MutableStateFlow = MutableStateFlow( + TroubleshootTestSuiteState( + mainState = AsyncAction.Uninitialized, + tests = emptyList().toImmutableList() + ) + ) + val state: StateFlow = _state + + suspend fun start(coroutineScope: CoroutineScope) { + val testFilterData = TestFilterData( + currentPushProviderName = getCurrentPushProvider.getCurrentPushProvider() + ) + tests = notificationTroubleshootTests + .filter { it.isRelevant(testFilterData) } + .sortedBy { it.order } + tests.forEach { + // Observe the state of the tests + it.state.onEach { + emitState() + }.launchIn(coroutineScope) + } + } + + suspend fun runTestSuite(coroutineScope: CoroutineScope) { + tests.forEach { + it.reset() + } + tests.forEach { + it.run(coroutineScope) + } + } + + suspend fun retryFailedTest(coroutineScope: CoroutineScope) { + tests + .filter { it.state.value.status is NotificationTroubleshootTestState.Status.Failure } + .forEach { + it.run(coroutineScope) + } + } + + private suspend fun emitState() { + val states = tests.map { it.state.value } + val mainState = states.computeMainState() + when (mainState) { + is AsyncAction.Success -> { + analyticsService.capture(NotificationTroubleshoot(hasError = false)) + } + is AsyncAction.Failure -> { + analyticsService.capture(NotificationTroubleshoot(hasError = true)) + } + else -> Unit + } + _state.emit( + TroubleshootTestSuiteState( + mainState = states.computeMainState(), + tests = states.toImmutableList() + ) + ) + } + + suspend fun quickFix(testIndex: Int, coroutineScope: CoroutineScope) { + tests[testIndex].quickFix(coroutineScope) + } +} + +fun List.computeMainState(): AsyncAction { + val isIdle = all { it.status is NotificationTroubleshootTestState.Status.Idle } + val isRunning = any { it.status is NotificationTroubleshootTestState.Status.InProgress } + return when { + isIdle -> AsyncAction.Uninitialized + isRunning -> AsyncAction.Loading + else -> { + if (any { it.status is NotificationTroubleshootTestState.Status.WaitingForUser }) { + AsyncAction.Confirming + } else if (any { it.status is NotificationTroubleshootTestState.Status.Failure }) { + AsyncAction.Failure(Exception("Some tests failed")) + } else { + AsyncAction.Success(Unit) + } + } + } +} diff --git a/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootTestSuiteState.kt b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootTestSuiteState.kt new file mode 100644 index 0000000000..32e71ff9a5 --- /dev/null +++ b/libraries/troubleshoot/impl/src/main/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootTestSuiteState.kt @@ -0,0 +1,26 @@ +/* + * 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.troubleshoot.impl + +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState +import kotlinx.collections.immutable.ImmutableList + +data class TroubleshootTestSuiteState( + val mainState: AsyncAction, + val tests: ImmutableList, +) diff --git a/libraries/troubleshoot/impl/src/main/res/values-be/translations.xml b/libraries/troubleshoot/impl/src/main/res/values-be/translations.xml new file mode 100644 index 0000000000..905b9e89b6 --- /dev/null +++ b/libraries/troubleshoot/impl/src/main/res/values-be/translations.xml @@ -0,0 +1,11 @@ + + + "Запусціць тэсты" + "Запусціце тэсты яшчэ раз" + "Некаторыя тэсты не ўдаліся. Калі ласка, праглядзіце дэталі." + "Запусціце тэсты, каб выявіць праблемы ў вашай канфігурацыі, з-за якіх апавяшчэння могуць паводзіць сябе не так, як чакалася." + "Спроба выпраўлення" + "Усе тэсты паспяхова пройдзены." + "Выпраўленне непаладак з апавяшчэннямі" + "Некаторыя тэсты патрабуюць вашай увагі. Калі ласка, праглядзіце дэталі." + diff --git a/libraries/troubleshoot/impl/src/main/res/values-cs/translations.xml b/libraries/troubleshoot/impl/src/main/res/values-cs/translations.xml new file mode 100644 index 0000000000..17e47cf56f --- /dev/null +++ b/libraries/troubleshoot/impl/src/main/res/values-cs/translations.xml @@ -0,0 +1,11 @@ + + + "Spustit testy" + "Spustit testy znovu" + "Některé testy selhaly. Zkontrolujte prosím podrobnosti." + "Spusťte testy, abyste zjistili jakýkoli problém ve vaší konfiguraci, který může způsobit, že se oznámení nebudou chovat podle očekávání." + "Pokus o opravu" + "Všechny testy proběhly úspěšně." + "Odstraňování problémů s upozorněními" + "Některé testy vyžadují vaši pozornost. Zkontrolujte prosím podrobnosti." + diff --git a/libraries/troubleshoot/impl/src/main/res/values-de/translations.xml b/libraries/troubleshoot/impl/src/main/res/values-de/translations.xml new file mode 100644 index 0000000000..98b8c579a9 --- /dev/null +++ b/libraries/troubleshoot/impl/src/main/res/values-de/translations.xml @@ -0,0 +1,11 @@ + + + "Tests durchführen" + "Tests erneut durchführen" + "Einige Tests sind fehlgeschlagen. Bitte überprüfe die Details." + "Führe die Tests durch, um Probleme zu erkennen, die dazu führen können, dass sich die Benachrichtigungen nicht wie erwartet verhalten." + "Versuche das Problem zu beheben" + "Alle Tests wurden erfolgreich bestanden." + "Fehlerbehebung für Benachrichtigungen" + "Einige Tests erfordern deine Aufmerksamkeit. Bitte überprüfe die Details." + diff --git a/libraries/troubleshoot/impl/src/main/res/values-fr/translations.xml b/libraries/troubleshoot/impl/src/main/res/values-fr/translations.xml new file mode 100644 index 0000000000..497b3e3b56 --- /dev/null +++ b/libraries/troubleshoot/impl/src/main/res/values-fr/translations.xml @@ -0,0 +1,11 @@ + + + "Exécuter les tests" + "Relancer les tests" + "Certains tests ont échoué. Veuillez vérifier les détails." + "Exécuter les tests pour détecter tout problème dans votre configuration susceptible de provoquer un dysfonctionnement des notifications." + "Tenter de corriger" + "Tous les tests ont réussi." + "Dépanner les notifications" + "Certains tests nécessitent votre attention. Veuillez vérifier les détails." + diff --git a/libraries/troubleshoot/impl/src/main/res/values-hu/translations.xml b/libraries/troubleshoot/impl/src/main/res/values-hu/translations.xml new file mode 100644 index 0000000000..211b1f4a1d --- /dev/null +++ b/libraries/troubleshoot/impl/src/main/res/values-hu/translations.xml @@ -0,0 +1,11 @@ + + + "Tesztek futtatása" + "Tesztek újbóli futtatása" + "Egyes tesztek sikertelenek voltak. Ellenőrizze a részleteket." + "A tesztek futtatása, hogy észlelje a konfigurációban felmerülő olyan problémákat, amelyek miatt az értesítések nem az elvárt módon viselkednek." + "Kísérlet a javításra" + "Minden teszt sikeresen lezajlott." + "Értesítések hibaelhárítása" + "Egyes tesztek a figyelmét igénylik. Ellenőrizze a részleteket." + diff --git a/libraries/troubleshoot/impl/src/main/res/values-ru/translations.xml b/libraries/troubleshoot/impl/src/main/res/values-ru/translations.xml new file mode 100644 index 0000000000..943652fc86 --- /dev/null +++ b/libraries/troubleshoot/impl/src/main/res/values-ru/translations.xml @@ -0,0 +1,11 @@ + + + "Выполнение тестов" + "Повторное выполнение тестов" + "Некоторые тесты провалились. Пожалуйста, проверьте детали." + "Выполните тесты, чтобы обнаружить любую проблему в конфигурации, из-за которой уведомления могут работать не так, как ожидалось." + "Попытка исправить" + "Все тесты прошли успешно." + "Уведомления об устранении неполадок" + "Некоторые тесты требуют вашего внимания. Пожалуйста, проверьте детали." + diff --git a/libraries/troubleshoot/impl/src/main/res/values-sk/translations.xml b/libraries/troubleshoot/impl/src/main/res/values-sk/translations.xml new file mode 100644 index 0000000000..3b7a08c2f0 --- /dev/null +++ b/libraries/troubleshoot/impl/src/main/res/values-sk/translations.xml @@ -0,0 +1,11 @@ + + + "Spustiť testy" + "Spustiť testy znova" + "Niektoré testy zlyhali. Skontrolujte prosím podrobnosti." + "Spustite testy, aby ste zistili akýkoľvek problém vo vašej konfigurácii, ktorý môže spôsobiť, že sa upozornenia nebudú správať podľa očakávania." + "Pokus o opravu" + "Všetky testy prebehli úspešne." + "Oznámenia riešení problémov" + "Niektoré testy si vyžadujú vašu pozornosť. Prosím skontrolujte podrobnosti." + diff --git a/libraries/troubleshoot/impl/src/main/res/values/localazy.xml b/libraries/troubleshoot/impl/src/main/res/values/localazy.xml new file mode 100644 index 0000000000..eee100d711 --- /dev/null +++ b/libraries/troubleshoot/impl/src/main/res/values/localazy.xml @@ -0,0 +1,11 @@ + + + "Run tests" + "Run tests again" + "Some tests failed. Please check the details." + "Run the tests to detect any issue in your configuration that may make notifications not behave as expected." + "Attempt to fix" + "All tests passed successfully." + "Troubleshoot notifications" + "Some tests require your attention. Please check the details." + diff --git a/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/FakeNotificationTroubleshootTest.kt b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/FakeNotificationTroubleshootTest.kt new file mode 100644 index 0000000000..90b8a988bb --- /dev/null +++ b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/FakeNotificationTroubleshootTest.kt @@ -0,0 +1,82 @@ +/* + * 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.troubleshoot.impl + +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +class FakeNotificationTroubleshootTest( + override val order: Int = 0, + private val defaultName: String = "test name", + private val defaultDescription: String = "test description", + private val firstStatus: NotificationTroubleshootTestState.Status = NotificationTroubleshootTestState.Status.Idle(visible = true), + private val runAction: () -> NotificationTroubleshootTestState? = { null }, + private val resetAction: () -> NotificationTroubleshootTestState? = { null }, + private val quickFixAction: () -> NotificationTroubleshootTestState? = { null }, +) : NotificationTroubleshootTest { + private val _state = MutableStateFlow( + NotificationTroubleshootTestState( + name = defaultName, + description = defaultDescription, + status = firstStatus + ) + ) + override val state: StateFlow = _state.asStateFlow() + + override suspend fun run(coroutineScope: CoroutineScope) { + updateState(NotificationTroubleshootTestState.Status.InProgress) + runAction()?.let { + _state.tryEmit(it) + } + } + + override suspend fun reset() { + updateState( + name = defaultName, + description = defaultDescription, + status = firstStatus, + ) + resetAction()?.let { + _state.emit(it) + } + } + + override suspend fun quickFix(coroutineScope: CoroutineScope) { + updateState(NotificationTroubleshootTestState.Status.InProgress) + quickFixAction()?.let { + _state.emit(it) + } + } + + suspend fun updateState( + status: NotificationTroubleshootTestState.Status, + name: String = defaultName, + description: String = defaultDescription, + ) { + _state.emit( + NotificationTroubleshootTestState( + name = name, + description = description, + status = status, + ) + ) + } +} diff --git a/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsPresenterTests.kt b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsPresenterTests.kt new file mode 100644 index 0000000000..25082b63c4 --- /dev/null +++ b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsPresenterTests.kt @@ -0,0 +1,128 @@ +/* + * 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.troubleshoot.impl + +import app.cash.molecule.RecompositionMode +import app.cash.molecule.moleculeFlow +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.push.test.FakeGetCurrentPushProvider +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTest +import io.element.android.libraries.troubleshoot.api.test.NotificationTroubleshootTestState +import io.element.android.services.analytics.test.FakeAnalyticsService +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class TroubleshootNotificationsPresenterTests { + @Test + fun `present - initial state`() = runTest { + val presenter = createTroubleshootNotificationsPresenter() + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + assertThat(initialState.testSuiteState.tests).isEmpty() + assertThat(initialState.testSuiteState.mainState).isEqualTo(AsyncAction.Uninitialized) + } + } + + @Test + fun `present - start test`() = runTest { + val troubleshootTestSuite = createTroubleshootTestSuite( + tests = setOf(FakeNotificationTroubleshootTest()) + ) + val presenter = createTroubleshootNotificationsPresenter( + troubleshootTestSuite = troubleshootTestSuite, + ) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink(TroubleshootNotificationsEvents.StartTests) + skipItems(1) + val stateAfterStart = awaitItem() + assertThat(stateAfterStart.testSuiteState.mainState).isEqualTo(AsyncAction.Loading) + } + } + + @Test + fun `present - start failed test`() = runTest { + val troubleshootTestSuite = createTroubleshootTestSuite( + tests = setOf( + FakeNotificationTroubleshootTest( + firstStatus = NotificationTroubleshootTestState.Status.Failure(hasQuickFix = false) + ) + ) + ) + val presenter = createTroubleshootNotificationsPresenter( + troubleshootTestSuite = troubleshootTestSuite, + ) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink(TroubleshootNotificationsEvents.RetryFailedTests) + skipItems(1) + val stateAfterStart = awaitItem() + assertThat(stateAfterStart.testSuiteState.mainState).isEqualTo(AsyncAction.Loading) + } + } + + @Test + fun `present - quick fix test`() = runTest { + val troubleshootTestSuite = createTroubleshootTestSuite( + tests = setOf( + FakeNotificationTroubleshootTest( + firstStatus = NotificationTroubleshootTestState.Status.Failure(hasQuickFix = false) + ) + ) + ) + val presenter = createTroubleshootNotificationsPresenter( + troubleshootTestSuite = troubleshootTestSuite, + ) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + skipItems(1) + val initialState = awaitItem() + assertThat(initialState.testSuiteState.mainState).isInstanceOf(AsyncAction.Failure::class.java) + initialState.eventSink(TroubleshootNotificationsEvents.QuickFix(0)) + val stateAfterStart = awaitItem() + assertThat(stateAfterStart.testSuiteState.mainState).isEqualTo(AsyncAction.Loading) + } + } + + private fun createTroubleshootTestSuite( + tests: Set = emptySet(), + currentPushProvider: String? = null, + ): TroubleshootTestSuite { + return TroubleshootTestSuite( + notificationTroubleshootTests = tests, + getCurrentPushProvider = FakeGetCurrentPushProvider(currentPushProvider), + analyticsService = FakeAnalyticsService(), + ) + } + + private fun createTroubleshootNotificationsPresenter( + troubleshootTestSuite: TroubleshootTestSuite = createTroubleshootTestSuite(), + ): TroubleshootNotificationsPresenter { + return TroubleshootNotificationsPresenter( + troubleshootTestSuite = troubleshootTestSuite, + ) + } +} diff --git a/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsViewTest.kt b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsViewTest.kt new file mode 100644 index 0000000000..5acd0e5635 --- /dev/null +++ b/libraries/troubleshoot/impl/src/test/kotlin/io/element/android/libraries/troubleshoot/impl/TroubleshootNotificationsViewTest.kt @@ -0,0 +1,123 @@ +/* + * 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.troubleshoot.impl + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.tests.testutils.EnsureNeverCalled +import io.element.android.tests.testutils.EventsRecorder +import io.element.android.tests.testutils.ensureCalledOnce +import io.element.android.tests.testutils.pressBack +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestRule +import org.junit.runner.RunWith +import org.robolectric.annotation.Config + +@RunWith(AndroidJUnit4::class) +class TroubleshootNotificationsViewTest { + @get:Rule + val rule = createAndroidComposeRule() + + @Test + fun `press menu back invokes the expected callback`() { + val eventsRecorder = EventsRecorder(expectEvents = false) + ensureCalledOnce { + rule.setTroubleshootNotificationsView( + state = aTroubleshootNotificationsState( + eventSink = eventsRecorder + ), + onBackPressed = it, + ) + rule.pressBack() + } + } + + @Test + fun `clicking on run test emits the expected Event`() { + val eventsRecorder = EventsRecorder() + rule.setTroubleshootNotificationsView( + aTroubleshootNotificationsState( + eventSink = eventsRecorder + ), + ) + rule.onNodeWithText("Run tests").performClick() + eventsRecorder.assertSingle(TroubleshootNotificationsEvents.StartTests) + } + + @Config(qualifiers = "h1024dp") + @Test + fun `clicking on run test again emits the expected Event`() { + val eventsRecorder = EventsRecorder() + rule.setTroubleshootNotificationsView( + aTroubleshootNotificationsState( + tests = listOf( + aTroubleshootTestStateFailure( + hasQuickFix = false + ) + ), + eventSink = eventsRecorder + ), + ) + rule.onNodeWithText("Run tests again").performClick() + eventsRecorder.assertList( + listOf( + TroubleshootNotificationsEvents.RetryFailedTests, + TroubleshootNotificationsEvents.StartTests, + ) + ) + } + + @Config(qualifiers = "h1024dp") + @Test + fun `clicking on quick fix emits the expected Event`() { + val eventsRecorder = EventsRecorder() + rule.setTroubleshootNotificationsView( + aTroubleshootNotificationsState( + tests = listOf( + aTroubleshootTestStateFailure( + hasQuickFix = true + ) + ), + eventSink = eventsRecorder + ), + ) + rule.onNodeWithText("Attempt to fix").performClick() + eventsRecorder.assertList( + listOf( + TroubleshootNotificationsEvents.RetryFailedTests, + TroubleshootNotificationsEvents.QuickFix(0), + ) + ) + } +} + +private fun AndroidComposeTestRule.setTroubleshootNotificationsView( + state: TroubleshootNotificationsState, + onBackPressed: () -> Unit = EnsureNeverCalled(), +) { + setContent { + TroubleshootNotificationsView( + state = state, + onBackPressed = onBackPressed, + ) + } +} diff --git a/libraries/ui-strings/src/main/res/values-be/translations.xml b/libraries/ui-strings/src/main/res/values-be/translations.xml index 223ee9740a..ffbafa87f2 100644 --- a/libraries/ui-strings/src/main/res/values-be/translations.xml +++ b/libraries/ui-strings/src/main/res/values-be/translations.xml @@ -275,57 +275,4 @@ "Месцазнаходжанне" "Версія: %1$s (%2$s)" "be" - "Выпраўленне непаладак" - "Выпраўленне непаладак з апавяшчэннямі" - "Запусціць тэсты" - "Запусціце тэсты яшчэ раз" - "Некаторыя тэсты не ўдаліся. Калі ласка, праглядзіце дэталі." - "Запусціце тэсты, каб выявіць праблемы ў вашай канфігурацыі, з-за якіх апавяшчэння могуць паводзіць сябе не так, як чакалася." - "Спроба выпраўлення" - "Усе тэсты паспяхова пройдзены." - "Выпраўленне непаладак з апавяшчэннямі" - "Некаторыя тэсты патрабуюць вашай увагі. Калі ласка, праглядзіце дэталі." - "Пераканайцеся, што праграма можа паказваць апавяшчэнні." - "Праверце дазволы" - "Атрымаць назву бягучага пастаўшчыка." - "Пастаўшчыкі push-апавяшчэнняў не выбраны." - "Бягучы пастаўшчык push-апавяшчэнняў: %1$s." - "Бягучы пастаўшчык push-апавяшчэнняў" - "Пераканайцеся, што ў праграме ёсць хаця б адзін пастаўшчык push-апавяшчэнняў." - "Пастаўшчыкі push-апавяшчэнняў не знойдзены." - - "Знайшлі %1$d пастаўшчыка push-апавяшчэнняў: %2$s" - "Знайшлі %1$d пастаўшчыкоў push-апавяшчэнняў: %2$s" - "Знайшлі %1$d пастаўшчыкоў push-апавяшчэнняў: %2$s" - - "Выяўленне пастаўшчыкоў push-паслуг" - "Праверце, ці можа праграма паказваць апавяшчэнні." - "Апавяшчэнне не было націснута." - "Немагчыма паказаць апавяшчэнне." - "Апавяшчэнне было націснута!" - "Паказаць апавяшчэнне" - "Націсніце на апавяшчэнне, каб працягнуць тэст." - "Пераканайцеся, што Firebase даступны." - "Firebase недаступны." - "Firebase даступны." - "Праверыць Firebase" - "Пераканайцеся, што маркер Firebase даступны." - "Маркер Firebase невядомы." - "Маркер Firebase: %1$s." - "Праверце маркер Firebase" - "Пераканайцеся, што праграма атрымлівае push-апавяшчэнні." - "Памылка: pusher адхіліў запыт." - "Памылка: %1$s." - "Памылка, немагчыма праверыць push-апавяшчэнне." - "Памылка, тайм-аўт у чаканні push-апавяшчэння." - "Зварот цыклу назад заняў %1$d мс." - "Тэст Націсніце кнопку вярнуцца" - "Пераканайцеся, што размеркавальнікі UnifiedPush даступныя." - "Размеркавальнікі не знойдзены." - - "%1$d знойдзены размеркавальнік: %2$s." - "%1$d знойдзена размеркавальніка: %2$s." - "%1$d знойдзена размеркавальнікаў: %2$s." - - "Праверыць UnifiedPush" diff --git a/libraries/ui-strings/src/main/res/values-bg/translations.xml b/libraries/ui-strings/src/main/res/values-bg/translations.xml index 0e6a31b71d..48c602706b 100644 --- a/libraries/ui-strings/src/main/res/values-bg/translations.xml +++ b/libraries/ui-strings/src/main/res/values-bg/translations.xml @@ -211,5 +211,4 @@ "Местоположение" "Версия: %1$s (%2$s)" "bg" - "Грешка: %1$s" diff --git a/libraries/ui-strings/src/main/res/values-cs/translations.xml b/libraries/ui-strings/src/main/res/values-cs/translations.xml index 04968d8548..9dde757dc1 100644 --- a/libraries/ui-strings/src/main/res/values-cs/translations.xml +++ b/libraries/ui-strings/src/main/res/values-cs/translations.xml @@ -275,57 +275,4 @@ "Poloha" "Verze: %1$s (%2$s)" "en" - "Odstraňování problémů" - "Odstraňování problémů s upozorněními" - "Spustit testy" - "Spustit testy znovu" - "Některé testy selhaly. Zkontrolujte prosím podrobnosti." - "Spusťte testy, abyste zjistili jakýkoli problém ve vaší konfiguraci, který může způsobit, že se oznámení nebudou chovat podle očekávání." - "Pokus o opravu" - "Všechny testy proběhly úspěšně." - "Odstraňování problémů s upozorněními" - "Některé testy vyžadují vaši pozornost. Zkontrolujte prosím podrobnosti." - "Ujistěte se, že aplikace může zobrazovat oznámení." - "Kontrola oprávnění" - "Získat název aktuálního poskytovatele." - "Nebyli vybráni žádní push poskytovatelé." - "Aktuální push poskytovatel: %1$s." - "Aktuální push poskytovatel" - "Ujistěte se, že aplikace má alespoň jednoho push poskytovatele." - "Nebyli nalezeni žádní push poskytovatelé." - - "Nalezen %1$d push poskytovatel: %2$s" - "Nalezeni %1$d push poskytovatelé: %2$s" - "Nalezeno %1$d push poskytovatelů: %2$s" - - "Zjistit push poskytovatele" - "Zkontrolujte, zda aplikace může zobrazit oznámení." - "Na oznámení nebylo kliknuto." - "Oznámení nelze zobrazit." - "Na oznámení bylo kliknuto!" - "Zobrazit oznámení" - "Kliknutím na oznámení pokračujte v testu." - "Ujistěte se, že je k dispozici Firebase." - "Firebase není k dispozici." - "Firebase je k dispozici." - "Zkontrolovat Firebase" - "Ujistěte se, že je k dispozici Firebase token." - "Firebase token není znám." - "Firebase token: %1$s." - "Zkontrolovat Firebase token" - "Ujistěte se, že aplikace přijímá push." - "Chyba: pusher odmítl požadavek." - "Chyba: %1$s." - "Chyba, nelze otestovat push." - "Chyba, časový limit čekání na push." - "Push zpětná smyčka trvala %1$d ms." - "Otestovat push zpětnou smyčku" - "Ujistěte se, že jsou k dispozici distributoři UnifiedPush." - "Nebyli nalezeni žádní push distributoři." - - "Nalezen %1$d distributor: %2$s." - "Nalezeni %1$d distributoři: %2$s." - "Nalezeno %1$d distributorů: %2$s." - - "Zkontrolovat UnifiedPush" diff --git a/libraries/ui-strings/src/main/res/values-de/translations.xml b/libraries/ui-strings/src/main/res/values-de/translations.xml index 19408138b7..8a3e035833 100644 --- a/libraries/ui-strings/src/main/res/values-de/translations.xml +++ b/libraries/ui-strings/src/main/res/values-de/translations.xml @@ -271,55 +271,4 @@ "Standort" "Version: %1$s (%2$s)" "en" - "Fehlerbehebung" - "Fehlerbehebung für Benachrichtigungen" - "Tests durchführen" - "Tests erneut durchführen" - "Einige Tests sind fehlgeschlagen. Bitte überprüfe die Details." - "Führe die Tests durch, um Probleme zu erkennen, die dazu führen können, dass sich die Benachrichtigungen nicht wie erwartet verhalten." - "Versuche das Problem zu beheben" - "Alle Tests wurden erfolgreich bestanden." - "Fehlerbehebung für Benachrichtigungen" - "Einige Tests erfordern deine Aufmerksamkeit. Bitte überprüfe die Details." - "Stelle sicher, dass die Anwendung Benachrichtigungen anzeigen kann." - "Berechtigungen überprüfen" - "Ermittele den Namen des aktuellen Anbieters." - "Kein Push-Anbieter ausgewählt." - "Aktueller Push-Anbieter:%1$s." - "Aktueller Push-Anbieter" - "Stelle sicher, dass die Anwendung mindestens einen Push-Anbieter hat." - "Keine Push-Anbieter gefunden." - - "%1$d Push-Anbieter gefunden: %2$s" - "%1$d Push-Anbieter gefunden: %2$s" - - "Push-Anbieter erkennen" - "Prüfe, ob die Anwendung Benachrichtigungen anzeigen kann." - "Die Benachrichtigung wurde nicht angeklickt." - "Die Benachrichtigung kann nicht angezeigt werden." - "Die Benachrichtigung wurde angeklickt!" - "Benachrichtigung anzeigen" - "Bitte klicke auf die Benachrichtigung, um den Test fortzusetzen." - "Stelle sicher, dass Firebase verfügbar ist." - "Firebase ist nicht verfügbar." - "Firebase ist verfügbar." - "Überprüfe Firebase" - "Stelle sicher, dass der Firebase Token verfügbar ist." - "Firebase Token ist nicht bekannt." - "Firebase Token: %1$s." - "Prüfe Firebase Token" - "Stelle sicher, dass die Anwendung Push-Nachrichten empfängt." - "Fehler: Der Pusher hat die Anfrage abgelehnt." - "Fehler:%1$s." - "Fehler: Push kann nicht getestet werden." - "Fehler: Timeout beim Warten auf Push." - "Push-Loop-Back Dauer: %1$d ms." - "Teste Push-Loop-Back" - "Stelle sicher, dass UnifiedPush-Verteiler verfügbar sind." - "Keine Push-Verteiler gefunden." - - "%1$d Verteiler gefunden: %2$s." - "%1$d Verteiler gefunden: %2$s." - - "UnifiedPush prüfen" diff --git a/libraries/ui-strings/src/main/res/values-fr/translations.xml b/libraries/ui-strings/src/main/res/values-fr/translations.xml index 9cd0d2715d..e3b6e3d0d9 100644 --- a/libraries/ui-strings/src/main/res/values-fr/translations.xml +++ b/libraries/ui-strings/src/main/res/values-fr/translations.xml @@ -271,55 +271,4 @@ "Position" "Version : %1$s ( %2$s )" "Ang." - "Dépannage" - "Résoudre les problèmes liés aux notifications" - "Exécuter les tests" - "Relancer les tests" - "Certains tests ont échoué. Veuillez vérifier les détails." - "Exécuter les tests pour détecter tout problème dans votre configuration susceptible de provoquer un dysfonctionnement des notifications." - "Tenter de corriger" - "Tous les tests ont réussi." - "Dépanner les notifications" - "Certains tests nécessitent votre attention. Veuillez vérifier les détails." - "Vérifie que l’application peut afficher des notifications." - "Vérifier les autorisations" - "Obtenir le nom du fournisseur de Push actuel." - "Aucun fournisseur de Push n’est sélectionné." - "Fournisseur de Push actuel : %1$s." - "Fournisseur de Push actuel" - "Vérifier que l’application possède au moins un fournisseur de Push." - "Aucun fournisseur de Push n’a été trouvé." - - "%1$d fournisseur de Push détecté : %2$s" - "%1$d fournisseurs de Push détectés : %2$s" - - "Détecter les fournisseurs de Push" - "Vérifier que l’application peut afficher des notifications." - "Vous n’avez pas cliqué sur la notification." - "Impossible d’afficher la notification." - "Vous avez cliqué sur la notification!" - "Affichage des notifications" - "Veuillez cliquer sur la notification pour continuer le test." - "Vérification que Firebase est disponible." - "Firebase n’est pas disponible." - "Firebase est disponible." - "Vérification de Firebase" - "Vérifier que le jeton Firebase est disponible." - "Le jeton Firebase n’est pas connu." - "Jeton Firebase :%1$s." - "Vérifier le jeton Firebase" - "Vérifier que l’application reçoit les Push." - "Erreur : le Pusher a rejeté la demande." - "Erreur :%1$s." - "Erreur, impossible de tester les Push." - "Erreur, le délai d’attente du Push est dépassé." - "La demande d’envoi de Push et sa réception ont pris %1$d ms." - "Tester la réception des Push" - "Vérifier qu’au moins un distributeur UnifiedPush est disponible." - "Aucun distributeur UnifiedPush n’a été trouvé." - - "%1$d distributeur détecté :%2$s." - "%1$d distributeurs détectés :%2$s." - - "Vérifier UnifiedPush" diff --git a/libraries/ui-strings/src/main/res/values-hu/translations.xml b/libraries/ui-strings/src/main/res/values-hu/translations.xml index 9c343ec200..43568cafb6 100644 --- a/libraries/ui-strings/src/main/res/values-hu/translations.xml +++ b/libraries/ui-strings/src/main/res/values-hu/translations.xml @@ -271,55 +271,4 @@ "Hely" "Verzió: %1$s (%2$s)" "hu" - "Hibaelhárítás" - "Értesítések hibaelhárítása" - "Tesztek futtatása" - "Tesztek újbóli futtatása" - "Egyes tesztek sikertelenek voltak. Ellenőrizze a részleteket." - "A tesztek futtatása, hogy észlelje a konfigurációban felmerülő olyan problémákat, amelyek miatt az értesítések nem az elvárt módon viselkednek." - "Kísérlet a javításra" - "Minden teszt sikeresen lezajlott." - "Értesítések hibaelhárítása" - "Egyes tesztek a figyelmét igénylik. Ellenőrizze a részleteket." - "Ellenőrizze, hogy az alkalmazás képes-e értesítéseket megjeleníteni." - "Engedélyek ellenőrzése" - "A jelenlegi szolgáltató nevének lekérdezése." - "Nincs kiválasztva leküldéses értesítési szolgáltató." - "Jelenlegi leküldéses értesítési szolgáltató: %1$s." - "Jelenlegi leküldéses értesítési szolgáltató" - "Győződjön meg arról, hogy az alkalmazás legalább egy leküldéses értesítési szolgáltatóval rendelkezik." - "Nem található leküldéses értesítési szolgáltató." - - "%1$d leküldéses szolgáltató találva: %2$s" - "%1$d leküldéses szolgáltató találva: %2$s" - - "Leküldéses értesítési szolgáltatók észlelése" - "Ellenőrizze, hogy az alkalmazás képes-e megjeleníteni az értesítést." - "Az értesítésre nem kattintottak rá." - "Az értesítés nem jeleníthető meg." - "Az értesítésre rákattintottak!" - "Értesítés megjelenítése" - "A teszt folytatásához kattintson az értesítésre." - "Győződjön meg arról, hogy a Firebase elérhető-e." - "A Firebase nem érhető el." - "A Firebase elérhető." - "Ellenőrizze a Firebase-t" - "Győződjön meg arról, hogy a Firebase-token elérhető." - "A Firebase-token nem ismert." - "Firebase-token: %1$s." - "Ellenőrizze a Firebase-tokent" - "Győződjön meg arról, hogy az alkalmazás megkapja-e a leküldéses értesítést." - "Hiba: a leküldő elutasította a kérést." - "Hiba: %1$s." - "Hiba, nem lehet tesztelni a leküldéses értesítést." - "Hiba, időtúllépés a leküldéses értesítésre való várakozás során." - "A leküldéses értesítés folyamata %1$d ezredmásodpercig tartott." - "Tesztelje a leküldéses értesítés folyamatát" - "Győződjön meg arról, hogy a UnifiedPush forgalmazói elérhetők." - "Nem található forgalmazó a leküldéses értesítésekhez." - - "%1$d forgalmazó található: %2$s." - "%1$d forgalmazó található: %2$s." - - "Ellenőrizze a UnifiedPush szolgáltatást" diff --git a/libraries/ui-strings/src/main/res/values-in/translations.xml b/libraries/ui-strings/src/main/res/values-in/translations.xml index a5a98b5d35..fc237cac27 100644 --- a/libraries/ui-strings/src/main/res/values-in/translations.xml +++ b/libraries/ui-strings/src/main/res/values-in/translations.xml @@ -267,53 +267,4 @@ "Lokasi" "Versi: %1$s (%2$s)" "id" - "Pemecahan masalah" - "Pecahkan masalah notifikasi" - "Jalankan tes" - "Jalankan tes lagi" - "Beberapa tes gagal. Silakan periksa detailnya." - "Jalankan pengujian untuk mendeteksi masalah apa pun dalam konfigurasi Anda yang mungkin membuat notifikasi tidak berperilaku seperti yang diharapkan." - "Mencoba untuk memperbaiki" - "Semua tes berhasil dilalui." - "Pecahkan masalah notifikasi" - "Beberapa tes membutuhkan perhatian Anda. Silakan periksa detailnya." - "Pastikan aplikasi dapat menampilkan notifikasi." - "Periksa izin" - "Dapatkan nama penyedia saat ini." - "Tidak ada penyedia notifikasi dorongan yang dipilih." - "Penyedia notifikasi dorongan saat ini: %1$s." - "Penyedia notifikasi dorongan saat ini" - "Pastikan aplikasi memiliki setidaknya satu penyedia notifikasi dorongan." - "Tidak ada penyedia notifikasi dorongan yang ditemukan." - - "Ditemukan %1$d penyedia notifikasi dorongan: %2$s" - - "Deteksi penyedia notifikasi dorongan" - "Periksa apakah aplikasi dapat menampilkan notifikasi." - "Notifikasi belum diklik." - "Tidak dapat menampilkan notifikasi." - "Notifikasi telah diklik!" - "Tampilan notifikasi" - "Silakan klik pada notifikasi untuk melanjutkan tes." - "Pastikan bahwa Firebase tersedia." - "Firebase tidak tersedia." - "Firebase tersedia." - "Periksa Firebase" - "Pastikan token Firebase tersedia." - "Token Firebase tidak diketahui." - "Token Firebase: %1$s." - "Periksa token Firebase" - "Pastikan aplikasi menerima notifikasi dorongan." - "Kesalahan: pendorong telah menolak permintaan." - "Kesalahan: %1$s." - "Terjadi kesalahan, tidak dapat menguji notifikasi dorongan." - "Terjadi kesalahan, melebihi batas waktu menunggu notifikasi dorongan." - "Ulangan notifikasi dorongan membutuhkan %1$d ms." - "Uji ulangan notifikasi dorongan lagi" - "Pastikan distributor UnifiedPush tersedia." - "Tidak ada distributor notifikasi dorongan yang ditemukan." - - "%1$d distributor ditemukan: %2$s." - - "Periksa UnifiedPush" diff --git a/libraries/ui-strings/src/main/res/values-ru/translations.xml b/libraries/ui-strings/src/main/res/values-ru/translations.xml index ca4731f34f..e7a564596d 100644 --- a/libraries/ui-strings/src/main/res/values-ru/translations.xml +++ b/libraries/ui-strings/src/main/res/values-ru/translations.xml @@ -275,57 +275,4 @@ "Местоположение" "Версия: %1$s (%2$s)" "ru" - "Устранение неполадок" - "Уведомления об устранении неполадок" - "Выполнение тестов" - "Повторное выполнение тестов" - "Некоторые тесты провалились. Пожалуйста, проверьте детали." - "Выполните тесты, чтобы обнаружить любую проблему в конфигурации, из-за которой уведомления могут работать не так, как ожидалось." - "Попытка исправить" - "Все тесты прошли успешно." - "Уведомления об устранении неполадок" - "Некоторые тесты требуют вашего внимания. Пожалуйста, проверьте детали." - "Убедитесь, что приложение может показывать уведомления." - "Проверка разрешений" - "Получение имени текущего поставщика." - "Поставщики push-уведомлений не выбраны." - "Текущий поставщик push-уведомлений: %1$s." - "Текущий поставщик push-уведомлений" - "Убедитесь, что у приложения есть хотя бы один поставщик push-сообщений." - "Поставщики push-уведомлений не найдены." - - "Найден %1$d push-провайдер: %2$s" - "Найдено %1$d push-провайдеров: %2$s" - "Найдено %1$d push-провайдеров: %2$s" - - "Обнаружение поставщиков push-уведомлений" - "Убедитесь, что приложение может отображать уведомление." - "Уведомление не было нажато." - "Невозможно отобразить уведомление." - "Уведомление было нажато!" - "Отобразить уведомление" - "Нажмите на уведомление, чтобы продолжить тест." - "Убедитесь, что Firebase доступен." - "Firebase недоступен." - "Firebase доступен." - "Проверить Firebase" - "Убедитесь, что токен Firebase доступен." - "Токен Firebase неизвестен." - "Токен Firebase: %1$s." - "Проверить токен Firebase" - "Убедитесь, что приложение получает push-сообщение." - "Ошибка: pusher отклонил запрос." - "Ошибка: %1$s." - "Ошибка, невозможно протестировать отправку." - "Ошибка, тайм-аут ожидания push-уведомления." - "Обратная отправка push-уведомления, заняла %1$d мс." - "Тест обратной отправки push-уведомления" - "Убедитесь, что дистрибьюторы UnifiedPush доступны." - "Поставщиков push-уведомлений не найдено." - - "%1$d провайдер найден: %2$s." - "%1$d провайдеров найдено: %2$s." - "%1$d провайдеров найдено: %2$s." - - "Проверка UnifiedPush" diff --git a/libraries/ui-strings/src/main/res/values-sk/translations.xml b/libraries/ui-strings/src/main/res/values-sk/translations.xml index 61e7b50cc0..f49201bda1 100644 --- a/libraries/ui-strings/src/main/res/values-sk/translations.xml +++ b/libraries/ui-strings/src/main/res/values-sk/translations.xml @@ -274,57 +274,4 @@ "Poloha" "Verzia: %1$s (%2$s)" "sk" - "Riešenie problémov" - "Oznámenia riešení problémov" - "Spustiť testy" - "Spustiť testy znova" - "Niektoré testy zlyhali. Skontrolujte prosím podrobnosti." - "Spustite testy, aby ste zistili akýkoľvek problém vo vašej konfigurácii, ktorý môže spôsobiť, že sa upozornenia nebudú správať podľa očakávania." - "Pokus o opravu" - "Všetky testy prebehli úspešne." - "Oznámenia riešení problémov" - "Niektoré testy si vyžadujú vašu pozornosť. Prosím skontrolujte podrobnosti." - "Uistite sa, že aplikácia dokáže zobrazovať upozornenia." - "Skontrolovať povolenia" - "Získaťe názov aktuálneho poskytovateľa." - "Nie sú vybraní žiadni poskytovatelia push." - "Aktuálny poskytovateľ push: %1$s." - "Aktuálny poskytovateľ push" - "Uistite sa, že aplikácia má aspoň jedného poskytovateľa push." - "Nenašli sa žiadni poskytovatelia push." - - "Nájdený %1$d poskytovateľ služby push: %2$s" - "Nájdení %1$d poskytovatelia služby push: %2$s" - "Nájdených %1$d poskytovateľov služby push: %2$s" - - "Zistiť poskytovateľov push" - "Skontrolujte, či aplikácia dokáže zobraziť upozornenie." - "Na oznámenie nebolo kliknuté." - "Nie je možné zobraziť upozornenie." - "Na oznámenie bolo kliknuté!" - "Zobraziť upozornenie" - "Kliknite na upozornenie a pokračujte v teste." - "Uistite sa, že Firebase je k dispozícii." - "Firebase nie je k dispozícii." - "Firebase je k dispozícii." - "Skontrolovať Firebase" - "Uistite sa, že je k dispozícii token Firebase." - "Token Firebase nie je známy." - "Token Firebase: %1$s." - "Skontrolovať token Firebase" - "Uistite sa, že aplikácia prijíma push oznámenia." - "Chyba: pusher odmietol požiadavku." - "Chyba: %1$s." - "Chyba, nie je možné testovať push." - "Chyba, časový limit na push vypršal." - "Push loop back trvalo %1$d ms." - "Testovať Push loop back" - "Uistite sa, že sú dostupní distribútori UnifiedPush." - "Nenašli sa žiadni distribútori push." - - "%1$d nájdený distribútor: %2$s." - "%1$d nájdení distribútori: %2$s." - "%1$d nájdených distribútorov: %2$s." - - "Skontrolovať UnifiedPush" diff --git a/libraries/ui-strings/src/main/res/values-sv/translations.xml b/libraries/ui-strings/src/main/res/values-sv/translations.xml index 7ed1b0daa8..b7029fa178 100644 --- a/libraries/ui-strings/src/main/res/values-sv/translations.xml +++ b/libraries/ui-strings/src/main/res/values-sv/translations.xml @@ -182,6 +182,7 @@ "%1$s kunde inte komma åt din plats. Vänligen försök igen senare." "%1$s är inte behörig att komma åt din plats. Du kan aktivera åtkomst i Inställningar." "%1$s är inte behörig att komma åt din plats. Aktivera åtkomst nedan." + "%1$s är inte behörig att komma åt din mikrofon. Aktivera åtkomst för att spela in ett röstmeddelande." "Vissa meddelanden har inte skickats" "Tyvärr, ett fel uppstod" "🔐️ Häng med mig på %1$s" diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index 1bc20364ce..2f9812fc79 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -272,55 +272,4 @@ "Version: %1$s (%2$s)" "en" "en" - "Troubleshoot" - "Troubleshoot notifications" - "Run tests" - "Run tests again" - "Some tests failed. Please check the details." - "Run the tests to detect any issue in your configuration that may make notifications not behave as expected." - "Attempt to fix" - "All tests passed successfully." - "Troubleshoot notifications" - "Some tests require your attention. Please check the details." - "Check that the application can show notifications." - "Check permissions" - "Get the name of the current provider." - "No push providers selected." - "Current push provider: %1$s." - "Current push provider" - "Ensure that the application has at least one push provider." - "No push providers found." - - "Found %1$d push provider: %2$s" - "Found %1$d push providers: %2$s" - - "Detect push providers" - "Check that the application can display notification." - "The notification has not been clicked." - "Cannot display the notification." - "The notification has been clicked!" - "Display notification" - "Please click on the notification to continue the test." - "Ensure that Firebase is available." - "Firebase is not available." - "Firebase is available." - "Check Firebase" - "Ensure that Firebase token is available." - "Firebase token is not known." - "Firebase token: %1$s." - "Check Firebase token" - "Ensure that the application is receiving push." - "Error: pusher has rejected the request." - "Error: %1$s." - "Error, cannot test push." - "Error, timeout waiting for push." - "Push loop back took %1$d ms." - "Test Push loop back" - "Ensure that UnifiedPush distributors are available." - "No push distributors found." - - "%1$d distributor found: %2$s." - "%1$d distributors found: %2$s." - - "Check UnifiedPush" diff --git a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt index cc254511bd..395f690d0e 100644 --- a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt +++ b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt @@ -114,6 +114,7 @@ fun DependencyHandlerScope.allLibrariesImpl() { implementation(project(":libraries:voicerecorder:impl")) implementation(project(":libraries:mediaplayer:impl")) implementation(project(":libraries:mediaviewer:impl")) + implementation(project(":libraries:troubleshoot:impl")) } fun DependencyHandlerScope.allServicesImpl() { diff --git a/services/analytics/api/build.gradle.kts b/services/analytics/api/build.gradle.kts index 28b871a659..c78a050571 100644 --- a/services/analytics/api/build.gradle.kts +++ b/services/analytics/api/build.gradle.kts @@ -14,7 +14,7 @@ * limitations under the License. */ plugins { - id("io.element.android-library") + id("io.element.android-compose-library") } android { @@ -23,6 +23,7 @@ android { dependencies { api(projects.services.analyticsproviders.api) + api(projects.services.toolbox.api) implementation(libs.coroutines.core) implementation(projects.libraries.matrix.api) implementation(projects.libraries.core) diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/ScreenTracker.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/ScreenTracker.kt new file mode 100644 index 0000000000..c29a045f77 --- /dev/null +++ b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/ScreenTracker.kt @@ -0,0 +1,27 @@ +/* + * 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.services.analytics.api + +import androidx.compose.runtime.Composable +import im.vector.app.features.analytics.plan.MobileScreen + +interface ScreenTracker { + @Composable + fun TrackScreen( + screen: MobileScreen.ScreenName, + ) +} diff --git a/services/analytics/impl/build.gradle.kts b/services/analytics/impl/build.gradle.kts index 5dd72d77bd..623ab493c3 100644 --- a/services/analytics/impl/build.gradle.kts +++ b/services/analytics/impl/build.gradle.kts @@ -35,6 +35,7 @@ dependencies { implementation(projects.libraries.androidutils) implementation(projects.libraries.core) implementation(projects.libraries.architecture) + implementation(projects.libraries.designsystem) implementation(projects.libraries.sessionStorage.api) api(projects.services.analyticsproviders.api) diff --git a/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/DefaultScreenTracker.kt b/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/DefaultScreenTracker.kt new file mode 100644 index 0000000000..c53757384d --- /dev/null +++ b/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/DefaultScreenTracker.kt @@ -0,0 +1,59 @@ +/* + * 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.services.analytics.impl + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableLongStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.lifecycle.Lifecycle +import com.squareup.anvil.annotations.ContributesBinding +import im.vector.app.features.analytics.plan.MobileScreen +import io.element.android.libraries.designsystem.utils.OnLifecycleEvent +import io.element.android.libraries.di.AppScope +import io.element.android.services.analytics.api.AnalyticsService +import io.element.android.services.analytics.api.ScreenTracker +import io.element.android.services.toolbox.api.systemclock.SystemClock +import javax.inject.Inject + +@ContributesBinding(AppScope::class) +class DefaultScreenTracker @Inject constructor( + private val analyticsService: AnalyticsService, + private val systemClock: SystemClock +) : ScreenTracker { + @Composable + override fun TrackScreen( + screen: MobileScreen.ScreenName, + ) { + var startTime by remember { mutableLongStateOf(0L) } + OnLifecycleEvent { _, event -> + when (event) { + Lifecycle.Event.ON_RESUME -> { + startTime = systemClock.epochMillis() + } + Lifecycle.Event.ON_PAUSE -> analyticsService.screen( + screen = MobileScreen( + durationMs = (systemClock.epochMillis() - startTime).toInt(), + screenName = screen + ) + ) + else -> Unit + } + } + } +} diff --git a/services/toolbox/test/src/main/kotlin/io/element/android/services/toolbox/test/strings/FakeStringProvider.kt b/services/toolbox/test/src/main/kotlin/io/element/android/services/toolbox/test/strings/FakeStringProvider.kt index c9f9a84fc9..870b3acf39 100644 --- a/services/toolbox/test/src/main/kotlin/io/element/android/services/toolbox/test/strings/FakeStringProvider.kt +++ b/services/toolbox/test/src/main/kotlin/io/element/android/services/toolbox/test/strings/FakeStringProvider.kt @@ -26,10 +26,10 @@ class FakeStringProvider( } override fun getString(resId: Int, vararg formatArgs: Any?): String { - return defaultResult + return defaultResult + formatArgs.joinToString() } override fun getQuantityString(resId: Int, quantity: Int, vararg formatArgs: Any?): String { - return defaultResult + return defaultResult + " ($quantity) " + formatArgs.joinToString() } } diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_DefaultNotificationSettingOption_null_DefaultNotificationSettingOption-Day-8_9_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_DefaultNotificationSettingOption_null_DefaultNotificationSettingOption-Day-7_8_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_DefaultNotificationSettingOption_null_DefaultNotificationSettingOption-Day-8_9_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_DefaultNotificationSettingOption_null_DefaultNotificationSettingOption-Day-7_8_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_DefaultNotificationSettingOption_null_DefaultNotificationSettingOption-Night-8_10_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_DefaultNotificationSettingOption_null_DefaultNotificationSettingOption-Night-7_9_null,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_DefaultNotificationSettingOption_null_DefaultNotificationSettingOption-Night-8_10_null,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_DefaultNotificationSettingOption_null_DefaultNotificationSettingOption-Night-7_9_null,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_3,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_3,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_3,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_4,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-9_10_null_4,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Day-8_9_null_4,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-9_11_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-9_11_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-9_11_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-9_11_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-9_11_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-9_11_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-9_11_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_3,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-9_11_null_3,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_3,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-9_11_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_4,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-9_11_null_4,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications.edit_EditDefaultNotificationSettingView_null_EditDefaultNotificationSettingView-Night-8_10_null_4,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_InvalidNotificationSettingsView_null_InvalidNotificationSettingsView-Day-7_8_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_InvalidNotificationSettingsView_null_InvalidNotificationSettingsView-Day-7_8_null,NEXUS_5,1.0,en].png deleted file mode 100644 index e39ff5329b..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_InvalidNotificationSettingsView_null_InvalidNotificationSettingsView-Day-7_8_null,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f99b8193089b4719c75acdee6339cc89307fb16255cf6e7deaf4486b03c42505 -size 42728 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_InvalidNotificationSettingsView_null_InvalidNotificationSettingsView-Night-7_9_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_InvalidNotificationSettingsView_null_InvalidNotificationSettingsView-Night-7_9_null,NEXUS_5,1.0,en].png deleted file mode 100644 index 3fb79982e8..0000000000 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_InvalidNotificationSettingsView_null_InvalidNotificationSettingsView-Night-7_9_null,NEXUS_5,1.0,en].png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ecc207ca338b66754c3d0e738f584c0712c8d46958e07bfd1ccd50b19df82006 -size 41413 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_0,NEXUS_5,1.0,en].png index e1135c1e76..8399a4c808 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cae06a603dd5947aa035e0be90a7a649abdce67f5cdcb921c2fa259ead90ac0d -size 60521 +oid sha256:0a566ec9dea61ea21c42cdbdb011f7b74a530d591642c5616038a0cdd6c20a74 +size 63452 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_1,NEXUS_5,1.0,en].png index cd66a12792..3abcdd8dfd 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e860dc70a35f5fdecc69eec550e781f6974e66a5a12196572ea169b3422136be -size 54821 +oid sha256:c36ca0694f603d7a3f0ca1965f1b0fe2bb6738aece2a908317a8043b2cb82bc4 +size 57380 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_2,NEXUS_5,1.0,en].png index 742e797349..844ed57393 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4f84e508a1a5ec9a402d4adbe5cbe963c0a06c776f7627a9c8eac29b6209c84a -size 54772 +oid sha256:dfd063ea23382a25ec6576b74674d6c2f30a7fbeb9e99d4c4a753a7909ece947 +size 57353 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..1da5935157 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b5ae68e5b9dcf951dbc7a77e6606daaa86eceee6b8b54c4fc5072774f149dae2 +size 47346 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_4,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..f9e918b587 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Day-6_7_null_4,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:585f8dcbeef19fbba8e3cddcba7c8ec2831a704ba3fbb3cc05369d42444d919c +size 47975 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_0,NEXUS_5,1.0,en].png index a76c60413f..af460dc892 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:22b0073e9e51a790231ab9feca750869e15b4c03e659f2b183dd6d5745be8918 -size 55839 +oid sha256:b9da4314613e463f2da12ac9e7d4c52446ca64133c13980b87a5c447873dfc5b +size 58568 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_1,NEXUS_5,1.0,en].png index 2484839eab..12405f0ce7 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:02b620889589dcd10057991dad95dd84dfddf9cef135059d1bae45ba4790f5ee -size 50971 +oid sha256:6eed8d6163471878f5281c5fc922537a22216e666366a4d1c6c11535c09aeb11 +size 53402 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_2,NEXUS_5,1.0,en].png index 0a1531e4d9..eba437daf5 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:308f6f6d7da94c68212d263fa30f1612387d2e1dbf355314fb128e757f481209 -size 50382 +oid sha256:860cc7bbab5f72a951f7b83fad121040bd1fa1852bd44715a390b8e5db52eaeb +size 52801 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..4ecedce7bd --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4eacf4ac22f6c49b96f0891bf53fcc96bd333a6b42e66c7ac12aa4be03232667 +size 45141 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_4,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..ac4c51e310 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.notifications_NotificationSettingsView_null_NotificationSettingsView-Night-6_8_null_4,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e6f6367b13643ae480f3d555ea3506bc4caeddeb51c0cd20de6c906b9886e5a5 +size 45015 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user.editprofile_EditUserProfileView_null_EditUserProfileView-Day-11_12_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user.editprofile_EditUserProfileView_null_EditUserProfileView-Day-10_11_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user.editprofile_EditUserProfileView_null_EditUserProfileView-Day-11_12_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user.editprofile_EditUserProfileView_null_EditUserProfileView-Day-10_11_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user.editprofile_EditUserProfileView_null_EditUserProfileView-Night-11_13_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user.editprofile_EditUserProfileView_null_EditUserProfileView-Night-10_12_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user.editprofile_EditUserProfileView_null_EditUserProfileView-Night-11_13_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user.editprofile_EditUserProfileView_null_EditUserProfileView-Night-10_12_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Day-10_11_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Day-9_10_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Day-10_11_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Day-9_10_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Day-10_11_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Day-9_10_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Day-10_11_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Day-9_10_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Day-10_11_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Day-9_10_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Day-10_11_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Day-9_10_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Night-10_12_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Night-9_11_null_0,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Night-10_12_null_0,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Night-9_11_null_0,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Night-10_12_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Night-9_11_null_1,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Night-10_12_null_1,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Night-9_11_null_1,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Night-10_12_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Night-9_11_null_2,NEXUS_5,1.0,en].png similarity index 100% rename from tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Night-10_12_null_2,NEXUS_5,1.0,en].png rename to tests/uitests/src/test/snapshots/images/ui_S_t[f.preferences.impl.user_UserPreferences_null_UserPreferences-Night-9_11_null_2,NEXUS_5,1.0,en].png diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..b232885ed5 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a7a8e9790e7d4b92e54a4161340c77f691685c28e9d40f272fb2c98808fec154 +size 32662 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..17ea7ac2f6 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5abc8664b6ef86b8f3357207635a3c038d78e3ff907ca56526242186c196cc22 +size 21152 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..4b53087f1c --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:504c858c17ef7229ba2bd52f3be7a6769ea2031a97fc7a39461a523cba120e9e +size 21512 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..9ee728e547 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:07ad31cb2c341ecddf9963ff93bdd9818f6209c9331c89cac9122293f76097cd +size 32472 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_4,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..26cbd8a27f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_4,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:05a287eaae96eb6c1d2aabf624d10a1ddf97f50cd9c680a65b0496123fe53b46 +size 26294 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_5,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..811a6f1f65 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_5,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d2a11615004b3b883be7093086a42efda0efabcb03cd580ec813fc827be02937 +size 38878 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_6,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..03e98787ba --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_6,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:853b90c82ad1dbbd9c1fa75feaa2115445fbbd6f674fa41f486826bd1ef89b1c +size 27022 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_7,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..a0f85489dd --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Day-0_1_null_7,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bb5f63b160d04544705cf778774d046dfc2915f49fe4adfaa473111c95c86be8 +size 25731 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_0,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..b2eb2fc50d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_0,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:01d2e5fdc0a9f1c5d534c965b0b6f30162dcb14d06e89af9eacc4c0084a13cdc +size 29052 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..99e171d097 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:99bb674c5cfdbf86789426fdfb676f84d244b0452ba557915d803d78e4959224 +size 20188 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..8c6c088e36 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ac08cfe788fa4bd19f405862050a49da399c51b6921c977937a022a3f180f7b +size 20559 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..f3877ac388 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a31489b3764a8eafe386fbcaf0b806d2acfeb7c92816230cba77e1f84704d645 +size 29818 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_4,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..0e6149ea48 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_4,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af94bf79e1602817e800adb9a382681a9bf63b4435b9d5c7437a9a6a22d8c373 +size 24581 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_5,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..73e0749733 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_5,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ca71cd36ee1679c55eac28fc271f02a5dbbfc33d08202137d93a93e7285ac45 +size 35533 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_6,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..840c0da5d1 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_6,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d7937d6b674014e42d2b7c95104d51da0644f3557d91ea04e4ec9b626a2f4a0 +size 25044 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_7,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_7,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..b878f43791 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.troubleshoot.impl_TroubleshootNotificationsView_null_TroubleshootNotificationsView-Night-0_2_null_7,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:75cec48fca9ca9d551b18fbfabd318c72c33eec16743984ae2be3409f22a8d47 +size 23902 diff --git a/tools/localazy/config.json b/tools/localazy/config.json index aaa1f6734b..d63ede5638 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -80,7 +80,29 @@ "name" : ":libraries:push:impl", "includeRegex" : [ "push_.*", - "notification_.*" + "notification_.*", + "troubleshoot_notifications_test_current_push_provider.*", + "troubleshoot_notifications_test_detect_push_provider.*", + "troubleshoot_notifications_test_display_notification_.*", + "troubleshoot_notifications_test_push_loop_back_.*" + ] + }, + { + "name" : ":libraries:permissions:impl", + "includeRegex" : [ + "troubleshoot_notifications_test_check_permission_.*" + ] + }, + { + "name" : ":libraries:pushproviders:firebase", + "includeRegex" : [ + "troubleshoot_notifications_test_firebase_.*" + ] + }, + { + "name" : ":libraries:pushproviders:unifiedpush", + "includeRegex" : [ + "troubleshoot_notifications_test_unified_push_.*" ] }, { @@ -182,7 +204,14 @@ "screen\\.advanced_settings\\..*", "screen_edit_profile_.*", "screen_notification_settings_.*", - "screen_blocked_users_.*" + "screen_blocked_users_.*", + "troubleshoot_notifications_entry_point_.*" + ] + }, + { + "name" : ":libraries:troubleshoot:impl", + "includeRegex" : [ + "troubleshoot_notifications_screen_.*" ] }, {