Skip to content

Commit 88b0fe5

Browse files
PM-20516: Update NetworkConnectionManager (#5085)
1 parent e4d0c48 commit 88b0fe5

File tree

9 files changed

+447
-30
lines changed

9 files changed

+447
-30
lines changed

app/src/main/java/com/x8bit/bitwarden/BitwardenApplication.kt

+4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import com.x8bit.bitwarden.data.auth.manager.AuthRequestNotificationManager
66
import com.x8bit.bitwarden.data.platform.manager.LogsManager
77
import com.x8bit.bitwarden.data.platform.manager.event.OrganizationEventManager
88
import com.x8bit.bitwarden.data.platform.manager.network.NetworkConfigManager
9+
import com.x8bit.bitwarden.data.platform.manager.network.NetworkConnectionManager
910
import com.x8bit.bitwarden.data.platform.manager.restriction.RestrictionManager
1011
import dagger.hilt.android.HiltAndroidApp
1112
import javax.inject.Inject
@@ -21,6 +22,9 @@ class BitwardenApplication : Application() {
2122
@Inject
2223
lateinit var logsManager: LogsManager
2324

25+
@Inject
26+
lateinit var networkConnectionManager: NetworkConnectionManager
27+
2428
@Inject
2529
lateinit var networkConfigManager: NetworkConfigManager
2630

app/src/main/java/com/x8bit/bitwarden/data/platform/manager/di/PlatformManagerModule.kt

+2
Original file line numberDiff line numberDiff line change
@@ -259,8 +259,10 @@ object PlatformManagerModule {
259259
@Singleton
260260
fun provideNetworkConnectionManager(
261261
application: Application,
262+
dispatcherManager: DispatcherManager,
262263
): NetworkConnectionManager = NetworkConnectionManagerImpl(
263264
context = application.applicationContext,
265+
dispatcherManager = dispatcherManager,
264266
)
265267

266268
@Provides
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.x8bit.bitwarden.data.platform.manager.model
2+
3+
/**
4+
* A representation of the current network connection.
5+
*/
6+
sealed class NetworkConnection {
7+
/**
8+
* Currently not connected to the internet.
9+
*/
10+
data object None : NetworkConnection()
11+
12+
/**
13+
* Currently connected to the internet via WiFi with a signal [strength] indication.
14+
*/
15+
data class Wifi(
16+
val strength: NetworkSignalStrength,
17+
) : NetworkConnection()
18+
19+
/**
20+
* Currently connected to the internet via cellular connection.
21+
*/
22+
data object Cellular : NetworkConnection()
23+
24+
/**
25+
* Currently connected to the internet via an unknown connection.
26+
*/
27+
data object Other : NetworkConnection()
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.x8bit.bitwarden.data.platform.manager.model
2+
3+
/**
4+
* An indicator of the signal strength for a network connection.
5+
*/
6+
enum class NetworkSignalStrength {
7+
EXCELLENT,
8+
GOOD,
9+
FAIR,
10+
WEAK,
11+
NONE,
12+
UNKNOWN,
13+
}

app/src/main/java/com/x8bit/bitwarden/data/platform/manager/network/NetworkConnectionManager.kt

+20
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package com.x8bit.bitwarden.data.platform.manager.network
22

3+
import com.x8bit.bitwarden.data.platform.manager.model.NetworkConnection
4+
import kotlinx.coroutines.flow.StateFlow
5+
36
/**
47
* Manager to detect and handle changes to network connectivity.
58
*/
@@ -9,4 +12,21 @@ interface NetworkConnectionManager {
912
* available.
1013
*/
1114
val isNetworkConnected: Boolean
15+
16+
/**
17+
* Emits `true` when the application has a network connection and access to the Internet is
18+
* available.
19+
*/
20+
val isNetworkConnectedFlow: StateFlow<Boolean>
21+
22+
/**
23+
* Returns the current network connection.
24+
*/
25+
val networkConnection: NetworkConnection
26+
27+
/**
28+
* Emits the current [NetworkConnection] indicating what type of network the app is currently
29+
* using to connect to the internet.
30+
*/
31+
val networkConnectionFlow: StateFlow<NetworkConnection>
1232
}

app/src/main/java/com/x8bit/bitwarden/data/platform/manager/network/NetworkConnectionManagerImpl.kt

+132
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,153 @@ package com.x8bit.bitwarden.data.platform.manager.network
22

33
import android.content.Context
44
import android.net.ConnectivityManager
5+
import android.net.LinkProperties
6+
import android.net.Network
57
import android.net.NetworkCapabilities
8+
import android.net.NetworkCapabilities.SIGNAL_STRENGTH_UNSPECIFIED
9+
import android.net.NetworkRequest
10+
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
11+
import com.bitwarden.data.manager.DispatcherManager
12+
import com.x8bit.bitwarden.data.platform.manager.model.NetworkConnection
13+
import com.x8bit.bitwarden.data.platform.manager.model.NetworkSignalStrength
14+
import kotlinx.coroutines.CoroutineScope
15+
import kotlinx.coroutines.flow.MutableSharedFlow
16+
import kotlinx.coroutines.flow.SharedFlow
17+
import kotlinx.coroutines.flow.SharingStarted
18+
import kotlinx.coroutines.flow.StateFlow
19+
import kotlinx.coroutines.flow.asSharedFlow
20+
import kotlinx.coroutines.flow.distinctUntilChanged
21+
import kotlinx.coroutines.flow.map
22+
import kotlinx.coroutines.flow.stateIn
623

724
/**
825
* Primary implementation of [NetworkConnectionManager].
926
*/
1027
class NetworkConnectionManagerImpl(
1128
context: Context,
29+
dispatcherManager: DispatcherManager,
1230
) : NetworkConnectionManager {
31+
private val unconfinedScope = CoroutineScope(context = dispatcherManager.unconfined)
32+
private val networkChangeCallback = ConnectionChangeCallback()
33+
1334
private val connectivityManager: ConnectivityManager = context
1435
.applicationContext
1536
.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
1637

38+
init {
39+
connectivityManager.registerNetworkCallback(
40+
NetworkRequest.Builder()
41+
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
42+
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
43+
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
44+
.build(),
45+
networkChangeCallback,
46+
)
47+
}
48+
1749
override val isNetworkConnected: Boolean
1850
get() = connectivityManager
1951
.getNetworkCapabilities(connectivityManager.activeNetwork)
2052
?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
2153
?: false
54+
55+
override val isNetworkConnectedFlow: StateFlow<Boolean> =
56+
networkChangeCallback
57+
.connectionChangeFlow
58+
.map { isNetworkConnected }
59+
.distinctUntilChanged()
60+
.stateIn(
61+
scope = unconfinedScope,
62+
started = SharingStarted.Eagerly,
63+
initialValue = isNetworkConnected,
64+
)
65+
66+
override val networkConnection: NetworkConnection
67+
get() = connectivityManager
68+
.getNetworkCapabilities(connectivityManager.activeNetwork)
69+
.networkConnection
70+
71+
override val networkConnectionFlow: StateFlow<NetworkConnection> = networkChangeCallback
72+
.connectionChangeFlow
73+
.map { _ -> networkConnection }
74+
.distinctUntilChanged()
75+
.stateIn(
76+
scope = unconfinedScope,
77+
started = SharingStarted.Eagerly,
78+
initialValue = networkConnection,
79+
)
80+
81+
/**
82+
* A callback used to monitor the connection of a [Network].
83+
*/
84+
private class ConnectionChangeCallback : ConnectivityManager.NetworkCallback() {
85+
private val mutableConnectionState: MutableSharedFlow<Unit> = bufferedMutableSharedFlow()
86+
87+
/**
88+
* A [StateFlow] that emits when the connection state to a network changes.
89+
*/
90+
val connectionChangeFlow: SharedFlow<Unit> = mutableConnectionState.asSharedFlow()
91+
92+
override fun onCapabilitiesChanged(
93+
network: Network,
94+
networkCapabilities: NetworkCapabilities,
95+
) {
96+
super.onCapabilitiesChanged(network, networkCapabilities)
97+
mutableConnectionState.tryEmit(Unit)
98+
}
99+
100+
override fun onLinkPropertiesChanged(network: Network, linkProperties: LinkProperties) {
101+
super.onLinkPropertiesChanged(network, linkProperties)
102+
mutableConnectionState.tryEmit(Unit)
103+
}
104+
105+
override fun onAvailable(network: Network) {
106+
super.onAvailable(network)
107+
mutableConnectionState.tryEmit(Unit)
108+
}
109+
110+
override fun onLost(network: Network) {
111+
super.onLost(network)
112+
mutableConnectionState.tryEmit(Unit)
113+
}
114+
}
22115
}
116+
117+
/**
118+
* Converts the [NetworkCapabilities] to a [NetworkConnection].
119+
*/
120+
private val NetworkCapabilities?.networkConnection: NetworkConnection
121+
get() = this
122+
?.let {
123+
if (it.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
124+
NetworkConnection.Wifi(it.networkStrength)
125+
} else if (it.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
126+
NetworkConnection.Cellular
127+
} else {
128+
NetworkConnection.Other
129+
}
130+
}
131+
?: NetworkConnection.None
132+
133+
/**
134+
* Converts an integer value to an enum signal strength based on the RSSI standard.
135+
*
136+
* * -50 dBm: Excellent signal
137+
* * -60 to -75 dBm: Good signal
138+
* * -76 to -90 dBm: Fair signal
139+
* * -91 to -110 dBm: Weak signal
140+
* * -110 dBm and below: No signal
141+
*/
142+
@Suppress("MagicNumber")
143+
private val NetworkCapabilities.networkStrength: NetworkSignalStrength
144+
get() {
145+
val strength = this.signalStrength
146+
return when {
147+
(strength <= SIGNAL_STRENGTH_UNSPECIFIED) -> NetworkSignalStrength.UNKNOWN
148+
(strength <= -110) -> NetworkSignalStrength.NONE
149+
(strength <= -91) -> NetworkSignalStrength.WEAK
150+
(strength <= -76) -> NetworkSignalStrength.FAIR
151+
(strength <= -60) -> NetworkSignalStrength.GOOD
152+
else -> NetworkSignalStrength.EXCELLENT
153+
}
154+
}

0 commit comments

Comments
 (0)