diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt index 2a7403862ec..7ef73986ac8 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt @@ -21,6 +21,7 @@ import im.vector.app.features.analytics.plan.CryptoSessionStateChange import im.vector.app.features.analytics.plan.UserProperties import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.core.log.logger.LoggerTag import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.matrix.api.MatrixClient @@ -125,7 +126,7 @@ class LoggedInPresenter @Inject constructor( } // Force the user to log out if they were using the proxy sliding sync as it's no longer supported by the SDK - private suspend fun MatrixClient.needsForcedNativeSlidingSyncMigration(): Result = runCatching { + private suspend fun MatrixClient.needsForcedNativeSlidingSyncMigration(): Result = runCatchingExceptions { val currentSlidingSyncVersion = currentSlidingSyncVersion().getOrThrow() currentSlidingSyncVersion == SlidingSyncVersion.Proxy } diff --git a/build.gradle.kts b/build.gradle.kts index 56fcb0ec7b9..317a6ab5bd0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -50,6 +50,7 @@ allprojects { } dependencies { detektPlugins("io.nlopez.compose.rules:detekt:0.4.22") + detektPlugins(project(":tests:detekt-rules")) } tasks.withType().configureEach { diff --git a/features/cachecleaner/impl/src/main/kotlin/io/element/android/features/cachecleaner/impl/DefaultCacheCleaner.kt b/features/cachecleaner/impl/src/main/kotlin/io/element/android/features/cachecleaner/impl/DefaultCacheCleaner.kt index 16a8a151deb..7951c3e78d2 100644 --- a/features/cachecleaner/impl/src/main/kotlin/io/element/android/features/cachecleaner/impl/DefaultCacheCleaner.kt +++ b/features/cachecleaner/impl/src/main/kotlin/io/element/android/features/cachecleaner/impl/DefaultCacheCleaner.kt @@ -10,6 +10,7 @@ package io.element.android.features.cachecleaner.impl import com.squareup.anvil.annotations.ContributesBinding import io.element.android.features.cachecleaner.api.CacheCleaner import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.CacheDirectory import kotlinx.coroutines.CoroutineScope @@ -33,7 +34,7 @@ class DefaultCacheCleaner @Inject constructor( override fun clearCache() { scope.launch(dispatchers.io) { - runCatching { + runCatchingExceptions { SUBDIRS_TO_CLEANUP.forEach { File(cacheDir.path, it).apply { if (exists()) { diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/services/CallForegroundService.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/services/CallForegroundService.kt index 42c2e19b561..b779465a342 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/services/CallForegroundService.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/services/CallForegroundService.kt @@ -24,6 +24,7 @@ import androidx.core.content.ContextCompat import androidx.core.graphics.drawable.IconCompat import io.element.android.features.call.impl.R import io.element.android.features.call.impl.ui.ElementCallActivity +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.designsystem.utils.CommonDrawables import io.element.android.libraries.push.api.notifications.ForegroundServiceType import io.element.android.libraries.push.api.notifications.NotificationIdProvider @@ -78,7 +79,7 @@ class CallForegroundService : Service() { } else { 0 } - runCatching { + runCatchingExceptions { ServiceCompat.startForeground(this, notificationId, notification, serviceType) }.onFailure { Timber.e(it, "Failed to start ongoing call foreground service") diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt index 8804a91f79f..57a02de56aa 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt @@ -21,6 +21,7 @@ import io.element.android.features.call.api.CallType import io.element.android.features.call.api.CurrentCall import io.element.android.features.call.impl.notifications.CallNotificationData import io.element.android.features.call.impl.notifications.RingingCallNotificationCreator +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.di.SingleIn @@ -218,7 +219,7 @@ class DefaultActiveCallManager @Inject constructor( timestamp = notificationData.timestamp, textContent = notificationData.textContent, ) ?: return - runCatching { + runCatchingExceptions { notificationManagerCompat.notify( NotificationIdProvider.getForegroundServiceNotificationId(ForegroundServiceType.INCOMING_CALL), notification, diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/DefaultCallWidgetProvider.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/DefaultCallWidgetProvider.kt index dd4de7abb53..d87826fe460 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/DefaultCallWidgetProvider.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/DefaultCallWidgetProvider.kt @@ -8,6 +8,7 @@ package io.element.android.features.call.impl.utils import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.di.AppScope import io.element.android.libraries.matrix.api.MatrixClientProvider import io.element.android.libraries.matrix.api.core.RoomId @@ -33,7 +34,7 @@ class DefaultCallWidgetProvider @Inject constructor( clientId: String, languageTag: String?, theme: String?, - ): Result = runCatching { + ): Result = runCatchingExceptions { val matrixClient = matrixClientsProvider.getOrRestore(sessionId).getOrThrow() val room = activeRoomsHolder.getActiveRoomMatching(sessionId, roomId) ?: matrixClient.getJoinedRoom(roomId) diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/WidgetMessageSerializer.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/WidgetMessageSerializer.kt index a78965a87f8..a21cadab7a8 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/WidgetMessageSerializer.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/WidgetMessageSerializer.kt @@ -8,13 +8,14 @@ package io.element.android.features.call.impl.utils import io.element.android.features.call.impl.data.WidgetMessage +import io.element.android.libraries.core.extensions.runCatchingExceptions import kotlinx.serialization.json.Json object WidgetMessageSerializer { private val coder = Json { ignoreUnknownKeys = true } fun deserialize(message: String): Result { - return runCatching { coder.decodeFromString(WidgetMessage.serializer(), message) } + return runCatchingExceptions { coder.decodeFromString(WidgetMessage.serializer(), message) } } fun serialize(message: WidgetMessage): String { diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootStateProvider.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootStateProvider.kt index 38a3af46ed0..9f2d59e3b6e 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootStateProvider.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootStateProvider.kt @@ -35,7 +35,7 @@ open class CreateRoomRootStateProvider : PreviewParameterProvider>(AsyncAction.Uninitialized) action.execute(aMatrixUser(), true, state) - assertThat(state.value).isEqualTo(AsyncAction.Failure(A_THROWABLE)) + assertThat(state.value).isEqualTo(AsyncAction.Failure(AN_EXCEPTION)) assertThat(analyticsService.capturedEvents).isEmpty() } diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureBaseRoomPresenterTest.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureBaseRoomPresenterTest.kt index d8a7085f0d2..a0a4783093b 100644 --- a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureBaseRoomPresenterTest.kt +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureBaseRoomPresenterTest.kt @@ -22,10 +22,10 @@ import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias import io.element.android.libraries.matrix.api.room.alias.RoomAliasHelper import io.element.android.libraries.matrix.test.AN_AVATAR_URL +import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.A_MESSAGE import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_ROOM_NAME -import io.element.android.libraries.matrix.test.A_THROWABLE import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.room.alias.FakeRoomAliasHelper import io.element.android.libraries.matrix.ui.components.aMatrixUser @@ -274,7 +274,7 @@ class ConfigureBaseRoomPresenterTest { createRoomDataStore.setAvatarUri(Uri.parse(AN_URI_FROM_GALLERY)) skipItems(1) mediaPreProcessor.givenResult(Result.success(MediaUploadInfo.Image(mockk(), mockk(), mockk()))) - matrixClient.givenUploadMediaResult(Result.failure(A_THROWABLE)) + matrixClient.givenUploadMediaResult(Result.failure(AN_EXCEPTION)) initialState.eventSink(ConfigureRoomEvents.CreateRoom) assertThat(awaitItem().createRoomAction).isInstanceOf(AsyncAction.Loading::class.java) @@ -298,7 +298,7 @@ class ConfigureBaseRoomPresenterTest { ) presenter.test { val initialState = initialState() - val createRoomResult = Result.failure(A_THROWABLE) + val createRoomResult = Result.failure(AN_EXCEPTION) fakeMatrixClient.givenCreateRoomResult(createRoomResult) diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateBaseRoomRootPresenterTest.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateBaseRoomRootPresenterTest.kt index 54cd1d201c7..d2d9959e2a6 100644 --- a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateBaseRoomRootPresenterTest.kt +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateBaseRoomRootPresenterTest.kt @@ -24,8 +24,8 @@ import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.user.MatrixUser +import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.A_ROOM_ID -import io.element.android.libraries.matrix.test.A_THROWABLE import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.libraries.usersearch.test.FakeUserRepository import io.element.android.tests.testutils.WarmUpRule @@ -42,7 +42,7 @@ class CreateBaseRoomRootPresenterTest { @Test fun `present - start DM action failure scenario`() = runTest { - val startDMFailureResult = AsyncAction.Failure(A_THROWABLE) + val startDMFailureResult = AsyncAction.Failure(AN_EXCEPTION) val executeResult = lambdaRecorder>, Unit> { _, _, actionState -> actionState.value = startDMFailureResult } diff --git a/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/acceptdecline/AcceptDeclineInviteStateProvider.kt b/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/acceptdecline/AcceptDeclineInviteStateProvider.kt index b0450975aff..4c33f9e48fe 100644 --- a/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/acceptdecline/AcceptDeclineInviteStateProvider.kt +++ b/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/acceptdecline/AcceptDeclineInviteStateProvider.kt @@ -29,10 +29,10 @@ open class AcceptDeclineInviteStateProvider : PreviewParameterProvider, JoinedRoom.Trigger, Result> { _, _, _ -> - Result.failure(Throwable("Join room failed")) + Result.failure(RuntimeException("Join room failed")) } val acceptInvite = DefaultAcceptInvite( diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt index b56ed8af4ec..a83450e9bde 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt @@ -89,7 +89,7 @@ open class KnockRequestsListStateProvider : PreviewParameterProvider>) = launch { loggedInState.value = AsyncAction.Loading - runCatching { + runCatchingExceptions { messageParser.parse(message) }.flatMap { externalSession -> authenticationService.importCreatedSession(externalSession) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountStateProvider.kt index 3618bc301c0..e2e7ac32511 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountStateProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountStateProvider.kt @@ -17,7 +17,7 @@ open class CreateAccountStateProvider : PreviewParameterProvider { _ -> anExternalSession() } + val sessionVerificationService = FakeSessionVerificationService() + val client = FakeMatrixClient(sessionVerificationService = sessionVerificationService) + val clientProvider = FakeMatrixClientProvider(getClient = { Result.success(client) }) val presenter = createPresenter( authenticationService = FakeMatrixAuthenticationService( importCreatedSessionLambda = { Result.success(A_SESSION_ID) } ), messageParser = FakeMessageParser(lambda), defaultLoginUserStory = defaultLoginUserStory, + clientProvider = clientProvider, ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -102,6 +109,7 @@ class CreateAccountPresenterTest { val initialState = awaitItem() initialState.eventSink(CreateAccountEvents.OnMessageReceived("aMessage")) assertThat(awaitItem().createAction.isLoading()).isTrue() + sessionVerificationService.emitVerifiedStatus(SessionVerifiedStatus.Verified) assertThat(awaitItem().createAction.dataOrNull()).isEqualTo(A_SESSION_ID) } lambda.assertions().isCalledOnce().with(value("aMessage")) diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenterTest.kt index 8e2fa4888ca..2413e09243c 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenterTest.kt @@ -14,10 +14,10 @@ import io.element.android.features.login.impl.DefaultLoginUserStory import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.core.SessionId +import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.A_HOMESERVER import io.element.android.libraries.matrix.test.A_PASSWORD import io.element.android.libraries.matrix.test.A_SESSION_ID -import io.element.android.libraries.matrix.test.A_THROWABLE import io.element.android.libraries.matrix.test.A_USER_NAME import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService import io.element.android.tests.testutils.WarmUpRule @@ -96,12 +96,12 @@ class LoginPasswordPresenterTest { initialState.eventSink.invoke(LoginPasswordEvents.SetPassword(A_PASSWORD)) skipItems(1) val loginAndPasswordState = awaitItem() - authenticationService.givenLoginError(A_THROWABLE) + authenticationService.givenLoginError(AN_EXCEPTION) loginAndPasswordState.eventSink.invoke(LoginPasswordEvents.Submit) val submitState = awaitItem() assertThat(submitState.loginAction).isInstanceOf(AsyncData.Loading::class.java) val loggedInState = awaitItem() - assertThat(loggedInState.loginAction).isEqualTo(AsyncData.Failure(A_THROWABLE)) + assertThat(loggedInState.loginAction).isEqualTo(AsyncData.Failure(AN_EXCEPTION)) } } @@ -117,13 +117,13 @@ class LoginPasswordPresenterTest { initialState.eventSink.invoke(LoginPasswordEvents.SetPassword(A_PASSWORD)) skipItems(1) val loginAndPasswordState = awaitItem() - authenticationService.givenLoginError(A_THROWABLE) + authenticationService.givenLoginError(AN_EXCEPTION) loginAndPasswordState.eventSink.invoke(LoginPasswordEvents.Submit) val submitState = awaitItem() assertThat(submitState.loginAction).isInstanceOf(AsyncData.Loading::class.java) val loggedInState = awaitItem() // Check an error was returned - assertThat(loggedInState.loginAction).isEqualTo(AsyncData.Failure(A_THROWABLE)) + assertThat(loggedInState.loginAction).isEqualTo(AsyncData.Failure(AN_EXCEPTION)) // Assert the error is then cleared loggedInState.eventSink(LoginPasswordEvents.ClearError) val clearedState = awaitItem() diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenterTest.kt index d57c67df7ee..7749d1502b0 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenterTest.kt @@ -24,9 +24,9 @@ import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_2 import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_3 +import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.A_HOMESERVER_URL import io.element.android.libraries.matrix.test.A_LOGIN_HINT -import io.element.android.libraries.matrix.test.A_THROWABLE import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.libraries.oidc.api.OidcActionFlow @@ -192,7 +192,7 @@ class OnBoardingPresenterTest { skipItems(3) awaitItem().also { assertThat(it.defaultAccountProvider).isEqualTo(A_HOMESERVER_URL) - authenticationService.givenChangeServerError(A_THROWABLE) + authenticationService.givenChangeServerError(AN_EXCEPTION) it.eventSink(OnBoardingEvents.OnSignIn(A_HOMESERVER_URL)) skipItems(1) // Loading diff --git a/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutPresenterTest.kt b/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutPresenterTest.kt index 61a1f1371d8..3a309ab7b93 100644 --- a/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutPresenterTest.kt +++ b/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutPresenterTest.kt @@ -18,7 +18,7 @@ import io.element.android.libraries.matrix.api.encryption.BackupState import io.element.android.libraries.matrix.api.encryption.BackupUploadState import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.api.encryption.RecoveryState -import io.element.android.libraries.matrix.test.A_THROWABLE +import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService import io.element.android.tests.testutils.WarmUpRule @@ -165,7 +165,7 @@ class LogoutPresenterTest { fun `present - logout with error then cancel`() = runTest { val matrixClient = FakeMatrixClient().apply { logoutLambda = { _, _ -> - throw A_THROWABLE + throw AN_EXCEPTION } } val presenter = createLogoutPresenter( @@ -182,7 +182,7 @@ class LogoutPresenterTest { val loadingState = awaitItem() assertThat(loadingState.logoutAction).isInstanceOf(AsyncAction.Loading::class.java) val errorState = awaitItem() - assertThat(errorState.logoutAction).isEqualTo(AsyncAction.Failure(A_THROWABLE)) + assertThat(errorState.logoutAction).isEqualTo(AsyncAction.Failure(AN_EXCEPTION)) errorState.eventSink.invoke(LogoutEvents.CloseDialogs) val finalState = awaitItem() assertThat(finalState.logoutAction).isEqualTo(AsyncAction.Uninitialized) @@ -194,9 +194,7 @@ class LogoutPresenterTest { val matrixClient = FakeMatrixClient().apply { logoutLambda = { ignoreSdkError, _ -> if (!ignoreSdkError) { - throw A_THROWABLE - } else { - null + throw AN_EXCEPTION } } } @@ -214,7 +212,7 @@ class LogoutPresenterTest { val loadingState = awaitItem() assertThat(loadingState.logoutAction).isInstanceOf(AsyncAction.Loading::class.java) val errorState = awaitItem() - assertThat(errorState.logoutAction).isEqualTo(AsyncAction.Failure(A_THROWABLE)) + assertThat(errorState.logoutAction).isEqualTo(AsyncAction.Failure(AN_EXCEPTION)) errorState.eventSink.invoke(LogoutEvents.Logout(ignoreSdkError = true)) val loadingState2 = awaitItem() assertThat(loadingState2.logoutAction).isInstanceOf(AsyncAction.Loading::class.java) diff --git a/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/direct/DirectLogoutPresenterTest.kt b/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/direct/DirectLogoutPresenterTest.kt index 158ce0c0ea7..d5dff82c5ca 100644 --- a/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/direct/DirectLogoutPresenterTest.kt +++ b/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/direct/DirectLogoutPresenterTest.kt @@ -17,7 +17,7 @@ import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.encryption.BackupUploadState import io.element.android.libraries.matrix.api.encryption.EncryptionService -import io.element.android.libraries.matrix.test.A_THROWABLE +import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService import io.element.android.tests.testutils.WarmUpRule @@ -117,7 +117,7 @@ class DirectLogoutPresenterTest { fun `present - logout with error then cancel`() = runTest { val matrixClient = FakeMatrixClient().apply { logoutLambda = { _, _ -> - throw A_THROWABLE + throw AN_EXCEPTION } } val presenter = createDirectLogoutPresenter( @@ -134,7 +134,7 @@ class DirectLogoutPresenterTest { val loadingState = awaitItem() assertThat(loadingState.logoutAction).isInstanceOf(AsyncAction.Loading::class.java) val errorState = awaitItem() - assertThat(errorState.logoutAction).isEqualTo(AsyncAction.Failure(A_THROWABLE)) + assertThat(errorState.logoutAction).isEqualTo(AsyncAction.Failure(AN_EXCEPTION)) errorState.eventSink.invoke(DirectLogoutEvents.CloseDialogs) val finalState = awaitItem() assertThat(finalState.logoutAction).isEqualTo(AsyncAction.Uninitialized) @@ -146,9 +146,7 @@ class DirectLogoutPresenterTest { val matrixClient = FakeMatrixClient().apply { logoutLambda = { ignoreSdkError, _ -> if (!ignoreSdkError) { - throw A_THROWABLE - } else { - null + throw AN_EXCEPTION } } } @@ -166,7 +164,7 @@ class DirectLogoutPresenterTest { val loadingState = awaitItem() assertThat(loadingState.logoutAction).isInstanceOf(AsyncAction.Loading::class.java) val errorState = awaitItem() - assertThat(errorState.logoutAction).isEqualTo(AsyncAction.Failure(A_THROWABLE)) + assertThat(errorState.logoutAction).isEqualTo(AsyncAction.Failure(AN_EXCEPTION)) errorState.eventSink.invoke(DirectLogoutEvents.Logout(ignoreSdkError = true)) val loadingState2 = awaitItem() assertThat(loadingState2.logoutAction).isInstanceOf(AsyncAction.Loading::class.java) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt index 6be56466492..74cb9799f22 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt @@ -56,6 +56,7 @@ import io.element.android.libraries.androidutils.clipboard.ClipboardHelper import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize @@ -387,7 +388,7 @@ class MessagesPresenter @AssistedInject constructor( private fun CoroutineScope.reinviteOtherUser(inviteProgress: MutableState>) = launch(dispatchers.io) { inviteProgress.value = AsyncData.Loading() - runCatching { + runCatchingExceptions { val memberList = when (val memberState = room.membersStateFlow.value) { is RoomMembersState.Ready -> memberState.roomMembers is RoomMembersState.Error -> memberState.prevRoomMembers.orEmpty() diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt index 90bc3d04274..b48ff98073e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt @@ -27,6 +27,7 @@ import io.element.android.libraries.androidutils.file.safeDelete import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.firstInstanceOf +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.di.annotations.SessionCoroutineScope import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlags @@ -240,7 +241,7 @@ class AttachmentsPreviewPresenter @AssistedInject constructor( sendActionState: MutableState, dismissAfterSend: Boolean, replyParameters: ReplyParameters?, - ) = runCatching { + ) = runCatchingExceptions { val context = coroutineContext val progressCallback = object : ProgressCallback { override fun onProgress(current: Long, total: Long) { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesStateProvider.kt index 9172a6c5aca..b1728f46572 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/forward/ForwardMessagesStateProvider.kt @@ -24,7 +24,7 @@ open class ForwardMessagesStateProvider : PreviewParameterProvider Result.success(true) } - val failurePinEventLambda = lambdaRecorder { _: EventId -> Result.failure(A_THROWABLE) } + val failurePinEventLambda = lambdaRecorder { _: EventId -> Result.failure(AN_EXCEPTION) } val analyticsService = FakeAnalyticsService() val timeline = FakeTimeline() val room = FakeJoinedRoom( @@ -932,7 +932,7 @@ class MessagesPresenterTest { @Test fun `present - handle action unpin`() = runTest { val successUnpinEventLambda = lambdaRecorder { _: EventId -> Result.success(true) } - val failureUnpinEventLambda = lambdaRecorder { _: EventId -> Result.failure(A_THROWABLE) } + val failureUnpinEventLambda = lambdaRecorder { _: EventId -> Result.failure(AN_EXCEPTION) } val timeline = FakeTimeline() val analyticsService = FakeAnalyticsService() val room = FakeJoinedRoom( diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenterTest.kt index 985e9f055f6..70f1d3f51b2 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenterTest.kt @@ -25,7 +25,7 @@ import io.element.android.libraries.matrix.api.sync.SyncService import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo import io.element.android.libraries.matrix.test.AN_EVENT_ID -import io.element.android.libraries.matrix.test.A_THROWABLE +import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.A_UNIQUE_ID import io.element.android.libraries.matrix.test.room.FakeBaseRoom import io.element.android.libraries.matrix.test.room.FakeJoinedRoom @@ -157,7 +157,7 @@ class PinnedMessagesListPresenterTest { @Test fun `present - unpin event`() = runTest { val successUnpinEventLambda = lambdaRecorder { _: EventId? -> Result.success(true) } - val failureUnpinEventLambda = lambdaRecorder { _: EventId? -> Result.failure(A_THROWABLE) } + val failureUnpinEventLambda = lambdaRecorder { _: EventId? -> Result.failure(AN_EXCEPTION) } val pinnedEventsTimeline = createPinnedMessagesTimeline() val analyticsService = FakeAnalyticsService() val room = FakeJoinedRoom( diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProviderTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProviderTest.kt index a5f3417d5d3..d2d59146741 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProviderTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/DefaultHtmlConverterProviderTest.kt @@ -12,6 +12,7 @@ import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.test.junit4.createComposeRule import com.google.common.truth.Truth.assertThat import io.element.android.features.messages.impl.utils.FakeMentionSpanFormatter +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider @@ -35,7 +36,7 @@ class DefaultHtmlConverterProviderTest { @Test fun `calling provide without calling Update first should throw an exception`() { - val exception = runCatching { provider.provide() }.exceptionOrNull() + val exception = runCatchingExceptions { provider.provide() }.exceptionOrNull() assertThat(exception).isInstanceOf(IllegalStateException::class.java) } @@ -47,7 +48,7 @@ class DefaultHtmlConverterProviderTest { provider.Update() } } - val htmlConverter = runCatching { provider.provide() }.getOrNull() + val htmlConverter = runCatchingExceptions { provider.provide() }.getOrNull() assertThat(htmlConverter).isNotNull() } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt index 5e9aa3e2147..9cbce38709f 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt @@ -562,7 +562,7 @@ import kotlin.time.Duration.Companion.seconds liveTimeline = FakeTimeline( timelineItems = flowOf(emptyList()), ), - createTimelineResult = { Result.failure(Throwable("An error")) }, + createTimelineResult = { Result.failure(RuntimeException("An error")) }, baseRoom = FakeBaseRoom(canUserSendMessageResult = { _, _ -> Result.success(true) }), ) ) diff --git a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration04.kt b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration04.kt index 3de2bb11e5e..6023663dac6 100644 --- a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration04.kt +++ b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/migrations/AppMigration04.kt @@ -9,6 +9,7 @@ package io.element.android.features.migration.impl.migrations import android.content.Context import com.squareup.anvil.annotations.ContributesMultibinding +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext import javax.inject.Inject @@ -26,6 +27,6 @@ class AppMigration04 @Inject constructor( override val order: Int = 4 override suspend fun migrate() { - runCatching { context.getDatabasePath(NOTIFICATION_FILE_NAME).delete() } + runCatchingExceptions { context.getDatabasePath(NOTIFICATION_FILE_NAME).delete() } } } diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/data/PollRepository.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/data/PollRepository.kt index ab711df996f..0fbdcdee36e 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/data/PollRepository.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/data/PollRepository.kt @@ -7,6 +7,7 @@ package io.element.android.features.poll.impl.data +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.poll.PollKind import io.element.android.libraries.matrix.api.room.JoinedRoom @@ -22,7 +23,7 @@ class PollRepository @Inject constructor( private val room: JoinedRoom, private val timelineProvider: TimelineProvider, ) { - suspend fun getPoll(eventId: EventId): Result = runCatching { + suspend fun getPoll(eventId: EventId): Result = runCatchingExceptions { timelineProvider .getActiveTimeline() .timelineItems diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersStateProvider.kt index 57335c5d97b..ba90f93ba57 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersStateProvider.kt @@ -21,7 +21,7 @@ class BlockedUsersStateProvider : PreviewParameterProvider { aBlockedUsersState(blockedUsers = emptyList()), aBlockedUsersState(unblockUserAction = AsyncAction.ConfirmingNoParams), aBlockedUsersState(unblockUserAction = AsyncAction.Loading), - aBlockedUsersState(unblockUserAction = AsyncAction.Failure(Throwable("Failed to unblock user"))), + aBlockedUsersState(unblockUserAction = AsyncAction.Failure(RuntimeException("Failed to unblock user"))), aBlockedUsersState(unblockUserAction = AsyncAction.Success(Unit)), ) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt index cc4ae25ebaf..9bd3d93dacf 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt @@ -29,6 +29,7 @@ import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.core.bool.orFalse +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.core.meta.BuildType import io.element.android.libraries.featureflag.api.Feature @@ -201,8 +202,8 @@ class DeveloperSettingsPresenter @Inject constructor( } private fun customElementCallUrlValidator(url: String?): Boolean { - return runCatching { - if (url.isNullOrEmpty()) return@runCatching + return runCatchingExceptions { + if (url.isNullOrEmpty()) return@runCatchingExceptions val parsedUrl = URL(url) if (parsedUrl.protocol !in listOf("http", "https")) error("Incorrect protocol") if (parsedUrl.host.isNullOrBlank()) error("Missing host") 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 3c1c81e758f..93b7f27b065 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 @@ -22,6 +22,7 @@ import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runUpdatingStateNoSuccess +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService @@ -209,7 +210,7 @@ class NotificationSettingsPresenter @Inject constructor( } private fun CoroutineScope.fixConfigurationMismatch(target: MutableState) = launch { - runCatching { + runCatchingExceptions { val groupDefaultMode = notificationSettingsService.getDefaultRoomNotificationMode(isEncrypted = false, isOneToOne = false).getOrThrow() val encryptedGroupDefaultMode = notificationSettingsService.getDefaultRoomNotificationMode(isEncrypted = true, isOneToOne = false).getOrThrow() 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 c0193bd6a26..0b46328b9bf 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,10 +23,10 @@ open class NotificationSettingsStateProvider : PreviewParameterProvider { - return runCatching { + return runCatchingExceptions { if (avatarUri != null) { val preprocessed = mediaPreProcessor.process( uri = avatarUri, diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/EditDefaultNotificationSettingsPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/EditDefaultNotificationSettingsPresenterTest.kt index aa5191d201d..b452b9c7442 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/EditDefaultNotificationSettingsPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/EditDefaultNotificationSettingsPresenterTest.kt @@ -14,7 +14,7 @@ import com.google.common.truth.Truth.assertThat import io.element.android.features.preferences.impl.notifications.edit.EditDefaultNotificationSettingPresenter import io.element.android.features.preferences.impl.notifications.edit.EditDefaultNotificationSettingStateEvents import io.element.android.libraries.matrix.api.room.RoomNotificationMode -import io.element.android.libraries.matrix.test.A_THROWABLE +import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService import io.element.android.libraries.matrix.test.room.aRoomSummary import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService @@ -81,7 +81,7 @@ class EditDefaultNotificationSettingsPresenterTest { fun `present - edit default notification setting failed`() = runTest { val notificationSettingsService = FakeNotificationSettingsService() val presenter = createEditDefaultNotificationSettingPresenter(notificationSettingsService) - notificationSettingsService.givenSetDefaultNotificationModeError(A_THROWABLE) + notificationSettingsService.givenSetDefaultNotificationModeError(AN_EXCEPTION) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenterTest.kt index fcefd5c43d6..8517d9d2859 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenterTest.kt @@ -16,7 +16,7 @@ import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermiss import io.element.android.libraries.fullscreenintent.api.aFullScreenIntentPermissionsState import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.room.RoomNotificationMode -import io.element.android.libraries.matrix.test.A_THROWABLE +import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService import io.element.android.libraries.push.api.PushService @@ -208,7 +208,7 @@ class NotificationSettingsPresenterTest { fun `present - clear notification settings change error`() = runTest { val notificationSettingsService = FakeNotificationSettingsService() val presenter = createNotificationSettingsPresenter(notificationSettingsService) - notificationSettingsService.givenSetAtRoomError(A_THROWABLE) + notificationSettingsService.givenSetAtRoomError(AN_EXCEPTION) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfilePresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfilePresenterTest.kt index 47830d2449f..01e56e8f59b 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfilePresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/user/editprofile/EditUserProfilePresenterTest.kt @@ -393,7 +393,7 @@ class EditUserProfilePresenterTest { ), ) fakePickerProvider.givenResult(anotherAvatarUri) - fakeMediaPreProcessor.givenResult(Result.failure(Throwable("Oh no"))) + fakeMediaPreProcessor.givenResult(Result.failure(RuntimeException("Oh no"))) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -410,7 +410,7 @@ class EditUserProfilePresenterTest { fun `present - sets save action to failure if name update fails`() = runTest { val user = aMatrixUser(id = A_USER_ID.value, displayName = "Name", avatarUrl = AN_AVATAR_URL) val matrixClient = FakeMatrixClient().apply { - givenSetDisplayNameResult(Result.failure(Throwable("!"))) + givenSetDisplayNameResult(Result.failure(RuntimeException("!"))) } saveAndAssertFailure(user, matrixClient, EditUserProfileEvents.UpdateDisplayName("New name")) } @@ -419,7 +419,7 @@ class EditUserProfilePresenterTest { fun `present - sets save action to failure if removing avatar fails`() = runTest { val user = aMatrixUser(id = A_USER_ID.value, displayName = "Name", avatarUrl = AN_AVATAR_URL) val matrixClient = FakeMatrixClient().apply { - givenRemoveAvatarResult(Result.failure(Throwable("!"))) + givenRemoveAvatarResult(Result.failure(RuntimeException("!"))) } saveAndAssertFailure(user, matrixClient, EditUserProfileEvents.HandleAvatarAction(AvatarAction.Remove)) } @@ -429,7 +429,7 @@ class EditUserProfilePresenterTest { givenPickerReturnsFile() val user = aMatrixUser(id = A_USER_ID.value, displayName = "Name", avatarUrl = AN_AVATAR_URL) val matrixClient = FakeMatrixClient().apply { - givenUploadAvatarResult(Result.failure(Throwable("!"))) + givenUploadAvatarResult(Result.failure(RuntimeException("!"))) } saveAndAssertFailure(user, matrixClient, EditUserProfileEvents.HandleAvatarAction(AvatarAction.ChoosePhoto)) } @@ -439,7 +439,7 @@ class EditUserProfilePresenterTest { givenPickerReturnsFile() val user = aMatrixUser(id = A_USER_ID.value, displayName = "Name", avatarUrl = AN_AVATAR_URL) val matrixClient = FakeMatrixClient().apply { - givenSetDisplayNameResult(Result.failure(Throwable("!"))) + givenSetDisplayNameResult(Result.failure(RuntimeException("!"))) } val presenter = createEditUserProfilePresenter(matrixUser = user, matrixClient = matrixClient) moleculeFlow(RecompositionMode.Immediate) { diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt index 36ece914084..b9ed1c7778b 100755 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/reporter/DefaultBugReporter.kt @@ -308,7 +308,7 @@ class DefaultBugReporter @Inject constructor( */ private fun getLogFiles(): List { return tryOrNull( - onError = { Timber.e(it, "## getLogFiles() failed") } + onException = { Timber.e(it, "## getLogFiles() failed") } ) { val logDirectory = logDirectory() logDirectory.listFiles()?.toList() diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditPresenter.kt index fb77530b6d4..fb530e601a5 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditPresenter.kt @@ -24,6 +24,7 @@ import io.element.android.libraries.androidutils.file.TemporaryUriDeleter import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.room.StateEventType @@ -216,7 +217,7 @@ class RoomDetailsEditPresenter @Inject constructor( } private suspend fun updateAvatar(avatarUri: Uri?): Result { - return runCatching { + return runCatchingExceptions { if (avatarUri != null) { val preprocessed = mediaPreProcessor.process( uri = avatarUri, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditStateProvider.kt index 0b979be743c..7ae59febc01 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditStateProvider.kt @@ -27,7 +27,7 @@ open class RoomDetailsEditStateProvider : PreviewParameterProvider Result.success(true) }, ) @@ -277,7 +277,7 @@ class RoomDetailsPresenterTest { when (stateEventType) { StateEventType.ROOM_TOPIC -> Result.success(true) StateEventType.ROOM_NAME -> Result.success(false) - else -> Result.failure(Throwable("Whelp")) + else -> Result.failure(RuntimeException("Whelp")) } }, canBanResult = { Result.success(false) }, @@ -306,7 +306,7 @@ class RoomDetailsPresenterTest { StateEventType.ROOM_TOPIC, StateEventType.ROOM_NAME, StateEventType.ROOM_AVATAR -> Result.success(true) - else -> Result.failure(Throwable("Whelp")) + else -> Result.failure(RuntimeException("Whelp")) } }, canKickResult = { Result.success(false) }, @@ -357,7 +357,7 @@ class RoomDetailsPresenterTest { StateEventType.ROOM_AVATAR, StateEventType.ROOM_TOPIC, StateEventType.ROOM_NAME -> Result.success(true) - else -> Result.failure(Throwable("Whelp")) + else -> Result.failure(RuntimeException("Whelp")) } }, userDisplayNameResult = { Result.success(A_USER_NAME) }, @@ -403,7 +403,7 @@ class RoomDetailsPresenterTest { StateEventType.ROOM_TOPIC, StateEventType.ROOM_NAME, StateEventType.ROOM_AVATAR -> Result.success(true) - else -> Result.failure(Throwable("Whelp")) + else -> Result.failure(RuntimeException("Whelp")) } }, canKickResult = { @@ -436,7 +436,7 @@ class RoomDetailsPresenterTest { StateEventType.ROOM_TOPIC, StateEventType.ROOM_NAME, StateEventType.ROOM_AVATAR -> Result.success(false) - else -> Result.failure(Throwable("Whelp")) + else -> Result.failure(RuntimeException("Whelp")) } }, canBanResult = { @@ -468,7 +468,7 @@ class RoomDetailsPresenterTest { StateEventType.ROOM_AVATAR, StateEventType.ROOM_NAME -> Result.success(true) StateEventType.ROOM_TOPIC -> Result.success(false) - else -> Result.failure(Throwable("Whelp")) + else -> Result.failure(RuntimeException("Whelp")) } }, canKickResult = { @@ -500,7 +500,7 @@ class RoomDetailsPresenterTest { StateEventType.ROOM_AVATAR, StateEventType.ROOM_TOPIC, StateEventType.ROOM_NAME -> Result.success(true) - else -> Result.failure(Throwable("Whelp")) + else -> Result.failure(RuntimeException("Whelp")) } }, canKickResult = { diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditPresenterTest.kt index 7a2ff4ee064..bb6a9b30277 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditPresenterTest.kt @@ -124,7 +124,7 @@ class RoomDetailsEditPresenterTest { when (stateEventType) { StateEventType.ROOM_NAME -> Result.success(true) StateEventType.ROOM_AVATAR -> Result.success(false) - StateEventType.ROOM_TOPIC -> Result.failure(Throwable("Oops")) + StateEventType.ROOM_TOPIC -> Result.failure(RuntimeException("Oops")) else -> lambdaError() } }, @@ -157,7 +157,7 @@ class RoomDetailsEditPresenterTest { when (stateEventType) { StateEventType.ROOM_NAME -> Result.success(false) StateEventType.ROOM_AVATAR -> Result.success(true) - StateEventType.ROOM_TOPIC -> Result.failure(Throwable("Oops")) + StateEventType.ROOM_TOPIC -> Result.failure(RuntimeException("Oops")) else -> lambdaError() } } @@ -188,7 +188,7 @@ class RoomDetailsEditPresenterTest { canSendStateResult = { _, stateEventType -> when (stateEventType) { StateEventType.ROOM_NAME -> Result.success(false) - StateEventType.ROOM_AVATAR -> Result.failure(Throwable("Oops")) + StateEventType.ROOM_AVATAR -> Result.failure(RuntimeException("Oops")) StateEventType.ROOM_TOPIC -> Result.success(true) else -> lambdaError() } @@ -559,7 +559,7 @@ class RoomDetailsEditPresenterTest { canSendStateResult = { _, _ -> Result.success(true) } ) fakePickerProvider.givenResult(anotherAvatarUri) - fakeMediaPreProcessor.givenResult(Result.failure(Throwable("Oh no"))) + fakeMediaPreProcessor.givenResult(Result.failure(RuntimeException("Oh no"))) val deleteCallback = lambdaRecorder {} val presenter = createRoomDetailsEditPresenter( room = room, @@ -580,7 +580,7 @@ class RoomDetailsEditPresenterTest { topic = "My topic", displayName = "Name", avatarUrl = AN_AVATAR_URL, - setNameResult = { Result.failure(Throwable("!")) }, + setNameResult = { Result.failure(RuntimeException("!")) }, canSendStateResult = { _, _ -> Result.success(true) } ) saveAndAssertFailure(room, RoomDetailsEditEvents.UpdateRoomName("New name"), deleteCallbackNumberOfInvocation = 1) @@ -592,7 +592,7 @@ class RoomDetailsEditPresenterTest { topic = "My topic", displayName = "Name", avatarUrl = AN_AVATAR_URL, - setTopicResult = { Result.failure(Throwable("!")) }, + setTopicResult = { Result.failure(RuntimeException("!")) }, canSendStateResult = { _, _ -> Result.success(true) } ) saveAndAssertFailure(room, RoomDetailsEditEvents.UpdateRoomTopic("New topic"), deleteCallbackNumberOfInvocation = 1) @@ -604,7 +604,7 @@ class RoomDetailsEditPresenterTest { topic = "My topic", displayName = "Name", avatarUrl = AN_AVATAR_URL, - removeAvatarResult = { Result.failure(Throwable("!")) }, + removeAvatarResult = { Result.failure(RuntimeException("!")) }, canSendStateResult = { _, _ -> Result.success(true) } ) saveAndAssertFailure(room, RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.Remove), deleteCallbackNumberOfInvocation = 2) @@ -617,7 +617,7 @@ class RoomDetailsEditPresenterTest { topic = "My topic", displayName = "Name", avatarUrl = AN_AVATAR_URL, - updateAvatarResult = { _, _ -> Result.failure(Throwable("!")) }, + updateAvatarResult = { _, _ -> Result.failure(RuntimeException("!")) }, canSendStateResult = { _, _ -> Result.success(true) } ) saveAndAssertFailure(room, RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.ChoosePhoto), deleteCallbackNumberOfInvocation = 2) @@ -630,7 +630,7 @@ class RoomDetailsEditPresenterTest { topic = "My topic", displayName = "Name", avatarUrl = AN_AVATAR_URL, - setTopicResult = { Result.failure(Throwable("!")) }, + setTopicResult = { Result.failure(RuntimeException("!")) }, canSendStateResult = { _, _ -> Result.success(true) } ) val deleteCallback = lambdaRecorder {} diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditViewTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditViewTest.kt index 5cc12d8b57d..2ac3d4397cd 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditViewTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditViewTest.kt @@ -205,7 +205,7 @@ class RoomDetailsEditViewTest { rule.setRoomDetailsEditView( aRoomDetailsEditState( eventSink = eventsRecorder, - saveAction = AsyncAction.Failure(Throwable("Whelp")), + saveAction = AsyncAction.Failure(RuntimeException("Whelp")), ), ) rule.clickOn(CommonStrings.action_ok) diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenterTest.kt index f2436179f75..aff05a55e61 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenterTest.kt @@ -186,7 +186,7 @@ class RoomMemberListPresenterTest { val presenter = createPresenter( joinedRoom = FakeJoinedRoom( baseRoom = FakeBaseRoom( - canInviteResult = { Result.failure(Throwable("Eek")) }, + canInviteResult = { Result.failure(RuntimeException("Eek")) }, updateMembersResult = { Result.success(Unit) } ) ) diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenterTest.kt index 17d49cfab94..73859b712f5 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenterTest.kt @@ -78,8 +78,8 @@ class RoomMemberDetailsPresenterTest { avatarUrl = "Alice Avatar url", ) val room = aJoinedRoom( - userDisplayNameResult = { Result.failure(Throwable()) }, - userAvatarUrlResult = { Result.failure(Throwable()) }, + userDisplayNameResult = { Result.failure(RuntimeException()) }, + userAvatarUrlResult = { Result.failure(RuntimeException()) }, getUpdatedMemberResult = { Result.failure(AN_EXCEPTION) }, ).apply { givenRoomMembersState(RoomMembersState.Ready(persistentListOf(roomMember))) diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenterTest.kt index 3cfc3597925..308ad943791 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenterTest.kt @@ -13,8 +13,8 @@ import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.roomdetails.impl.aJoinedRoom import io.element.android.libraries.matrix.api.room.RoomNotificationMode +import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.A_ROOM_ID -import io.element.android.libraries.matrix.test.A_THROWABLE import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService import io.element.android.libraries.matrix.test.room.FakeJoinedRoom import io.element.android.tests.testutils.awaitLastSequentialItem @@ -71,7 +71,7 @@ class RoomNotificationSettingsPresenterTest { @Test fun `present - notification settings set custom failed`() = runTest { val notificationSettingsService = FakeNotificationSettingsService() - notificationSettingsService.givenSetNotificationModeError(A_THROWABLE) + notificationSettingsService.givenSetNotificationModeError(AN_EXCEPTION) val presenter = createRoomNotificationSettingsPresenter(notificationSettingsService) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -131,7 +131,7 @@ class RoomNotificationSettingsPresenterTest { @Test fun `present - notification settings restore default failed`() = runTest { val notificationSettingsService = FakeNotificationSettingsService() - notificationSettingsService.givenRestoreDefaultNotificationModeError(A_THROWABLE) + notificationSettingsService.givenRestoreDefaultNotificationModeError(AN_EXCEPTION) val presenter = createRoomNotificationSettingsPresenter(notificationSettingsService) moleculeFlow(RecompositionMode.Immediate) { presenter.present() diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesViewTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesViewTest.kt index da3a3fa4d03..4bb0253e6af 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesViewTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesViewTest.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.test.performClick import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.designsystem.theme.components.SearchBarResultState import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.toMatrixUser @@ -41,7 +42,7 @@ class ChangeRolesViewTest { @Test fun `passing a 'USER' role throws an exception`() { - val exception = runCatching { + val exception = runCatchingExceptions { rule.setChangeRolesContent( state = aChangeRolesState( role = RoomMember.Role.USER, diff --git a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/SharePresenter.kt b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/SharePresenter.kt index 1c75daa828e..e953b3568a3 100644 --- a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/SharePresenter.kt +++ b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/SharePresenter.kt @@ -27,6 +27,7 @@ import io.element.android.libraries.preferences.api.store.SessionPreferencesStor import io.element.android.services.appnavstate.api.ActiveRoomsHolder import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import kotlin.coroutines.cancellation.CancellationException class SharePresenter @AssistedInject constructor( @Assisted private val intent: Intent, @@ -89,12 +90,21 @@ class SharePresenter @AssistedInject constructor( ) filesToShare .map { fileToShare -> - mediaSender.sendMedia( + val result = mediaSender.sendMedia( uri = fileToShare.uri, mimeType = fileToShare.mimeType, - ).isSuccess + ) + // If the coroutine was cancelled, destroy the room and rethrow the exception + val cancellationException = result.exceptionOrNull() as? CancellationException + if (cancellationException != null) { + if (activeRoomsHolder.getActiveRoomMatching(matrixClient.sessionId, roomId) == null) { + room.destroy() + } + throw cancellationException + } + result.isSuccess } - .all { it } + .all { isSuccess -> isSuccess } .also { if (activeRoomsHolder.getActiveRoomMatching(matrixClient.sessionId, roomId) == null) { room.destroy() diff --git a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareStateProvider.kt b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareStateProvider.kt index 62664cdd0d2..5afc622fb9c 100644 --- a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareStateProvider.kt +++ b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/ShareStateProvider.kt @@ -24,7 +24,7 @@ open class ShareStateProvider : PreviewParameterProvider { ) ), aShareState( - shareAction = AsyncAction.Failure(Throwable("error")), + shareAction = AsyncAction.Failure(RuntimeException("error")), ), ) } diff --git a/features/userprofile/impl/src/test/kotlin/io/element/android/features/userprofile/impl/UserProfilePresenterTest.kt b/features/userprofile/impl/src/test/kotlin/io/element/android/features/userprofile/impl/UserProfilePresenterTest.kt index d0369da660f..b748430383e 100644 --- a/features/userprofile/impl/src/test/kotlin/io/element/android/features/userprofile/impl/UserProfilePresenterTest.kt +++ b/features/userprofile/impl/src/test/kotlin/io/element/android/features/userprofile/impl/UserProfilePresenterTest.kt @@ -30,7 +30,6 @@ import io.element.android.libraries.matrix.api.encryption.identity.IdentityState import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.A_ROOM_ID -import io.element.android.libraries.matrix.test.A_THROWABLE import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.A_USER_ID_2 import io.element.android.libraries.matrix.test.FakeMatrixClient @@ -215,7 +214,7 @@ class UserProfilePresenterTest { @Test fun `present - BlockUser with error`() = runTest { val matrixClient = createFakeMatrixClient( - ignoreUserResult = { Result.failure(A_THROWABLE) } + ignoreUserResult = { Result.failure(AN_EXCEPTION) } ) val presenter = createUserProfilePresenter(client = matrixClient) presenter.test { @@ -223,7 +222,7 @@ class UserProfilePresenterTest { initialState.eventSink(UserProfileEvents.BlockUser(needsConfirmation = false)) assertThat(awaitItem().isBlocked.isLoading()).isTrue() val errorState = awaitItem() - assertThat(errorState.isBlocked.errorOrNull()).isEqualTo(A_THROWABLE) + assertThat(errorState.isBlocked.errorOrNull()).isEqualTo(AN_EXCEPTION) // Clear error initialState.eventSink(UserProfileEvents.ClearBlockUserError) assertThat(awaitItem().isBlocked).isEqualTo(AsyncData.Success(false)) @@ -233,7 +232,7 @@ class UserProfilePresenterTest { @Test fun `present - UnblockUser with error`() = runTest { val matrixClient = createFakeMatrixClient( - unIgnoreUserResult = { Result.failure(A_THROWABLE) } + unIgnoreUserResult = { Result.failure(AN_EXCEPTION) } ) val presenter = createUserProfilePresenter(client = matrixClient) presenter.test { @@ -241,7 +240,7 @@ class UserProfilePresenterTest { initialState.eventSink(UserProfileEvents.UnblockUser(needsConfirmation = false)) assertThat(awaitItem().isBlocked.isLoading()).isTrue() val errorState = awaitItem() - assertThat(errorState.isBlocked.errorOrNull()).isEqualTo(A_THROWABLE) + assertThat(errorState.isBlocked.errorOrNull()).isEqualTo(AN_EXCEPTION) // Clear error initialState.eventSink(UserProfileEvents.ClearBlockUserError) assertThat(awaitItem().isBlocked).isEqualTo(AsyncData.Success(true)) @@ -265,7 +264,7 @@ class UserProfilePresenterTest { @Test fun `present - start DM action failure scenario`() = runTest { - val startDMFailureResult = AsyncAction.Failure(A_THROWABLE) + val startDMFailureResult = AsyncAction.Failure(AN_EXCEPTION) val executeResult = lambdaRecorder>, Unit> { _, _, actionState -> actionState.value = startDMFailureResult } diff --git a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationPresenterTest.kt b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationPresenterTest.kt index eaa51d33ec8..06940f37a5f 100644 --- a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationPresenterTest.kt +++ b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/outgoing/OutgoingVerificationPresenterTest.kt @@ -206,6 +206,7 @@ class OutgoingVerificationPresenterTest { ) ) service.emitVerificationFlowState(VerificationFlowState.DidFinish) + service.emitVerifiedStatus(SessionVerifiedStatus.Verified) assertThat(awaitItem().step).isEqualTo(Step.Completed) } } diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileContentReader.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileContentReader.kt index a7a9f12456b..e94bde4a53f 100644 --- a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileContentReader.kt +++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileContentReader.kt @@ -9,6 +9,7 @@ package io.element.android.features.viewfolder.impl.file import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.di.AppScope import kotlinx.coroutines.withContext import java.io.File @@ -23,7 +24,7 @@ class DefaultFileContentReader @Inject constructor( private val dispatchers: CoroutineDispatchers, ) : FileContentReader { override suspend fun getLines(path: String): Result> = withContext(dispatchers.io) { - runCatching { + runCatchingExceptions { File(path).readLines() } } diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileSave.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileSave.kt index 4337ffd13f6..78648c67387 100644 --- a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileSave.kt +++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileSave.kt @@ -16,6 +16,7 @@ import androidx.annotation.RequiresApi import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.androidutils.system.toast import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext @@ -40,7 +41,7 @@ class DefaultFileSave @Inject constructor( path: String, ) { withContext(dispatchers.io) { - runCatching { + runCatchingExceptions { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { saveOnDiskUsingMediaStore(path) } else { diff --git a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileShare.kt b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileShare.kt index 393139d1821..471f2df5d4e 100644 --- a/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileShare.kt +++ b/features/viewfolder/impl/src/main/kotlin/io/element/android/features/viewfolder/impl/file/FileShare.kt @@ -13,6 +13,7 @@ import android.net.Uri import androidx.core.content.FileProvider import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.di.AppScope @@ -37,7 +38,7 @@ class DefaultFileShare @Inject constructor( override suspend fun share( path: String, ) { - runCatching { + runCatchingExceptions { val file = File(path) val shareableUri = file.toShareableUri() val shareMediaIntent = Intent(Intent.ACTION_SEND) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 57dcdcaa7c3..975f5cd516e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -37,6 +37,7 @@ datetime = "0.6.2" serialization_json = "1.8.1" #other +detekt = "1.23.8" coil = "3.1.0" showkase = "1.0.3" appyx = "1.7.1" @@ -152,6 +153,8 @@ test_parameter_injector = "com.google.testparameterinjector:test-parameter-injec test_robolectric = "org.robolectric:robolectric:4.14.1" test_appyx_junit = { module = "com.bumble.appyx:testing-junit4", version.ref = "appyx" } test_composable_preview_scanner = "io.github.sergio-sastre.ComposablePreviewScanner:android:0.6.1" +test_detekt_api = { module = "io.gitlab.arturbosch.detekt:detekt-api", version.ref = "detekt" } +test_detekt_test = { module = "io.gitlab.arturbosch.detekt:detekt-test", version.ref = "detekt" } # Others coil = { module = "io.coil-kt.coil3:coil", version.ref = "coil" } @@ -231,7 +234,7 @@ kotlin_serialization = { id = "org.jetbrains.kotlin.plugin.serialization", versi kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } anvil = { id = "dev.zacsweers.anvil", version.ref = "anvil" } -detekt = "io.gitlab.arturbosch.detekt:1.23.8" +detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } ktlint = "org.jlleitschuh.gradle.ktlint:12.3.0" dependencygraph = "com.savvasdalkitsis.module-dependency-graph:0.12" dependencycheck = "org.owasp.dependencycheck:12.1.1" diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/compat/AudioManager.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/compat/AudioManager.kt index 5e77d0d00a4..780cce94612 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/compat/AudioManager.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/compat/AudioManager.kt @@ -41,7 +41,7 @@ fun AudioManager.enableExternalAudioDevice() { selectedDevice?.let { device -> Timber.d("Audio device selected, type: ${device.type}") tryOrNull( - onError = { failure -> + onException = { failure -> Timber.e(failure, "Audio: exception when setting communication device") } ) { diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/Context.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/Context.kt index 8697ae9da7a..eaa0b578bc6 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/Context.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/Context.kt @@ -12,6 +12,7 @@ import android.content.Context import android.net.Uri import android.provider.OpenableColumns import androidx.core.net.toFile +import io.element.android.libraries.core.extensions.runCatchingExceptions fun Context.getMimeType(uri: Uri): String? = when (uri.scheme) { ContentResolver.SCHEME_CONTENT -> contentResolver.getType(uri) @@ -32,14 +33,14 @@ fun Context.getFileSize(uri: Uri): Long { } ?: 0 } -private fun Context.getContentFileSize(uri: Uri): Long? = runCatching { +private fun Context.getContentFileSize(uri: Uri): Long? = runCatchingExceptions { contentResolver.query(uri, null, null, null, null)?.use { cursor -> cursor.moveToFirst() return@use cursor.getColumnIndexOrThrow(OpenableColumns.SIZE).let(cursor::getLong) } }.getOrNull() -private fun Context.getContentFileName(uri: Uri): String? = runCatching { +private fun Context.getContentFileName(uri: Uri): String? = runCatchingExceptions { contentResolver.query(uri, null, null, null, null)?.use { cursor -> cursor.moveToFirst() return@use cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME).let(cursor::getString) diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/File.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/File.kt index ef55ed2e08c..414b1231290 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/File.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/File.kt @@ -17,7 +17,7 @@ import java.util.UUID fun File.safeDelete() { if (exists().not()) return tryOrNull( - onError = { + onException = { Timber.e(it, "Error, unable to delete file $path") }, operation = { @@ -30,7 +30,7 @@ fun File.safeDelete() { fun File.safeRenameTo(dest: File) { tryOrNull( - onError = { + onException = { Timber.e(it, "Error, unable to rename file $path to ${dest.path}") }, operation = { diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/text/LinkifyHelper.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/text/LinkifyHelper.kt index 5c24925be27..c0cb5a766a6 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/text/LinkifyHelper.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/text/LinkifyHelper.kt @@ -13,6 +13,7 @@ import android.text.util.Linkify import androidx.core.text.getSpans import androidx.core.text.toSpannable import androidx.core.text.util.LinkifyCompat +import io.element.android.libraries.core.extensions.runCatchingExceptions import timber.log.Timber import kotlin.collections.component1 import kotlin.collections.component2 @@ -48,7 +49,7 @@ object LinkifyHelper { // Try to avoid including trailing punctuation in the link. // Since this might fail in some edge cases, we catch the exception and just use the original end index. - val newEnd = runCatching { + val newEnd = runCatchingExceptions { adjustLinkifiedUrlSpanEndIndex(spannable, start, end) }.onFailure { Timber.e(it, "Failed to adjust end index for link span") diff --git a/libraries/architecture/build.gradle.kts b/libraries/architecture/build.gradle.kts index 31c3d60757e..8db06089f3b 100644 --- a/libraries/architecture/build.gradle.kts +++ b/libraries/architecture/build.gradle.kts @@ -15,6 +15,7 @@ android { dependencies { api(projects.libraries.di) + api(projects.libraries.core) api(libs.dagger) api(libs.appyx.core) api(libs.androidx.lifecycle.runtime) diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/AsyncAction.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/AsyncAction.kt index 6c4d286c2fd..f080b64d015 100644 --- a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/AsyncAction.kt +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/AsyncAction.kt @@ -9,6 +9,7 @@ package io.element.android.libraries.architecture import androidx.compose.runtime.MutableState import androidx.compose.runtime.Stable +import io.element.android.libraries.core.extensions.runCatchingExceptions import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract @@ -90,7 +91,7 @@ suspend inline fun MutableState>.runCatchingUpdatingState( state = this, errorTransform = errorTransform, resultBlock = { - runCatching { + runCatchingExceptions { block() } }, @@ -103,7 +104,7 @@ suspend inline fun (suspend () -> T).runCatchingUpdatingState( state = state, errorTransform = errorTransform, resultBlock = { - runCatching { + runCatchingExceptions { this() } }, diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/AsyncData.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/AsyncData.kt index c425597ba75..b7f22cbe233 100644 --- a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/AsyncData.kt +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/AsyncData.kt @@ -9,6 +9,7 @@ package io.element.android.libraries.architecture import androidx.compose.runtime.MutableState import androidx.compose.runtime.Stable +import io.element.android.libraries.core.extensions.runCatchingExceptions import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract @@ -93,7 +94,7 @@ suspend inline fun MutableState>.runCatchingUpdatingState( state = this, errorTransform = errorTransform, resultBlock = { - runCatching { + runCatchingExceptions { block() } }, @@ -106,7 +107,7 @@ suspend inline fun (suspend () -> T).runCatchingUpdatingState( state = state, errorTransform = errorTransform, resultBlock = { - runCatching { + runCatchingExceptions { this() } }, diff --git a/libraries/architecture/src/test/kotlin/io/element/android/libraries/architecture/AsyncDataKtTest.kt b/libraries/architecture/src/test/kotlin/io/element/android/libraries/architecture/AsyncDataKtTest.kt index 1818e3e99e2..1551360da2b 100644 --- a/libraries/architecture/src/test/kotlin/io/element/android/libraries/architecture/AsyncDataKtTest.kt +++ b/libraries/architecture/src/test/kotlin/io/element/android/libraries/architecture/AsyncDataKtTest.kt @@ -38,15 +38,15 @@ class AsyncDataKtTest { val result = runUpdatingState(state) { delay(1) - Result.failure(MyThrowable("hello")) + Result.failure(MyException("hello")) } assertThat(result.isFailure).isTrue() - assertThat(result.exceptionOrNull()).isEqualTo(MyThrowable("hello")) + assertThat(result.exceptionOrNull()).isEqualTo(MyException("hello")) assertThat(state.popFirst()).isEqualTo(AsyncData.Uninitialized) assertThat(state.popFirst()).isEqualTo(AsyncData.Loading(null)) - assertThat(state.popFirst()).isEqualTo(AsyncData.Failure(MyThrowable("hello"))) + assertThat(state.popFirst()).isEqualTo(AsyncData.Failure(MyException("hello"))) state.assertNoMoreValues() } @@ -54,17 +54,17 @@ class AsyncDataKtTest { fun `updates state when block returns failure transforming the error`() = runTest { val state = TestableMutableState>(AsyncData.Uninitialized) - val result = runUpdatingState(state, { MyThrowable(it.message + " world") }) { + val result = runUpdatingState(state, { MyException(it.message + " world") }) { delay(1) - Result.failure(MyThrowable("hello")) + Result.failure(MyException("hello")) } assertThat(result.isFailure).isTrue() - assertThat(result.exceptionOrNull()).isEqualTo(MyThrowable("hello world")) + assertThat(result.exceptionOrNull()).isEqualTo(MyException("hello world")) assertThat(state.popFirst()).isEqualTo(AsyncData.Uninitialized) assertThat(state.popFirst()).isEqualTo(AsyncData.Loading(null)) - assertThat(state.popFirst()).isEqualTo(AsyncData.Failure(MyThrowable("hello world"))) + assertThat(state.popFirst()).isEqualTo(AsyncData.Failure(MyException("hello world"))) state.assertNoMoreValues() } } @@ -101,4 +101,4 @@ private class TestableMutableState( /** * An exception that is also a data class so we can compare it using equals. */ -private data class MyThrowable(val myMessage: String) : Throwable(myMessage) +private data class MyException(val myMessage: String) : Exception(myMessage) diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/data/Try.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/data/Try.kt index 222dcb8f30a..0610fa04e4d 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/data/Try.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/data/Try.kt @@ -7,11 +7,20 @@ package io.element.android.libraries.core.data -inline fun tryOrNull(onError: ((Throwable) -> Unit) = { }, operation: () -> A): A? { +import kotlin.coroutines.cancellation.CancellationException + +/** + * Can be used to catch [Exception]s in a block of code, returning `null` if an exception occurs. + * + * If the block throws a [CancellationException], it will be rethrown. + */ +inline fun tryOrNull(onException: ((Exception) -> Unit) = { }, operation: () -> A): A? { return try { operation() - } catch (any: Throwable) { - onError.invoke(any) + } catch (e: CancellationException) { + throw e + } catch (e: Exception) { + onException.invoke(e) null } } diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/Result.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/Result.kt index 3524a579b6d..e46f37cffbb 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/Result.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/Result.kt @@ -7,6 +7,61 @@ package io.element.android.libraries.core.extensions +import kotlin.coroutines.cancellation.CancellationException + +/** + * Can be used to catch exceptions in a block of code and return a [Result]. + * If the block throws a [CancellationException], it will be rethrown. + * If it throws any other exception, it will be wrapped in a [Result.failure]. + * + * [Error]s are not caught by this function, as they are not meant to be caught in normal application flow. + */ +inline fun runCatchingExceptions( + block: () -> T +): Result { + return try { + Result.success(block()) + } catch (e: CancellationException) { + throw e + } catch (e: Exception) { + Result.failure(e) + } +} + +/** + * Can be used to catch exceptions in a block of code and return a [Result]. + * If the block throws a [CancellationException], it will be rethrown. + * If it throws any other exception, it will be wrapped in a [Result.failure]. + * + * [Error]s are not caught by this function, as they are not meant to be caught in normal application flow. + */ +inline fun T.runCatchingExceptions( + block: T.() -> R +): Result { + return try { + Result.success(block()) + } catch (e: CancellationException) { + throw e + } catch (e: Exception) { + Result.failure(e) + } +} + +/** + * Can be used to transform a [Result] into another [Result] by applying a [block] to the value if it is successful. + * If the original [Result] is a failure, the exception will be wrapped in a new [Result.failure]. + * + * This is a safer version of [Result.mapCatching]. + */ +inline fun Result.mapCatchingExceptions( + block: (T) -> R, +): Result { + return fold( + onSuccess = { value -> runCatchingExceptions { block(value) } }, + onFailure = { exception -> Result.failure(exception) } + ) +} + /** * Can be used to transform some Throwable into some other. */ @@ -33,12 +88,16 @@ inline fun Result.flatMap(transform: (T) -> Result): Result { * @return The result of the transform or a caught exception wrapped in a [Result]. */ inline fun Result.flatMapCatching(transform: (T) -> Result): Result { - return mapCatching(transform).fold( + return mapCatchingExceptions(transform).fold( onSuccess = { it }, onFailure = { Result.failure(it) } ) } +/** + * Can be used to execute a block of code after the [Result] has been processed, regardless of whether it was successful or not. + * The block receives the exception if there was one, or `null` if the result was successful. + */ inline fun Result.finally(block: (exception: Throwable?) -> Unit): Result { onSuccess { block(null) } onFailure(block) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/RoomPowerLevels.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/RoomPowerLevels.kt index 95cbf58e17b..8f548cc6013 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/RoomPowerLevels.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/powerlevels/RoomPowerLevels.kt @@ -7,6 +7,7 @@ package io.element.android.libraries.matrix.api.room.powerlevels +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.matrix.api.room.BaseRoom import io.element.android.libraries.matrix.api.room.MessageEventType import io.element.android.libraries.matrix.api.room.StateEventType @@ -60,7 +61,7 @@ suspend fun BaseRoom.canRedactOther(): Result = canUserRedactOther(sess /** * Shortcut for checking if current user can handle knock requests. */ -suspend fun BaseRoom.canHandleKnockRequests(): Result = runCatching { +suspend fun BaseRoom.canHandleKnockRequests(): Result = runCatchingExceptions { canInvite().getOrThrow() || canBan().getOrThrow() || canKick().getOrThrow() } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index 816e4f26bd8..a8954d7dfe2 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -13,6 +13,7 @@ import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.childScope import io.element.android.libraries.core.data.tryOrNull import io.element.android.libraries.core.extensions.mapFailure +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.DeviceId @@ -250,7 +251,7 @@ class RustMatrixClient( } override fun userIdServerName(): String { - return runCatching { + return runCatchingExceptions { innerClient.userIdServerName() } .onFailure { @@ -261,7 +262,7 @@ class RustMatrixClient( } override suspend fun getUrl(url: String): Result = withContext(sessionDispatcher) { - runCatching { + runCatchingExceptions { innerClient.getUrl(url) } } @@ -301,19 +302,19 @@ class RustMatrixClient( } override suspend fun ignoreUser(userId: UserId): Result = withContext(sessionDispatcher) { - runCatching { + runCatchingExceptions { innerClient.ignoreUser(userId.value) } } override suspend fun unignoreUser(userId: UserId): Result = withContext(sessionDispatcher) { - runCatching { + runCatchingExceptions { innerClient.unignoreUser(userId.value) } } override suspend fun createRoom(createRoomParams: CreateRoomParameters): Result = withContext(sessionDispatcher) { - runCatching { + runCatchingExceptions { val rustParams = RustCreateRoomParameters( name = createRoomParams.name, topic = createRoomParams.topic, @@ -363,7 +364,7 @@ class RustMatrixClient( } override suspend fun getProfile(userId: UserId): Result = withContext(sessionDispatcher) { - runCatching { + runCatchingExceptions { innerClient.getProfile(userId.value).let(UserProfileMapper::map) } } @@ -373,28 +374,28 @@ class RustMatrixClient( override suspend fun searchUsers(searchTerm: String, limit: Long): Result = withContext(sessionDispatcher) { - runCatching { + runCatchingExceptions { innerClient.searchUsers(searchTerm, limit.toULong()).let(UserSearchResultMapper::map) } } override suspend fun setDisplayName(displayName: String): Result = withContext(sessionDispatcher) { - runCatching { innerClient.setDisplayName(displayName) } + runCatchingExceptions { innerClient.setDisplayName(displayName) } } override suspend fun uploadAvatar(mimeType: String, data: ByteArray): Result = withContext(sessionDispatcher) { - runCatching { innerClient.uploadAvatar(mimeType, data) } + runCatchingExceptions { innerClient.uploadAvatar(mimeType, data) } } override suspend fun removeAvatar(): Result = withContext(sessionDispatcher) { - runCatching { innerClient.removeAvatar() } + runCatchingExceptions { innerClient.removeAvatar() } } override suspend fun joinRoom(roomId: RoomId): Result = withContext(sessionDispatcher) { - runCatching { + runCatchingExceptions { innerClient.joinRoomById(roomId.value).destroy() try { awaitRoom(roomId.toRoomIdOrAlias(), 10.seconds, CurrentUserMembership.JOINED) @@ -406,7 +407,7 @@ class RustMatrixClient( }.mapFailure { it.mapClientException() } override suspend fun joinRoomByIdOrAlias(roomIdOrAlias: RoomIdOrAlias, serverNames: List): Result = withContext(sessionDispatcher) { - runCatching { + runCatchingExceptions { innerClient.joinRoomByIdOrAlias( roomIdOrAlias = roomIdOrAlias.identifier, serverNames = serverNames, @@ -423,7 +424,7 @@ class RustMatrixClient( override suspend fun knockRoom(roomIdOrAlias: RoomIdOrAlias, message: String, serverNames: List): Result = withContext( sessionDispatcher ) { - runCatching { + runCatchingExceptions { innerClient.knock(roomIdOrAlias.identifier, message, serverNames).destroy() try { awaitRoom(roomIdOrAlias, 10.seconds, CurrentUserMembership.KNOCKED) @@ -435,19 +436,19 @@ class RustMatrixClient( } override suspend fun trackRecentlyVisitedRoom(roomId: RoomId): Result = withContext(sessionDispatcher) { - runCatching { + runCatchingExceptions { innerClient.trackRecentlyVisitedRoom(roomId.value) } } override suspend fun getRecentlyVisitedRooms(): Result> = withContext(sessionDispatcher) { - runCatching { + runCatchingExceptions { innerClient.getRecentlyVisitedRooms().map(::RoomId) } } override suspend fun resolveRoomAlias(roomAlias: RoomAlias): Result> = withContext(sessionDispatcher) { - runCatching { + runCatchingExceptions { val result = innerClient.resolveRoomAlias(roomAlias.value)?.let { ResolvedRoomAlias( roomId = RoomId(it.roomId), @@ -459,7 +460,7 @@ class RustMatrixClient( } override suspend fun getRoomPreview(roomIdOrAlias: RoomIdOrAlias, serverNames: List): Result = withContext(sessionDispatcher) { - runCatching { + runCatchingExceptions { when (roomIdOrAlias) { is RoomIdOrAlias.Alias -> { val roomId = innerClient.resolveRoomAlias(roomIdOrAlias.roomAlias.value)?.roomId?.let { RoomId(it) } @@ -556,7 +557,7 @@ class RustMatrixClient( } override fun canDeactivateAccount(): Boolean { - return runCatching { + return runCatchingExceptions { innerClient.canDeactivateAccount() } .getOrNull() @@ -568,9 +569,9 @@ class RustMatrixClient( // Remove current delegate so we don't receive an auth error clientDelegateTaskHandle?.cancelAndDestroy() clientDelegateTaskHandle = null - runCatching { + runCatchingExceptions { // First call without AuthData, should fail - val firstAttempt = runCatching { + val firstAttempt = runCatchingExceptions { innerClient.deactivateAccount( authData = null, eraseData = eraseData, @@ -579,7 +580,7 @@ class RustMatrixClient( if (firstAttempt.isFailure) { Timber.w(firstAttempt.exceptionOrNull(), "Expected failure, try again") // This is expected, try again with the password - runCatching { + runCatchingExceptions { innerClient.deactivateAccount( authData = AuthData.Password( passwordDetails = AuthDataPasswordDetails( @@ -606,13 +607,13 @@ class RustMatrixClient( override suspend fun getAccountManagementUrl(action: AccountManagementAction?): Result = withContext(sessionDispatcher) { val rustAction = action?.toRustAction() - runCatching { + runCatchingExceptions { innerClient.accountUrl(rustAction) } } override suspend fun uploadMedia(mimeType: String, data: ByteArray, progressCallback: ProgressCallback?): Result = withContext(sessionDispatcher) { - runCatching { + runCatchingExceptions { innerClient.uploadMedia(mimeType, data, progressCallback?.toProgressWatcher()) } } @@ -654,19 +655,19 @@ class RustMatrixClient( }.buffer(Channel.UNLIMITED) override suspend fun availableSlidingSyncVersions(): Result> = withContext(sessionDispatcher) { - runCatching { + runCatchingExceptions { innerClient.availableSlidingSyncVersions().map { it.map() } } } override suspend fun currentSlidingSyncVersion(): Result = withContext(sessionDispatcher) { - runCatching { + runCatchingExceptions { innerClient.session().slidingSyncVersion.map() } } override suspend fun canReportRoom(): Boolean = withContext(sessionDispatcher) { - runCatching { + runCatchingExceptions { innerClient.isReportRoomApiSupported() }.getOrDefault(false) } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt index 8f7135a9143..506abcf5da2 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/RustMatrixAuthenticationService.kt @@ -10,6 +10,7 @@ package io.element.android.libraries.matrix.impl.auth import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.extensions.mapFailure +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.SingleIn import io.element.android.libraries.matrix.api.MatrixClient @@ -91,7 +92,7 @@ class RustMatrixAuthenticationService @Inject constructor( } override suspend fun restoreSession(sessionId: SessionId): Result = withContext(coroutineDispatchers.io) { - runCatching { + runCatchingExceptions { val sessionData = sessionStore.getSession(sessionId.value) if (sessionData != null) { if (sessionData.isTokenValid) { @@ -126,7 +127,7 @@ class RustMatrixAuthenticationService @Inject constructor( override suspend fun setHomeserver(homeserver: String): Result = withContext(coroutineDispatchers.io) { val emptySessionPath = rotateSessionPath() - runCatching { + runCatchingExceptions { val client = makeClient(sessionPaths = emptySessionPath) { serverNameOrHomeserverUrl(homeserver) } @@ -144,7 +145,7 @@ class RustMatrixAuthenticationService @Inject constructor( override suspend fun login(username: String, password: String): Result = withContext(coroutineDispatchers.io) { - runCatching { + runCatchingExceptions { val client = currentClient ?: error("You need to call `setHomeserver()` first") val currentSessionPaths = sessionPaths ?: error("You need to call `setHomeserver()` first") client.login(username, password, "Element X Android", null) @@ -170,7 +171,7 @@ class RustMatrixAuthenticationService @Inject constructor( override suspend fun importCreatedSession(externalSession: ExternalSession): Result = withContext(coroutineDispatchers.io) { - runCatching { + runCatchingExceptions { currentClient ?: error("You need to call `setHomeserver()` first") val currentSessionPaths = sessionPaths ?: error("You need to call `setHomeserver()` first") val sessionData = externalSession.toSessionData( @@ -192,7 +193,7 @@ class RustMatrixAuthenticationService @Inject constructor( loginHint: String?, ): Result { return withContext(coroutineDispatchers.io) { - runCatching { + runCatchingExceptions { val client = currentClient ?: error("You need to call `setHomeserver()` first") val oAuthAuthorizationData = client.urlForOidc( oidcConfiguration = oidcConfigurationProvider.get(), @@ -211,7 +212,7 @@ class RustMatrixAuthenticationService @Inject constructor( override suspend fun cancelOidcLogin(): Result { return withContext(coroutineDispatchers.io) { - runCatching { + runCatchingExceptions { pendingOAuthAuthorizationData?.use { currentClient?.abortOidcAuth(it) } @@ -228,7 +229,7 @@ class RustMatrixAuthenticationService @Inject constructor( */ override suspend fun loginWithOidc(callbackUrl: String): Result { return withContext(coroutineDispatchers.io) { - runCatching { + runCatchingExceptions { val client = currentClient ?: error("You need to call `setHomeserver()` first") val currentSessionPaths = sessionPaths ?: error("You need to call `setHomeserver()` first") client.loginWithOidcCallback(callbackUrl) @@ -268,7 +269,7 @@ class RustMatrixAuthenticationService @Inject constructor( progress(state.toStep()) } } - runCatching { + runCatchingExceptions { val client = makeQrCodeLoginClient( sessionPaths = emptySessionPaths, passphrase = pendingPassphrase, diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/RustQrCodeLoginDataFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/RustQrCodeLoginDataFactory.kt index 7efb918bfae..367a272a75c 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/RustQrCodeLoginDataFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/qrlogin/RustQrCodeLoginDataFactory.kt @@ -8,6 +8,7 @@ package io.element.android.libraries.matrix.impl.auth.qrlogin import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.di.AppScope import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginDataFactory @@ -17,6 +18,6 @@ import javax.inject.Inject @ContributesBinding(AppScope::class) class RustQrCodeLoginDataFactory @Inject constructor() : MatrixQrCodeLoginDataFactory { override fun parseQrCodeData(data: ByteArray): Result { - return runCatching { SdkQrCodeLoginData(QrCodeData.fromBytes(data)) } + return runCatchingExceptions { SdkQrCodeLoginData(QrCodeData.fromBytes(data)) } } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/call/ElementWellKnownParser.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/call/ElementWellKnownParser.kt index 46d601f5230..710e18f5acb 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/call/ElementWellKnownParser.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/call/ElementWellKnownParser.kt @@ -8,6 +8,7 @@ package io.element.android.libraries.matrix.impl.call import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.di.AppScope import org.matrix.rustcomponents.sdk.ElementWellKnown import org.matrix.rustcomponents.sdk.makeElementWellKnown @@ -20,7 +21,7 @@ interface ElementWellKnownParser { @ContributesBinding(AppScope::class) class RustElementWellKnownParser @Inject constructor() : ElementWellKnownParser { override fun parse(str: String): Result { - return runCatching { + return runCatchingExceptions { makeElementWellKnown(str) } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/core/RustSendHandle.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/core/RustSendHandle.kt index 41a6edf2405..9d5e1137038 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/core/RustSendHandle.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/core/RustSendHandle.kt @@ -7,13 +7,14 @@ package io.element.android.libraries.matrix.impl.core +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.matrix.api.core.SendHandle class RustSendHandle( val inner: org.matrix.rustcomponents.sdk.SendHandle, ) : SendHandle { override suspend fun retry(): Result { - return runCatching { + return runCatchingExceptions { inner.tryResend() } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt index 0ffe5208f7f..7c87666fe7f 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt @@ -10,6 +10,7 @@ package io.element.android.libraries.matrix.impl.encryption import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.extensions.flatMap import io.element.android.libraries.core.extensions.mapFailure +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.encryption.BackupState @@ -96,7 +97,7 @@ internal class RustEncryptionService( .stateIn(sessionCoroutineScope, SharingStarted.Eagerly, false) override suspend fun enableBackups(): Result = withContext(dispatchers.io) { - runCatching { + runCatchingExceptions { service.enableBackups() }.mapFailure { it.mapRecoveryException() @@ -106,7 +107,7 @@ internal class RustEncryptionService( override suspend fun enableRecovery( waitForBackupsToUpload: Boolean, ): Result = withContext(dispatchers.io) { - runCatching { + runCatchingExceptions { service.enableRecovery( waitForBackupsToUpload = waitForBackupsToUpload, progressListener = object : EnableRecoveryProgressListener { @@ -124,14 +125,14 @@ internal class RustEncryptionService( } override suspend fun doesBackupExistOnServer(): Result = withContext(dispatchers.io) { - runCatching { + runCatchingExceptions { service.backupExistsOnServer() } } override fun waitForBackupUploadSteadyState(): Flow { return callbackFlow { - runCatching { + runCatchingExceptions { service.waitForBackupUploadSteadyState( progressListener = object : BackupSteadyStateListener { override fun onUpdate(status: RustBackupUploadState) { @@ -155,7 +156,7 @@ internal class RustEncryptionService( } override suspend fun disableRecovery(): Result = withContext(dispatchers.io) { - runCatching { + runCatchingExceptions { service.disableRecovery() }.mapFailure { it.mapRecoveryException() @@ -163,7 +164,7 @@ internal class RustEncryptionService( } private suspend fun isLastDevice(): Result = withContext(dispatchers.io) { - runCatching { + runCatchingExceptions { service.isLastDevice() }.mapFailure { it.mapRecoveryException() @@ -171,7 +172,7 @@ internal class RustEncryptionService( } override suspend fun resetRecoveryKey(): Result = withContext(dispatchers.io) { - runCatching { + runCatchingExceptions { service.resetRecoveryKey() }.mapFailure { it.mapRecoveryException() @@ -179,7 +180,7 @@ internal class RustEncryptionService( } override suspend fun recover(recoveryKey: String): Result = withContext(dispatchers.io) { - runCatching { + runCatchingExceptions { service.recover(recoveryKey) }.mapFailure { it.mapRecoveryException() @@ -187,34 +188,34 @@ internal class RustEncryptionService( } override suspend fun deviceCurve25519(): String? { - return runCatching { service.curve25519Key() }.getOrNull() + return runCatchingExceptions { service.curve25519Key() }.getOrNull() } override suspend fun deviceEd25519(): String? { - return runCatching { service.ed25519Key() }.getOrNull() + return runCatchingExceptions { service.ed25519Key() }.getOrNull() } override suspend fun startIdentityReset(): Result { - return runCatching { + return runCatchingExceptions { service.resetIdentity() }.flatMap { handle -> RustIdentityResetHandleFactory.create(sessionId, handle) } } - override suspend fun isUserVerified(userId: UserId): Result = runCatching { + override suspend fun isUserVerified(userId: UserId): Result = runCatchingExceptions { getUserIdentityInternal(userId).isVerified() } - override suspend fun pinUserIdentity(userId: UserId): Result = runCatching { + override suspend fun pinUserIdentity(userId: UserId): Result = runCatchingExceptions { getUserIdentityInternal(userId).pin() } - override suspend fun withdrawVerification(userId: UserId): Result = runCatching { + override suspend fun withdrawVerification(userId: UserId): Result = runCatchingExceptions { getUserIdentityInternal(userId).withdrawVerification() } - override suspend fun getUserIdentity(userId: UserId): Result = runCatching { + override suspend fun getUserIdentity(userId: UserId): Result = runCatchingExceptions { val identity = getUserIdentityInternal(userId) val isVerified = identity.isVerified() when { diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustIdentityResetHandle.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustIdentityResetHandle.kt index e16c094569c..3651cc52ac2 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustIdentityResetHandle.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustIdentityResetHandle.kt @@ -7,6 +7,7 @@ package io.element.android.libraries.matrix.impl.encryption +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.encryption.IdentityOidcResetHandle import io.element.android.libraries.matrix.api.encryption.IdentityPasswordResetHandle @@ -20,7 +21,7 @@ object RustIdentityResetHandleFactory { userId: UserId, identityResetHandle: org.matrix.rustcomponents.sdk.IdentityResetHandle? ): Result { - return runCatching { + return runCatchingExceptions { identityResetHandle?.let { when (val authType = identityResetHandle.authType()) { is CrossSigningResetAuthType.Oidc -> RustOidcIdentityResetHandle(identityResetHandle, authType.info.approvalUrl) @@ -37,7 +38,7 @@ class RustPasswordIdentityResetHandle( private val identityResetHandle: org.matrix.rustcomponents.sdk.IdentityResetHandle, ) : IdentityPasswordResetHandle { override suspend fun resetPassword(password: String): Result { - return runCatching { identityResetHandle.reset(AuthData.Password(AuthDataPasswordDetails(userId.value, password))) } + return runCatchingExceptions { identityResetHandle.reset(AuthData.Password(AuthDataPasswordDetails(userId.value, password))) } } override suspend fun cancel() { @@ -50,7 +51,7 @@ class RustOidcIdentityResetHandle( override val url: String, ) : IdentityOidcResetHandle { override suspend fun resetOidc(): Result { - return runCatching { identityResetHandle.reset(null) } + return runCatchingExceptions { identityResetHandle.reset(null) } } override suspend fun cancel() { diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/MediaUploadHandlerImpl.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/MediaUploadHandlerImpl.kt index 185bdf42c71..dbfa67f374a 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/MediaUploadHandlerImpl.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/MediaUploadHandlerImpl.kt @@ -8,6 +8,7 @@ package io.element.android.libraries.matrix.impl.media import io.element.android.libraries.androidutils.file.safeDelete +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.matrix.api.media.MediaUploadHandler import org.matrix.rustcomponents.sdk.SendAttachmentJoinHandle import java.io.File @@ -17,7 +18,7 @@ class MediaUploadHandlerImpl( private val sendAttachmentJoinHandle: SendAttachmentJoinHandle, ) : MediaUploadHandler { override suspend fun await(): Result = - runCatching { + runCatchingExceptions { sendAttachmentJoinHandle.join() } .also { cleanUpFiles() } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaLoader.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaLoader.kt index a7a926239b8..9fb527b50ee 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaLoader.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/RustMediaLoader.kt @@ -8,6 +8,7 @@ package io.element.android.libraries.matrix.impl.media import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.matrix.api.media.MatrixMediaLoader import io.element.android.libraries.matrix.api.media.MediaFile @@ -34,7 +35,7 @@ class RustMediaLoader( @OptIn(ExperimentalUnsignedTypes::class) override suspend fun loadMediaContent(source: MediaSource): Result = withContext(mediaDispatcher) { - runCatching { + runCatchingExceptions { source.toRustMediaSource().use { source -> innerClient.getMediaContent(source) } @@ -48,7 +49,7 @@ class RustMediaLoader( height: Long ): Result = withContext(mediaDispatcher) { - runCatching { + runCatchingExceptions { source.toRustMediaSource().use { mediaSource -> innerClient.getMediaThumbnail( mediaSource = mediaSource, @@ -66,7 +67,7 @@ class RustMediaLoader( useCache: Boolean, ): Result = withContext(mediaDispatcher) { - runCatching { + runCatchingExceptions { source.toRustMediaSource().use { mediaSource -> val mediaFile = innerClient.getMediaFile( mediaSource = mediaSource, diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/RustNotificationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/RustNotificationService.kt index 7cf432bad41..bab7cdea025 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/RustNotificationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/RustNotificationService.kt @@ -8,6 +8,7 @@ package io.element.android.libraries.matrix.impl.notification import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.core.extensions.runCatchingExceptions 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 @@ -30,7 +31,7 @@ class RustNotificationService( override suspend fun getNotifications( ids: Map> ): Result> = withContext(dispatchers.io) { - runCatching { + runCatchingExceptions { val requests = ids.map { (roomId, eventIds) -> NotificationItemsRequest( roomId = roomId.value, diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notificationsettings/RustNotificationSettingsService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notificationsettings/RustNotificationSettingsService.kt index 5cfd9741ddc..b4f3e70754e 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notificationsettings/RustNotificationSettingsService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notificationsettings/RustNotificationSettingsService.kt @@ -9,6 +9,7 @@ package io.element.android.libraries.matrix.impl.notificationsettings import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.suspendLazy +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService import io.element.android.libraries.matrix.api.room.RoomNotificationMode @@ -48,12 +49,12 @@ class RustNotificationSettingsService( } override suspend fun getRoomNotificationSettings(roomId: RoomId, isEncrypted: Boolean, isOneToOne: Boolean): Result = - runCatching { + runCatchingExceptions { notificationSettings.await().getRoomNotificationSettings(roomId.value, isEncrypted, isOneToOne).let(RoomNotificationSettingsMapper::map) } override suspend fun getDefaultRoomNotificationMode(isEncrypted: Boolean, isOneToOne: Boolean): Result = - runCatching { + runCatchingExceptions { notificationSettings.await().getDefaultRoomNotificationMode(isEncrypted, isOneToOne).let(RoomNotificationSettingsMapper::mapMode) } @@ -62,7 +63,7 @@ class RustNotificationSettingsService( mode: RoomNotificationMode, isOneToOne: Boolean ): Result = withContext(dispatchers.io) { - runCatching { + runCatchingExceptions { try { notificationSettings.await().setDefaultRoomNotificationMode(isEncrypted, isOneToOne, mode.let(RoomNotificationSettingsMapper::mapMode)) } catch (exception: NotificationSettingsException.RuleNotFound) { @@ -74,13 +75,13 @@ class RustNotificationSettingsService( } override suspend fun setRoomNotificationMode(roomId: RoomId, mode: RoomNotificationMode): Result = withContext(dispatchers.io) { - runCatching { + runCatchingExceptions { notificationSettings.await().setRoomNotificationMode(roomId.value, mode.let(RoomNotificationSettingsMapper::mapMode)) } } override suspend fun restoreDefaultRoomNotificationMode(roomId: RoomId): Result = withContext(dispatchers.io) { - runCatching { + runCatchingExceptions { notificationSettings.await().restoreDefaultRoomNotificationMode(roomId.value) } } @@ -88,54 +89,54 @@ class RustNotificationSettingsService( override suspend fun muteRoom(roomId: RoomId): Result = setRoomNotificationMode(roomId, RoomNotificationMode.MUTE) override suspend fun unmuteRoom(roomId: RoomId, isEncrypted: Boolean, isOneToOne: Boolean) = withContext(dispatchers.io) { - runCatching { + runCatchingExceptions { notificationSettings.await().unmuteRoom(roomId.value, isEncrypted, isOneToOne) } } override suspend fun isRoomMentionEnabled(): Result = withContext(dispatchers.io) { - runCatching { + runCatchingExceptions { notificationSettings.await().isRoomMentionEnabled() } } override suspend fun setRoomMentionEnabled(enabled: Boolean): Result = withContext(dispatchers.io) { - runCatching { + runCatchingExceptions { notificationSettings.await().setRoomMentionEnabled(enabled) } } override suspend fun isCallEnabled(): Result = withContext(dispatchers.io) { - runCatching { + runCatchingExceptions { notificationSettings.await().isCallEnabled() } } override suspend fun setCallEnabled(enabled: Boolean): Result = withContext(dispatchers.io) { - runCatching { + runCatchingExceptions { notificationSettings.await().setCallEnabled(enabled) } } override suspend fun isInviteForMeEnabled(): Result = withContext(dispatchers.io) { - runCatching { + runCatchingExceptions { notificationSettings.await().isInviteForMeEnabled() } } override suspend fun setInviteForMeEnabled(enabled: Boolean): Result = withContext(dispatchers.io) { - runCatching { + runCatchingExceptions { notificationSettings.await().setInviteForMeEnabled(enabled) } } override suspend fun getRoomsWithUserDefinedRules(): Result> = - runCatching { + runCatchingExceptions { notificationSettings.await().getRoomsWithUserDefinedRules(enabled = true) } override suspend fun canHomeServerPushEncryptedEventsToDevice(): Result = - runCatching { + runCatchingExceptions { notificationSettings.await().canPushEncryptedEventToDevice() } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkBuilder.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkBuilder.kt index 50b3eb1d3e0..eb0524886f1 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkBuilder.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkBuilder.kt @@ -8,6 +8,7 @@ package io.element.android.libraries.matrix.impl.permalink import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.di.AppScope import io.element.android.libraries.matrix.api.core.MatrixPatterns import io.element.android.libraries.matrix.api.core.RoomAlias @@ -24,7 +25,7 @@ class DefaultPermalinkBuilder @Inject constructor() : PermalinkBuilder { if (!MatrixPatterns.isUserId(userId.value)) { return Result.failure(PermalinkBuilderError.InvalidData) } - return runCatching { + return runCatchingExceptions { matrixToUserPermalink(userId.value) } } @@ -33,7 +34,7 @@ class DefaultPermalinkBuilder @Inject constructor() : PermalinkBuilder { if (!MatrixPatterns.isRoomAlias(roomAlias.value)) { return Result.failure(PermalinkBuilderError.InvalidData) } - return runCatching { + return runCatchingExceptions { matrixToRoomAliasPermalink(roomAlias.value) } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkParser.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkParser.kt index 80f59b94698..0bf3e908b65 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkParser.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkParser.kt @@ -9,6 +9,7 @@ package io.element.android.libraries.matrix.impl.permalink import androidx.core.net.toUri import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.di.AppScope import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomAlias @@ -44,7 +45,7 @@ class DefaultPermalinkParser @Inject constructor( // so convert URI to matrix.to to simplify parsing process val matrixToUri = matrixToConverter.convert(uri) ?: return PermalinkData.FallbackLink(uri) - val result = runCatching { + val result = runCatchingExceptions { parseMatrixEntityFrom(matrixToUri.toString()) }.getOrNull() return if (result == null) { diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/pushers/RustPushersService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/pushers/RustPushersService.kt index 3ff24e85d8d..0fed390b143 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/pushers/RustPushersService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/pushers/RustPushersService.kt @@ -9,6 +9,7 @@ package io.element.android.libraries.matrix.impl.pushers import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.extensions.mapFailure +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.matrix.api.pusher.PushersService import io.element.android.libraries.matrix.api.pusher.SetHttpPusherData import io.element.android.libraries.matrix.api.pusher.UnsetHttpPusherData @@ -26,7 +27,7 @@ class RustPushersService( ) : PushersService { override suspend fun setHttpPusher(setHttpPusherData: SetHttpPusherData): Result { return withContext(dispatchers.io) { - runCatching { + runCatchingExceptions { client.setPusher( identifiers = PusherIdentifiers( pushkey = setHttpPusherData.pushKey, @@ -51,7 +52,7 @@ class RustPushersService( override suspend fun unsetHttpPusher(unsetHttpPusherData: UnsetHttpPusherData): Result { return withContext(dispatchers.io) { - runCatching { + runCatchingExceptions { client.deletePusher( identifiers = PusherIdentifiers( pushkey = unsetHttpPusherData.pushKey, diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/JoinedRustRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/JoinedRustRoom.kt index cca8af2785b..18e6571a273 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/JoinedRustRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/JoinedRustRoom.kt @@ -10,6 +10,7 @@ package io.element.android.libraries.matrix.impl.room import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.childScope import io.element.android.libraries.core.extensions.mapFailure +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.matrix.api.core.DeviceId import io.element.android.libraries.matrix.api.core.EventId @@ -204,7 +205,7 @@ class JoinedRustRoom( // Track read receipts only for focused timeline for performance optimization val trackReadReceipts = createTimelineParams is CreateTimelineParams.Focused - runCatching { + runCatchingExceptions { innerRoom.timelineWithConfiguration( configuration = TimelineConfiguration( focus = focus, @@ -243,7 +244,7 @@ class JoinedRustRoom( htmlBody: String?, intentionalMentions: List ): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { MessageEventContent.from(body, htmlBody, intentionalMentions).use { newContent -> innerRoom.edit(eventId.value, newContent) } @@ -251,43 +252,43 @@ class JoinedRustRoom( } override suspend fun typingNotice(isTyping: Boolean) = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.typingNotice(isTyping) } } override suspend fun inviteUserById(id: UserId): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.inviteUserById(id.value) } } override suspend fun updateAvatar(mimeType: String, data: ByteArray): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.uploadAvatar(mimeType, data, null) } } override suspend fun removeAvatar(): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.removeAvatar() } } override suspend fun setName(name: String): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.setName(name) } } override suspend fun setTopic(topic: String): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.setTopic(topic) } } override suspend fun reportContent(eventId: EventId, reason: String, blockUserId: UserId?): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.reportContent(eventId = eventId.value, score = null, reason = reason) if (blockUserId != null) { innerRoom.ignoreUser(blockUserId.value) @@ -299,7 +300,7 @@ class JoinedRustRoom( val currentState = roomNotificationSettingsStateFlow.value val currentRoomNotificationSettings = currentState.roomNotificationSettings() roomNotificationSettingsStateFlow.value = RoomNotificationSettingsState.Pending(prevRoomNotificationSettings = currentRoomNotificationSettings) - runCatching { + runCatchingExceptions { val isEncrypted = roomInfoFlow.value.isEncrypted ?: getUpdatedIsEncrypted().getOrThrow() notificationSettingsService.getRoomNotificationSettings(roomId, isEncrypted, isOneToOne).getOrThrow() }.map { @@ -313,56 +314,56 @@ class JoinedRustRoom( } override suspend fun updateCanonicalAlias(canonicalAlias: RoomAlias?, alternativeAliases: List): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.updateCanonicalAlias(canonicalAlias?.value, alternativeAliases.map { it.value }) } } override suspend fun publishRoomAliasInRoomDirectory(roomAlias: RoomAlias): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.publishRoomAliasInRoomDirectory(roomAlias.value) } } override suspend fun removeRoomAliasFromRoomDirectory(roomAlias: RoomAlias): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.removeRoomAliasFromRoomDirectory(roomAlias.value) } } override suspend fun updateRoomVisibility(roomVisibility: RoomVisibility): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.updateRoomVisibility(roomVisibility.map()) } } override suspend fun updateHistoryVisibility(historyVisibility: RoomHistoryVisibility): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.updateHistoryVisibility(historyVisibility.map()) } } override suspend fun enableEncryption(): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.enableEncryption() } } override suspend fun updateJoinRule(joinRule: JoinRule): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.updateJoinRules(joinRule.map()) } } override suspend fun updateUsersRoles(changes: List): Result { - return runCatching { + return runCatchingExceptions { val powerLevelChanges = changes.map { UserPowerLevelUpdate(it.userId.value, it.powerLevel) } innerRoom.updatePowerLevelsForUsers(powerLevelChanges) } } override suspend fun updatePowerLevels(roomPowerLevels: RoomPowerLevels): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { val changes = RoomPowerLevelChanges( ban = roomPowerLevels.ban, invite = roomPowerLevels.invite, @@ -378,25 +379,25 @@ class JoinedRustRoom( } override suspend fun resetPowerLevels(): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { RoomPowerLevelsMapper.map(innerRoom.resetPowerLevels()) } } override suspend fun kickUser(userId: UserId, reason: String?): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.kickUser(userId.value, reason) } } override suspend fun banUser(userId: UserId, reason: String?): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.banUser(userId.value, reason) } } override suspend fun unbanUser(userId: UserId, reason: String?): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.unbanUser(userId.value, reason) } } @@ -407,13 +408,13 @@ class JoinedRustRoom( languageTag: String?, theme: String?, ) = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { widgetSettings.generateWidgetWebViewUrl(innerRoom, clientId, languageTag, theme) } } override fun getWidgetDriver(widgetSettings: MatrixWidgetSettings): Result { - return runCatching { + return runCatchingExceptions { RustWidgetDriver( widgetSettings = widgetSettings, room = innerRoom, @@ -427,7 +428,7 @@ class JoinedRustRoom( } override suspend fun sendCallNotificationIfNeeded(): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.sendCallNotificationIfNeeded() } } @@ -435,14 +436,14 @@ class JoinedRustRoom( override suspend fun setSendQueueEnabled(enabled: Boolean) { withContext(roomDispatcher) { Timber.d("setSendQueuesEnabled: $enabled") - runCatching { + runCatchingExceptions { innerRoom.enableSendQueue(enabled) } } } override suspend fun ignoreDeviceTrustAndResend(devices: Map>, sendHandle: SendHandle) = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.ignoreDeviceTrustAndResend( devices = devices.entries.associate { entry -> entry.key.value to entry.value.map { it.value } @@ -453,7 +454,7 @@ class JoinedRustRoom( } override suspend fun withdrawVerificationAndResend(userIds: List, sendHandle: SendHandle) = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.withdrawVerificationAndResend( userIds = userIds.map { it.value }, sendHandle = (sendHandle as RustSendHandle).inner, diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/NotJoinedRustRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/NotJoinedRustRoom.kt index 3f3cb4f6cdf..fb88cf625bf 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/NotJoinedRustRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/NotJoinedRustRoom.kt @@ -8,6 +8,7 @@ package io.element.android.libraries.matrix.impl.room import androidx.compose.runtime.Immutable +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.room.NotJoinedRoom import io.element.android.libraries.matrix.api.room.RoomMembershipDetails @@ -20,8 +21,8 @@ class NotJoinedRustRoom( override val localRoom: RustBaseRoom?, override val previewInfo: RoomPreviewInfo, ) : NotJoinedRoom { - override suspend fun membershipDetails(): Result = runCatching { - val room = localRoom?.innerRoom ?: return@runCatching null + override suspend fun membershipDetails(): Result = runCatchingExceptions { + val room = localRoom?.innerRoom ?: return@runCatchingExceptions null val (ownMember, senderInfo) = room.memberWithSenderInfo(sessionId.value) RoomMembershipDetails( currentUserMember = RoomMemberMapper.map(ownMember), diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomContentForwarder.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomContentForwarder.kt index 63f22dbf7d4..9d7495691da 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomContentForwarder.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomContentForwarder.kt @@ -8,6 +8,7 @@ package io.element.android.libraries.matrix.impl.room import io.element.android.libraries.core.coroutine.parallelMap +import io.element.android.libraries.core.extensions.runCatchingExceptions 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.room.ForwardEventException @@ -52,7 +53,7 @@ class RoomContentForwarder( val failedForwardingTo = mutableSetOf() targetRooms.parallelMap { room -> room.use { targetRoom -> - runCatching { + runCatchingExceptions { // Sending a message requires a registered timeline listener targetRoom.timeline().runWithTimelineListenerRegistered { withTimeout(timeoutMs.milliseconds) { diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustBaseRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustBaseRoom.kt index 3a7a78d4446..f86a179af29 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustBaseRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustBaseRoom.kt @@ -9,6 +9,7 @@ package io.element.android.libraries.matrix.impl.room import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.childScope +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.matrix.api.core.DeviceId import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId @@ -90,7 +91,7 @@ class RustBaseRoom( } override suspend fun getMembers(limit: Int) = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.members().use { it.nextChunk(limit.toUInt()).orEmpty().map { roomMember -> RoomMemberMapper.map(roomMember) @@ -100,7 +101,7 @@ class RustBaseRoom( } override suspend fun getUpdatedMember(userId: UserId): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { RoomMemberMapper.map(innerRoom.member(userId.value)) } } @@ -113,32 +114,32 @@ class RustBaseRoom( } override suspend fun userDisplayName(userId: UserId): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.memberDisplayName(userId.value) } } override suspend fun userRole(userId: UserId): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { RoomMemberMapper.mapRole(innerRoom.suggestedRoleForUser(userId.value)) } } override suspend fun powerLevels(): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { RoomPowerLevelsMapper.map(innerRoom.getPowerLevels()) } } override suspend fun userAvatarUrl(userId: UserId): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.memberAvatarUrl(userId.value) } } override suspend fun leave(): Result = withContext(roomDispatcher) { val membershipBeforeLeft = roomInfoFlow.value.currentUserMembership - runCatching { + runCatchingExceptions { innerRoom.leave() }.onSuccess { roomMembershipObserver.notifyUserLeftRoom(roomId, membershipBeforeLeft) @@ -146,142 +147,142 @@ class RustBaseRoom( } override suspend fun join(): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.join() } } override suspend fun forget(): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.forget() } } override suspend fun canUserInvite(userId: UserId): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.canUserInvite(userId.value) } } override suspend fun canUserKick(userId: UserId): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.canUserKick(userId.value) } } override suspend fun canUserBan(userId: UserId): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.canUserBan(userId.value) } } override suspend fun canUserRedactOwn(userId: UserId): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.canUserRedactOwn(userId.value) } } override suspend fun canUserRedactOther(userId: UserId): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.canUserRedactOther(userId.value) } } override suspend fun canUserSendState(userId: UserId, type: StateEventType): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.canUserSendState(userId.value, type.map()) } } override suspend fun canUserSendMessage(userId: UserId, type: MessageEventType): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.canUserSendMessage(userId.value, type.map()) } } override suspend fun canUserTriggerRoomNotification(userId: UserId): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.canUserTriggerRoomNotification(userId.value) } } override suspend fun canUserPinUnpin(userId: UserId): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.canUserPinUnpin(userId.value) } } override suspend fun clearEventCacheStorage(): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.clearEventCacheStorage() } } override suspend fun setIsFavorite(isFavorite: Boolean): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.setIsFavourite(isFavorite, null) } } override suspend fun markAsRead(receiptType: ReceiptType): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.markAsRead(receiptType.toRustReceiptType()) } } override suspend fun setUnreadFlag(isUnread: Boolean): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.setUnreadFlag(isUnread) } } override suspend fun getPermalink(): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.matrixToPermalink() } } override suspend fun getPermalinkFor(eventId: EventId): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.matrixToEventPermalink(eventId.value) } } override suspend fun getRoomVisibility(): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.getRoomVisibility().map() } } override suspend fun getUpdatedIsEncrypted(): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { innerRoom.latestEncryptionState() == EncryptionState.ENCRYPTED } } override suspend fun saveComposerDraft(composerDraft: ComposerDraft): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { Timber.d("saveComposerDraft: $composerDraft into $roomId") innerRoom.saveComposerDraft(composerDraft.into()) } } override suspend fun loadComposerDraft(): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { Timber.d("loadComposerDraft for $roomId") innerRoom.loadComposerDraft()?.into() } } override suspend fun clearComposerDraft(): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { Timber.d("clearComposerDraft for $roomId") innerRoom.clearComposerDraft() } } override suspend fun reportRoom(reason: String?): Result = withContext(roomDispatcher) { - runCatching { + runCatchingExceptions { Timber.d("reportRoom $roomId") innerRoom.reportRoom(reason) } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/knock/RustKnockRequest.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/knock/RustKnockRequest.kt index 70167ec45c7..02e405969b6 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/knock/RustKnockRequest.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/knock/RustKnockRequest.kt @@ -7,6 +7,7 @@ package io.element.android.libraries.matrix.impl.room.knock +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.knock.KnockRequest @@ -23,19 +24,19 @@ class RustKnockRequest( override val timestamp: Long? = inner.timestamp?.toLong() override val isSeen: Boolean = inner.isSeen - override suspend fun accept(): Result = runCatching { + override suspend fun accept(): Result = runCatchingExceptions { inner.actions.accept() } - override suspend fun decline(reason: String?): Result = runCatching { + override suspend fun decline(reason: String?): Result = runCatchingExceptions { inner.actions.decline(reason) } - override suspend fun declineAndBan(reason: String?): Result = runCatching { + override suspend fun declineAndBan(reason: String?): Result = runCatchingExceptions { inner.actions.declineAndBan(reason) } - override suspend fun markAsSeen(): Result = runCatching { + override suspend fun markAsSeen(): Result = runCatchingExceptions { inner.actions.markAsSeen() } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListExtensions.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListExtensions.kt index adcdfee9e19..8cdcd13f224 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListExtensions.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomlist/RoomListExtensions.kt @@ -116,7 +116,7 @@ internal fun RoomListServiceInterface.syncIndicator(): Flow = timelineDiffProcessor.membershipChangeEventReceived override suspend fun sendReadReceipt(eventId: EventId, receiptType: ReceiptType): Result = withContext(dispatcher) { - runCatching { + runCatchingExceptions { inner.sendReadReceipt(receiptType.toRustReceiptType(), eventId.value) } } @@ -181,7 +182,7 @@ class RustTimeline( override suspend fun paginate(direction: Timeline.PaginationDirection): Result = withContext(NonCancellable) { withContext(dispatcher) { initLatch.await() - runCatching { + runCatchingExceptions { if (!canPaginate(direction)) throw TimelineException.CannotPaginate updatePaginationStatus(direction) { it.copy(isPaginating = true) } when (direction) { @@ -275,14 +276,14 @@ class RustTimeline( intentionalMentions: List, ): Result = withContext(dispatcher) { MessageEventContent.from(body, htmlBody, intentionalMentions).use { content -> - runCatching { + runCatchingExceptions { inner.send(content) } } } override suspend fun redactEvent(eventOrTransactionId: EventOrTransactionId, reason: String?): Result = withContext(dispatcher) { - runCatching { + runCatchingExceptions { inner.redactEvent( eventOrTransactionId = eventOrTransactionId.toRustEventOrTransactionId(), reason = reason, @@ -296,7 +297,7 @@ class RustTimeline( htmlBody: String?, intentionalMentions: List, ): Result = withContext(dispatcher) { - runCatching { + runCatchingExceptions { val editedContent = EditedContent.RoomMessage( content = MessageEventContent.from( body = body, @@ -316,7 +317,7 @@ class RustTimeline( caption: String?, formattedCaption: String?, ): Result = withContext(dispatcher) { - runCatching { + runCatchingExceptions { val editedContent = EditedContent.MediaCaption( caption = caption, formattedCaption = formattedCaption?.let { @@ -340,7 +341,7 @@ class RustTimeline( intentionalMentions: List, fromNotification: Boolean, ): Result = withContext(dispatcher) { - runCatching { + runCatchingExceptions { val msg = MessageEventContent.from(body, htmlBody, intentionalMentions) inner.sendReply( msg = msg, @@ -462,7 +463,7 @@ class RustTimeline( } override suspend fun toggleReaction(emoji: String, eventOrTransactionId: EventOrTransactionId): Result = withContext(dispatcher) { - runCatching { + runCatchingExceptions { inner.toggleReaction( key = emoji, itemId = eventOrTransactionId.toRustEventOrTransactionId(), @@ -471,7 +472,7 @@ class RustTimeline( } override suspend fun forwardEvent(eventId: EventId, roomIds: List): Result = withContext(dispatcher) { - runCatching { + runCatchingExceptions { roomContentForwarder.forward(fromTimeline = inner, eventId = eventId, toRoomIds = roomIds) }.onFailure { Timber.e(it) @@ -485,7 +486,7 @@ class RustTimeline( zoomLevel: Int?, assetType: AssetType?, ): Result = withContext(dispatcher) { - runCatching { + runCatchingExceptions { inner.sendLocation( body = body, geoUri = geoUri, @@ -528,7 +529,7 @@ class RustTimeline( maxSelections: Int, pollKind: PollKind, ): Result = withContext(dispatcher) { - runCatching { + runCatchingExceptions { inner.createPoll( question = question, answers = answers, @@ -545,7 +546,7 @@ class RustTimeline( maxSelections: Int, pollKind: PollKind, ): Result = withContext(dispatcher) { - runCatching { + runCatchingExceptions { val editedContent = EditedContent.PollStart( pollData = PollData( question = question, @@ -565,7 +566,7 @@ class RustTimeline( pollStartId: EventId, answers: List ): Result = withContext(dispatcher) { - runCatching { + runCatchingExceptions { inner.sendPollResponse( pollStartEventId = pollStartId.value, answers = answers, @@ -577,7 +578,7 @@ class RustTimeline( pollStartId: EventId, text: String ): Result = withContext(dispatcher) { - runCatching { + runCatchingExceptions { inner.endPoll( pollStartEventId = pollStartId.value, text = text, @@ -586,7 +587,7 @@ class RustTimeline( } private fun sendAttachment(files: List, handle: () -> SendAttachmentJoinHandle): Result { - return runCatching { + return runCatchingExceptions { MediaUploadHandlerImpl(files, handle()) } } @@ -609,19 +610,19 @@ class RustTimeline( } override suspend fun pinEvent(eventId: EventId): Result = withContext(dispatcher) { - runCatching { + runCatchingExceptions { inner.pinEvent(eventId = eventId.value) } } override suspend fun unpinEvent(eventId: EventId): Result = withContext(dispatcher) { - runCatching { + runCatchingExceptions { inner.unpinEvent(eventId = eventId.value) } } private suspend fun fetchDetailsForEvent(eventId: EventId): Result = withContext(dispatcher) { - runCatching { + runCatchingExceptions { inner.fetchDetailsForEvent(eventId.value) } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt index e714d511bf7..12e72eac0b7 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt @@ -8,6 +8,7 @@ package io.element.android.libraries.matrix.impl.verification import io.element.android.libraries.core.data.tryOrNull +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.verification.SessionVerificationData import io.element.android.libraries.matrix.api.verification.SessionVerificationService @@ -100,7 +101,6 @@ class RustSessionVerificationService( init { // Instantiate the verification controller when possible, this is needed to get incoming verification requests sessionCoroutineScope.launch { - // Needed to avoid crashes on unit tests due to the Rust SDK not being available tryOrNull { encryptionService.waitForE2eeInitializationTasks() initVerificationControllerIfNeeded() @@ -152,7 +152,7 @@ class RustSessionVerificationService( } private suspend fun tryOrFail(block: suspend () -> Unit) { - runCatching { + runCatchingExceptions { // Ensure the block cannot be cancelled, else if the Rust SDK emit a new state during the API execution, // the state machine may cancel the api call. withContext(NonCancellable) { @@ -184,7 +184,7 @@ class RustSessionVerificationService( sessionCoroutineScope.launch { // Ideally this should be `verificationController?.isVerified().orFalse()` but for some reason it returns false if run immediately // It also sometimes unexpectedly fails to report the session as verified, so we have to handle that possibility and fail if needed - runCatching { + runCatchingExceptions { withTimeout(20.seconds) { // Wait until the SDK reports the state as verified sessionVerifiedStatus.first { it == SessionVerifiedStatus.Verified } @@ -252,7 +252,7 @@ class RustSessionVerificationService( } private fun updateVerificationStatus() { - runCatching { + runCatchingExceptions { _sessionVerifiedStatus.value = encryptionService.verificationState().map() Timber.d("New verification status: ${_sessionVerifiedStatus.value}") } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustClient.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustClient.kt index 583e397daa0..7371486c653 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustClient.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustClient.kt @@ -15,6 +15,7 @@ import io.element.android.tests.testutils.simulateLongTask import org.matrix.rustcomponents.sdk.Client import org.matrix.rustcomponents.sdk.ClientDelegate import org.matrix.rustcomponents.sdk.Encryption +import org.matrix.rustcomponents.sdk.IgnoredUsersListener import org.matrix.rustcomponents.sdk.NoPointer import org.matrix.rustcomponents.sdk.NotificationClient import org.matrix.rustcomponents.sdk.NotificationProcessSetup @@ -23,9 +24,11 @@ import org.matrix.rustcomponents.sdk.PusherIdentifiers import org.matrix.rustcomponents.sdk.PusherKind import org.matrix.rustcomponents.sdk.RoomDirectorySearch import org.matrix.rustcomponents.sdk.Session +import org.matrix.rustcomponents.sdk.SessionVerificationController import org.matrix.rustcomponents.sdk.SyncServiceBuilder import org.matrix.rustcomponents.sdk.TaskHandle import org.matrix.rustcomponents.sdk.UnableToDecryptDelegate +import org.matrix.rustcomponents.sdk.UserProfile class FakeRustClient( private val userId: String = A_USER_ID.value, @@ -61,5 +64,16 @@ class FakeRustClient( override suspend fun deletePusher(identifiers: PusherIdentifiers) = Unit override suspend fun clearCaches() = simulateLongTask { clearCachesResult() } override suspend fun setUtdDelegate(utdDelegate: UnableToDecryptDelegate) = withUtdHook(utdDelegate) + override suspend fun getSessionVerificationController(): SessionVerificationController = FakeRustSessionVerificationController() + override suspend fun ignoredUsers(): List { + return emptyList() + } + override fun subscribeToIgnoredUsers(listener: IgnoredUsersListener): TaskHandle { + return FakeRustTaskHandle() + } + + override suspend fun getProfile(userId: String): UserProfile { + return UserProfile(userId = userId, displayName = null, avatarUrl = null) + } override fun close() = closeResult() } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustEncryption.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustEncryption.kt index c966025139c..eb539686b35 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustEncryption.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustEncryption.kt @@ -7,8 +7,12 @@ package io.element.android.libraries.matrix.impl.fixtures.fakes +import io.element.android.tests.testutils.simulateLongTask +import org.matrix.rustcomponents.sdk.BackupState +import org.matrix.rustcomponents.sdk.BackupStateListener import org.matrix.rustcomponents.sdk.Encryption import org.matrix.rustcomponents.sdk.NoPointer +import org.matrix.rustcomponents.sdk.RecoveryState import org.matrix.rustcomponents.sdk.RecoveryStateListener import org.matrix.rustcomponents.sdk.TaskHandle import org.matrix.rustcomponents.sdk.VerificationStateListener @@ -21,4 +25,22 @@ class FakeRustEncryption : Encryption(NoPointer) { override fun recoveryStateListener(listener: RecoveryStateListener): TaskHandle { return FakeRustTaskHandle() } + + override suspend fun waitForE2eeInitializationTasks() = simulateLongTask {} + + override suspend fun isLastDevice(): Boolean { + return false + } + + override fun backupState(): BackupState { + return BackupState.ENABLED + } + + override fun recoveryState(): RecoveryState { + return RecoveryState.ENABLED + } + + override fun backupStateListener(listener: BackupStateListener): TaskHandle { + return FakeRustTaskHandle() + } } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustRoomListService.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustRoomListService.kt index 1251bd514f2..b0d42f98075 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustRoomListService.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustRoomListService.kt @@ -10,6 +10,7 @@ package io.element.android.libraries.matrix.impl.fixtures.fakes import org.matrix.rustcomponents.sdk.NoPointer import org.matrix.rustcomponents.sdk.RoomList import org.matrix.rustcomponents.sdk.RoomListService +import org.matrix.rustcomponents.sdk.RoomListServiceStateListener import org.matrix.rustcomponents.sdk.RoomListServiceSyncIndicator import org.matrix.rustcomponents.sdk.RoomListServiceSyncIndicatorListener import org.matrix.rustcomponents.sdk.TaskHandle @@ -32,4 +33,8 @@ class FakeRustRoomListService : RoomListService(NoPointer) { fun emitRoomListServiceSyncIndicator(syncIndicator: RoomListServiceSyncIndicator) { listener?.onUpdate(syncIndicator) } + + override fun state(listener: RoomListServiceStateListener): TaskHandle { + return FakeRustTaskHandle() + } } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustSessionVerificationController.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustSessionVerificationController.kt new file mode 100644 index 00000000000..e79663a8800 --- /dev/null +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustSessionVerificationController.kt @@ -0,0 +1,16 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.fixtures.fakes + +import org.matrix.rustcomponents.sdk.NoPointer +import org.matrix.rustcomponents.sdk.SessionVerificationController +import org.matrix.rustcomponents.sdk.SessionVerificationControllerDelegate + +class FakeRustSessionVerificationController : SessionVerificationController(NoPointer) { + override fun setDelegate(delegate: SessionVerificationControllerDelegate?) {} +} diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustSyncService.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustSyncService.kt index 734968bedab..a057f47032c 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustSyncService.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustSyncService.kt @@ -10,9 +10,15 @@ package io.element.android.libraries.matrix.impl.fixtures.fakes import org.matrix.rustcomponents.sdk.NoPointer import org.matrix.rustcomponents.sdk.RoomListService import org.matrix.rustcomponents.sdk.SyncService +import org.matrix.rustcomponents.sdk.SyncServiceStateObserver +import org.matrix.rustcomponents.sdk.TaskHandle class FakeRustSyncService( private val roomListService: RoomListService = FakeRustRoomListService(), ) : SyncService(NoPointer) { override fun roomListService(): RoomListService = roomListService + override fun state(listener: SyncServiceStateObserver): TaskHandle { + return FakeRustTaskHandle() + } + override suspend fun stop() {} } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustTimeline.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustTimeline.kt index 6fa8e75704f..03bbe006461 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustTimeline.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustTimeline.kt @@ -36,5 +36,9 @@ class FakeRustTimeline : Timeline(NoPointer) { paginationStatusListener!!.onUpdate(status) } + override suspend fun paginateBackwards(numEvents: UShort): Boolean { + return true + } + override suspend fun fetchMembers() = Unit } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt index 3a354df6b9a..87b8a348dcd 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/TestData.kt @@ -81,6 +81,7 @@ const val AN_AVATAR_URL = "mxc://data" const val A_FAILURE_REASON = "There has been a failure" +@Suppress("unused") val A_THROWABLE = Throwable(A_FAILURE_REASON) val AN_EXCEPTION = Exception(A_FAILURE_REASON) diff --git a/libraries/mediaplayer/impl/build.gradle.kts b/libraries/mediaplayer/impl/build.gradle.kts index 9d7bdfd1ae9..6bd19680a36 100644 --- a/libraries/mediaplayer/impl/build.gradle.kts +++ b/libraries/mediaplayer/impl/build.gradle.kts @@ -22,6 +22,7 @@ dependencies { implementation(libs.dagger) implementation(projects.libraries.audio.api) + implementation(projects.libraries.core) implementation(projects.libraries.di) implementation(libs.coroutines.core) diff --git a/libraries/mediaplayer/impl/src/test/kotlin/io/element/android/libraries/mediaplayer/impl/DefaultMediaPlayerTest.kt b/libraries/mediaplayer/impl/src/test/kotlin/io/element/android/libraries/mediaplayer/impl/DefaultMediaPlayerTest.kt index 8a36671fc29..8fe6ed00ab9 100644 --- a/libraries/mediaplayer/impl/src/test/kotlin/io/element/android/libraries/mediaplayer/impl/DefaultMediaPlayerTest.kt +++ b/libraries/mediaplayer/impl/src/test/kotlin/io/element/android/libraries/mediaplayer/impl/DefaultMediaPlayerTest.kt @@ -337,6 +337,7 @@ class DefaultMediaPlayerTest { duration = null, ) ) + @Suppress("RunCatchingNotAllowed") val result = runCatching { sut.setMedia("uri", "mediaId", "mimeType", 12) } diff --git a/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaSender.kt b/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaSender.kt index 47451faef43..5f542bd91a1 100644 --- a/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaSender.kt +++ b/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaSender.kt @@ -192,8 +192,10 @@ class MediaSender @Inject constructor( } } + // We handle the cancellations here manually, so we suppress the warning + @Suppress("RunCatchingNotAllowed") return handler - .flatMapCatching { uploadHandler -> + .mapCatching { uploadHandler -> ongoingUploadJobs[Job] = uploadHandler uploadHandler.await() } diff --git a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/AndroidMediaPreProcessor.kt b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/AndroidMediaPreProcessor.kt index f438d1e8333..6363fa06c95 100644 --- a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/AndroidMediaPreProcessor.kt +++ b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/AndroidMediaPreProcessor.kt @@ -21,6 +21,7 @@ import io.element.android.libraries.androidutils.media.runAndRelease import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.data.tryOrNull import io.element.android.libraries.core.extensions.mapFailure +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeAudio import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeImage @@ -73,7 +74,7 @@ class AndroidMediaPreProcessor @Inject constructor( deleteOriginal: Boolean, compressIfPossible: Boolean, ): Result = withContext(coroutineDispatchers.computation) { - runCatching { + runCatchingExceptions { val result = when { // Special case for SVG, since Android can't read its metadata or create a thumbnail, it must be sent as a file mimeType == MimeTypes.Svg -> { @@ -188,7 +189,7 @@ class AndroidMediaPreProcessor @Inject constructor( } private suspend fun processVideo(uri: Uri, mimeType: String?, shouldBeCompressed: Boolean): MediaUploadInfo { - val resultFile = runCatching { + val resultFile = runCatchingExceptions { videoCompressor.compress(uri, shouldBeCompressed) .onEach { // TODO handle progress diff --git a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/ImageCompressor.kt b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/ImageCompressor.kt index 2d66a68b06f..f14264c5d77 100644 --- a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/ImageCompressor.kt +++ b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/ImageCompressor.kt @@ -16,6 +16,7 @@ import io.element.android.libraries.androidutils.bitmap.resizeToMax import io.element.android.libraries.androidutils.bitmap.rotateToMetadataOrientation import io.element.android.libraries.androidutils.file.createTmpFile import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.di.ApplicationContext import kotlinx.coroutines.withContext import java.io.File @@ -38,7 +39,7 @@ class ImageCompressor @Inject constructor( orientation: Int = ExifInterface.ORIENTATION_UNDEFINED, desiredQuality: Int = 78, ): Result = withContext(dispatchers.io) { - runCatching { + runCatchingExceptions { val format = mimeTypeToCompressFormat(mimeType) val extension = mimeTypeToCompressFileExtension(mimeType) val compressedBitmap = compressToBitmap(inputStreamProvider, resizeMode, orientation).getOrThrow() @@ -65,7 +66,7 @@ class ImageCompressor @Inject constructor( inputStreamProvider: () -> InputStream, resizeMode: ResizeMode, orientation: Int, - ): Result = runCatching { + ): Result = runCatchingExceptions { val options = BitmapFactory.Options() // Decode bounds inputStreamProvider().use { input -> diff --git a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt index db3666a3b44..9f36b49bc06 100644 --- a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt +++ b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt @@ -22,6 +22,7 @@ import com.otaliastudios.transcoder.validator.WriteAlwaysValidator import io.element.android.libraries.androidutils.file.createTmpFile import io.element.android.libraries.androidutils.file.getMimeType import io.element.android.libraries.androidutils.file.safeDelete +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.di.ApplicationContext import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.callbackFlow @@ -81,7 +82,7 @@ class VideoCompressor @Inject constructor( } private fun getVideoMetadata(uri: Uri): VideoFileMetadata? { - return runCatching { + return runCatchingExceptions { MediaMetadataRetriever().use { it.setDataSource(context, uri) diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt index f4764e2d6bb..27fcf2ce3e6 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt @@ -22,6 +22,7 @@ import dagger.assisted.AssistedInject import io.element.android.libraries.androidutils.R import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.core.extensions.mapCatchingExceptions import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState @@ -154,7 +155,7 @@ class MediaGalleryPresenter @AssistedInject constructor( mimeType = mediaItem.mediaInfo().mimeType, filename = mediaItem.mediaInfo().filename ) - .mapCatching { mediaFile -> + .mapCatchingExceptions { mediaFile -> localMediaFactory.createFromMediaFile( mediaFile = mediaFile, mediaInfo = mediaItem.mediaInfo() @@ -164,7 +165,7 @@ class MediaGalleryPresenter @AssistedInject constructor( private suspend fun saveOnDisk(mediaItem: MediaItem.Event) { downloadMedia(mediaItem) - .mapCatching { localMedia -> + .mapCatchingExceptions { localMedia -> localMediaActions.saveOnDisk(localMedia) } .onSuccess { @@ -179,7 +180,7 @@ class MediaGalleryPresenter @AssistedInject constructor( private suspend fun share(mediaItem: MediaItem.Event) { downloadMedia(mediaItem) - .mapCatching { localMedia -> + .mapCatchingExceptions { localMedia -> localMediaActions.share(localMedia) } .onFailure { diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaActions.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaActions.kt index 56af3ca2c8d..d6c43ce4c41 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaActions.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/AndroidLocalMediaActions.kt @@ -32,6 +32,7 @@ import androidx.core.net.toFile import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.androidutils.system.startInstallFromSourceIntent import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.di.AppScope @@ -83,7 +84,7 @@ class AndroidLocalMediaActions @Inject constructor( override suspend fun saveOnDisk(localMedia: LocalMedia): Result = withContext(coroutineDispatchers.io) { require(localMedia.uri.scheme == ContentResolver.SCHEME_FILE) - runCatching { + runCatchingExceptions { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { saveOnDiskUsingMediaStore(localMedia) } else { @@ -98,7 +99,7 @@ class AndroidLocalMediaActions @Inject constructor( override suspend fun share(localMedia: LocalMedia): Result = withContext(coroutineDispatchers.io) { require(localMedia.uri.scheme == ContentResolver.SCHEME_FILE) - runCatching { + runCatchingExceptions { val shareableUri = localMedia.toShareableUri() val shareMediaIntent = Intent(Intent.ACTION_SEND) .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) @@ -117,7 +118,7 @@ class AndroidLocalMediaActions @Inject constructor( override suspend fun open(localMedia: LocalMedia): Result = withContext(coroutineDispatchers.io) { require(localMedia.uri.scheme == ContentResolver.SCHEME_FILE) - runCatching { + runCatchingExceptions { when (localMedia.info.mimeType) { MimeTypes.Apk -> { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/pdf/ParcelFileDescriptorFactory.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/pdf/ParcelFileDescriptorFactory.kt index fc1110086a5..9f6d10b6b06 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/pdf/ParcelFileDescriptorFactory.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/pdf/ParcelFileDescriptorFactory.kt @@ -10,10 +10,11 @@ package io.element.android.libraries.mediaviewer.impl.local.pdf import android.content.Context import android.net.Uri import android.os.ParcelFileDescriptor +import io.element.android.libraries.core.extensions.runCatchingExceptions import java.io.File class ParcelFileDescriptorFactory(private val context: Context) { - fun create(model: Any?) = runCatching { + fun create(model: Any?) = runCatchingExceptions { when (model) { is File -> ParcelFileDescriptor.open(model, ParcelFileDescriptor.MODE_READ_ONLY) is Uri -> context.contentResolver.openFileDescriptor(model, "r")!! diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/pdf/PdfRendererManager.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/pdf/PdfRendererManager.kt index fa3719bf8ba..c7562e98e7e 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/pdf/PdfRendererManager.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/pdf/PdfRendererManager.kt @@ -10,6 +10,7 @@ package io.element.android.libraries.mediaviewer.impl.local.pdf import android.graphics.pdf.PdfRenderer import android.os.ParcelFileDescriptor import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.core.extensions.runCatchingExceptions import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope @@ -35,7 +36,7 @@ class PdfRendererManager( coroutineScope.launch { mutex.withLock { withContext(Dispatchers.IO) { - pdfRenderer = runCatching { + pdfRenderer = runCatchingExceptions { PdfRenderer(parcelFileDescriptor) }.fold( onSuccess = { pdfRenderer -> diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/txt/TextFileView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/txt/TextFileView.kt index c025ab8bed1..4c4429b775c 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/txt/TextFileView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/txt/TextFileView.kt @@ -21,6 +21,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter import io.element.android.features.viewfolder.api.TextFileViewer import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator @@ -43,7 +44,7 @@ fun TextFileView( data.value = AsyncData.Loading() if (localMedia?.uri != null) { // Load the file content - val result = runCatching { + val result = runCatchingExceptions { context.contentResolver.openInputStream(localMedia.uri).use { it?.bufferedReader()?.readLines()?.toList().orEmpty() } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerDataSource.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerDataSource.kt index 4bb37abf65f..b2eb7e7b6f9 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerDataSource.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/viewer/MediaViewerDataSource.kt @@ -15,6 +15,7 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.core.extensions.mapCatchingExceptions import io.element.android.libraries.matrix.api.media.MatrixMediaLoader import io.element.android.libraries.matrix.api.media.MediaFile import io.element.android.libraries.matrix.api.timeline.Timeline @@ -173,7 +174,7 @@ class MediaViewerDataSource( .onSuccess { mediaFile -> mediaFiles.add(mediaFile) } - .mapCatching { mediaFile -> + .mapCatchingExceptions { mediaFile -> localMediaFactory.createFromMediaFile( mediaFile = mediaFile, mediaInfo = data.mediaInfo diff --git a/libraries/oidc/impl/src/test/kotlin/io/element/android/libraries/oidc/impl/webview/OidcPresenterTest.kt b/libraries/oidc/impl/src/test/kotlin/io/element/android/libraries/oidc/impl/webview/OidcPresenterTest.kt index 433c77f73a2..118951dac5f 100644 --- a/libraries/oidc/impl/src/test/kotlin/io/element/android/libraries/oidc/impl/webview/OidcPresenterTest.kt +++ b/libraries/oidc/impl/src/test/kotlin/io/element/android/libraries/oidc/impl/webview/OidcPresenterTest.kt @@ -14,7 +14,7 @@ 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.matrix.test.A_THROWABLE +import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.auth.A_OIDC_DATA import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService import io.element.android.libraries.oidc.api.OidcAction @@ -68,7 +68,7 @@ class OidcPresenterTest { A_OIDC_DATA, authenticationService, ) - authenticationService.givenOidcCancelError(A_THROWABLE) + authenticationService.givenOidcCancelError(AN_EXCEPTION) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -77,7 +77,7 @@ class OidcPresenterTest { val loadingState = awaitItem() assertThat(loadingState.requestState).isEqualTo(AsyncAction.Loading) val finalState = awaitItem() - assertThat(finalState.requestState).isEqualTo(AsyncAction.Failure(A_THROWABLE)) + assertThat(finalState.requestState).isEqualTo(AsyncAction.Failure(AN_EXCEPTION)) // Note: in real life I do not think this can happen, and the app should not block the user. } } @@ -124,7 +124,7 @@ class OidcPresenterTest { A_OIDC_DATA, authenticationService, ) - authenticationService.givenLoginError(A_THROWABLE) + authenticationService.givenLoginError(AN_EXCEPTION) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -133,7 +133,7 @@ class OidcPresenterTest { val loadingState = awaitItem() assertThat(loadingState.requestState).isEqualTo(AsyncAction.Loading) val errorState = awaitItem() - assertThat(errorState.requestState).isEqualTo(AsyncAction.Failure(A_THROWABLE)) + assertThat(errorState.requestState).isEqualTo(AsyncAction.Failure(AN_EXCEPTION)) errorState.eventSink.invoke(OidcEvents.ClearError) val finalState = awaitItem() assertThat(finalState.requestState).isEqualTo(AsyncAction.Uninitialized) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/CallNotificationEventResolver.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/CallNotificationEventResolver.kt index 2e56c45395a..119eda3423e 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/CallNotificationEventResolver.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/CallNotificationEventResolver.kt @@ -8,6 +8,7 @@ package io.element.android.libraries.push.impl.notifications import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.di.AppScope import io.element.android.libraries.matrix.api.MatrixClientProvider import io.element.android.libraries.matrix.api.core.SessionId @@ -54,13 +55,13 @@ class DefaultCallNotificationEventResolver @Inject constructor( sessionId: SessionId, notificationData: NotificationData, forceNotify: Boolean - ): Result = runCatching { + ): Result = runCatchingExceptions { val content = notificationData.content as? NotificationContent.MessageLike.CallNotify ?: throw ResolvingException("content is not a call notify") val previousRingingCallStatus = appForegroundStateService.hasRingingCall.value // We need the sync service working to get the updated room info - val isRoomCallActive = runCatching { + val isRoomCallActive = runCatchingExceptions { if (content.type == CallNotifyType.RING) { appForegroundStateService.updateHasRingingCall(true) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolver.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolver.kt index 6ee03ed4185..d5eef835644 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolver.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolver.kt @@ -11,6 +11,8 @@ import android.content.Context import android.net.Uri import androidx.core.content.FileProvider import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.core.extensions.mapCatchingExceptions +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.core.log.logger.LoggerTag import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext @@ -90,7 +92,7 @@ class DefaultNotifiableEventResolver @Inject constructor( val ids = notificationEventRequests.groupBy { it.roomId }.mapValues { (_, value) -> value.map { it.eventId } } // TODO this notificationData is not always valid at the moment, sometimes the Rust SDK can't fetch the matching event - val notifications = client.notificationService().getNotifications(ids).mapCatching { map -> + val notifications = client.notificationService().getNotifications(ids).mapCatchingExceptions { map -> map.mapValues { (_, notificationData) -> notificationData.asNotifiableEvent(client, sessionId) } @@ -112,7 +114,7 @@ class DefaultNotifiableEventResolver @Inject constructor( private suspend fun NotificationData.asNotifiableEvent( client: MatrixClient, userId: SessionId, - ): Result = runCatching { + ): Result = runCatchingExceptions { when (val content = this.content) { is NotificationContent.MessageLike.RoomMessage -> { val senderDisambiguatedDisplayName = getDisambiguatedDisplayName(content.senderId) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManager.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManager.kt index f485ba29542..4ac0307291b 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManager.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotificationDrawerManager.kt @@ -208,7 +208,7 @@ class DefaultNotificationDrawerManager @Inject constructor( private suspend fun MatrixClient.getSafeUserProfile(): MatrixUser { return tryOrNull( - onError = { Timber.tag(loggerTag.value).e(it, "Unable to retrieve info for user ${sessionId.value}") }, + onException = { Timber.tag(loggerTag.value).e(it, "Unable to retrieve info for user ${sessionId.value}") }, operation = { val profile = getUserProfile().getOrNull() // displayName cannot be empty else NotificationCompat.MessagingStyle() will crash diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationMediaRepo.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationMediaRepo.kt index 41dd3914707..5483c765786 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationMediaRepo.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationMediaRepo.kt @@ -11,6 +11,7 @@ import com.squareup.anvil.annotations.ContributesBinding import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import io.element.android.libraries.core.extensions.mapCatchingExceptions import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.CacheDirectory import io.element.android.libraries.matrix.api.MatrixClient @@ -85,7 +86,7 @@ class DefaultNotificationMediaRepo @AssistedInject constructor( source = mediaSource, mimeType = mimeType, filename = filename, - ).mapCatching { + ).mapCatchingExceptions { it.use { mediaFile -> val dest = cachedFile.apply { parentFile?.mkdirs() } if (mediaFile.persist(dest.path)) { 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 index 01f728d23c6..ee140e7f6cf 100644 --- 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 @@ -63,6 +63,7 @@ class NotificationTest @Inject constructor( notificationClickHandler.state.first() Timber.d("Notification clicked!") } + @Suppress("RunCatchingNotAllowed") runCatching { withTimeout(30.seconds) { job.join() 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 index 3285ecea100..3f392a3a178 100644 --- 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 @@ -76,6 +76,7 @@ class PushLoopbackTest @Inject constructor( job.cancel() return } + @Suppress("RunCatchingNotAllowed") runCatching { withTimeout(10.seconds) { completable.await() diff --git a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTokenRotator.kt b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTokenRotator.kt index d648e3475f7..cf6eb4b2cc4 100644 --- a/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTokenRotator.kt +++ b/libraries/pushproviders/firebase/src/main/kotlin/io/element/android/libraries/pushproviders/firebase/FirebaseTokenRotator.kt @@ -8,6 +8,7 @@ package io.element.android.libraries.pushproviders.firebase import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.di.AppScope import javax.inject.Inject @@ -24,7 +25,7 @@ class DefaultFirebaseTokenRotator @Inject constructor( private val firebaseTokenGetter: FirebaseTokenGetter, ) : FirebaseTokenRotator { override suspend fun rotate(): Result { - return runCatching { + return runCatchingExceptions { firebaseTokenDeleter.delete() firebaseTokenGetter.get() } 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 5ed3fb95800..8326496dd2d 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 @@ -8,6 +8,7 @@ package io.element.android.libraries.pushproviders.firebase import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.di.AppScope import javax.inject.Inject @@ -24,7 +25,7 @@ class DefaultFirebaseTroubleshooter @Inject constructor( private val firebaseTokenGetter: FirebaseTokenGetter, ) : FirebaseTroubleshooter { override suspend fun troubleshoot(): Result { - return runCatching { + return runCatchingExceptions { val token = firebaseTokenGetter.get() newTokenHandler.handle(token) } diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/RegisterUnifiedPushUseCase.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/RegisterUnifiedPushUseCase.kt index 5df7c1e5c3a..862212c3331 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/RegisterUnifiedPushUseCase.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/RegisterUnifiedPushUseCase.kt @@ -35,6 +35,7 @@ class DefaultRegisterUnifiedPushUseCase @Inject constructor( // VectorUnifiedPushMessagingReceiver.onNewEndpoint UnifiedPush.register(context = context, instance = clientSecret) // Wait for VectorUnifiedPushMessagingReceiver.onNewEndpoint to proceed + @Suppress("RunCatchingNotAllowed") return runCatching { withTimeout(30.seconds) { val result = endpointRegistrationHandler.state diff --git a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushGatewayResolver.kt b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushGatewayResolver.kt index a6fbd7051fb..4b80ae42ff2 100644 --- a/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushGatewayResolver.kt +++ b/libraries/pushproviders/unifiedpush/src/main/kotlin/io/element/android/libraries/pushproviders/unifiedpush/UnifiedPushGatewayResolver.kt @@ -36,7 +36,7 @@ class DefaultUnifiedPushGatewayResolver @Inject constructor( ) : UnifiedPushGatewayResolver { override suspend fun getGateway(endpoint: String): UnifiedPushGatewayResolverResult { val url = tryOrNull( - onError = { Timber.tag("DefaultUnifiedPushGatewayResolver").d(it, "Cannot parse endpoint as an URL") } + onException = { Timber.tag("DefaultUnifiedPushGatewayResolver").d(it, "Cannot parse endpoint as an URL") } ) { URL(endpoint) } diff --git a/libraries/usersearch/impl/src/test/kotlin/io/element/android/libraries/usersearch/impl/MatrixUserListDataSourceTest.kt b/libraries/usersearch/impl/src/test/kotlin/io/element/android/libraries/usersearch/impl/MatrixUserListDataSourceTest.kt index 515aab80722..43dcb308361 100644 --- a/libraries/usersearch/impl/src/test/kotlin/io/element/android/libraries/usersearch/impl/MatrixUserListDataSourceTest.kt +++ b/libraries/usersearch/impl/src/test/kotlin/io/element/android/libraries/usersearch/impl/MatrixUserListDataSourceTest.kt @@ -50,7 +50,7 @@ internal class MatrixUserListDataSourceTest { val matrixClient = FakeMatrixClient() matrixClient.givenSearchUsersResult( searchTerm = "test", - result = Result.failure(Throwable("Ruhroh")) + result = Result.failure(RuntimeException("Ruhroh")) ) val dataSource = MatrixUserListDataSource(matrixClient) @@ -76,7 +76,7 @@ internal class MatrixUserListDataSourceTest { val matrixClient = FakeMatrixClient() matrixClient.givenGetProfileResult( userId = A_USER_ID, - result = Result.failure(Throwable("Ruhroh")) + result = Result.failure(RuntimeException("Ruhroh")) ) val dataSource = MatrixUserListDataSource(matrixClient) diff --git a/libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessageMediaRepo.kt b/libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessageMediaRepo.kt index 922751852fc..9f000b8c32a 100644 --- a/libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessageMediaRepo.kt +++ b/libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessageMediaRepo.kt @@ -11,6 +11,7 @@ import com.squareup.anvil.annotations.ContributesBinding import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import io.element.android.libraries.core.extensions.mapCatchingExceptions import io.element.android.libraries.di.CacheDirectory import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.media.MatrixMediaLoader @@ -80,7 +81,7 @@ class DefaultVoiceMessageMediaRepo @AssistedInject constructor( source = mediaSource, mimeType = mimeType, filename = filename, - ).mapCatching { + ).mapCatchingExceptions { it.use { mediaFile -> val dest = cachedFile.apply { parentFile?.mkdirs() } if (mediaFile.persist(dest.path)) { diff --git a/libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessagePlayer.kt b/libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessagePlayer.kt index 275abebdf51..e57f065f060 100644 --- a/libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessagePlayer.kt +++ b/libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessagePlayer.kt @@ -8,6 +8,7 @@ package io.element.android.libraries.voiceplayer.impl import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.core.extensions.mapCatchingExceptions import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.core.EventId @@ -180,7 +181,7 @@ class DefaultVoiceMessagePlayer( }.distinctUntilChanged() override suspend fun prepare(): Result = if (eventId != null) { - repo.getMediaFile().mapCatching { mediaFile -> + repo.getMediaFile().mapCatchingExceptions { mediaFile -> val state = internalState.value mediaPlayer.setMedia( uri = mediaFile.path, diff --git a/libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessagePresenter.kt b/libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessagePresenter.kt index 85492fe6740..58824b857e9 100644 --- a/libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessagePresenter.kt +++ b/libraries/voiceplayer/impl/src/main/kotlin/io/element/android/libraries/voiceplayer/impl/VoiceMessagePresenter.kt @@ -17,6 +17,7 @@ import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runUpdatingState import io.element.android.libraries.core.extensions.flatMap +import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.ui.utils.time.formatShort import io.element.android.libraries.voiceplayer.api.VoiceMessageEvents @@ -101,7 +102,7 @@ class VoiceMessagePresenter( }, ) { player.prepare().flatMap { - runCatching { player.play() } + runCatchingExceptions { player.play() } } } } diff --git a/settings.gradle.kts b/settings.gradle.kts index e594999bd8c..f523b72fecb 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -56,6 +56,7 @@ include(":appnav") include(":appconfig") include(":appicon:element") include(":appicon:enterprise") +include(":tests:detekt-rules") include(":tests:konsist") include(":tests:uitests") include(":tests:testutils") diff --git a/tests/detekt-rules/.gitignore b/tests/detekt-rules/.gitignore new file mode 100644 index 00000000000..42afabfd2ab --- /dev/null +++ b/tests/detekt-rules/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/tests/detekt-rules/build.gradle.kts b/tests/detekt-rules/build.gradle.kts new file mode 100644 index 00000000000..7fbcdb3a8af --- /dev/null +++ b/tests/detekt-rules/build.gradle.kts @@ -0,0 +1,20 @@ +plugins { + alias(libs.plugins.kotlin.jvm) +} +java { + sourceCompatibility = Versions.javaVersion + targetCompatibility = Versions.javaVersion +} + +kotlin { + jvmToolchain { + languageVersion = Versions.javaLanguageVersion + } +} + +dependencies { + compileOnly(libs.test.detekt.api) + testImplementation(libs.test.detekt.test) + + testImplementation(libs.test.truth) +} diff --git a/tests/detekt-rules/src/main/kotlin/io/element/android/detektrules/ElementRuleSetProvider.kt b/tests/detekt-rules/src/main/kotlin/io/element/android/detektrules/ElementRuleSetProvider.kt new file mode 100644 index 00000000000..8e0ed3ee502 --- /dev/null +++ b/tests/detekt-rules/src/main/kotlin/io/element/android/detektrules/ElementRuleSetProvider.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.detektrules + +import io.gitlab.arturbosch.detekt.api.Config +import io.gitlab.arturbosch.detekt.api.RuleSet +import io.gitlab.arturbosch.detekt.api.RuleSetProvider + +class ElementRuleSetProvider : RuleSetProvider { + override val ruleSetId: String = "ElementXRules" + + override fun instance(config: Config): RuleSet = RuleSet( + id = ruleSetId, + rules = listOf( + RunCatchingRule(config), + ) + ) +} diff --git a/tests/detekt-rules/src/main/kotlin/io/element/android/detektrules/RunCatchingRule.kt b/tests/detekt-rules/src/main/kotlin/io/element/android/detektrules/RunCatchingRule.kt new file mode 100644 index 00000000000..094de51be36 --- /dev/null +++ b/tests/detekt-rules/src/main/kotlin/io/element/android/detektrules/RunCatchingRule.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.detektrules + +import io.gitlab.arturbosch.detekt.api.CodeSmell +import io.gitlab.arturbosch.detekt.api.Config +import io.gitlab.arturbosch.detekt.api.Debt +import io.gitlab.arturbosch.detekt.api.Entity +import io.gitlab.arturbosch.detekt.api.Issue +import io.gitlab.arturbosch.detekt.api.Rule +import io.gitlab.arturbosch.detekt.api.Severity +import org.jetbrains.kotlin.psi.KtCallExpression +import org.jetbrains.kotlin.psi.psiUtil.getCallNameExpression + +class RunCatchingRule(config: Config) : Rule(config) { + override val issue: Issue = Issue( + id = "RunCatchingNotAllowed", + severity = Severity.Style, + description = "Avoid using `runCatching`, use `runCatchingExceptions` or `tryOrNull` instead. " + + "Avoid `mapCatching`, use `mapCatchingExceptions` instead.", + debt = Debt.FIVE_MINS, + ) + + override fun visitCallExpression(expression: KtCallExpression) { + super.visitCallExpression(expression) + + val callNameExpression = expression.getCallNameExpression() ?: return + val hasRunCatchingCall = callNameExpression.text == "runCatching" + val hasMapCatchingCall = callNameExpression.text == "mapCatching" + if (hasRunCatchingCall || hasMapCatchingCall) { + report(CodeSmell( + issue = issue, + entity = Entity.from(expression), + message = "Use `runCatchingExceptions` or `tryOrNull` instead of `runCatching`. Avoid `mapCatching`, use `mapCatchingExceptions` instead." + )) + } + } +} diff --git a/tests/detekt-rules/src/main/resources/META-INF/services/io.gitlab.arturbosch.detekt.api.RuleSetProvider b/tests/detekt-rules/src/main/resources/META-INF/services/io.gitlab.arturbosch.detekt.api.RuleSetProvider new file mode 100644 index 00000000000..454797e7a0c --- /dev/null +++ b/tests/detekt-rules/src/main/resources/META-INF/services/io.gitlab.arturbosch.detekt.api.RuleSetProvider @@ -0,0 +1 @@ +io.element.android.detektrules.ElementRuleSetProvider diff --git a/tests/detekt-rules/src/test/kotlin/io/element/android/detektrules/RunCatchingRuleTest.kt b/tests/detekt-rules/src/test/kotlin/io/element/android/detektrules/RunCatchingRuleTest.kt new file mode 100644 index 00000000000..c11d4d88680 --- /dev/null +++ b/tests/detekt-rules/src/test/kotlin/io/element/android/detektrules/RunCatchingRuleTest.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.detektrules + +import com.google.common.truth.Truth.assertThat +import io.gitlab.arturbosch.detekt.api.Config +import io.gitlab.arturbosch.detekt.test.compileAndLint +import org.junit.Test + +class RunCatchingRuleTest { + private val subject = RunCatchingRule(Config.empty) + + @Test + fun `test RunCatchingRule`() { + val findings = subject.compileAndLint(code) + assertThat(findings).hasSize(3) + } + + private val code = """ + object Foo { + fun bar() { + runCatching {} + kotlin.runCatching {} + Result.success(true).mapCatching { false } + } + } + """.trimIndent() +} diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/Error.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/Error.kt index b96900cee12..f9e2ec0cbb2 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/Error.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/lambda/Error.kt @@ -7,19 +7,8 @@ package io.element.android.tests.testutils.lambda -import kotlin.system.exitProcess - fun lambdaError( message: String = "This lambda should never be called." ): Nothing { - // Throwing an exception here is not enough, it can be caught. - // Instead exit the process to make sure the test fails. - // The error will be: - // "Could not stop all services." - // In this case, put a breakpoint here and run the test in debug mode to identify which lambda is failing. - System.err.println(message) - Thread.currentThread().stackTrace.forEach { - System.err.println(it) - } - exitProcess(1) + throw AssertionError(message) } diff --git a/tools/dependencies/checkDependencies.py b/tools/dependencies/checkDependencies.py index 3067e322617..29ef7ea7fcb 100755 --- a/tools/dependencies/checkDependencies.py +++ b/tools/dependencies/checkDependencies.py @@ -56,7 +56,7 @@ def checkThatThereIsNoTestDependency(dependencies): continue else: subProject = line.split(" ")[-1] - if subProject.endswith(":test") or ":tests:" in subProject: + if subProject.endswith(":test") or ":tests:" in subProject and "detekt-rules" not in subProject: error = "Error: '" + currentProject + "' depends on the test project '" + subProject + "'\n" error += " Please replace occurrence(s) of 'implementation(projects" + subProject.replace(":", ".") + ")'" error += " with 'testImplementation(projects" + subProject.replace(":", ".") + ")'."