@@ -7,44 +7,139 @@ import android.net.wifi.WifiManager
77import android.os.Build
88import androidx.core.content.ContextCompat
99import androidx.core.content.PermissionChecker.PERMISSION_GRANTED
10- import kotlinx.coroutines.Dispatchers
11- import kotlinx.coroutines.withContext
10+ import kotlinx.coroutines.ExperimentalCoroutinesApi
11+ import kotlinx.coroutines.channels.awaitClose
12+ import kotlinx.coroutines.flow.Flow
13+ import kotlinx.coroutines.flow.callbackFlow
1214import net.kuama.wifiMonitor.data.WifiStatus
15+ import net.kuama.wifiMonitor.data.WifiStatus.State
1316import net.kuama.wifiMonitor.implementation.AndroidQWifiListener
1417import net.kuama.wifiMonitor.implementation.BeforeAndroidQWifiListener
1518
16- class WifiMonitor (context : Context ) {
19+ /* *
20+ * This class allows to check if any permission from the manifest has been granted
21+ */
22+ class PermissionChecker (private val context : Context ) {
23+ class Builder {
24+ private var context: Context ? = null
25+ fun context (context : Context ) =
26+ apply {
27+ this .context = context
28+ }
1729
18- private val listener = if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .Q ) {
19- AndroidQWifiListener (context)
20- } else {
21- // on android < 10 we need to take a totally different approach
22- BeforeAndroidQWifiListener (context)
30+ fun build (): PermissionChecker {
31+ val context = checkNotNull(context) { " Please provide a valid context" }
32+ return PermissionChecker (context)
33+ }
2334 }
2435
25- private val wifiManager: WifiManager =
26- context.applicationContext.getSystemService(Context .WIFI_SERVICE ) as WifiManager
27-
2836 /* *
29- * Whether we received at least a Wi-Fi change status. When false, we cannot say we are connected
37+ * Passing a permission, it checks if the user granted.
38+ * The permission must be in Manifest.permission.PERMISSION_NAME form
3039 */
31- private var didReceiveChange = false
40+ fun check (permission : String ): Boolean {
41+ return ContextCompat .checkSelfPermission(
42+ context,
43+ permission
44+ ) == PERMISSION_GRANTED
45+ }
46+ }
3247
33- /* *
34- * Stop listening to Wi-Fi changes
35- */
36- fun stop () = listener.stop()
48+ class WifiMonitor private constructor(
49+ private val listener : WifiListener ,
50+ private val wifiManager : WifiManager ,
51+ permissionChecker : PermissionChecker
52+ ) {
3753
3854 /* *
39- * Triggers the on change callback whenever the Wi-Fi changes its status
55+ * In order to be able to build a WifiMonitor instance, it is necessary to pass either:
56+ * - a valid context
57+ * or
58+ * - a WifiListener, WifiManager and a PermissionChecker
4059 */
41- suspend fun observe (onChange : (WifiStatus ) -> Unit ) =
42- withContext(Dispatchers .Default ) {
43- listener.start {
44- didReceiveChange = true
45- onChange(info)
60+ class WifiMonitorBuilder {
61+
62+ private var listener: WifiListener ? = null
63+ fun listener (listener : WifiListener ) =
64+ apply {
65+ this .listener = listener
66+ }
67+
68+ private fun listenerBuilder (context : Context ): WifiListener {
69+ return if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .Q ) {
70+ AndroidQWifiListener (context)
71+ } else {
72+ // on android < 10 we need to take a totally different approach
73+ BeforeAndroidQWifiListener (context)
74+ }
75+ }
76+
77+ private var wifiManager: WifiManager ? = null
78+ fun wifiManager (wifiManager : WifiManager ) = apply {
79+ this .wifiManager = wifiManager
80+ }
81+
82+ private fun wifiManagerBuilder (context : Context ): WifiManager {
83+ return context.applicationContext
84+ .getSystemService(Context .WIFI_SERVICE ) as WifiManager
85+ }
86+
87+ private var permissionChecker: PermissionChecker ? = null
88+ fun permissionChecker (permissionChecker : PermissionChecker ) = apply {
89+ this .permissionChecker = permissionChecker
90+ }
91+
92+ private fun permissionCheckerBuilder (context : Context ): PermissionChecker {
93+ return PermissionChecker .Builder ()
94+ .context(context)
95+ .build()
96+ }
97+
98+ private var context: Context ? = null
99+ fun context (context : Context ) =
100+ apply {
101+ this .context = context
102+ }
103+
104+ fun build (): WifiMonitor {
105+ if (context == null ) {
106+ // If we don't have a context, make sure we have all params to build a valid wifi monitor
107+ val listener = checkNotNull(listener) { " Please provide a valid wi-fi listener" }
108+ val wifiManager =
109+ checkNotNull(wifiManager) { " Please provide a valid wi-fi manager" }
110+ val permissionChecker =
111+ checkNotNull(permissionChecker) { " Please provide a valid permission checker" }
112+ return WifiMonitor (
113+ listener,
114+ wifiManager,
115+ permissionChecker
116+ )
117+ } else {
118+ // If we have a context, build all the missing params in order to have a wifi monitor
119+ val context = checkNotNull(context) { " Please provide a valid context" }
120+ val listener = if (listener == null ) {
121+ listenerBuilder(context)
122+ } else {
123+ this .listener!!
124+ }
125+ val wifiManager = if (wifiManager == null ) {
126+ wifiManagerBuilder(context)
127+ } else {
128+ this .wifiManager!!
129+ }
130+ val permissionChecker = if (permissionChecker == null ) {
131+ permissionCheckerBuilder(context)
132+ } else {
133+ this .permissionChecker!!
134+ }
135+ return WifiMonitor (
136+ listener,
137+ wifiManager,
138+ permissionChecker
139+ )
46140 }
47141 }
142+ }
48143
49144 private val state: Int
50145 get() = wifiManager.wifiState
@@ -53,44 +148,52 @@ class WifiMonitor(context: Context) {
53148 get() = wifiManager.connectionInfo
54149
55150 private val band: WifiStatus .NetworkBand
56- get() = if ( Build . VERSION . SDK_INT >= Build . VERSION_CODES . LOLLIPOP ) {
151+ get() =
57152 if (connectionInfo.frequency > 3000 ) {
58153 WifiStatus .NetworkBand .WIFI_5_GHZ
59154 } else {
60155 WifiStatus .NetworkBand .WIFI_2_4_GHZ
61156 }
62- } else {
63- WifiStatus .NetworkBand .UNKNOWN
64- }
65157
66158 /* *
67- * Holds the current information on the Wi-Fi connection.
159+ * True if the user granted access to [Manifest.permission.ACCESS_FINE_LOCATION]
160+ */
161+ private val isFineLocationAccessGranted: Boolean =
162+ permissionChecker.check(Manifest .permission.ACCESS_FINE_LOCATION )
163+
164+ /* *
165+ * Start listening to Wi-fi changes exposing a flow of WifiStatus.
166+ * It can throw an exception when the channel publishing on a channel that is closed
68167 */
69- val info: WifiStatus
70- get() = if (didReceiveChange) {
71- when (state) {
168+ @ExperimentalCoroutinesApi
169+ suspend fun start (): Flow <WifiStatus > = callbackFlow {
170+ var wifiStatus: WifiStatus
171+ listener.start {
172+ // Update wifiStatus value accordingly with the new state
173+ wifiStatus = when (state) {
72174 WifiManager .WIFI_STATE_DISABLED , WifiManager .WIFI_STATE_DISABLING -> WifiStatus (
73- WifiStatus . State .DISCONNECTED
175+ State .DISCONNECTED
74176 )
75177 WifiManager .WIFI_STATE_ENABLED -> WifiStatus (
76- state = if (isFineLocationAccessGranted) WifiStatus . State .CONNECTED else WifiStatus . State .CONNECTED_MISSING_FINE_LOCATION_PERMISSION ,
178+ state = if (isFineLocationAccessGranted) State .CONNECTED else State .CONNECTED_MISSING_FINE_LOCATION_PERMISSION ,
77179 ssid = connectionInfo.ssid,
78180 bssid = connectionInfo.bssid,
79181 band = band,
80182 rssi = connectionInfo.rssi
81183 )
82- WifiManager .WIFI_STATE_ENABLING -> WifiStatus (WifiStatus . State .ENABLING )
83- else -> WifiStatus (WifiStatus . State .UNKNOWN )
184+ WifiManager .WIFI_STATE_ENABLING -> WifiStatus (State .ENABLING )
185+ else -> WifiStatus (State .UNKNOWN )
84186 }
85- } else {
86- WifiStatus (WifiStatus .State .UNKNOWN )
187+ channel.trySend(wifiStatus)
87188 }
189+ awaitClose {
190+ wifiStatus = WifiStatus (State .UNKNOWN )
191+ stop()
192+ }
193+ }
88194
89195 /* *
90- * True if the user granted access to [Manifest.permission.ACCESS_FINE_LOCATION]
196+ * Stop listening to Wi-Fi changes
91197 */
92- val isFineLocationAccessGranted: Boolean = ContextCompat .checkSelfPermission(
93- context,
94- Manifest .permission.ACCESS_FINE_LOCATION
95- ) == PERMISSION_GRANTED
198+ fun stop () = listener.stop()
96199}
0 commit comments