diff --git a/app/src/main/java/com/anytypeio/anytype/di/main/NetworkModeModule.kt b/app/src/main/java/com/anytypeio/anytype/di/main/NetworkModeModule.kt index 6de63f41dc..82ba8ea05a 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/main/NetworkModeModule.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/main/NetworkModeModule.kt @@ -3,6 +3,10 @@ package com.anytypeio.anytype.di.main import android.content.Context import android.content.SharedPreferences import androidx.preference.PreferenceManager +import com.anytypeio.anytype.device.network_type.NetworkConnectionStatusImpl +import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers +import com.anytypeio.anytype.domain.block.repo.BlockRepository +import com.anytypeio.anytype.domain.device.NetworkConnectionStatus import com.anytypeio.anytype.persistence.networkmode.DefaultNetworkModeProvider import com.anytypeio.anytype.persistence.networkmode.DefaultNetworkModeProvider.NetworkModeConstants.NAMED_NETWORK_MODE_PREFS import com.anytypeio.anytype.persistence.networkmode.NetworkModeProvider @@ -10,6 +14,7 @@ import dagger.Module import dagger.Provides import javax.inject.Named import javax.inject.Singleton +import kotlinx.coroutines.CoroutineScope @Module object NetworkModeModule { @@ -28,4 +33,19 @@ object NetworkModeModule { fun provider( @Named(NAMED_NETWORK_MODE_PREFS) sharedPreferences: SharedPreferences ): NetworkModeProvider = DefaultNetworkModeProvider(sharedPreferences) + + @JvmStatic + @Provides + @Singleton + fun provideNetworkConnectionStatus( + context: Context, + dispatcher: AppCoroutineDispatchers, + blockRepository: BlockRepository, + @Named(ConfigModule.DEFAULT_APP_COROUTINE_SCOPE) scope: CoroutineScope + ): NetworkConnectionStatus = NetworkConnectionStatusImpl( + context = context, + dispatchers = dispatcher, + coroutineScope = scope, + blockRepository = blockRepository + ) } \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/di/main/SubscriptionsModule.kt b/app/src/main/java/com/anytypeio/anytype/di/main/SubscriptionsModule.kt index a6198fd78b..8676cde833 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/main/SubscriptionsModule.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/main/SubscriptionsModule.kt @@ -10,6 +10,7 @@ import com.anytypeio.anytype.domain.block.repo.BlockRepository import com.anytypeio.anytype.domain.config.ConfigStorage import com.anytypeio.anytype.domain.debugging.DebugAccountSelectTrace import com.anytypeio.anytype.domain.debugging.Logger +import com.anytypeio.anytype.domain.device.NetworkConnectionStatus import com.anytypeio.anytype.domain.event.interactor.SpaceSyncAndP2PStatusProvider import com.anytypeio.anytype.domain.library.StorelessSubscriptionContainer import com.anytypeio.anytype.domain.multiplayer.ActiveSpaceMemberSubscriptionContainer @@ -221,13 +222,15 @@ object SubscriptionsModule { relations: RelationsSubscriptionManager, permissions: UserPermissionProvider, isSpaceDeleted: SpaceDeletedStatusWatcher, - profileSubscriptionManager: ProfileSubscriptionManager + profileSubscriptionManager: ProfileSubscriptionManager, + networkConnectionStatus: NetworkConnectionStatus ): GlobalSubscriptionManager = GlobalSubscriptionManager.Default( types = types, relations = relations, permissions = permissions, isSpaceDeleted = isSpaceDeleted, - profile = profileSubscriptionManager + profile = profileSubscriptionManager, + networkConnectionStatus = networkConnectionStatus ) @JvmStatic diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/DeviceNetworkType.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/DeviceNetworkType.kt new file mode 100644 index 0000000000..f564a5923a --- /dev/null +++ b/core-models/src/main/java/com/anytypeio/anytype/core_models/DeviceNetworkType.kt @@ -0,0 +1,7 @@ +package com.anytypeio.anytype.core_models + +enum class DeviceNetworkType { + WIFI, + CELLULAR, + NOT_CONNECTED +} \ No newline at end of file diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataRepository.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataRepository.kt index bec28bc816..2149419c09 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataRepository.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataRepository.kt @@ -9,6 +9,7 @@ import com.anytypeio.anytype.core_models.DVFilter import com.anytypeio.anytype.core_models.DVSort import com.anytypeio.anytype.core_models.DVViewer import com.anytypeio.anytype.core_models.DVViewerType +import com.anytypeio.anytype.core_models.DeviceNetworkType import com.anytypeio.anytype.core_models.Event import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.Key @@ -1096,4 +1097,8 @@ class BlockDataRepository( override suspend fun objectDateByTimestamp(command: Command.ObjectDateByTimestamp): Struct? { return remote.objectDateByTimestamp(command) } + + override suspend fun setDeviceNetworkState(type: DeviceNetworkType) { + remote.setDeviceNetworkState(type) + } } \ No newline at end of file diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemote.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemote.kt index db09f050a6..c6bcba866d 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemote.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemote.kt @@ -9,6 +9,7 @@ import com.anytypeio.anytype.core_models.DVFilter import com.anytypeio.anytype.core_models.DVSort import com.anytypeio.anytype.core_models.DVViewer import com.anytypeio.anytype.core_models.DVViewerType +import com.anytypeio.anytype.core_models.DeviceNetworkType import com.anytypeio.anytype.core_models.Event import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.Key @@ -465,4 +466,6 @@ interface BlockRemote { suspend fun objectRelationListWithValue(command: Command.RelationListWithValue): List suspend fun debugAccountSelectTrace(dir: String): String + + suspend fun setDeviceNetworkState(type: DeviceNetworkType) } \ No newline at end of file diff --git a/device/src/main/AndroidManifest.xml b/device/src/main/AndroidManifest.xml index cc947c5679..d7546021b5 100644 --- a/device/src/main/AndroidManifest.xml +++ b/device/src/main/AndroidManifest.xml @@ -1 +1,3 @@ - + + + diff --git a/device/src/main/java/com/anytypeio/anytype/device/network_type/NetworkConnectionStatus.kt b/device/src/main/java/com/anytypeio/anytype/device/network_type/NetworkConnectionStatus.kt new file mode 100644 index 0000000000..e104163127 --- /dev/null +++ b/device/src/main/java/com/anytypeio/anytype/device/network_type/NetworkConnectionStatus.kt @@ -0,0 +1,105 @@ +package com.anytypeio.anytype.device.network_type + +import android.content.Context +import android.net.ConnectivityManager +import android.net.Network +import android.net.NetworkCapabilities +import com.anytypeio.anytype.core_models.DeviceNetworkType +import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers +import com.anytypeio.anytype.domain.block.repo.BlockRepository +import com.anytypeio.anytype.domain.device.NetworkConnectionStatus +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import timber.log.Timber + +class NetworkConnectionStatusImpl( + context: Context, + private val coroutineScope: CoroutineScope, + private val blockRepository: BlockRepository, + private val dispatchers: AppCoroutineDispatchers +) : NetworkConnectionStatus { + + private val connectivityManager = + context.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager + private var isMonitoring = false + + private val networkCallback = object : ConnectivityManager.NetworkCallback() { + override fun onAvailable(network: Network) { + // Called when the network is available + updateNetworkState(networkCapabilities = getNetworkCapabilities()) + } + + override fun onLost(network: Network) { + // Called when the network is disconnected + updateNetworkState(networkCapabilities = getNetworkCapabilities()) + } + + override fun onCapabilitiesChanged( + network: Network, + networkCapabilities: NetworkCapabilities + ) { + // Called when the network capabilities (like Wi-Fi vs Cellular) change + updateNetworkState(networkCapabilities) + } + } + + override fun start() { + if (!isMonitoring) { + try { + connectivityManager?.registerDefaultNetworkCallback(networkCallback) + isMonitoring = true + updateNetworkState(networkCapabilities = getNetworkCapabilities()) + } catch ( + e: RuntimeException + ) { + Timber.w(e, "Failed to register network callback") + isMonitoring = false + } + } + } + + override fun stop() { + if (isMonitoring) { + try { + connectivityManager?.unregisterNetworkCallback(networkCallback) + isMonitoring = false + } catch (e: Throwable) { + Timber.w(e, "Failed to unregister network callback") + isMonitoring = false + } + } + } + + private fun updateNetworkState(networkCapabilities: NetworkCapabilities?) { + coroutineScope.launch { + val networkType = mapNetworkType(networkCapabilities) + withContext(dispatchers.io) { + try { + blockRepository.setDeviceNetworkState(networkType) + } catch ( + e: Throwable + ) { + Timber.w(e, "Failed to update network state") + } + } + } + } + + private fun getNetworkCapabilities(): NetworkCapabilities? { + try { + return connectivityManager?.getNetworkCapabilities(connectivityManager.activeNetwork) + } catch (e: Throwable) { + Timber.w(e, "Failed to get network capabilities") + return null + } + } + + private fun mapNetworkType(networkCapabilities: NetworkCapabilities?): DeviceNetworkType = + when { + networkCapabilities == null -> DeviceNetworkType.NOT_CONNECTED + networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> DeviceNetworkType.WIFI + networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> DeviceNetworkType.CELLULAR + else -> DeviceNetworkType.NOT_CONNECTED + } +} \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/block/repo/BlockRepository.kt b/domain/src/main/java/com/anytypeio/anytype/domain/block/repo/BlockRepository.kt index aa376c6348..ca76f96195 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/block/repo/BlockRepository.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/block/repo/BlockRepository.kt @@ -9,6 +9,7 @@ import com.anytypeio.anytype.core_models.DVFilter import com.anytypeio.anytype.core_models.DVSort import com.anytypeio.anytype.core_models.DVViewer import com.anytypeio.anytype.core_models.DVViewerType +import com.anytypeio.anytype.core_models.DeviceNetworkType import com.anytypeio.anytype.core_models.Event import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.Key @@ -508,4 +509,6 @@ interface BlockRepository { suspend fun debugAccountSelectTrace(dir: String): String suspend fun objectDateByTimestamp(command: Command.ObjectDateByTimestamp): Struct? + + suspend fun setDeviceNetworkState(type: DeviceNetworkType) } \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/device/NetworkConnectionStatus.kt b/domain/src/main/java/com/anytypeio/anytype/domain/device/NetworkConnectionStatus.kt new file mode 100644 index 0000000000..f34257caa5 --- /dev/null +++ b/domain/src/main/java/com/anytypeio/anytype/domain/device/NetworkConnectionStatus.kt @@ -0,0 +1,6 @@ +package com.anytypeio.anytype.domain.device + +interface NetworkConnectionStatus { + fun start() + fun stop() +} \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/subscriptions/GlobalSubscriptionManager.kt b/domain/src/main/java/com/anytypeio/anytype/domain/subscriptions/GlobalSubscriptionManager.kt index 6290837803..12f3328aeb 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/subscriptions/GlobalSubscriptionManager.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/subscriptions/GlobalSubscriptionManager.kt @@ -1,5 +1,6 @@ package com.anytypeio.anytype.domain.subscriptions +import com.anytypeio.anytype.domain.device.NetworkConnectionStatus import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider import com.anytypeio.anytype.domain.search.ObjectTypesSubscriptionManager import com.anytypeio.anytype.domain.search.ProfileSubscriptionManager @@ -17,7 +18,8 @@ interface GlobalSubscriptionManager { private val relations: RelationsSubscriptionManager, private val permissions: UserPermissionProvider, private val isSpaceDeleted: SpaceDeletedStatusWatcher, - private val profile: ProfileSubscriptionManager + private val profile: ProfileSubscriptionManager, + private val networkConnectionStatus: NetworkConnectionStatus ) : GlobalSubscriptionManager { override fun onStart() { @@ -26,6 +28,7 @@ interface GlobalSubscriptionManager { permissions.start() isSpaceDeleted.onStart() profile.onStart() + networkConnectionStatus.start() } override fun onStop() { @@ -34,6 +37,7 @@ interface GlobalSubscriptionManager { permissions.stop() isSpaceDeleted.onStop() profile.onStop() + networkConnectionStatus.stop() } } diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/block/BlockMiddleware.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/block/BlockMiddleware.kt index 15dd5250b6..25e9ddc1e2 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/block/BlockMiddleware.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/block/BlockMiddleware.kt @@ -10,6 +10,7 @@ import com.anytypeio.anytype.core_models.DVFilter import com.anytypeio.anytype.core_models.DVSort import com.anytypeio.anytype.core_models.DVViewer import com.anytypeio.anytype.core_models.DVViewerType +import com.anytypeio.anytype.core_models.DeviceNetworkType import com.anytypeio.anytype.core_models.Event import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.Key @@ -1070,4 +1071,8 @@ class BlockMiddleware( override suspend fun objectDateByTimestamp(command: Command.ObjectDateByTimestamp): Struct? { return middleware.objectDateByTimestamp(command) } + + override suspend fun setDeviceNetworkState(type: DeviceNetworkType) { + middleware.setDeviceNetworkState(type) + } } \ No newline at end of file diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt index fe1e1b1972..65e9e0cf7b 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt @@ -15,6 +15,7 @@ import com.anytypeio.anytype.core_models.DVFilter import com.anytypeio.anytype.core_models.DVSort import com.anytypeio.anytype.core_models.DVViewer import com.anytypeio.anytype.core_models.DVViewerType +import com.anytypeio.anytype.core_models.DeviceNetworkType import com.anytypeio.anytype.core_models.Event import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.Key @@ -2856,6 +2857,16 @@ class Middleware @Inject constructor( return response.details } + @Throws(Exception::class) + fun setDeviceNetworkState(type: DeviceNetworkType) { + val request = Rpc.Device.NetworkState.Set.Request( + deviceNetworkType = type.mw() + ) + logRequestIfDebug(request) + val (response, time) = measureTimedValue { service.deviceNetworkStateSet(request) } + logResponseIfDebug(response, time) + } + private fun logRequestIfDebug(request: Any) { if (BuildConfig.DEBUG) { logger.logRequest(request).also { diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/Alias.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/Alias.kt index 1a8dc67c57..92f7a14ac7 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/Alias.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/Alias.kt @@ -109,4 +109,6 @@ typealias MSpaceSyncError = anytype.Event.Space.SyncError typealias MP2PStatus = anytype.Event.P2PStatus.Status typealias MP2PStatusUpdate = P2PStatus.Update -typealias MSyncStatusUpdate = Space.SyncStatus.Update \ No newline at end of file +typealias MSyncStatusUpdate = Space.SyncStatus.Update + +typealias MDeviceNetworkType = anytype.model.DeviceNetworkType \ No newline at end of file diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/ToMiddlewareModelMappers.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/ToMiddlewareModelMappers.kt index b0ade8883a..d4f9eeb9a3 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/ToMiddlewareModelMappers.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/ToMiddlewareModelMappers.kt @@ -8,6 +8,7 @@ import com.anytypeio.anytype.core_models.Block import com.anytypeio.anytype.core_models.BlockSplitMode import com.anytypeio.anytype.core_models.Command import com.anytypeio.anytype.core_models.DVSortEmptyType +import com.anytypeio.anytype.core_models.DeviceNetworkType import com.anytypeio.anytype.core_models.InternalFlags import com.anytypeio.anytype.core_models.NetworkMode import com.anytypeio.anytype.core_models.ObjectType @@ -630,3 +631,9 @@ fun Rpc.Object.SearchWithMeta.Response.toCoreModelSearchResults(): List MDeviceNetworkType.WIFI + DeviceNetworkType.CELLULAR -> MDeviceNetworkType.CELLULAR + DeviceNetworkType.NOT_CONNECTED -> MDeviceNetworkType.NOT_CONNECTED +} + diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareService.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareService.kt index 0415066db3..fe9ff68f81 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareService.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareService.kt @@ -609,4 +609,7 @@ interface MiddlewareService { @Throws(Exception::class) fun debugAccountSelectTrace(request: Rpc.Debug.AccountSelectTrace.Request): Rpc.Debug.AccountSelectTrace.Response + + @Throws(Exception::class) + fun deviceNetworkStateSet(request: Rpc.Device.NetworkState.Set.Request): Rpc.Device.NetworkState.Set.Response } \ No newline at end of file diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareServiceImplementation.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareServiceImplementation.kt index 12ef1b2be2..c3078571fc 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareServiceImplementation.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareServiceImplementation.kt @@ -2450,4 +2450,17 @@ class MiddlewareServiceImplementation @Inject constructor( return response } } + + override fun deviceNetworkStateSet(request: Rpc.Device.NetworkState.Set.Request): Rpc.Device.NetworkState.Set.Response { + val encoded = Service.deviceNetworkStateSet( + Rpc.Device.NetworkState.Set.Request.ADAPTER.encode(request) + ) + val response = Rpc.Device.NetworkState.Set.Response.ADAPTER.decode(encoded) + val error = response.error + if (error != null && error.code != Rpc.Device.NetworkState.Set.Response.Error.Code.NULL) { + throw Exception(error.description) + } else { + return response + } + } } \ No newline at end of file