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?
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.
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.
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
I'm working on an Android application which monitors the device's network usage in the background. As far as I know, a couple years ago the go-to approach would have been to register a BroadcastReceiver which catches every CONNECTIVITY_CHANGE event, but it seems like it was deprecated in Android 7. I can see a class named JobScheduler has been introduced since as a sort of replacement, but I'm not sure that might suit this specific use case.
Use ConnectivityManager class to listen about connectivity change.
From official doc
Class that answers queries about the state of network connectivity. It
also notifies applications when network connectivity changes.
Use registerNetworkCallback to receive notifications about all networks which satisfy the given NetworkRequest
Here is sample code in Kotlin:
enum class NetworkResult {
CONNECTED,
DISCONNECTED,
DISCONNECTING
}
class NetworkCallback : ConnectivityManager.NetworkCallback(){
val result = MutableLiveData<NetworkResult>()
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
super.onCapabilitiesChanged(network, networkCapabilities)
}
override fun onLost(network: Network) {
Timber.i("onLost networkL $network")
result.postValue(NetworkResult.DISCONNECTED)
}
override fun onLosing(network: Network, maxMsToLive: Int) {
Timber.i("onLosing: network: $network maxMsToLive: $maxMsToLive")
result.postValue(NetworkResult.DISCONNECTING)
}
override fun onAvailable(network: Network) {
Timber.i("onAvailable: network: $network")
result.postValue(NetworkResult.CONNECTED)
}
}
class ConnectivityFactory {
fun internetRequest() : NetworkRequest{
return NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
.build()
}
}
class NetworkConnectionManager(val context: Context){
private val factory : ConnectivityFactory = ConnectivityFactory()
private val callback : NetworkCallback = NetworkCallback()
private val connectivityManager = context.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
val result : LiveData<NetworkResult> get() = callback.result
fun registerCallback(){
val request = factory.internetRequest()
connectivityManager.registerNetworkCallback(request, callback)
}
fun unregisterCallback(){
connectivityManager.unregisterNetworkCallback(callback)
}
}
Usage:
val networkManager : NetworkConnectionManager by lazy { NetworkConnectionManager(this) }
// observer state
networkManager.result.observe(this, Observer {
Timber.i(it.name)
})
// register
networkManager.registerCallback()()
// unregister
networkManager.unregisterCallback()