Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions app/src/main/kotlin/com/wire/android/di/KaliumConfigsModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ package com.wire.android.di
import android.content.Context
import android.os.Build
import com.wire.android.BuildConfig
import com.wire.android.emm.ManagedConfigurationsManager
import com.wire.android.util.isWebsocketEnabledByDefault
import com.wire.kalium.logic.featureFlags.BuildFileRestrictionState
import com.wire.kalium.logic.featureFlags.KaliumConfigs
Expand All @@ -34,7 +35,10 @@ import dagger.hilt.components.SingletonComponent
class KaliumConfigsModule {

@Provides
fun provideKaliumConfigs(context: Context): KaliumConfigs {
fun provideKaliumConfigs(
context: Context,
managedConfigurationsManager: ManagedConfigurationsManager
): KaliumConfigs {
val fileRestriction: BuildFileRestrictionState = if (BuildConfig.FILE_RESTRICTION_ENABLED) {
BuildConfig.FILE_RESTRICTION_LIST.split(",").map { it.trim() }.let {
BuildFileRestrictionState.AllowSome(it)
Expand All @@ -57,7 +61,10 @@ class KaliumConfigsModule {
wipeOnCookieInvalid = BuildConfig.WIPE_ON_COOKIE_INVALID,
wipeOnDeviceRemoval = BuildConfig.WIPE_ON_DEVICE_REMOVAL,
wipeOnRootedDevice = BuildConfig.WIPE_ON_ROOTED_DEVICE,
isWebSocketEnabledByDefault = isWebsocketEnabledByDefault(context),
isWebSocketEnabledByDefault = isWebsocketEnabledByDefault(
context,
managedConfigurationsManager.persistentWebSocketEnforcedByMDM.value
),
certPinningConfig = BuildConfig.CERTIFICATE_PINNING_CONFIG,
maxRemoteSearchResultCount = BuildConfig.MAX_REMOTE_SEARCH_RESULT_COUNT,
limitTeamMembersFetchDuringSlowSync = BuildConfig.LIMIT_TEAM_MEMBERS_FETCH_DURING_SLOW_SYNC,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ package com.wire.android.emm

enum class ManagedConfigurationsKeys {
DEFAULT_SERVER_URLS,
SSO_CODE;
SSO_CODE,
KEEP_WEBSOCKET_CONNECTION;

fun asKey() = name.lowercase()
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ import com.wire.android.config.ServerConfigProvider
import com.wire.android.util.EMPTY
import com.wire.android.util.dispatchers.DispatcherProvider
import com.wire.kalium.logic.configuration.server.ServerConfig
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import java.util.concurrent.atomic.AtomicReference
Expand Down Expand Up @@ -66,6 +69,19 @@ interface ManagedConfigurationsManager {
* empty if no config found or cleared, or failure with reason.
*/
suspend fun refreshSSOCodeConfig(): SSOCodeConfigResult

/**
* Whether persistent WebSocket connection is enforced by MDM.
* When true, the persistent WebSocket service should always be started
* and users should not be able to change this setting.
*/
val persistentWebSocketEnforcedByMDM: StateFlow<Boolean>

/**
* Refresh the persistent WebSocket configuration from managed restrictions.
* This should be called when the app starts or when broadcast receiver triggers.
*/
suspend fun refreshPersistentWebSocketConfig()
}

internal class ManagedConfigurationsManagerImpl(
Expand All @@ -82,13 +98,17 @@ internal class ManagedConfigurationsManagerImpl(

private val _currentServerConfig = AtomicReference<ServerConfig.Links?>(null)
private val _currentSSOCodeConfig = AtomicReference(String.EMPTY)
private val _persistentWebSocketEnforcedByMDM = MutableStateFlow(false)

override val currentServerConfig: ServerConfig.Links
get() = _currentServerConfig.get() ?: serverConfigProvider.getDefaultServerConfig()

override val currentSSOCodeConfig: String
get() = _currentSSOCodeConfig.get()

override val persistentWebSocketEnforcedByMDM: StateFlow<Boolean>
get() = _persistentWebSocketEnforcedByMDM.asStateFlow()

override suspend fun refreshServerConfig(): ServerConfigResult = withContext(dispatchers.io()) {
val managedServerConfig = getServerConfig()
val serverConfig: ServerConfig.Links = when (managedServerConfig) {
Expand Down Expand Up @@ -118,6 +138,22 @@ internal class ManagedConfigurationsManagerImpl(
managedSSOCodeConfig
}

override suspend fun refreshPersistentWebSocketConfig() {
withContext(dispatchers.io()) {
val restrictions = restrictionsManager.applicationRestrictions
val isEnforced = if (restrictions == null || restrictions.isEmpty) {
false
} else {
restrictions.getBoolean(
ManagedConfigurationsKeys.KEEP_WEBSOCKET_CONNECTION.asKey(),
false
)
}
_persistentWebSocketEnforcedByMDM.value = isEnforced
logger.i("Persistent WebSocket enforced by MDM refreshed to: $isEnforced")
}
}

private suspend fun getSSOCodeConfig(): SSOCodeConfigResult =
withContext(dispatchers.io()) {
val restrictions = restrictionsManager.applicationRestrictions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,12 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.wire.android.appLogger
import com.wire.android.di.KaliumCoreLogic
import com.wire.android.feature.StartPersistentWebsocketIfNecessaryUseCase
import com.wire.android.util.EMPTY
import com.wire.android.util.dispatchers.DispatcherProvider
import com.wire.kalium.logic.CoreLogic
import dagger.Lazy
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
Expand All @@ -33,6 +37,8 @@ import javax.inject.Singleton
class ManagedConfigurationsReceiver @Inject constructor(
private val managedConfigurationsManager: ManagedConfigurationsManager,
private val managedConfigurationsReporter: ManagedConfigurationsReporter,
@KaliumCoreLogic private val coreLogic: Lazy<CoreLogic>,
private val startPersistentWebsocketIfNecessary: StartPersistentWebsocketIfNecessaryUseCase,
dispatcher: DispatcherProvider
) : BroadcastReceiver() {

Expand All @@ -48,6 +54,7 @@ class ManagedConfigurationsReceiver @Inject constructor(
logger.i("Received intent to refresh managed configurations")
updateServerConfig()
updateSSOCodeConfig()
updatePersistentWebSocketConfig()
}
}

Expand Down Expand Up @@ -103,6 +110,26 @@ class ManagedConfigurationsReceiver @Inject constructor(
}
}

private suspend fun updatePersistentWebSocketConfig() {
val wasEnforced = managedConfigurationsManager.persistentWebSocketEnforcedByMDM.value
managedConfigurationsManager.refreshPersistentWebSocketConfig()
val isEnforced = managedConfigurationsManager.persistentWebSocketEnforcedByMDM.value

// Only bulk update when MDM enforcement turns ON
if (!wasEnforced && isEnforced) {
coreLogic.get().getGlobalScope().setAllPersistentWebSocketEnabled(true)
}

// Trigger service start/stop based on current state
startPersistentWebsocketIfNecessary()

managedConfigurationsReporter.reportAppliedState(
key = ManagedConfigurationsKeys.KEEP_WEBSOCKET_CONNECTION.asKey(),
message = if (isEnforced) "Persistent WebSocket enforced" else "Persistent WebSocket not enforced",
data = isEnforced.toString()
)
}

companion object {
private const val TAG = "ManagedConfigurationsReceiver"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package com.wire.android.feature

import com.wire.android.di.KaliumCoreLogic
import com.wire.android.emm.ManagedConfigurationsManager
import com.wire.kalium.logic.CoreLogic
import com.wire.kalium.logic.feature.user.webSocketStatus.ObservePersistentWebSocketConnectionStatusUseCase
import kotlinx.coroutines.flow.firstOrNull
Expand All @@ -27,9 +28,15 @@ import javax.inject.Singleton

@Singleton
class ShouldStartPersistentWebSocketServiceUseCase @Inject constructor(
@KaliumCoreLogic private val coreLogic: CoreLogic
@KaliumCoreLogic private val coreLogic: CoreLogic,
private val managedConfigurationsManager: ManagedConfigurationsManager
) {
suspend operator fun invoke(): Result {
// MDM takes priority - if enforced, always start service
if (managedConfigurationsManager.persistentWebSocketEnforcedByMDM.value) {
return Result.Success(true)
}

return coreLogic.getGlobalScope().observePersistentWebSocketConnectionStatus().let { result ->
when (result) {
is ObservePersistentWebSocketConnectionStatusUseCase.Result.Failure -> Result.Failure
Expand Down
1 change: 1 addition & 0 deletions app/src/main/kotlin/com/wire/android/ui/WireActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ class WireActivity : AppCompatActivity() {
lifecycleScope.launch(Dispatchers.IO) {
managedConfigurationsManager.refreshServerConfig()
managedConfigurationsManager.refreshSSOCodeConfig()
managedConfigurationsManager.refreshPersistentWebSocketConfig()
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ fun NetworkSettingsScreen(
NetworkSettingsScreenContent(
onBackPressed = navigator::navigateBack,
isWebSocketEnabled = networkSettingsViewModel.networkSettingsState.isPersistentWebSocketConnectionEnabled,
isEnforcedByMDM = networkSettingsViewModel.networkSettingsState.isEnforcedByMDM,
setWebSocketState = { networkSettingsViewModel.setWebSocketState(it) },
)
}
Expand All @@ -57,6 +58,7 @@ fun NetworkSettingsScreen(
fun NetworkSettingsScreenContent(
onBackPressed: () -> Unit,
isWebSocketEnabled: Boolean,
isEnforcedByMDM: Boolean,
setWebSocketState: (Boolean) -> Unit,
modifier: Modifier = Modifier
) {
Expand All @@ -79,20 +81,26 @@ fun NetworkSettingsScreenContent(
val isWebSocketEnforcedByDefault = remember {
isWebsocketEnabledByDefault(appContext)
}
val switchState = remember(isWebSocketEnabled) {
if (isWebSocketEnforcedByDefault) {
SwitchState.TextOnly(true)
} else {
SwitchState.Enabled(
val switchState = remember(isWebSocketEnabled, isEnforcedByMDM) {
when {
isEnforcedByMDM -> SwitchState.TextOnly(true)
isWebSocketEnforcedByDefault -> SwitchState.TextOnly(true)
else -> SwitchState.Enabled(
value = isWebSocketEnabled,
onCheckedChange = setWebSocketState
)
}
}

val subtitle = if (isEnforcedByMDM) {
stringResource(R.string.settings_keep_websocket_enforced_by_organization)
} else {
stringResource(R.string.settings_keep_connection_to_websocket_description)
}

GroupConversationOptionsItem(
title = stringResource(R.string.settings_keep_connection_to_websocket),
subtitle = stringResource(R.string.settings_keep_connection_to_websocket_description),
subtitle = subtitle,
switchState = switchState,
arrowType = ArrowType.NONE
)
Expand All @@ -106,6 +114,7 @@ fun PreviewNetworkSettingsScreen() = WireTheme {
NetworkSettingsScreenContent(
onBackPressed = {},
isWebSocketEnabled = true,
isEnforcedByMDM = false,
setWebSocketState = {},
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@
package com.wire.android.ui.home.settings.appsettings.networkSettings

data class NetworkSettingsState(
val isPersistentWebSocketConnectionEnabled: Boolean = false
val isPersistentWebSocketConnectionEnabled: Boolean = false,
val isEnforcedByMDM: Boolean = false
)
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.wire.android.appLogger
import com.wire.android.emm.ManagedConfigurationsManager
import com.wire.kalium.logic.feature.session.CurrentSessionResult
import com.wire.kalium.logic.feature.session.CurrentSessionUseCase
import com.wire.kalium.logic.feature.user.webSocketStatus.ObservePersistentWebSocketConnectionStatusUseCase
Expand All @@ -37,12 +38,29 @@ class NetworkSettingsViewModel
@Inject constructor(
private val persistPersistentWebSocketConnectionStatus: PersistPersistentWebSocketConnectionStatusUseCase,
private val observePersistentWebSocketConnectionStatus: ObservePersistentWebSocketConnectionStatusUseCase,
private val currentSession: CurrentSessionUseCase
private val currentSession: CurrentSessionUseCase,
private val managedConfigurationsManager: ManagedConfigurationsManager
) : ViewModel() {
var networkSettingsState by mutableStateOf(NetworkSettingsState())

init {
observePersistentWebSocketConnection()
observeMDMEnforcement()
}

private fun observeMDMEnforcement() {
viewModelScope.launch {
managedConfigurationsManager.persistentWebSocketEnforcedByMDM.collect { isEnforced ->
networkSettingsState = networkSettingsState.copy(
isEnforcedByMDM = isEnforced,
isPersistentWebSocketConnectionEnabled = if (isEnforced) {
true
} else {
networkSettingsState.isPersistentWebSocketConnectionEnabled
}
)
}
}
}

private fun observePersistentWebSocketConnection() =
Expand All @@ -57,14 +75,15 @@ class NetworkSettingsViewModel
is ObservePersistentWebSocketConnectionStatusUseCase.Result.Failure -> {
appLogger.e("Failure while fetching persistent web socket status flow from network settings")
}

is ObservePersistentWebSocketConnectionStatusUseCase.Result.Success -> {
it.persistentWebSocketStatusListFlow.collect {
it.map { persistentWebSocketStatus ->
if (persistentWebSocketStatus.userId == userId) {
networkSettingsState =
networkSettingsState.copy(
isPersistentWebSocketConnectionEnabled =
persistentWebSocketStatus.isPersistentWebSocketEnabled
persistentWebSocketStatus.isPersistentWebSocketEnabled
)
}
}
Expand All @@ -73,13 +92,18 @@ class NetworkSettingsViewModel
}
}
}

else -> {
// NO SESSION - Nothing to do
}
}
}

fun setWebSocketState(isEnabled: Boolean) {
// Block changes when MDM enforces the setting
if (networkSettingsState.isEnforcedByMDM) {
return
}
viewModelScope.launch {
persistPersistentWebSocketConnectionStatus(isEnabled)
}
Expand Down
16 changes: 12 additions & 4 deletions app/src/main/kotlin/com/wire/android/util/WebsocketHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,16 @@ import com.wire.android.BuildConfig
import com.wire.android.util.extension.isGoogleServicesAvailable

/**
* If [BuildConfig.WEBSOCKET_ENABLED_BY_DEFAULT] is true, the websocket should be enabled by default always.
* Otherwise, it should be enabled by default only if Google Play Services are not available.
* Determines if websocket should be enabled by default.
*
* Returns true if:
* - MDM enforces persistent websocket, OR
* - [BuildConfig.WEBSOCKET_ENABLED_BY_DEFAULT] is true, OR
* - Google Play Services are not available
*/
fun isWebsocketEnabledByDefault(context: Context) =
BuildConfig.WEBSOCKET_ENABLED_BY_DEFAULT || !context.isGoogleServicesAvailable()
fun isWebsocketEnabledByDefault(
context: Context,
persistentWebSocketEnforcedByMDM: Boolean = false
) = persistentWebSocketEnforcedByMDM ||
BuildConfig.WEBSOCKET_ENABLED_BY_DEFAULT ||
!context.isGoogleServicesAvailable()
3 changes: 3 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1875,6 +1875,9 @@ In group conversations, the group admin can overwrite this setting.</string>
<string name="restriction_sso_code_title">SSO code configuration</string>
<string name="restriction_server_urls_description">JSON value with the server endpoints configuration</string>
<string name="restriction_sso_code_description">JSON value with the default SSO code configuration</string>
<string name="restriction_keep_websocket_title">Keep WebSocket Connection</string>
<string name="restriction_keep_websocket_description">Force persistent WebSocket connection for all users</string>
<string name="settings_keep_websocket_enforced_by_organization">This setting is managed by your organization.</string>

<!-- channel not available dialog-->
<string name="channel_not_available_dialog_title">Channels are available for team members.</string>
Expand Down
8 changes: 8 additions & 0 deletions app/src/main/res/xml/app_restrictions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,12 @@
android:restrictionType="string"
android:title="@string/restriction_sso_code_title" />

<!-- Keep WebSocket Connection Configuration -->
<restriction
android:key="keep_websocket_connection"
android:restrictionType="bool"
android:defaultValue="false"
android:title="@string/restriction_keep_websocket_title"
android:description="@string/restriction_keep_websocket_description" />

</restrictions>
Loading
Loading