From 84fdbbe573923d9a6881bf91f4b5a244efb27873 Mon Sep 17 00:00:00 2001 From: mxandreas Date: Mon, 5 Oct 2026 09:30:01 +0300 Subject: [PATCH 1/4] Add a hook to disable public rooms/spaces on UI. --- enterprise | 2 +- .../configureroom/ConfigureRoomPresenter.kt | 9 +++- .../impl/ConfigureRoomPresenterTest.kt | 43 +++++++++++++++++- .../api/SessionEnterpriseService.kt | 6 +++ .../impl/DefaultSessionEnterpriseService.kt | 1 + .../test/FakeSessionEnterpriseService.kt | 5 +++ .../impl/root/SecurityAndPrivacyPresenter.kt | 5 +++ .../impl/root/SecurityAndPrivacyState.kt | 6 +++ .../SecurityAndPrivacyStatePreviewParam.kt | 6 +++ .../impl/root/SecurityAndPrivacyView.kt | 16 ++++--- .../root/SecurityAndPrivacyPresenterTest.kt | 44 ++++++++++++++++++- 11 files changed, 130 insertions(+), 13 deletions(-) diff --git a/enterprise b/enterprise index 073abe3b44..d25d658acb 160000 --- a/enterprise +++ b/enterprise @@ -1 +1 @@ -Subproject commit 073abe3b44598a072a87a9c92d15c6e5bc4a88af +Subproject commit d25d658acbee55e25d20a2f22f411040adfb078c 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 849647a9d3..86cd67546e 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 @@ -16,6 +16,7 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue @@ -92,6 +93,9 @@ class ConfigureRoomPresenter( val isKnockFeatureEnabled by remember { featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock) }.collectAsState(initial = false) + val arePublicRoomsDisabled by produceState(false) { + value = sessionEnterpriseService.arePublicRoomsDisabledByHomeserver() + } val roomAddressValidity = remember { mutableStateOf(RoomAddressValidity.Unknown) } @@ -145,12 +149,13 @@ class ConfigureRoomPresenter( // 1. If we are creating a space. // 2. If it has a parent space. // 3. If knocking is enabled. + // 4. If the homeserver forbids public rooms. val parentSpace = createRoomConfig.parentSpace - val availableJoinRules = remember(parentSpace, isSpace, isKnockFeatureEnabled) { + val availableJoinRules = remember(parentSpace, isSpace, isKnockFeatureEnabled, arePublicRoomsDisabled) { when { isSpace && parentSpace != null -> TODO("Adding a space to a parent space is not supported yet! How did you get here?") parentSpace == null || parentSpace.joinRule == JoinRule.Public -> listOfNotNull( - JoinRuleItem.PublicVisibility.Public, + JoinRuleItem.PublicVisibility.Public.takeIf { !arePublicRoomsDisabled }, JoinRuleItem.PublicVisibility.AskToJoin.takeIf { !isSpace && isKnockFeatureEnabled }, JoinRuleItem.PrivateVisibility.Private, ).toImmutableList() 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 09afd38f90..ebd3cdcdc2 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 @@ -57,6 +57,7 @@ import io.element.android.libraries.previewutils.room.aSpaceRoom 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.consumeItemsUntilPredicate import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.matching import io.element.android.tests.testutils.robolectric.RobolectricTest @@ -522,7 +523,10 @@ 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 sessionEnterpriseService = FakeSessionEnterpriseService( + isEncryptionDisabledResult = { true }, + arePublicRoomsDisabledResult = { false }, + ) val createRoomLambda = lambdaRecorder> { Result.success(A_ROOM_ID) } val matrixClient = createMatrixClient().apply { createRoomResult = createRoomLambda @@ -544,6 +548,38 @@ class ConfigureRoomPresenterTest : RobolectricTest() { } } + @Test + fun `present - when public rooms are disabled by the HS, the public join rule is not available`() = runTest { + val presenter = createConfigureRoomPresenter( + sessionEnterpriseService = FakeSessionEnterpriseService( + isEncryptionDisabledResult = { false }, + arePublicRoomsDisabledResult = { true }, + ), + ) + presenter.test { + val expectedJoinRules = listOf(JoinRuleItem.PublicVisibility.AskToJoin, JoinRuleItem.PrivateVisibility.Private) + val state = consumeItemsUntilPredicate { it.availableJoinRules == expectedJoinRules }.last() + assertThat(state.availableJoinRules).containsExactlyElementsIn(expectedJoinRules).inOrder() + cancelAndIgnoreRemainingEvents() + } + } + + @Test + fun `present - when public rooms are disabled by the HS, only the private join rule is available for a space`() = runTest { + val presenter = createConfigureRoomPresenter( + isSpace = true, + sessionEnterpriseService = FakeSessionEnterpriseService( + isEncryptionDisabledResult = { false }, + arePublicRoomsDisabledResult = { true }, + ), + ) + presenter.test { + val state = consumeItemsUntilPredicate { it.availableJoinRules == listOf(JoinRuleItem.PrivateVisibility.Private) }.last() + assertThat(state.config.visibilityState).isEqualTo(RoomVisibilityState.Private(JoinRuleItem.PrivateVisibility.Private)) + cancelAndIgnoreRemainingEvents() + } + } + private suspend fun TurbineTestContext.initialState(): ConfigureRoomState { skipItems(1) return awaitItem() @@ -579,7 +615,10 @@ class ConfigureRoomPresenterTest : RobolectricTest() { permissionsPresenter: PermissionsPresenter = FakePermissionsPresenter(), isKnockFeatureEnabled: Boolean = true, mediaOptimizationConfigProvider: FakeMediaOptimizationConfigProvider = FakeMediaOptimizationConfigProvider(), - sessionEnterpriseService: FakeSessionEnterpriseService = FakeSessionEnterpriseService(isEncryptionDisabledResult = { false }), + sessionEnterpriseService: FakeSessionEnterpriseService = FakeSessionEnterpriseService( + isEncryptionDisabledResult = { false }, + arePublicRoomsDisabledResult = { false }, + ), ) = ConfigureRoomPresenter( isSpace = isSpace, initialParentSpaceId = initialParenSpaceId, 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 94f455f2ff..aff9a3ae5c 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 @@ -29,4 +29,10 @@ interface SessionEnterpriseService { /** Whether the homeserver forbids creating encrypted rooms, in which case the app must not offer the option. */ suspend fun isEncryptionDisabledByHomeserver(): Boolean + + /** + * Whether the homeserver forbids public rooms and spaces, in which case the app must not offer to create one, + * nor to make an existing room or space public. + */ + suspend fun arePublicRoomsDisabledByHomeserver(): Boolean } 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 3ac10d8a08..8a528257a6 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 @@ -18,4 +18,5 @@ class DefaultSessionEnterpriseService : SessionEnterpriseService { override suspend fun tweakMasUrl(url: String): String = url override suspend fun isElementCallAvailable(): Boolean = true override suspend fun isEncryptionDisabledByHomeserver(): Boolean = false + override suspend fun arePublicRoomsDisabledByHomeserver(): 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 e198fca867..a461b87c95 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 @@ -16,6 +16,7 @@ class FakeSessionEnterpriseService( private val isElementCallAvailableResult: () -> Boolean = { lambdaError() }, private val tweakMasUrlResult: (String) -> String = { lambdaError() }, private val isEncryptionDisabledResult: () -> Boolean = { lambdaError() }, + private val arePublicRoomsDisabledResult: () -> Boolean = { lambdaError() }, ) : SessionEnterpriseService { override suspend fun init() { } @@ -31,4 +32,8 @@ class FakeSessionEnterpriseService( override suspend fun isEncryptionDisabledByHomeserver(): Boolean { return isEncryptionDisabledResult() } + + override suspend fun arePublicRoomsDisabledByHomeserver(): Boolean { + return arePublicRoomsDisabledResult() + } } 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 32517cd18d..498e71dd0a 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 @@ -89,6 +89,10 @@ class SecurityAndPrivacyPresenter( value = sessionEnterpriseService.isEncryptionDisabledByHomeserver() } + val arePublicRoomsDisabledByHomeserver by produceState(false) { + value = sessionEnterpriseService.arePublicRoomsDisabledByHomeserver() + } + val savedIsVisibleInRoomDirectory = remember { mutableStateOf>(AsyncData.Uninitialized) } LaunchedEffect(Unit) { isRoomVisibleInRoomDirectory(savedIsVisibleInRoomDirectory) @@ -254,6 +258,7 @@ class SecurityAndPrivacyPresenter( selectableJoinedSpaces = selectableJoinedSpaces, spaceSelectionMode = spaceSelectionMode, isEncryptionDisabledByHomeserver = isEncryptionDisabledByHomeserver, + arePublicRoomsDisabledByHomeserver = arePublicRoomsDisabledByHomeserver, 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 ddba5d0caa..5b61953674 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 @@ -28,6 +28,7 @@ data class SecurityAndPrivacyState( val editedSettings: SecurityAndPrivacySettings, val homeserverName: String, val isEncryptionDisabledByHomeserver: Boolean, + private val arePublicRoomsDisabledByHomeserver: Boolean, val showEnableEncryptionConfirmation: Boolean, private val isKnockEnabled: Boolean, val saveAction: AsyncAction, @@ -39,6 +40,11 @@ data class SecurityAndPrivacyState( ) { val isSpaceMemberSelectable = spaceSelectionMode != SpaceSelectionMode.None + // Show Anyone option when: + // - Anyone is the current saved value, OR + // - The homeserver allows public rooms + val showAnyoneOption = savedSettings.roomAccess == SecurityAndPrivacyRoomAccess.Anyone || !arePublicRoomsDisabledByHomeserver + // Show SpaceMember option in two cases: // - SpaceMember is the current saved value // - SpaceMember option is selectable (ie. the FF is enabled and there is at least one space to select) diff --git a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyStatePreviewParam.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyStatePreviewParam.kt index 102adb4015..45383bf814 100644 --- a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyStatePreviewParam.kt +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyStatePreviewParam.kt @@ -37,6 +37,10 @@ open class SecurityAndPrivacyStatePreviewParam : PreviewParameterProvider = emptySet(), spaceSelectionMode: SpaceSelectionMode = SpaceSelectionMode.None, isEncryptionDisabledByHomeserver: Boolean = false, + arePublicRoomsDisabledByHomeserver: Boolean = false, eventSink: (SecurityAndPrivacyEvent) -> Unit = {} ) = SecurityAndPrivacyState( editedSettings = editedSettings, @@ -152,5 +157,6 @@ fun aSecurityAndPrivacyState( selectableJoinedSpaces = selectableJoinedSpaces.toImmutableSet(), spaceSelectionMode = spaceSelectionMode, isEncryptionDisabledByHomeserver = isEncryptionDisabledByHomeserver, + arePublicRoomsDisabledByHomeserver = arePublicRoomsDisabledByHomeserver, eventSink = eventSink, ) diff --git a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyView.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyView.kt index eeaa9bbc13..5b224f682c 100644 --- a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyView.kt +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyView.kt @@ -232,13 +232,15 @@ private fun RoomAccessSection( title = stringResource(R.string.screen_security_and_privacy_room_access_section_header), modifier = modifier, ) { - ListItem( - content = { Text(text = stringResource(R.string.screen_security_and_privacy_room_access_anyone_option_title)) }, - supportingContent = { Text(text = stringResource(R.string.screen_security_and_privacy_room_access_anyone_option_description)) }, - trailingContent = ListItemContent.RadioButton(selected = edited == SecurityAndPrivacyRoomAccess.Anyone), - leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Public())), - onClick = { onSelectOption(SecurityAndPrivacyRoomAccess.Anyone) }, - ) + if (state.showAnyoneOption) { + ListItem( + content = { Text(text = stringResource(R.string.screen_security_and_privacy_room_access_anyone_option_title)) }, + supportingContent = { Text(text = stringResource(R.string.screen_security_and_privacy_room_access_anyone_option_description)) }, + trailingContent = ListItemContent.RadioButton(selected = edited == SecurityAndPrivacyRoomAccess.Anyone), + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Public())), + onClick = { onSelectOption(SecurityAndPrivacyRoomAccess.Anyone) }, + ) + } if (state.showSpaceMemberOption) { ListItem( content = { Text(text = stringResource(R.string.screen_security_and_privacy_room_access_space_members_option_title)) }, 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 8cdba871be..5a0cf743e2 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 @@ -34,6 +34,8 @@ import io.element.android.libraries.matrix.test.room.aRoomInfo import io.element.android.libraries.matrix.test.room.powerlevels.FakeRoomPermissions import io.element.android.libraries.matrix.test.spaces.FakeSpaceService import io.element.android.libraries.previewutils.room.aSpaceRoom +import io.element.android.tests.testutils.consumeItemsUntilPredicate +import io.element.android.tests.testutils.consumeItemsUntilTimeout import io.element.android.tests.testutils.lambda.assert import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.lambda.lambdaRecorder @@ -1048,7 +1050,10 @@ class SecurityAndPrivacyPresenterTest { @Test fun `present - showEncryptionSection is false if the HS disabled encryption`() = runTest { - val sessionEnterpriseService = FakeSessionEnterpriseService(isEncryptionDisabledResult = { true }) + val sessionEnterpriseService = FakeSessionEnterpriseService( + isEncryptionDisabledResult = { true }, + arePublicRoomsDisabledResult = { false }, + ) val presenter = createSecurityAndPrivacyPresenter( sessionEnterpriseService = sessionEnterpriseService, ) @@ -1060,6 +1065,42 @@ class SecurityAndPrivacyPresenterTest { } } + @Test + fun `present - showAnyoneOption is false if the HS disabled public rooms`() = runTest { + val presenter = createSecurityAndPrivacyPresenter( + sessionEnterpriseService = FakeSessionEnterpriseService( + isEncryptionDisabledResult = { false }, + arePublicRoomsDisabledResult = { true }, + ), + ) + presenter.test { + consumeItemsUntilPredicate { !it.showAnyoneOption } + cancelAndIgnoreRemainingEvents() + } + } + + @Test + fun `present - showAnyoneOption is true if the HS disabled public rooms but the room is already public`() = runTest { + val presenter = createSecurityAndPrivacyPresenter( + room = FakeJoinedRoom( + baseRoom = FakeBaseRoom( + roomPermissions = roomPermissions(), + getRoomVisibilityResult = { Result.success(RoomVisibility.Public) }, + initialRoomInfo = aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared, joinRule = JoinRule.Public) + ), + ), + sessionEnterpriseService = FakeSessionEnterpriseService( + isEncryptionDisabledResult = { false }, + arePublicRoomsDisabledResult = { true }, + ), + ) + presenter.test { + val states = consumeItemsUntilTimeout() + assertThat(states).isNotEmpty() + assertThat(states.all { it.showAnyoneOption }).isTrue() + } + } + private fun roomPermissions( canChangeRoomAccess: Boolean = true, canChangeHistoryVisibility: Boolean = true, @@ -1100,6 +1141,7 @@ class SecurityAndPrivacyPresenterTest { spaceSelectionStateHolder: SpaceSelectionStateHolder = SpaceSelectionStateHolder(), sessionEnterpriseService: FakeSessionEnterpriseService = FakeSessionEnterpriseService( isEncryptionDisabledResult = { false }, + arePublicRoomsDisabledResult = { false }, ) ): SecurityAndPrivacyPresenter { return SecurityAndPrivacyPresenter( From 54409a82af0395b68af5afdb467939bc9d9fe97f Mon Sep 17 00:00:00 2001 From: ElementBot Date: Mon, 5 Oct 2026 10:02:52 +0000 Subject: [PATCH 2/4] Update screenshots --- ...tyandprivacy.impl.root_SecurityAndPrivacyViewDark_24_en.png | 3 +++ ...yandprivacy.impl.root_SecurityAndPrivacyViewLight_24_en.png | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 tests/uitests/src/test/snapshots/images/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_24_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_24_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_24_en.png b/tests/uitests/src/test/snapshots/images/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_24_en.png new file mode 100644 index 0000000000..94f775e378 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.securityandprivacy.impl.root_SecurityAndPrivacyViewDark_24_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:47690874ef06996ff6a6617443f79596c5ed124de15831e0a3ec791a99890613 +size 35568 diff --git a/tests/uitests/src/test/snapshots/images/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_24_en.png b/tests/uitests/src/test/snapshots/images/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_24_en.png new file mode 100644 index 0000000000..cba7c2b779 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.securityandprivacy.impl.root_SecurityAndPrivacyViewLight_24_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b3cd133c2ca779fcf03c9afa227a3187cb2de655bc313aa176505593f855f6ff +size 37039 From f2310cc53063de2bc149e119f68ccaae3cf147c9 Mon Sep 17 00:00:00 2001 From: mxandreas Date: Tue, 6 Oct 2026 09:53:26 +0300 Subject: [PATCH 3/4] Made the hooks of room creation and security & privacy screen more generic. --- enterprise | 2 +- .../configureroom/ConfigureRoomExtension.kt | 14 ++++++ .../configureroom/ConfigureRoomPresenter.kt | 11 ++--- .../NoopConfigureRoomExtension.kt | 16 +++++++ .../impl/ConfigureRoomPresenterTest.kt | 45 ++----------------- .../api/SessionEnterpriseService.kt | 6 --- .../impl/DefaultSessionEnterpriseService.kt | 1 - .../test/FakeSessionEnterpriseService.kt | 5 --- .../root/NoopSecurityAndPrivacyExtension.kt | 16 +++++++ .../impl/root/SecurityAndPrivacyExtension.kt | 14 ++++++ .../impl/root/SecurityAndPrivacyPresenter.kt | 7 +-- .../impl/root/SecurityAndPrivacyState.kt | 6 +-- .../SecurityAndPrivacyStatePreviewParam.kt | 8 +--- .../root/SecurityAndPrivacyPresenterTest.kt | 45 +------------------ 14 files changed, 82 insertions(+), 114 deletions(-) create mode 100644 features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomExtension.kt create mode 100644 features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/NoopConfigureRoomExtension.kt create mode 100644 features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/NoopSecurityAndPrivacyExtension.kt create mode 100644 features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyExtension.kt diff --git a/enterprise b/enterprise index d25d658acb..eb05f3b813 160000 --- a/enterprise +++ b/enterprise @@ -1 +1 @@ -Subproject commit d25d658acbee55e25d20a2f22f411040adfb078c +Subproject commit eb05f3b8131dead9dceca483a1372d8bb4306ab1 diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomExtension.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomExtension.kt new file mode 100644 index 0000000000..0b2f162e8e --- /dev/null +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomExtension.kt @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2026 Element Creations 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.features.createroom.impl.configureroom + +/** Lets a build-specific implementation adjust what the configure room screen offers. */ +interface ConfigureRoomExtension { + /** Whether the public join rule may be offered. */ + suspend fun isPublicAccessAllowed(): Boolean +} 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 86cd67546e..57ae875771 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 @@ -76,6 +76,7 @@ class ConfigureRoomPresenter( private val roomAliasHelper: RoomAliasHelper, private val mediaOptimizationConfigProvider: MediaOptimizationConfigProvider, private val sessionEnterpriseService: SessionEnterpriseService, + private val configureRoomExtension: ConfigureRoomExtension, ) : Presenter { @AssistedFactory interface Factory { @@ -93,8 +94,8 @@ class ConfigureRoomPresenter( val isKnockFeatureEnabled by remember { featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock) }.collectAsState(initial = false) - val arePublicRoomsDisabled by produceState(false) { - value = sessionEnterpriseService.arePublicRoomsDisabledByHomeserver() + val isPublicAccessAllowed by produceState(true) { + value = configureRoomExtension.isPublicAccessAllowed() } val roomAddressValidity = remember { mutableStateOf(RoomAddressValidity.Unknown) @@ -149,13 +150,13 @@ class ConfigureRoomPresenter( // 1. If we are creating a space. // 2. If it has a parent space. // 3. If knocking is enabled. - // 4. If the homeserver forbids public rooms. + // 4. If public access is allowed. val parentSpace = createRoomConfig.parentSpace - val availableJoinRules = remember(parentSpace, isSpace, isKnockFeatureEnabled, arePublicRoomsDisabled) { + val availableJoinRules = remember(parentSpace, isSpace, isKnockFeatureEnabled, isPublicAccessAllowed) { when { isSpace && parentSpace != null -> TODO("Adding a space to a parent space is not supported yet! How did you get here?") parentSpace == null || parentSpace.joinRule == JoinRule.Public -> listOfNotNull( - JoinRuleItem.PublicVisibility.Public.takeIf { !arePublicRoomsDisabled }, + JoinRuleItem.PublicVisibility.Public.takeIf { isPublicAccessAllowed }, JoinRuleItem.PublicVisibility.AskToJoin.takeIf { !isSpace && isKnockFeatureEnabled }, JoinRuleItem.PrivateVisibility.Private, ).toImmutableList() diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/NoopConfigureRoomExtension.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/NoopConfigureRoomExtension.kt new file mode 100644 index 0000000000..8735ffde33 --- /dev/null +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/NoopConfigureRoomExtension.kt @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2026 Element Creations 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.features.createroom.impl.configureroom + +import dev.zacsweers.metro.ContributesBinding +import io.element.android.libraries.di.SessionScope + +@ContributesBinding(SessionScope::class) +class NoopConfigureRoomExtension : ConfigureRoomExtension { + override suspend fun isPublicAccessAllowed(): Boolean = true +} 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 ebd3cdcdc2..5a247a78d8 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 @@ -18,6 +18,7 @@ import io.element.android.features.createroom.impl.configureroom.ConfigureRoomSt import io.element.android.features.createroom.impl.configureroom.CreateRoomConfig import io.element.android.features.createroom.impl.configureroom.CreateRoomConfigStore import io.element.android.features.createroom.impl.configureroom.JoinRuleItem +import io.element.android.features.createroom.impl.configureroom.NoopConfigureRoomExtension 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 @@ -57,7 +58,6 @@ import io.element.android.libraries.previewutils.room.aSpaceRoom 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.consumeItemsUntilPredicate import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.matching import io.element.android.tests.testutils.robolectric.RobolectricTest @@ -523,10 +523,7 @@ 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 }, - arePublicRoomsDisabledResult = { false }, - ) + val sessionEnterpriseService = FakeSessionEnterpriseService(isEncryptionDisabledResult = { true }) val createRoomLambda = lambdaRecorder> { Result.success(A_ROOM_ID) } val matrixClient = createMatrixClient().apply { createRoomResult = createRoomLambda @@ -548,38 +545,6 @@ class ConfigureRoomPresenterTest : RobolectricTest() { } } - @Test - fun `present - when public rooms are disabled by the HS, the public join rule is not available`() = runTest { - val presenter = createConfigureRoomPresenter( - sessionEnterpriseService = FakeSessionEnterpriseService( - isEncryptionDisabledResult = { false }, - arePublicRoomsDisabledResult = { true }, - ), - ) - presenter.test { - val expectedJoinRules = listOf(JoinRuleItem.PublicVisibility.AskToJoin, JoinRuleItem.PrivateVisibility.Private) - val state = consumeItemsUntilPredicate { it.availableJoinRules == expectedJoinRules }.last() - assertThat(state.availableJoinRules).containsExactlyElementsIn(expectedJoinRules).inOrder() - cancelAndIgnoreRemainingEvents() - } - } - - @Test - fun `present - when public rooms are disabled by the HS, only the private join rule is available for a space`() = runTest { - val presenter = createConfigureRoomPresenter( - isSpace = true, - sessionEnterpriseService = FakeSessionEnterpriseService( - isEncryptionDisabledResult = { false }, - arePublicRoomsDisabledResult = { true }, - ), - ) - presenter.test { - val state = consumeItemsUntilPredicate { it.availableJoinRules == listOf(JoinRuleItem.PrivateVisibility.Private) }.last() - assertThat(state.config.visibilityState).isEqualTo(RoomVisibilityState.Private(JoinRuleItem.PrivateVisibility.Private)) - cancelAndIgnoreRemainingEvents() - } - } - private suspend fun TurbineTestContext.initialState(): ConfigureRoomState { skipItems(1) return awaitItem() @@ -615,10 +580,7 @@ class ConfigureRoomPresenterTest : RobolectricTest() { permissionsPresenter: PermissionsPresenter = FakePermissionsPresenter(), isKnockFeatureEnabled: Boolean = true, mediaOptimizationConfigProvider: FakeMediaOptimizationConfigProvider = FakeMediaOptimizationConfigProvider(), - sessionEnterpriseService: FakeSessionEnterpriseService = FakeSessionEnterpriseService( - isEncryptionDisabledResult = { false }, - arePublicRoomsDisabledResult = { false }, - ), + sessionEnterpriseService: FakeSessionEnterpriseService = FakeSessionEnterpriseService(isEncryptionDisabledResult = { false }), ) = ConfigureRoomPresenter( isSpace = isSpace, initialParentSpaceId = initialParenSpaceId, @@ -634,5 +596,6 @@ class ConfigureRoomPresenterTest : RobolectricTest() { ), mediaOptimizationConfigProvider = mediaOptimizationConfigProvider, sessionEnterpriseService = sessionEnterpriseService, + configureRoomExtension = NoopConfigureRoomExtension(), ) } 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 aff9a3ae5c..94f455f2ff 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 @@ -29,10 +29,4 @@ interface SessionEnterpriseService { /** Whether the homeserver forbids creating encrypted rooms, in which case the app must not offer the option. */ suspend fun isEncryptionDisabledByHomeserver(): Boolean - - /** - * Whether the homeserver forbids public rooms and spaces, in which case the app must not offer to create one, - * nor to make an existing room or space public. - */ - suspend fun arePublicRoomsDisabledByHomeserver(): Boolean } 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 8a528257a6..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 @@ -18,5 +18,4 @@ class DefaultSessionEnterpriseService : SessionEnterpriseService { override suspend fun tweakMasUrl(url: String): String = url override suspend fun isElementCallAvailable(): Boolean = true override suspend fun isEncryptionDisabledByHomeserver(): Boolean = false - override suspend fun arePublicRoomsDisabledByHomeserver(): 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 a461b87c95..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 @@ -16,7 +16,6 @@ class FakeSessionEnterpriseService( private val isElementCallAvailableResult: () -> Boolean = { lambdaError() }, private val tweakMasUrlResult: (String) -> String = { lambdaError() }, private val isEncryptionDisabledResult: () -> Boolean = { lambdaError() }, - private val arePublicRoomsDisabledResult: () -> Boolean = { lambdaError() }, ) : SessionEnterpriseService { override suspend fun init() { } @@ -32,8 +31,4 @@ class FakeSessionEnterpriseService( override suspend fun isEncryptionDisabledByHomeserver(): Boolean { return isEncryptionDisabledResult() } - - override suspend fun arePublicRoomsDisabledByHomeserver(): Boolean { - return arePublicRoomsDisabledResult() - } } diff --git a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/NoopSecurityAndPrivacyExtension.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/NoopSecurityAndPrivacyExtension.kt new file mode 100644 index 0000000000..3471a48a90 --- /dev/null +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/NoopSecurityAndPrivacyExtension.kt @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2026 Element Creations 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.features.securityandprivacy.impl.root + +import dev.zacsweers.metro.ContributesBinding +import io.element.android.libraries.di.RoomScope + +@ContributesBinding(RoomScope::class) +class NoopSecurityAndPrivacyExtension : SecurityAndPrivacyExtension { + override suspend fun isPublicAccessAllowed(): Boolean = true +} diff --git a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyExtension.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyExtension.kt new file mode 100644 index 0000000000..320d04a48d --- /dev/null +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyExtension.kt @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2026 Element Creations 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.features.securityandprivacy.impl.root + +/** Lets a build-specific implementation adjust what the security and privacy screen offers. */ +interface SecurityAndPrivacyExtension { + /** Whether the room access may be changed to anyone. */ + suspend fun isPublicAccessAllowed(): Boolean +} 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 498e71dd0a..c79401ff1c 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 @@ -65,6 +65,7 @@ class SecurityAndPrivacyPresenter( private val room: JoinedRoom, private val featureFlagService: FeatureFlagService, private val sessionEnterpriseService: SessionEnterpriseService, + private val securityAndPrivacyExtension: SecurityAndPrivacyExtension, ) : Presenter { @AssistedFactory interface Factory { @@ -89,8 +90,8 @@ class SecurityAndPrivacyPresenter( value = sessionEnterpriseService.isEncryptionDisabledByHomeserver() } - val arePublicRoomsDisabledByHomeserver by produceState(false) { - value = sessionEnterpriseService.arePublicRoomsDisabledByHomeserver() + val isPublicAccessAllowed by produceState(true) { + value = securityAndPrivacyExtension.isPublicAccessAllowed() } val savedIsVisibleInRoomDirectory = remember { mutableStateOf>(AsyncData.Uninitialized) } @@ -258,7 +259,7 @@ class SecurityAndPrivacyPresenter( selectableJoinedSpaces = selectableJoinedSpaces, spaceSelectionMode = spaceSelectionMode, isEncryptionDisabledByHomeserver = isEncryptionDisabledByHomeserver, - arePublicRoomsDisabledByHomeserver = arePublicRoomsDisabledByHomeserver, + isPublicAccessAllowed = isPublicAccessAllowed, 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 5b61953674..d65493fda9 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 @@ -28,7 +28,7 @@ data class SecurityAndPrivacyState( val editedSettings: SecurityAndPrivacySettings, val homeserverName: String, val isEncryptionDisabledByHomeserver: Boolean, - private val arePublicRoomsDisabledByHomeserver: Boolean, + private val isPublicAccessAllowed: Boolean, val showEnableEncryptionConfirmation: Boolean, private val isKnockEnabled: Boolean, val saveAction: AsyncAction, @@ -42,8 +42,8 @@ data class SecurityAndPrivacyState( // Show Anyone option when: // - Anyone is the current saved value, OR - // - The homeserver allows public rooms - val showAnyoneOption = savedSettings.roomAccess == SecurityAndPrivacyRoomAccess.Anyone || !arePublicRoomsDisabledByHomeserver + // - Public access is allowed + val showAnyoneOption = savedSettings.roomAccess == SecurityAndPrivacyRoomAccess.Anyone || isPublicAccessAllowed // Show SpaceMember option in two cases: // - SpaceMember is the current saved value diff --git a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyStatePreviewParam.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyStatePreviewParam.kt index 45383bf814..44d13c4ca3 100644 --- a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyStatePreviewParam.kt +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyStatePreviewParam.kt @@ -37,10 +37,6 @@ open class SecurityAndPrivacyStatePreviewParam : PreviewParameterProvider = emptySet(), spaceSelectionMode: SpaceSelectionMode = SpaceSelectionMode.None, isEncryptionDisabledByHomeserver: Boolean = false, - arePublicRoomsDisabledByHomeserver: Boolean = false, + isPublicAccessAllowed: Boolean = true, eventSink: (SecurityAndPrivacyEvent) -> Unit = {} ) = SecurityAndPrivacyState( editedSettings = editedSettings, @@ -157,6 +153,6 @@ fun aSecurityAndPrivacyState( selectableJoinedSpaces = selectableJoinedSpaces.toImmutableSet(), spaceSelectionMode = spaceSelectionMode, isEncryptionDisabledByHomeserver = isEncryptionDisabledByHomeserver, - arePublicRoomsDisabledByHomeserver = arePublicRoomsDisabledByHomeserver, + isPublicAccessAllowed = isPublicAccessAllowed, 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 5a0cf743e2..10669fc15a 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 @@ -34,8 +34,6 @@ import io.element.android.libraries.matrix.test.room.aRoomInfo import io.element.android.libraries.matrix.test.room.powerlevels.FakeRoomPermissions import io.element.android.libraries.matrix.test.spaces.FakeSpaceService import io.element.android.libraries.previewutils.room.aSpaceRoom -import io.element.android.tests.testutils.consumeItemsUntilPredicate -import io.element.android.tests.testutils.consumeItemsUntilTimeout import io.element.android.tests.testutils.lambda.assert import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.lambda.lambdaRecorder @@ -1050,10 +1048,7 @@ class SecurityAndPrivacyPresenterTest { @Test fun `present - showEncryptionSection is false if the HS disabled encryption`() = runTest { - val sessionEnterpriseService = FakeSessionEnterpriseService( - isEncryptionDisabledResult = { true }, - arePublicRoomsDisabledResult = { false }, - ) + val sessionEnterpriseService = FakeSessionEnterpriseService(isEncryptionDisabledResult = { true }) val presenter = createSecurityAndPrivacyPresenter( sessionEnterpriseService = sessionEnterpriseService, ) @@ -1065,42 +1060,6 @@ class SecurityAndPrivacyPresenterTest { } } - @Test - fun `present - showAnyoneOption is false if the HS disabled public rooms`() = runTest { - val presenter = createSecurityAndPrivacyPresenter( - sessionEnterpriseService = FakeSessionEnterpriseService( - isEncryptionDisabledResult = { false }, - arePublicRoomsDisabledResult = { true }, - ), - ) - presenter.test { - consumeItemsUntilPredicate { !it.showAnyoneOption } - cancelAndIgnoreRemainingEvents() - } - } - - @Test - fun `present - showAnyoneOption is true if the HS disabled public rooms but the room is already public`() = runTest { - val presenter = createSecurityAndPrivacyPresenter( - room = FakeJoinedRoom( - baseRoom = FakeBaseRoom( - roomPermissions = roomPermissions(), - getRoomVisibilityResult = { Result.success(RoomVisibility.Public) }, - initialRoomInfo = aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared, joinRule = JoinRule.Public) - ), - ), - sessionEnterpriseService = FakeSessionEnterpriseService( - isEncryptionDisabledResult = { false }, - arePublicRoomsDisabledResult = { true }, - ), - ) - presenter.test { - val states = consumeItemsUntilTimeout() - assertThat(states).isNotEmpty() - assertThat(states.all { it.showAnyoneOption }).isTrue() - } - } - private fun roomPermissions( canChangeRoomAccess: Boolean = true, canChangeHistoryVisibility: Boolean = true, @@ -1141,7 +1100,6 @@ class SecurityAndPrivacyPresenterTest { spaceSelectionStateHolder: SpaceSelectionStateHolder = SpaceSelectionStateHolder(), sessionEnterpriseService: FakeSessionEnterpriseService = FakeSessionEnterpriseService( isEncryptionDisabledResult = { false }, - arePublicRoomsDisabledResult = { false }, ) ): SecurityAndPrivacyPresenter { return SecurityAndPrivacyPresenter( @@ -1151,6 +1109,7 @@ class SecurityAndPrivacyPresenterTest { featureFlagService = featureFlagService, spaceSelectionStateHolder = spaceSelectionStateHolder, sessionEnterpriseService = sessionEnterpriseService, + securityAndPrivacyExtension = NoopSecurityAndPrivacyExtension(), ) } } From 118b5448ac6917b23ddce6ba3084d0cd6b2fc481 Mon Sep 17 00:00:00 2001 From: mxandreas Date: Fri, 9 Oct 2026 09:50:14 +0300 Subject: [PATCH 4/4] Revert to the SessionEnterpriseService approach --- enterprise | 2 +- .../configureroom/ConfigureRoomExtension.kt | 14 ------ .../configureroom/ConfigureRoomPresenter.kt | 11 +++-- .../NoopConfigureRoomExtension.kt | 16 ------- .../impl/ConfigureRoomPresenterTest.kt | 45 +++++++++++++++++-- .../api/SessionEnterpriseService.kt | 6 +++ .../impl/DefaultSessionEnterpriseService.kt | 1 + .../test/FakeSessionEnterpriseService.kt | 5 +++ .../root/NoopSecurityAndPrivacyExtension.kt | 16 ------- .../impl/root/SecurityAndPrivacyExtension.kt | 14 ------ .../impl/root/SecurityAndPrivacyPresenter.kt | 7 ++- .../impl/root/SecurityAndPrivacyState.kt | 6 +-- .../SecurityAndPrivacyStatePreviewParam.kt | 8 +++- .../root/SecurityAndPrivacyPresenterTest.kt | 45 ++++++++++++++++++- 14 files changed, 114 insertions(+), 82 deletions(-) delete mode 100644 features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomExtension.kt delete mode 100644 features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/NoopConfigureRoomExtension.kt delete mode 100644 features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/NoopSecurityAndPrivacyExtension.kt delete mode 100644 features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyExtension.kt diff --git a/enterprise b/enterprise index eb05f3b813..1a64cd5b86 160000 --- a/enterprise +++ b/enterprise @@ -1 +1 @@ -Subproject commit eb05f3b8131dead9dceca483a1372d8bb4306ab1 +Subproject commit 1a64cd5b86b07b6be0be6a1d6515764743344013 diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomExtension.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomExtension.kt deleted file mode 100644 index 0b2f162e8e..0000000000 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomExtension.kt +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright (c) 2026 Element Creations 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.features.createroom.impl.configureroom - -/** Lets a build-specific implementation adjust what the configure room screen offers. */ -interface ConfigureRoomExtension { - /** Whether the public join rule may be offered. */ - suspend fun isPublicAccessAllowed(): Boolean -} 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 57ae875771..86cd67546e 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 @@ -76,7 +76,6 @@ class ConfigureRoomPresenter( private val roomAliasHelper: RoomAliasHelper, private val mediaOptimizationConfigProvider: MediaOptimizationConfigProvider, private val sessionEnterpriseService: SessionEnterpriseService, - private val configureRoomExtension: ConfigureRoomExtension, ) : Presenter { @AssistedFactory interface Factory { @@ -94,8 +93,8 @@ class ConfigureRoomPresenter( val isKnockFeatureEnabled by remember { featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock) }.collectAsState(initial = false) - val isPublicAccessAllowed by produceState(true) { - value = configureRoomExtension.isPublicAccessAllowed() + val arePublicRoomsDisabled by produceState(false) { + value = sessionEnterpriseService.arePublicRoomsDisabledByHomeserver() } val roomAddressValidity = remember { mutableStateOf(RoomAddressValidity.Unknown) @@ -150,13 +149,13 @@ class ConfigureRoomPresenter( // 1. If we are creating a space. // 2. If it has a parent space. // 3. If knocking is enabled. - // 4. If public access is allowed. + // 4. If the homeserver forbids public rooms. val parentSpace = createRoomConfig.parentSpace - val availableJoinRules = remember(parentSpace, isSpace, isKnockFeatureEnabled, isPublicAccessAllowed) { + val availableJoinRules = remember(parentSpace, isSpace, isKnockFeatureEnabled, arePublicRoomsDisabled) { when { isSpace && parentSpace != null -> TODO("Adding a space to a parent space is not supported yet! How did you get here?") parentSpace == null || parentSpace.joinRule == JoinRule.Public -> listOfNotNull( - JoinRuleItem.PublicVisibility.Public.takeIf { isPublicAccessAllowed }, + JoinRuleItem.PublicVisibility.Public.takeIf { !arePublicRoomsDisabled }, JoinRuleItem.PublicVisibility.AskToJoin.takeIf { !isSpace && isKnockFeatureEnabled }, JoinRuleItem.PrivateVisibility.Private, ).toImmutableList() diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/NoopConfigureRoomExtension.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/NoopConfigureRoomExtension.kt deleted file mode 100644 index 8735ffde33..0000000000 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/NoopConfigureRoomExtension.kt +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright (c) 2026 Element Creations 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.features.createroom.impl.configureroom - -import dev.zacsweers.metro.ContributesBinding -import io.element.android.libraries.di.SessionScope - -@ContributesBinding(SessionScope::class) -class NoopConfigureRoomExtension : ConfigureRoomExtension { - override suspend fun isPublicAccessAllowed(): Boolean = true -} 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 5a247a78d8..ebd3cdcdc2 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 @@ -18,7 +18,6 @@ import io.element.android.features.createroom.impl.configureroom.ConfigureRoomSt import io.element.android.features.createroom.impl.configureroom.CreateRoomConfig import io.element.android.features.createroom.impl.configureroom.CreateRoomConfigStore import io.element.android.features.createroom.impl.configureroom.JoinRuleItem -import io.element.android.features.createroom.impl.configureroom.NoopConfigureRoomExtension 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 @@ -58,6 +57,7 @@ import io.element.android.libraries.previewutils.room.aSpaceRoom 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.consumeItemsUntilPredicate import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.matching import io.element.android.tests.testutils.robolectric.RobolectricTest @@ -523,7 +523,10 @@ 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 sessionEnterpriseService = FakeSessionEnterpriseService( + isEncryptionDisabledResult = { true }, + arePublicRoomsDisabledResult = { false }, + ) val createRoomLambda = lambdaRecorder> { Result.success(A_ROOM_ID) } val matrixClient = createMatrixClient().apply { createRoomResult = createRoomLambda @@ -545,6 +548,38 @@ class ConfigureRoomPresenterTest : RobolectricTest() { } } + @Test + fun `present - when public rooms are disabled by the HS, the public join rule is not available`() = runTest { + val presenter = createConfigureRoomPresenter( + sessionEnterpriseService = FakeSessionEnterpriseService( + isEncryptionDisabledResult = { false }, + arePublicRoomsDisabledResult = { true }, + ), + ) + presenter.test { + val expectedJoinRules = listOf(JoinRuleItem.PublicVisibility.AskToJoin, JoinRuleItem.PrivateVisibility.Private) + val state = consumeItemsUntilPredicate { it.availableJoinRules == expectedJoinRules }.last() + assertThat(state.availableJoinRules).containsExactlyElementsIn(expectedJoinRules).inOrder() + cancelAndIgnoreRemainingEvents() + } + } + + @Test + fun `present - when public rooms are disabled by the HS, only the private join rule is available for a space`() = runTest { + val presenter = createConfigureRoomPresenter( + isSpace = true, + sessionEnterpriseService = FakeSessionEnterpriseService( + isEncryptionDisabledResult = { false }, + arePublicRoomsDisabledResult = { true }, + ), + ) + presenter.test { + val state = consumeItemsUntilPredicate { it.availableJoinRules == listOf(JoinRuleItem.PrivateVisibility.Private) }.last() + assertThat(state.config.visibilityState).isEqualTo(RoomVisibilityState.Private(JoinRuleItem.PrivateVisibility.Private)) + cancelAndIgnoreRemainingEvents() + } + } + private suspend fun TurbineTestContext.initialState(): ConfigureRoomState { skipItems(1) return awaitItem() @@ -580,7 +615,10 @@ class ConfigureRoomPresenterTest : RobolectricTest() { permissionsPresenter: PermissionsPresenter = FakePermissionsPresenter(), isKnockFeatureEnabled: Boolean = true, mediaOptimizationConfigProvider: FakeMediaOptimizationConfigProvider = FakeMediaOptimizationConfigProvider(), - sessionEnterpriseService: FakeSessionEnterpriseService = FakeSessionEnterpriseService(isEncryptionDisabledResult = { false }), + sessionEnterpriseService: FakeSessionEnterpriseService = FakeSessionEnterpriseService( + isEncryptionDisabledResult = { false }, + arePublicRoomsDisabledResult = { false }, + ), ) = ConfigureRoomPresenter( isSpace = isSpace, initialParentSpaceId = initialParenSpaceId, @@ -596,6 +634,5 @@ class ConfigureRoomPresenterTest : RobolectricTest() { ), mediaOptimizationConfigProvider = mediaOptimizationConfigProvider, sessionEnterpriseService = sessionEnterpriseService, - configureRoomExtension = NoopConfigureRoomExtension(), ) } 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 94f455f2ff..aff9a3ae5c 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 @@ -29,4 +29,10 @@ interface SessionEnterpriseService { /** Whether the homeserver forbids creating encrypted rooms, in which case the app must not offer the option. */ suspend fun isEncryptionDisabledByHomeserver(): Boolean + + /** + * Whether the homeserver forbids public rooms and spaces, in which case the app must not offer to create one, + * nor to make an existing room or space public. + */ + suspend fun arePublicRoomsDisabledByHomeserver(): Boolean } 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 3ac10d8a08..8a528257a6 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 @@ -18,4 +18,5 @@ class DefaultSessionEnterpriseService : SessionEnterpriseService { override suspend fun tweakMasUrl(url: String): String = url override suspend fun isElementCallAvailable(): Boolean = true override suspend fun isEncryptionDisabledByHomeserver(): Boolean = false + override suspend fun arePublicRoomsDisabledByHomeserver(): 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 e198fca867..a461b87c95 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 @@ -16,6 +16,7 @@ class FakeSessionEnterpriseService( private val isElementCallAvailableResult: () -> Boolean = { lambdaError() }, private val tweakMasUrlResult: (String) -> String = { lambdaError() }, private val isEncryptionDisabledResult: () -> Boolean = { lambdaError() }, + private val arePublicRoomsDisabledResult: () -> Boolean = { lambdaError() }, ) : SessionEnterpriseService { override suspend fun init() { } @@ -31,4 +32,8 @@ class FakeSessionEnterpriseService( override suspend fun isEncryptionDisabledByHomeserver(): Boolean { return isEncryptionDisabledResult() } + + override suspend fun arePublicRoomsDisabledByHomeserver(): Boolean { + return arePublicRoomsDisabledResult() + } } diff --git a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/NoopSecurityAndPrivacyExtension.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/NoopSecurityAndPrivacyExtension.kt deleted file mode 100644 index 3471a48a90..0000000000 --- a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/NoopSecurityAndPrivacyExtension.kt +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright (c) 2026 Element Creations 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.features.securityandprivacy.impl.root - -import dev.zacsweers.metro.ContributesBinding -import io.element.android.libraries.di.RoomScope - -@ContributesBinding(RoomScope::class) -class NoopSecurityAndPrivacyExtension : SecurityAndPrivacyExtension { - override suspend fun isPublicAccessAllowed(): Boolean = true -} diff --git a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyExtension.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyExtension.kt deleted file mode 100644 index 320d04a48d..0000000000 --- a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyExtension.kt +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright (c) 2026 Element Creations 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.features.securityandprivacy.impl.root - -/** Lets a build-specific implementation adjust what the security and privacy screen offers. */ -interface SecurityAndPrivacyExtension { - /** Whether the room access may be changed to anyone. */ - suspend fun isPublicAccessAllowed(): Boolean -} 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 c79401ff1c..498e71dd0a 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 @@ -65,7 +65,6 @@ class SecurityAndPrivacyPresenter( private val room: JoinedRoom, private val featureFlagService: FeatureFlagService, private val sessionEnterpriseService: SessionEnterpriseService, - private val securityAndPrivacyExtension: SecurityAndPrivacyExtension, ) : Presenter { @AssistedFactory interface Factory { @@ -90,8 +89,8 @@ class SecurityAndPrivacyPresenter( value = sessionEnterpriseService.isEncryptionDisabledByHomeserver() } - val isPublicAccessAllowed by produceState(true) { - value = securityAndPrivacyExtension.isPublicAccessAllowed() + val arePublicRoomsDisabledByHomeserver by produceState(false) { + value = sessionEnterpriseService.arePublicRoomsDisabledByHomeserver() } val savedIsVisibleInRoomDirectory = remember { mutableStateOf>(AsyncData.Uninitialized) } @@ -259,7 +258,7 @@ class SecurityAndPrivacyPresenter( selectableJoinedSpaces = selectableJoinedSpaces, spaceSelectionMode = spaceSelectionMode, isEncryptionDisabledByHomeserver = isEncryptionDisabledByHomeserver, - isPublicAccessAllowed = isPublicAccessAllowed, + arePublicRoomsDisabledByHomeserver = arePublicRoomsDisabledByHomeserver, 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 d65493fda9..5b61953674 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 @@ -28,7 +28,7 @@ data class SecurityAndPrivacyState( val editedSettings: SecurityAndPrivacySettings, val homeserverName: String, val isEncryptionDisabledByHomeserver: Boolean, - private val isPublicAccessAllowed: Boolean, + private val arePublicRoomsDisabledByHomeserver: Boolean, val showEnableEncryptionConfirmation: Boolean, private val isKnockEnabled: Boolean, val saveAction: AsyncAction, @@ -42,8 +42,8 @@ data class SecurityAndPrivacyState( // Show Anyone option when: // - Anyone is the current saved value, OR - // - Public access is allowed - val showAnyoneOption = savedSettings.roomAccess == SecurityAndPrivacyRoomAccess.Anyone || isPublicAccessAllowed + // - The homeserver allows public rooms + val showAnyoneOption = savedSettings.roomAccess == SecurityAndPrivacyRoomAccess.Anyone || !arePublicRoomsDisabledByHomeserver // Show SpaceMember option in two cases: // - SpaceMember is the current saved value diff --git a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyStatePreviewParam.kt b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyStatePreviewParam.kt index 44d13c4ca3..45383bf814 100644 --- a/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyStatePreviewParam.kt +++ b/features/securityandprivacy/impl/src/main/kotlin/io/element/android/features/securityandprivacy/impl/root/SecurityAndPrivacyStatePreviewParam.kt @@ -37,6 +37,10 @@ open class SecurityAndPrivacyStatePreviewParam : PreviewParameterProvider = emptySet(), spaceSelectionMode: SpaceSelectionMode = SpaceSelectionMode.None, isEncryptionDisabledByHomeserver: Boolean = false, - isPublicAccessAllowed: Boolean = true, + arePublicRoomsDisabledByHomeserver: Boolean = false, eventSink: (SecurityAndPrivacyEvent) -> Unit = {} ) = SecurityAndPrivacyState( editedSettings = editedSettings, @@ -153,6 +157,6 @@ fun aSecurityAndPrivacyState( selectableJoinedSpaces = selectableJoinedSpaces.toImmutableSet(), spaceSelectionMode = spaceSelectionMode, isEncryptionDisabledByHomeserver = isEncryptionDisabledByHomeserver, - isPublicAccessAllowed = isPublicAccessAllowed, + arePublicRoomsDisabledByHomeserver = arePublicRoomsDisabledByHomeserver, 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 10669fc15a..5a0cf743e2 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 @@ -34,6 +34,8 @@ import io.element.android.libraries.matrix.test.room.aRoomInfo import io.element.android.libraries.matrix.test.room.powerlevels.FakeRoomPermissions import io.element.android.libraries.matrix.test.spaces.FakeSpaceService import io.element.android.libraries.previewutils.room.aSpaceRoom +import io.element.android.tests.testutils.consumeItemsUntilPredicate +import io.element.android.tests.testutils.consumeItemsUntilTimeout import io.element.android.tests.testutils.lambda.assert import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.lambda.lambdaRecorder @@ -1048,7 +1050,10 @@ class SecurityAndPrivacyPresenterTest { @Test fun `present - showEncryptionSection is false if the HS disabled encryption`() = runTest { - val sessionEnterpriseService = FakeSessionEnterpriseService(isEncryptionDisabledResult = { true }) + val sessionEnterpriseService = FakeSessionEnterpriseService( + isEncryptionDisabledResult = { true }, + arePublicRoomsDisabledResult = { false }, + ) val presenter = createSecurityAndPrivacyPresenter( sessionEnterpriseService = sessionEnterpriseService, ) @@ -1060,6 +1065,42 @@ class SecurityAndPrivacyPresenterTest { } } + @Test + fun `present - showAnyoneOption is false if the HS disabled public rooms`() = runTest { + val presenter = createSecurityAndPrivacyPresenter( + sessionEnterpriseService = FakeSessionEnterpriseService( + isEncryptionDisabledResult = { false }, + arePublicRoomsDisabledResult = { true }, + ), + ) + presenter.test { + consumeItemsUntilPredicate { !it.showAnyoneOption } + cancelAndIgnoreRemainingEvents() + } + } + + @Test + fun `present - showAnyoneOption is true if the HS disabled public rooms but the room is already public`() = runTest { + val presenter = createSecurityAndPrivacyPresenter( + room = FakeJoinedRoom( + baseRoom = FakeBaseRoom( + roomPermissions = roomPermissions(), + getRoomVisibilityResult = { Result.success(RoomVisibility.Public) }, + initialRoomInfo = aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared, joinRule = JoinRule.Public) + ), + ), + sessionEnterpriseService = FakeSessionEnterpriseService( + isEncryptionDisabledResult = { false }, + arePublicRoomsDisabledResult = { true }, + ), + ) + presenter.test { + val states = consumeItemsUntilTimeout() + assertThat(states).isNotEmpty() + assertThat(states.all { it.showAnyoneOption }).isTrue() + } + } + private fun roomPermissions( canChangeRoomAccess: Boolean = true, canChangeHistoryVisibility: Boolean = true, @@ -1100,6 +1141,7 @@ class SecurityAndPrivacyPresenterTest { spaceSelectionStateHolder: SpaceSelectionStateHolder = SpaceSelectionStateHolder(), sessionEnterpriseService: FakeSessionEnterpriseService = FakeSessionEnterpriseService( isEncryptionDisabledResult = { false }, + arePublicRoomsDisabledResult = { false }, ) ): SecurityAndPrivacyPresenter { return SecurityAndPrivacyPresenter( @@ -1109,7 +1151,6 @@ class SecurityAndPrivacyPresenterTest { featureFlagService = featureFlagService, spaceSelectionStateHolder = spaceSelectionStateHolder, sessionEnterpriseService = sessionEnterpriseService, - securityAndPrivacyExtension = NoopSecurityAndPrivacyExtension(), ) } }