diff --git a/.maestro/tests/roomList/cleanUpRoomList.yaml b/.maestro/tests/roomList/cleanUpRoomList.yaml new file mode 100644 index 0000000000..3d9fb3d6e4 --- /dev/null +++ b/.maestro/tests/roomList/cleanUpRoomList.yaml @@ -0,0 +1,21 @@ +appId: ${MAESTRO_APP_ID} +--- +# Clean up the room list if it was left in a bad state by previous tests +- runFlow: + when: + visible: 'aRoomName' + commands: + - longPressOn: 'aRoomName' + - tapOn: + text: "Leave room" + index: 0 + - tapOn: "Leave" +- runFlow: + when: + visible: ${MAESTRO_INVITEE1_MXID} + commands: + - longPressOn: ${MAESTRO_INVITEE1_MXID} + - tapOn: + text: "Leave room" + index: 0 + - tapOn: "Leave" diff --git a/.maestro/tests/roomList/createAndDeleteDM.yaml b/.maestro/tests/roomList/createAndDeleteDM.yaml index eed576c04f..8372d6dbc9 100644 --- a/.maestro/tests/roomList/createAndDeleteDM.yaml +++ b/.maestro/tests/roomList/createAndDeleteDM.yaml @@ -7,7 +7,16 @@ appId: ${MAESTRO_APP_ID} - tapOn: text: ${MAESTRO_INVITEE1_MXID} index: 1 -- tapOn: "Continue" +- runFlow: + when: + visible: 'Start a chat with this new contact?' + commands: + - tapOn: 'Continue' +- runFlow: + when: + visible: 'Send invite?' + commands: + - tapOn: 'Send invite' - takeScreenshot: build/maestro/330-createAndDeleteDM - tapOn: "maestroelement2" - scroll diff --git a/.maestro/tests/roomList/createAndDeleteRoom.yaml b/.maestro/tests/roomList/createAndDeleteRoom.yaml index adf9d7cf29..6b4d745fae 100644 --- a/.maestro/tests/roomList/createAndDeleteRoom.yaml +++ b/.maestro/tests/roomList/createAndDeleteRoom.yaml @@ -15,6 +15,12 @@ appId: ${MAESTRO_APP_ID} text: ${MAESTRO_INVITEE1_MXID} index: 1 - tapOn: "Finish" +- runFlow: + when: + visible: 'Invite new contact to this room?' + commands: + - tapOn: + id: "confirm_invite_unknown" - tapOn: "aRoomName" - tapOn: "Invite" # assert there's 1 member and 1 invitee @@ -23,6 +29,10 @@ appId: ${MAESTRO_APP_ID} - tapOn: text: ${MAESTRO_INVITEE2_MXID} index: 1 +# Hide keyboard by tapping on an external element in case it's displayed, otherwise it will 'eat' the back button press +- tapOn: + # Navigation title + text: "Invite people" - tapOn: "Invite" - runFlow: when: @@ -30,10 +40,6 @@ appId: ${MAESTRO_APP_ID} commands: - tapOn: id: "confirm_invite_unknown" -# Close the keyboard if it's still open -- tapOn: "Back" -# Go back to the room details screen -- tapOn: "Back" - scrollUntilVisible: direction: DOWN element: diff --git a/.maestro/tests/roomList/roomList.yaml b/.maestro/tests/roomList/roomList.yaml index 5cc9e269c5..704d983b7d 100644 --- a/.maestro/tests/roomList/roomList.yaml +++ b/.maestro/tests/roomList/roomList.yaml @@ -1,5 +1,6 @@ appId: ${MAESTRO_APP_ID} --- +- runFlow: cleanUpRoomList.yaml - runFlow: searchRoomList.yaml - takeScreenshot: build/maestro/300-RoomList - runFlow: timeline/timeline.yaml diff --git a/enterprise b/enterprise index bc38a11f95..cca96c4fc2 160000 --- a/enterprise +++ b/enterprise @@ -1 +1 @@ -Subproject commit bc38a11f95988cc1bbc209decb998f21c465ded0 +Subproject commit cca96c4fc24b6e2fd1ccf9265c8283ac9bf63dc0 diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenPresenter.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenPresenter.kt index 7d8e20967f..cc3468a845 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenPresenter.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/CallScreenPresenter.kt @@ -22,7 +22,6 @@ import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedFactory import dev.zacsweers.metro.AssistedInject import im.vector.app.features.analytics.plan.MobileScreen -import io.element.android.compound.theme.ElementTheme import io.element.android.features.call.api.CallData import io.element.android.features.call.impl.data.WidgetMessage import io.element.android.features.call.impl.utils.ActiveCallManager @@ -84,7 +83,7 @@ class CallScreenPresenter( var ignoreWebViewError by rememberSaveable { mutableStateOf(false) } var webViewError by remember { mutableStateOf(null) } val languageTag = languageTagProvider.provideLanguageTag() - val theme = if (ElementTheme.isLightTheme) "light" else "dark" + val theme = "dark" DisposableEffect(Unit) { coroutineScope.launch { diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt index 26df7c160c..9a093ff5a6 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/ui/ElementCallActivity.kt @@ -38,6 +38,7 @@ import androidx.core.view.WindowInsetsControllerCompat import androidx.lifecycle.Lifecycle import dev.zacsweers.metro.Inject import io.element.android.compound.colors.SemanticColorsLightDark +import io.element.android.compound.theme.ForcedDarkElementTheme import io.element.android.features.call.api.CallData import io.element.android.features.call.impl.DefaultElementCallEntryPoint import io.element.android.features.call.impl.di.CallBindings @@ -143,24 +144,28 @@ class ElementCallActivity : compoundDark = colors.dark, buildMeta = buildMeta, ) { - val state = presenter.present() - eventSink = state.eventSink - LaunchedEffect(state.isCallActive) { - if (state.isCallActive) { - setCallIsActive() + ForcedDarkElementTheme( + colors = colors, + ) { + val state = presenter.present() + eventSink = state.eventSink + LaunchedEffect(state.isCallActive) { + if (state.isCallActive) { + setCallIsActive() + } } + CallScreenView( + state = state, + pipState = pipState, + onConsoleMessage = { + consoleMessageLogger.log("ElementCall", it) + }, + requestPermissions = { permissions, callback -> + requestPermissionCallback = callback + requestPermissionsLauncher.launch(permissions) + } + ) } - CallScreenView( - state = state, - pipState = pipState, - onConsoleMessage = { - consoleMessageLogger.log("ElementCall", it) - }, - requestPermissions = { permissions, callback -> - requestPermissionCallback = callback - requestPermissionsLauncher.launch(permissions) - } - ) } } } diff --git a/features/createroom/impl/build.gradle.kts b/features/createroom/impl/build.gradle.kts index 13a195e940..2cd3961ead 100644 --- a/features/createroom/impl/build.gradle.kts +++ b/features/createroom/impl/build.gradle.kts @@ -43,6 +43,7 @@ dependencies { implementation(libs.coil.compose) implementation(projects.libraries.featureflag.api) implementation(projects.features.invitepeople.api) + implementation(projects.features.enterprise.api) api(projects.features.createroom.api) testCommonDependencies(libs, true) @@ -53,4 +54,5 @@ dependencies { testImplementation(projects.libraries.permissions.test) testImplementation(projects.features.startchat.test) testImplementation(projects.libraries.featureflag.test) + testImplementation(projects.features.enterprise.test) } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt index 0d74ca05bf..abedc2bd49 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt @@ -24,6 +24,7 @@ import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedFactory import dev.zacsweers.metro.AssistedInject import im.vector.app.features.analytics.plan.CreatedRoom +import io.element.android.features.enterprise.api.SessionEnterpriseService import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState @@ -73,6 +74,7 @@ class ConfigureRoomPresenter( private val featureFlagService: FeatureFlagService, private val roomAliasHelper: RoomAliasHelper, private val mediaOptimizationConfigProvider: MediaOptimizationConfigProvider, + private val sessionEnterpriseService: SessionEnterpriseService, ) : Presenter { @AssistedFactory interface Factory { @@ -256,7 +258,7 @@ class ConfigureRoomPresenter( CreateRoomParameters( name = config.roomName, topic = config.topic, - isEncrypted = true, + isEncrypted = !sessionEnterpriseService.isEncryptionDisabledByHomeserver(), isDirect = false, visibility = RoomVisibility.Private, historyVisibilityOverride = RoomHistoryVisibility.Invited, diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/ConfigureRoomPresenterTest.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/ConfigureRoomPresenterTest.kt index c0c4a8a5fe..046d00a5c6 100644 --- a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/ConfigureRoomPresenterTest.kt +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/ConfigureRoomPresenterTest.kt @@ -20,11 +20,13 @@ import io.element.android.features.createroom.impl.configureroom.CreateRoomConfi import io.element.android.features.createroom.impl.configureroom.JoinRuleItem import io.element.android.features.createroom.impl.configureroom.RoomAddress import io.element.android.features.createroom.impl.configureroom.RoomVisibilityState +import io.element.android.features.enterprise.test.FakeSessionEnterpriseService import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters import io.element.android.libraries.matrix.api.room.RoomInfo import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias import io.element.android.libraries.matrix.api.room.alias.RoomAliasHelper @@ -56,6 +58,7 @@ import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.lambda.matching import io.element.android.tests.testutils.robolectric.RobolectricTest import io.element.android.tests.testutils.test import io.mockk.mockk @@ -517,6 +520,30 @@ class ConfigureRoomPresenterTest : RobolectricTest() { } } + @Test + fun `present - creating a private room when encryption is disabled by the HS creates it unencrypted`() = runTest { + val sessionEnterpriseService = FakeSessionEnterpriseService(isEncryptionDisabledResult = { true }) + val createRoomLambda = lambdaRecorder> { Result.success(A_ROOM_ID) } + val matrixClient = createMatrixClient().apply { + createRoomResult = createRoomLambda + } + val presenter = createConfigureRoomPresenter( + matrixClient = matrixClient, + sessionEnterpriseService = sessionEnterpriseService, + ) + presenter.test { + val initialState = initialState() + + initialState.eventSink(ConfigureRoomEvents.JoinRuleChanged(JoinRuleItem.PrivateVisibility.Private)) + initialState.eventSink(ConfigureRoomEvents.CreateRoom) + + assertThat(awaitItem().createRoomAction.isLoading()).isTrue() + assertThat(awaitItem().createRoomAction.isSuccess()).isTrue() + + createRoomLambda.assertions().isCalledOnce().with(matching { !it.isEncrypted }) + } + } + private suspend fun TurbineTestContext.initialState(): ConfigureRoomState { skipItems(1) return awaitItem() @@ -552,6 +579,7 @@ class ConfigureRoomPresenterTest : RobolectricTest() { permissionsPresenter: PermissionsPresenter = FakePermissionsPresenter(), isKnockFeatureEnabled: Boolean = true, mediaOptimizationConfigProvider: FakeMediaOptimizationConfigProvider = FakeMediaOptimizationConfigProvider(), + sessionEnterpriseService: FakeSessionEnterpriseService = FakeSessionEnterpriseService(isEncryptionDisabledResult = { false }), ) = ConfigureRoomPresenter( isSpace = isSpace, initialParentSpaceId = initialParenSpaceId, @@ -566,5 +594,6 @@ class ConfigureRoomPresenterTest : RobolectricTest() { mapOf(FeatureFlags.Knock.key to isKnockFeatureEnabled) ), mediaOptimizationConfigProvider = mediaOptimizationConfigProvider, + sessionEnterpriseService = sessionEnterpriseService, ) } diff --git a/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/SessionEnterpriseService.kt b/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/SessionEnterpriseService.kt index f87dc743e8..ee005a0b6b 100644 --- a/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/SessionEnterpriseService.kt +++ b/features/enterprise/api/src/main/kotlin/io/element/android/features/enterprise/api/SessionEnterpriseService.kt @@ -12,5 +12,7 @@ interface SessionEnterpriseService { suspend fun isElementCallAvailable(): Boolean suspend fun tweakMasUrl(url: String): String + suspend fun isEncryptionDisabledByHomeserver(): Boolean + suspend fun init() } diff --git a/features/enterprise/impl-foss/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultSessionEnterpriseService.kt b/features/enterprise/impl-foss/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultSessionEnterpriseService.kt index 9aafcd343c..3ac10d8a08 100644 --- a/features/enterprise/impl-foss/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultSessionEnterpriseService.kt +++ b/features/enterprise/impl-foss/src/main/kotlin/io/element/android/features/enterprise/impl/DefaultSessionEnterpriseService.kt @@ -17,4 +17,5 @@ class DefaultSessionEnterpriseService : SessionEnterpriseService { override suspend fun init() = Unit override suspend fun tweakMasUrl(url: String): String = url override suspend fun isElementCallAvailable(): Boolean = true + override suspend fun isEncryptionDisabledByHomeserver(): Boolean = false } diff --git a/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeSessionEnterpriseService.kt b/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeSessionEnterpriseService.kt index 0bcad13033..e198fca867 100644 --- a/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeSessionEnterpriseService.kt +++ b/features/enterprise/test/src/main/kotlin/io/element/android/features/enterprise/test/FakeSessionEnterpriseService.kt @@ -15,6 +15,7 @@ import io.element.android.tests.testutils.simulateLongTask class FakeSessionEnterpriseService( private val isElementCallAvailableResult: () -> Boolean = { lambdaError() }, private val tweakMasUrlResult: (String) -> String = { lambdaError() }, + private val isEncryptionDisabledResult: () -> Boolean = { lambdaError() }, ) : SessionEnterpriseService { override suspend fun init() { } @@ -26,4 +27,8 @@ class FakeSessionEnterpriseService( override suspend fun isElementCallAvailable(): Boolean = simulateLongTask { isElementCallAvailableResult() } + + override suspend fun isEncryptionDisabledByHomeserver(): Boolean { + return isEncryptionDisabledResult() + } } diff --git a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/InvitePeopleView.kt b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/InvitePeopleView.kt index 2bbd64c977..0ee3559aa3 100644 --- a/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/InvitePeopleView.kt +++ b/features/invitepeople/impl/src/main/kotlin/io/element/android/features/invitepeople/impl/InvitePeopleView.kt @@ -302,13 +302,13 @@ private fun InvitePeopleConfirmModal( text = stringResource(CommonStrings.action_remove), onClick = onRemove, leadingIcon = IconSource.Vector(CompoundIcons.Close()), - modifier = Modifier.weight(1f).testTag(TestTags.confirmInviteUnknown), + modifier = Modifier.weight(1f), ) Button( text = stringResource(CommonStrings.action_invite), onClick = onInvite, leadingIcon = IconSource.Vector(CompoundIcons.Check()), - modifier = Modifier.weight(1f) + modifier = Modifier.weight(1f).testTag(TestTags.confirmInviteUnknown), ) } } diff --git a/features/securityandprivacy/impl/build.gradle.kts b/features/securityandprivacy/impl/build.gradle.kts index bb9764eb60..d42bdef883 100644 --- a/features/securityandprivacy/impl/build.gradle.kts +++ b/features/securityandprivacy/impl/build.gradle.kts @@ -41,10 +41,12 @@ dependencies { implementation(projects.libraries.uiStrings) implementation(projects.services.analytics.api) implementation(projects.libraries.featureflag.api) + implementation(projects.features.enterprise.api) testCommonDependencies(libs, true) testImplementation(projects.services.analytics.test) testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.featureflag.test) testImplementation(projects.libraries.testtags) + testImplementation(projects.features.enterprise.test) } diff --git a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyPresenter.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyPresenter.kt index 48ab41a820..32517cd18d 100644 --- a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyPresenter.kt +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyPresenter.kt @@ -22,6 +22,7 @@ import androidx.compose.runtime.setValue import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedFactory import dev.zacsweers.metro.AssistedInject +import io.element.android.features.enterprise.api.SessionEnterpriseService import io.element.android.features.securityandprivacy.api.SecurityAndPrivacyPermissions import io.element.android.features.securityandprivacy.api.securityAndPrivacyPermissions import io.element.android.features.securityandprivacy.impl.SecurityAndPrivacyNavigator @@ -63,6 +64,7 @@ class SecurityAndPrivacyPresenter( private val matrixClient: MatrixClient, private val room: JoinedRoom, private val featureFlagService: FeatureFlagService, + private val sessionEnterpriseService: SessionEnterpriseService, ) : Presenter { @AssistedFactory interface Factory { @@ -83,6 +85,10 @@ class SecurityAndPrivacyPresenter( val homeserverName = remember { matrixClient.userIdServerName() } val roomInfo by room.roomInfoFlow.collectAsState() + val isEncryptionDisabledByHomeserver by produceState(false) { + value = sessionEnterpriseService.isEncryptionDisabledByHomeserver() + } + val savedIsVisibleInRoomDirectory = remember { mutableStateOf>(AsyncData.Uninitialized) } LaunchedEffect(Unit) { isRoomVisibleInRoomDirectory(savedIsVisibleInRoomDirectory) @@ -247,6 +253,7 @@ class SecurityAndPrivacyPresenter( isSpace = roomInfo.isSpace, selectableJoinedSpaces = selectableJoinedSpaces, spaceSelectionMode = spaceSelectionMode, + isEncryptionDisabledByHomeserver = isEncryptionDisabledByHomeserver, eventSink = ::handleEvent, ) diff --git a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyState.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyState.kt index 26e77b3c70..ddba5d0caa 100644 --- a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyState.kt +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyState.kt @@ -27,6 +27,7 @@ data class SecurityAndPrivacyState( // the settings the user wants to apply. val editedSettings: SecurityAndPrivacySettings, val homeserverName: String, + val isEncryptionDisabledByHomeserver: Boolean, val showEnableEncryptionConfirmation: Boolean, private val isKnockEnabled: Boolean, val saveAction: AsyncAction, @@ -84,7 +85,7 @@ data class SecurityAndPrivacyState( editedSettings.roomAccess.canConfigureRoomVisibility() val showHistoryVisibilitySection = permissions.canChangeHistoryVisibility && !isSpace - val showEncryptionSection = permissions.canChangeEncryption && !isSpace + val showEncryptionSection = !isEncryptionDisabledByHomeserver && permissions.canChangeEncryption && !isSpace @Composable fun spaceMemberDescription(): String { diff --git a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyStateProvider.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyStateProvider.kt index 19124302e3..998d61b564 100644 --- a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyStateProvider.kt +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyStateProvider.kt @@ -138,6 +138,7 @@ fun aSecurityAndPrivacyState( isSpace: Boolean = false, selectableJoinedSpaces: Set = emptySet(), spaceSelectionMode: SpaceSelectionMode = SpaceSelectionMode.None, + isEncryptionDisabledByHomeserver: Boolean = false, eventSink: (SecurityAndPrivacyEvent) -> Unit = {} ) = SecurityAndPrivacyState( editedSettings = editedSettings, @@ -150,5 +151,6 @@ fun aSecurityAndPrivacyState( isSpace = isSpace, selectableJoinedSpaces = selectableJoinedSpaces.toImmutableSet(), spaceSelectionMode = spaceSelectionMode, + isEncryptionDisabledByHomeserver = isEncryptionDisabledByHomeserver, eventSink = eventSink, ) diff --git a/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyPresenterTest.kt b/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyPresenterTest.kt index 34b4222053..0b5133fe6a 100644 --- a/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyPresenterTest.kt +++ b/features/securityandprivacy/impl/src/test/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyPresenterTest.kt @@ -8,6 +8,7 @@ package io.element.android.features.securityandprivacy.impl.root import com.google.common.truth.Truth.assertThat +import io.element.android.features.enterprise.test.FakeSessionEnterpriseService import io.element.android.features.securityandprivacy.impl.FakeSecurityAndPrivacyNavigator import io.element.android.features.securityandprivacy.impl.SecurityAndPrivacyNavigator import io.element.android.features.securityandprivacy.impl.manageauthorizedspaces.SpaceSelectionStateHolder @@ -1046,6 +1047,20 @@ class SecurityAndPrivacyPresenterTest { } } + @Test + fun `present - showEncryptionSection is false if the HS disabled encryption`() = runTest { + val sessionEnterpriseService = FakeSessionEnterpriseService(isEncryptionDisabledResult = { true }) + val presenter = createSecurityAndPrivacyPresenter( + sessionEnterpriseService = sessionEnterpriseService, + ) + presenter.test { + skipItems(1) + + // The HS has disabled encryption, so the encryption section should not be shown + assertThat(awaitItem().showEncryptionSection).isFalse() + } + } + private fun roomPermissions( canChangeRoomAccess: Boolean = true, canChangeHistoryVisibility: Boolean = true, @@ -1084,6 +1099,9 @@ class SecurityAndPrivacyPresenterTest { ), ), spaceSelectionStateHolder: SpaceSelectionStateHolder = SpaceSelectionStateHolder(), + sessionEnterpriseService: FakeSessionEnterpriseService = FakeSessionEnterpriseService( + isEncryptionDisabledResult = { false }, + ) ): SecurityAndPrivacyPresenter { return SecurityAndPrivacyPresenter( room = room, @@ -1091,6 +1109,7 @@ class SecurityAndPrivacyPresenterTest { navigator = navigator, featureFlagService = featureFlagService, spaceSelectionStateHolder = spaceSelectionStateHolder, + sessionEnterpriseService = sessionEnterpriseService, ) } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index edf63b0fb9..9debb63d59 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -175,7 +175,7 @@ test_truth = "com.google.truth:truth:1.4.5" test_parameter_injector = "com.google.testparameterinjector:test-parameter-injector:1.22" test_robolectric = "org.robolectric:robolectric:4.16.1" test_appyx_junit = { module = "com.bumble.appyx:testing-junit4", version.ref = "appyx" } -test_composable_preview_scanner = "io.github.sergio-sastre.ComposablePreviewScanner:android:0.9.0" +test_composable_preview_scanner = "io.github.sergio-sastre.ComposablePreviewScanner:android:0.9.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" } @@ -209,7 +209,7 @@ sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", ver sqldelight-driver-jvm = { module = "app.cash.sqldelight:sqlite-driver", version.ref = "sqldelight" } sqldelight-coroutines = { module = "app.cash.sqldelight:coroutines-extensions", version.ref = "sqldelight" } sqlcipher = "net.zetetic:sqlcipher-android:4.16.0" -sqlite = "androidx.sqlite:sqlite-ktx:2.6.2" +sqlite = "androidx.sqlite:sqlite-ktx:2.7.0" unifiedpush = "org.unifiedpush.android:connector:3.3.3" vanniktech_blurhash = "com.vanniktech:blurhash:0.3.0" telephoto_zoomableimage = { module = "me.saket.telephoto:zoomable-image-coil3", version.ref = "telephoto" } @@ -220,7 +220,7 @@ maplibre_ktx = "org.maplibre.gl:android-sdk-ktx-v7:3.0.2" maplibre_compose = "org.maplibre.compose:maplibre-compose:0.13.0" maplibre_annotation = "org.maplibre.gl:android-plugin-annotation-v9:3.0.2" opusencoder = "io.element.android:opusencoder:1.2.0" -zxing_cpp = "io.github.zxing-cpp:android:3.0.2" +zxing_cpp = "io.github.zxing-cpp:android:3.1.0" google_zxing = "com.google.zxing:core:3.5.4" google_guava = "com.google.guava:guava:33.6.0-android" diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationData.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationData.kt index 6edb361215..8a4891a5bf 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationData.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationData.kt @@ -90,7 +90,6 @@ sealed interface NotificationContent { val question: String, ) : MessageLike - // TODO put real implementation data object Beacon : MessageLike } @@ -119,9 +118,7 @@ sealed interface NotificationContent { data class RoomTopic(val topic: String) : StateEvent data object SpaceChild : StateEvent data object SpaceParent : StateEvent - - // TODO put real implementation - data object Beacon : StateEvent + data object BeaconInfo : StateEvent } data class Invite( diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/TimelineEventToNotificationContentMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/TimelineEventToNotificationContentMapper.kt index 8afe6cba40..e4914019ea 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/TimelineEventToNotificationContentMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/TimelineEventToNotificationContentMapper.kt @@ -71,7 +71,7 @@ private fun StateEventContent.toContent(): NotificationContent.StateEvent { is StateEventContent.RoomTopic -> NotificationContent.StateEvent.RoomTopic(topic) StateEventContent.SpaceChild -> NotificationContent.StateEvent.SpaceChild StateEventContent.SpaceParent -> NotificationContent.StateEvent.SpaceParent - StateEventContent.BeaconInfo -> NotificationContent.StateEvent.Beacon + StateEventContent.BeaconInfo -> NotificationContent.StateEvent.BeaconInfo } } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientTest.kt index 745fbf02d5..49cf59a954 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientTest.kt @@ -90,6 +90,8 @@ class RustMatrixClientTest { userId = userId, displayName = A_USER_NAME, avatarUrl = AN_AVATAR_URL, + status = null, + call = null, ) }, ), diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/UserProfile.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/UserProfile.kt index 8713e08f1c..947e6e7aa1 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/UserProfile.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/UserProfile.kt @@ -9,14 +9,20 @@ package io.element.android.libraries.matrix.impl.fixtures.factories import io.element.android.libraries.matrix.test.A_USER_ID +import org.matrix.rustcomponents.sdk.UserCall import org.matrix.rustcomponents.sdk.UserProfile +import org.matrix.rustcomponents.sdk.UserStatus internal fun aRustUserProfile( userId: String = A_USER_ID.value, displayName: String = "displayName", avatarUrl: String = "avatarUrl", + status: UserStatus? = null, + call: UserCall? = null, ) = UserProfile( userId = userId, displayName = displayName, avatarUrl = avatarUrl, + status = status, + call = call, ) diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiClient.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiClient.kt index 4c23744d53..efae379415 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiClient.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeFfiClient.kt @@ -9,6 +9,7 @@ package io.element.android.libraries.matrix.impl.fixtures.fakes import io.element.android.libraries.matrix.impl.fixtures.factories.aRustSession +import io.element.android.libraries.matrix.impl.fixtures.factories.aRustUserProfile import io.element.android.libraries.matrix.test.A_DEVICE_ID import io.element.android.libraries.matrix.test.A_HOMESERVER_URL import io.element.android.libraries.matrix.test.A_USER_ID @@ -49,7 +50,7 @@ class FakeFfiClient( private val session: Session = aRustSession(), private val clearCachesResult: () -> Unit = { lambdaError() }, private val withUtdHook: (UnableToDecryptDelegate) -> Unit = { lambdaError() }, - private val getProfileResult: (String) -> UserProfile = { UserProfile(userId = userId, displayName = null, avatarUrl = null) }, + private val getProfileResult: (String) -> UserProfile = { aRustUserProfile() }, private val homeserverLoginDetailsResult: () -> HomeserverLoginDetails = { lambdaError() }, private val getStoreSizesResult: () -> StoreSizes = { lambdaError() }, private val createRoomResult: (CreateRoomParameters) -> String = { lambdaError() }, diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt index dc7593904d..1c9fcfa482 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt @@ -126,7 +126,7 @@ class FakeMatrixClient( private val getMapStyleUrlResult: () -> Result = { lambdaError() }, private val getDatabaseSizesLambda: () -> Result = { lambdaError() }, private val resetWellKnownConfigLambda: () -> Result = { lambdaError() }, - override val contentScanner: ContentScanner? = null + override val contentScanner: ContentScanner? = null, ) : MatrixClient { var setDisplayNameCalled: Boolean = false private set @@ -144,7 +144,7 @@ class FakeMatrixClient( private val _userProfile: MutableStateFlow = MutableStateFlow(MatrixUser(sessionId, userDisplayName, userAvatarUrl)) override val userProfile: StateFlow = _userProfile - private var createRoomResult: Result = Result.success(A_ROOM_ID) + var createRoomResult: (CreateRoomParameters) -> Result = { Result.success(A_ROOM_ID) } private var createDmResult: Result = Result.success(A_ROOM_ID) private var findDmResult: Result = Result.success(A_ROOM_ID) private val getRoomResults = mutableMapOf() @@ -193,7 +193,7 @@ class FakeMatrixClient( } override suspend fun createRoom(createRoomParams: CreateRoomParameters): Result = simulateLongTask { - return createRoomResult + return createRoomResult(createRoomParams) } override suspend fun createDM(userId: UserId): Result = simulateLongTask { @@ -297,7 +297,7 @@ class FakeMatrixClient( // Mocks fun givenCreateRoomResult(result: Result) { - createRoomResult = result + createRoomResult = { result } } fun givenCreateDmResult(result: Result) { 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 98490c98b4..d35f16bd04 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 @@ -299,6 +299,10 @@ class DefaultNotifiableEventResolver( Timber.tag(loggerTag.value).d("Ignoring notification for sticker") throw NotificationResolverException.EventFilteredOut } + NotificationContent.MessageLike.Beacon -> { + Timber.tag(loggerTag.value).d("Ignoring notification for beacon") + throw NotificationResolverException.EventFilteredOut + } is NotificationContent.StateEvent.RoomMemberContent, NotificationContent.StateEvent.PolicyRuleRoom, NotificationContent.StateEvent.PolicyRuleServer, @@ -318,7 +322,8 @@ class DefaultNotifiableEventResolver( NotificationContent.StateEvent.RoomTombstone, is NotificationContent.StateEvent.RoomTopic, NotificationContent.StateEvent.SpaceChild, - NotificationContent.StateEvent.SpaceParent -> { + NotificationContent.StateEvent.SpaceParent, + NotificationContent.StateEvent.BeaconInfo -> { Timber.tag(loggerTag.value).d("Ignoring notification for state event ${content.javaClass.simpleName}") throw NotificationResolverException.EventFilteredOut } diff --git a/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/ElementWellKnown.kt b/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/ElementWellKnown.kt index 45448f66c2..a9c42b7121 100644 --- a/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/ElementWellKnown.kt +++ b/libraries/wellknown/api/src/main/kotlin/io/element/android/libraries/wellknown/api/ElementWellKnown.kt @@ -17,4 +17,5 @@ data class ElementWellKnown( val identityProviderAppScheme: String?, val customRecoveryPassphrase: CustomRecoveryPassphrase?, val contentScannerUrl: String?, + val forceDisableE2EE: Boolean?, ) diff --git a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalElementWellKnown.kt b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalElementWellKnown.kt index ad60caefa5..c585264f55 100644 --- a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalElementWellKnown.kt +++ b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/InternalElementWellKnown.kt @@ -46,6 +46,8 @@ data class InternalElementWellKnown( val customRecoveryPassphrase: InternalCustomRecoveryPassphrase? = null, @SerialName("content_scanner_url") val contentScannerUrl: String? = null, + @SerialName("force_disable_e2ee") + val forceDisableE2EE: Boolean? = null, ) @Serializable diff --git a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/Mapper.kt b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/Mapper.kt index 420dea2bae..c4d15e7325 100644 --- a/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/Mapper.kt +++ b/libraries/wellknown/impl/src/main/kotlin/io/element/android/libraries/wellknown/impl/Mapper.kt @@ -31,6 +31,7 @@ fun InternalElementWellKnown.map() = ElementWellKnown( identityProviderAppScheme = identityProviderAppScheme, customRecoveryPassphrase = customRecoveryPassphrase?.toPublic(), contentScannerUrl = contentScannerUrl, + forceDisableE2EE = forceDisableE2EE, ) private fun InternalCustomRecoveryPassphrase.toPublic(): CustomRecoveryPassphrase { diff --git a/libraries/wellknown/impl/src/test/kotlin/io/element/android/libraries/wellknown/impl/DefaultSessionWellknownRetrieverTest.kt b/libraries/wellknown/impl/src/test/kotlin/io/element/android/libraries/wellknown/impl/DefaultSessionWellknownRetrieverTest.kt index ec939b9988..6a2a12774b 100644 --- a/libraries/wellknown/impl/src/test/kotlin/io/element/android/libraries/wellknown/impl/DefaultSessionWellknownRetrieverTest.kt +++ b/libraries/wellknown/impl/src/test/kotlin/io/element/android/libraries/wellknown/impl/DefaultSessionWellknownRetrieverTest.kt @@ -50,6 +50,7 @@ class DefaultSessionWellknownRetrieverTest { identityProviderAppScheme = null, customRecoveryPassphrase = null, contentScannerUrl = null, + forceDisableE2EE = null, ) ) ) @@ -77,6 +78,7 @@ class DefaultSessionWellknownRetrieverTest { identityProviderAppScheme = "an_app_scheme", customRecoveryPassphrase = null, contentScannerUrl = "https://content-scanner.example.com", + forceDisableE2EE = false, ) ) ) @@ -108,6 +110,7 @@ class DefaultSessionWellknownRetrieverTest { identityProviderAppScheme = null, contentScannerUrl = null, customRecoveryPassphrase = null, + forceDisableE2EE = null, ) ) ) @@ -255,6 +258,7 @@ class DefaultSessionWellknownRetrieverTest { identityProviderAppScheme = "an_app_scheme", customRecoveryPassphrase = null, contentScannerUrl = "https://content-scanner.example.com", + forceDisableE2EE = false, ) ) ) @@ -303,6 +307,7 @@ class DefaultSessionWellknownRetrieverTest { identityProviderAppScheme = "an_app_scheme", customRecoveryPassphrase = null, contentScannerUrl = "https://content-scanner.example.com", + forceDisableE2EE = false, ) ) ) @@ -340,7 +345,8 @@ class DefaultSessionWellknownRetrieverTest { "brand_color": "#FF0000", "notification_sound": "a_notification_sound.flac", "idp_app_scheme": "an_app_scheme", - "content_scanner_url": "https://content-scanner.example.com" + "content_scanner_url": "https://content-scanner.example.com", + "force_disable_e2ee": false, }""" } } diff --git a/libraries/wellknown/impl/src/test/kotlin/io/element/android/libraries/wellknown/impl/DefaultWellknownRetrieverTest.kt b/libraries/wellknown/impl/src/test/kotlin/io/element/android/libraries/wellknown/impl/DefaultWellknownRetrieverTest.kt index 0e9288f658..3ee5b2b91f 100644 --- a/libraries/wellknown/impl/src/test/kotlin/io/element/android/libraries/wellknown/impl/DefaultWellknownRetrieverTest.kt +++ b/libraries/wellknown/impl/src/test/kotlin/io/element/android/libraries/wellknown/impl/DefaultWellknownRetrieverTest.kt @@ -50,6 +50,7 @@ class DefaultWellknownRetrieverTest { identityProviderAppScheme = null, customRecoveryPassphrase = null, contentScannerUrl = null, + forceDisableE2EE = null, ) ) ) @@ -72,6 +73,7 @@ class DefaultWellknownRetrieverTest { identityProviderAppScheme = "an_app_scheme", customRecoveryPassphrase = null, contentScannerUrl = "https://content-scanner.example.com", + forceDisableE2EE = false, ) ) ) @@ -101,6 +103,7 @@ class DefaultWellknownRetrieverTest { identityProviderAppScheme = null, contentScannerUrl = null, customRecoveryPassphrase = null, + forceDisableE2EE = null, ) ) ) @@ -266,7 +269,8 @@ class DefaultWellknownRetrieverTest { "brand_color": "#FF0000", "notification_sound": "a_notification_sound.flac", "idp_app_scheme": "an_app_scheme", - "content_scanner_url": "https://content-scanner.example.com" + "content_scanner_url": "https://content-scanner.example.com", + "force_disable_e2ee": false }""" } } diff --git a/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/Fixtures.kt b/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/Fixtures.kt index 8ec8874ad9..941e5f4cc7 100644 --- a/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/Fixtures.kt +++ b/libraries/wellknown/test/src/main/kotlin/io/element/android/features/wellknown/test/Fixtures.kt @@ -20,6 +20,7 @@ fun anElementWellKnown( identityProviderAppScheme: String? = null, customRecoveryPassphrase: CustomRecoveryPassphrase? = null, contentScannerUrl: String? = null, + forceDisableE2EE: Boolean? = null, ) = ElementWellKnown( registrationHelperUrl = registrationHelperUrl, enforceElementPro = enforceElementPro, @@ -29,6 +30,7 @@ fun anElementWellKnown( identityProviderAppScheme = identityProviderAppScheme, customRecoveryPassphrase = customRecoveryPassphrase, contentScannerUrl = contentScannerUrl, + forceDisableE2EE = forceDisableE2EE, ) fun aCustomRecoveryPassphrase(