I'm currently using NetworkRequest and NetworkCallback approach (recommended by Google official) to get status of Wifi connection, and it works partially.
I'm expecting the onUnavailable() will get called when: close app -> turn off Wifi -> launch app, however there is nothing happened:
private fun getNetworkRequest(): NetworkRequest {
return NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI) //restric to Wifi type only
.build()
}
private fun getNetworkCallBack(): ConnectivityManager.NetworkCallback {
return object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) { //works
super.onAvailable(network)
Toast.makeText(requireContext(), "Wifi is on!", Toast.LENGTH_SHORT).show()
}
override fun onLost(network: Network) { //works
super.onLost(network)
Toast.makeText(requireContext(), "Wifi turns off!", Toast.LENGTH_SHORT).show()
}
override fun onUnavailable() { //not works as expected
super.onUnavailable()
Toast.makeText(requireContext(), "Wifi unavailable!", Toast.LENGTH_SHORT).show()
}
}
}
fun Fragment.getConnectivityManager() = requireContext().getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
override fun onResume() {
super.onResume()
getConnectivityManager().registerNetworkCallback(networkRequest, networkCallback)
}
Ok, I got the solution:
val isWifiOn = with(getConnectivityManager()) {
getNetworkCapabilities(activeNetwork)?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
}
Demo: https://youtu.be/OHFrtXVW4x4
Related
I'm trying to connect to wifi from my android application, using the wifi Network request API.
Here is the code:
val builder =
WifiNetworkSpecifier.Builder()
.setBssid(MacAddress.fromString(item.bssid))
.setWpa2Passphrase("somePassword")
if (item.ssid.isNotEmpty()) {
builder.setSsid(item.ssid)
}
val networkRequest = NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.setNetworkSpecifier(builder.build())
.build()
Next I do that
cm.requestNetwork(
networkRequest,
WifiConnectionCallBack(ConnectivityManager)
)
WifiConnectionCallBack is my class, what extends NetworkCallback
class WifiConnectionCallBack(
private val connectionWatcher : ConnectionWatcher,
private val bssid : String,
private val connManager: ConnectivityManager,
private val onConnection : (Boolean, Network) -> Unit
) : NetworkCallback() {
override fun onAvailable(network: Network) {
super.onAvailable(network)
val result = connManager.bindProcessToNetwork(network)
onConnection.invoke(result, network)
connectionWatcher.onChangeState(ConnectionState.WifiConnected(bssid, network))
}
override fun onUnavailable() {
super.onUnavailable()
connectionWatcher.onChangeState(ConnectionState.UnAvailable)
}
override fun onLinkPropertiesChanged(network: Network, linkProperties: LinkProperties) {
super.onLinkPropertiesChanged(network, linkProperties)
connectionWatcher.onChangeState(
ConnectionState.LinkPropertiesChanged(
bssid,
network,
linkProperties
)
)
}
override fun onLost(network: Network) {
super.onLost(network)
connectionWatcher.onChangeState(ConnectionState.Lost(network))
}
It works fine... But, when I HAVE NO access to the internet. Any suggestions, what can be wrong here?
Thank you for reading this!
I tried methods from this topic Connect to Wifi in Android Q programmatically
But it doesnt work
I'm trying to get my app to connect to a WiFi AP to provide Internet with WifiNetworkSpecifier using code like this. But it's always calling onUnavailable in the NetworkCallback.
private val callback: ConnectivityManager.NetworkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
super.onAvailable(network)
}
override fun onLost(network: Network) {
super.onLost(network)
}
override fun onLosing(network: Network, maxMsToLive: Int) {
super.onLosing(network, maxMsToLive)
}
override fun onUnavailable() {
super.onUnavailable()
}
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
super.onCapabilitiesChanged(network, networkCapabilities)
}
override fun onLinkPropertiesChanged(network: Network, linkProperties: LinkProperties) {
super.onLinkPropertiesChanged(network, linkProperties)
}
override fun onBlockedStatusChanged(network: Network, blocked: Boolean) {
super.onBlockedStatusChanged(network, blocked)
}
}
val networkSpecifier: NetworkSpecifier = WifiNetworkSpecifier.Builder()
.setSsid("SsidName")
.setWpa2Passphrase("wifipassword")
.build()
val networkRequest: NetworkRequest = NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.setNetworkSpecifier(networkSpecifier)
.build()
connectivityManager.requestNetwork(networkRequest, callback, 100000)
The WifiNetworkSpecifier is only meant to connect to local-only Wifi networks, for example, to set up IoT devices, as confirmed here: https://developer.android.com/guide/topics/connectivity/wifi-bootstrap
So if you .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET), Android will always just do nothing and call onUnavailable.
You can see why in the Android platform code here: https://cs.android.com/android/platform/superproject/+/master:packages/modules/Wifi/service/java/com/android/server/wifi/WifiNetworkFactory.java;drc=08124f52b883c61f3e17bc57dc28eca4c7f7bb72;l=487
The message in your LogCat will be E/WifiNetworkFactory: Request with wifi network specifier cannot contain NET_CAPABILITY_INTERNET. Rejecting
If you want Internet, you need to remove the .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) line (which makes a lot of sense, wha!) and add connectivityManager.bindProcessToNetwork(network) in onAvailable as described over in this answer. Note this is a bit of a hack and will enable Internet via Wifi for your app only.
Here I am trying to connect to wifi through it's ssid by following code. After calling bindProcessToNetwork, it does not getting connected to wifi, nor any warning or error. I have also provide required permissions already.
requireContext().getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val networkCallback: NetworkCallback = object : NetworkCallback() {
override fun onAvailable(network: Network) {
super.onAvailable(network)
connectivityManager.bindProcessToNetwork(network)
}
override fun onLosing(network: Network, maxMsToLive: Int) {
super.onLosing(network, maxMsToLive)
}
override fun onLost(network: Network) {
super.onLost(network)
}
override fun onUnavailable() {
super.onUnavailable()
}
override fun onCapabilitiesChanged(
network: Network,
networkCapabilities: NetworkCapabilities,
) {
super.onCapabilitiesChanged(network, networkCapabilities)
}
override fun onLinkPropertiesChanged(network: Network, linkProperties: LinkProperties) {
super.onLinkPropertiesChanged(network, linkProperties)
}
override fun onBlockedStatusChanged(network: Network, blocked: Boolean) {
super.onBlockedStatusChanged(network, blocked)
}
}
// connectivityManager.requestNetwork(networkRequest,networkCallback);
connectivityManager.registerNetworkCallback(NetworkRequest.Builder()
.build(), networkCallback)```
my current android application has a requirement to inform the user when internet access has been lost during a user session
i employ the following callback listener:-
#OptIn(DelicateCoroutinesApi::class)
class ConnectivityListener #Inject constructor() : CoroutineScope by GlobalScope, ConnectivityManager.NetworkCallback() {
private var backgroundStateJob: Job? = null
private val internalSharedWorkState = MutableSharedFlow<BackgroundState>(replay = 1, extraBufferCapacity = 7, onBufferOverflow = BufferOverflow.DROP_OLDEST)
private val sharedWorkScheduleState: SharedFlow<BackgroundState> = internalSharedWorkState
override fun onBlockedStatusChanged(network: Network, blocked: Boolean) {
Timber.e( "onBlockedStatusChanged() called with: network = $network, blocked = $blocked")
}
override fun onLosing(network: Network, maxMsToLive: Int) {
Timber.e( "onLosing() called with: network = $network, maxMsToLive = $maxMsToLive")
}
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
Timber.e( "onCapabilitiesChanged() called with: network = $network, networkCapabilities = $networkCapabilities")
}
override fun onLinkPropertiesChanged(network: Network, linkProperties: LinkProperties) {
Timber.e( "onLinkPropertiesChanged() called with: network = $network, linkProperties = $linkProperties")
}
override fun onUnavailable() {
Timber.e( "onUnavailable() called")
}
override fun onAvailable(network: Network) {
Timber.e("onAvailable() called with: network = $network")
backgroundStateJob?.cancel()
backgroundStateJob = launch(Dispatchers.IO) {
internalSharedWorkState.emit(BackgroundState.Connected())
}
}
override fun onLost(network: Network) {
Timber.e("onLost() called with: network = $network")
backgroundStateJob?.cancel()
backgroundStateJob = launch(Dispatchers.IO) {
internalSharedWorkState.emit(BackgroundState.Disconnected())
}
}
fun stateFlow(): SharedFlow<BackgroundState> {
return sharedWorkScheduleState
}
}
this listener is employed within my base activity as follow:-
private val networkRequest = NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.build()
...
override fun onResume() {
Timber.e("onResume() called")
super.onResume()
val connectivityManager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
connectivityManager.registerNetworkCallback(networkRequest, connectivityListener)
}
override fun onPause() {
Timber.e("onPause() called")
super.onPause()
val connectivityManager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
connectivityManager.unregisterNetworkCallback(connectivityListener)
}
the listener works as expected while the application is in the foreground and reliable informs the user on the current network status whenever internet access is lost.
the listener also works fine when i exit the application, it goes into the background and is subsequently resumed. the listener successfully reports i have internet access.
however the listener does not work as required when i put the application into the background, enable airplane mode then resume the application.
it does not detect that there is no internet access, even though it does detect when internet access is available when the app is resumed from the background and airplane mode is not enabled.
what am i doing wrong?
how can i detect the current network status when resuming my application from the background?
i managed to achieve the desired result by adding a second
network callback as follows with a timeout by employing requestNetwork()
override fun onResume() {
super.onResume()
val connectivityManager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
connectivityManager.registerNetworkCallback(networkRequest, connectivityListenerMonitor)
connectivityManager.requestNetwork(networkRequest, connectivityListener, 1000)
}
override fun onPause() {
super.onPause()
val connectivityManager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
connectivityManager.unregisterNetworkCallback(connectivityListener)
connectivityManager.unregisterNetworkCallback(connectivityListenerMonitor)
}
now when my application is in the background and i enable airplane mode
then bring my application to the foreground my users are notified that there is no internet connection.
When android device is not connected to the Internet,
I'm going to check if Wi-Fi can be connected with connection live data and connect to a specific Wi-Fi.
ConnectionLiveData
private val TAG = "ConnectionLiveData"
class ConnectionLiveData(context: Context) : LiveData<Boolean>() {
private lateinit var networkCallback: ConnectivityManager.NetworkCallback
private val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
private val wifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager
private val validNetworks: MutableSet<Network> = HashSet()
private fun checkValidNetworks() {
postValue(validNetworks.size > 0)
}
override fun onActive() {
networkCallback = createNetworkCallback()
val networkRequest = NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build()
connectivityManager.registerNetworkCallback(networkRequest, networkCallback)
}
override fun onInactive() {
connectivityManager.unregisterNetworkCallback(networkCallback)
}
private fun createNetworkCallback() = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
Log.d(TAG, "---Location onAvailable: $network")
val networkCapabilities = connectivityManager.getNetworkCapabilities(network)
val hasInternetCapability = networkCapabilities?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
Log.d(TAG, "---Location onAvailable: $network, $hasInternetCapability")
if (hasInternetCapability == true) {
CoroutineScope(Dispatchers.IO).launch {
val hasInternet = DoesNetworkHaveInternet.execute(network.socketFactory)
if (hasInternet) {
withContext(Dispatchers.Main) {
Log.d(TAG, "onAvailable: adding network. $network")
validNetworks.add(network)
checkValidNetworks()
}
}
}
}else {
Log.d(TAG, "There is no active network")
}
}
override fun onLost(network: Network) {
Log.d(TAG,
"The application no longer has a default network. The last default network was $network"
)
validNetworks.remove(network)
checkValidNetworks()
}
override fun onCapabilitiesChanged(
network: Network,
networkCapabilities: NetworkCapabilities
) {
Log.d(TAG, "The default network changed capabilities: $networkCapabilities")
}
override fun onLinkPropertiesChanged(
network: Network,
linkProperties: LinkProperties
) {
Log.d(TAG, "The default network changed link properties: $linkProperties")
}
}
}
ConnectivityManager
#Singleton
class ConnectivityManager
#Inject
constructor(
application: Application,
) {
private val connectionLiveData = ConnectionLiveData(application)
// observe this in ui
val isNetworkAvailable = mutableStateOf(false)
fun registerConnectionObserver(lifecycleOwner: LifecycleOwner){
connectionLiveData.observe(lifecycleOwner) { isConnected ->
isConnected?.let { isNetworkAvailable.value = it }
}
}
fun unregisterConnectionObserver(lifecycleOwner: LifecycleOwner){
connectionLiveData.removeObservers(lifecycleOwner)
}
}
And then, when I connect the wifi, it logs like this.
2021-11-27 10:09:10.808 28100-28143/com.connectapp D/ConnectionLiveData: ---Location onAvailable: 152
2021-11-27 10:09:10.810 28100-28143/com.connectapp D/ConnectionLiveData: ---Location onAvailable: 152, true
2021-11-27 10:09:10.812 28100-28143/com.connectapp D/ConnectionLiveData: The default network changed capabilities: [ Transports: WIFI Capabilities: NOT_METERED&INTERNET&NOT_RESTRICTED&TRUSTED&NOT_VPN&VALIDATED&NOT_ROAMING&FOREGROUND&NOT_CONGESTED&NOT_SUSPENDED LinkUpBandwidth>=1048576Kbps LinkDnBandwidth>=1048576Kbps SignalStrength: -56]
2021-11-27 10:09:10.814 28100-28143/com.connectapp D/ConnectionLiveData: The default network changed link properties: {Network Interface}
2021-11-27 10:09:10.885 28100-28100/com.connectapp D/ConnectionLiveData: onAvailable: adding network. 152
But when I remove stored Wi-Fi data at device, it doesn't return Log
So my question is this, when android device is not connected to internet, connect to specific Wi-Fi.
How I can connect it?