Connecting to specific wifi in android 10 - android

I'm trying to connect to a specific wifi network in android 10. I already have the credentials available to me, and i'm adding the network suggestion:
val suggestion = WifiNetworkSuggestion.Builder()
.setSsid("ssid")
.setWpa2Passphrase("password")
.build()
wifiManager.removeNetworkSuggestions(mutableListOf(suggestion))
when (wifiManager.addNetworkSuggestions(mutableListOf(suggestion))) {
WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS -> {
networkAdded()
}
WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_DUPLICATE -> {
networkDuplicate()
}
else -> {
networkAddFailed()
}
}
I'm able to see the notification (though only the first time the app requests this, which makes sense for the most part, since it's asking for permission really to be able to suggest networks). However, after I allow it, i can't seem to get connected to that network. In particular if i already have a wifi connection to some other network.
The new network does not appear in my list of saved networks, and if i find it in the wifi scan list, and click on it, it treats it like a new network.
I don't expect the app to be able to force the system to connect, but i would expect the user to be able to do it on their own via the wifi settings without having to configure it from scratch.
I've also tried using ConnectivityManager to forcibly connect, but that does not add an actual persistent wifi connection. It's only meant to force connection for a specific app while the app is in memory (at least that's my understanding of it).

I had the same issue but after I added this piece of code the device was connected to the network:
final IntentFilter intentFilter = new IntentFilter(WifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION);
final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if (!intent.getAction().equals(WifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION)) {
return;
} else {
// Do something with success
}
}
};
context.registerReceiver(broadcastReceiver, intentFilter);
Hope that it helps. You can find the complete code example in: Wi-Fi suggestion API

Related

WiFiManager API in Android Q, R

I am writing a sample application which is scanning for available wifis and connects to them programmatically
For that propose, I'm using WiFiManager, but recently Google deprecated most of the methods that are allowing to scan for WiFis and Connect to them programmatically. And yes it seems logical from Google's side to not allow anyone to try to connect to any WiFi programmatically.
But my questions will be following
Is there still a way to Scan for available WiFi-s
Is there a way to connect to a Public WiFi
Is there a way to connect to a Private WiFi programmatically, or if no if there is a way to suggest user connect to a private WiFi, so user can enter a password
In the new API I found a way how I can suggest user connect to the WiFi
wifiManager.addNetworkSuggestions(listOf(WifiNetworkSuggestion
.Builder()
.setSsid("testWiFi")
.setWpa2Passphrase("test1234")
.build()))
but this is working really unpredictable, the first time when I run my all and call this method, on Android Q it's showing a notification and on Android R it's showing dialog asking the user if he allows connecting to the WiFi-s when app somehow connects to the WiFi but after that calling the same function for another WiFis does nothing and app is not showing any more ant notification or dialog (it this a bug? )
also I tried requestNetwoerk method
val specifier = WifiNetworkSpecifier
.Builder()
.setSsid("testWiFi")
.setBssid(MacAddress.fromString("testWiFiMac"))
.build()
val request = NetworkRequest
.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.setNetworkSpecifier(specifier)
.build()
connectivityManager.requestNetwork(request, networkCallback)
but in this case, it's not very clear if it is possible to connect to password-protected WiFi, and if yes how to do that.
So if there are some examples or explanations, please let me know.
even if start scan is deprecated Google still recommends to use it.
The three steps here are:
Register a broadcast receiver for SCAN_RESULTS_AVAILABLE_ACTION.
Request a scan using WifiManager.startScan()
Get the scan results with WifiManager.getScanResults(). Note that if you are using these scan results before receiving the results from the broadcast, these results might be a bit older from previous scans.
Sample code might be look like this:
val wifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager
val wifiScanReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val success = intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false)
if (success) {
scanSuccess()
} else {
scanFailure()
}
}
}
val intentFilter = IntentFilter()
intentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)
context.registerReceiver(wifiScanReceiver, intentFilter)
val success = wifiManager.startScan()
if (!success) {
// scan failure handling
scanFailure()
}
....
private fun scanSuccess() {
val results = wifiManager.scanResults
... use new scan results ...
}
private fun scanFailure() {
// handle failure: new scan did NOT succeed
// consider using old scan results: these are the OLD results!
val results = wifiManager.scanResults
... potentially use older scan results ...
}
For devices running on Android Q or higher, the only method to properly to connect to a public or private wifi which is not intended as a peer to peer network is to use the NetworkSuggestionAPI as you already showed in your question.
The reason why you sometimes don't see the network suggestion is the following:
"If the user uses the Wi-Fi picker to explicitly disconnect from one of the network suggestions when connected to it, then that network is ignored for 24 hours. During this period, that network will not be considered for auto-connection, even if the app removes and re-adds the network suggestion corresponding to the network.", cited from the Google documentation website.
For more information about the wifi scanning look at the official documentation:
https://developer.android.com/guide/topics/connectivity/wifi-scan#kotlin
More information about the WifiNetworkSuggestion API can be found here:
https://developer.android.com/guide/topics/connectivity/wifi-suggest

Android, communicate with mobile data while connected to wifi without internet access

I have an automotive companion app that needs to communicate with both wifi and mobile data networks.
My vehicle control unit provides a wifi network without internet access which exposes an API service that we can call from the app.
In addition to this we need to communicate with another backend reachable from the internet using phone mobile data (3G/4G).
I immediately noticed that some android phones, when connected to a wifi network without internet using android settings menu, show a system dialog informing user that current network has no internet access. Here user have two choises: keep this wifi network or disconnect and switch to another one.
Here some examples:
Samsung J7 - Android 7.0
Motorola moto G7 power - Android 9.0
Xiaomi mi 9T - Android 10
Huawei p9 lite - Android 6.0
After a short analysis I understood that if the user clicks 'NO' option, the system disconnects from the current wifi network and if another network is available connect to this.
If instead user clicks 'YES' option, we can have two differents behavior:
Phone keep connected to wifi network without internet access and all applications cannot communicate anymore with internet, because android try to use the wifi interface.
Phone keep connected to wifi network without internet access but android system rebind all existing sockets and those that will open in the future on moblie data network (if sim is available).
Then i tried same thing but using programmatic connection.
My sample code for differents android versions:
fun connectToWifi(ssid: String, key: String) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
connectPost10(ssid, key)
} else {
connectPre10(ssid, key)
}
}
#RequiresApi(Build.VERSION_CODES.O)
private fun connectPost10(ssid: String, wpa2Passphrase: String) {
val specifier = WifiNetworkSpecifier.Builder()
.setSsid(ssid)
.setWpa2Passphrase(wpa2Passphrase)
.build()
val request = NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.setNetworkSpecifier(specifier)
.build()
val networkCallback = object: ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
val networkSSID = wifiManager.connectionInfo.ssid
.trim()
.removeSurrounding("\"")
if (networkSSID == "MY_NETWORK_WITHOUT_INTERNET_SSID") {
// i'm connected here
}
}
}
connectivityManager.requestNetwork(request, networkCallback)
}
private fun connectPre10(ssid: String, key: String) {
// setup wifi configuration
val config = WifiConfiguration().apply {
SSID = "\"$ssid\""
preSharedKey = "\"$key\""
}
val networkId = wifiManager.addNetwork(config)
wifiManager.disconnect() // disconnect from current (if connected)
wifiManager.enableNetwork(networkId, true) // enable next attempt
wifiManager.reconnect()
}
Please note that in order to read the network ssid android require ACCESS_FINE_LOCATION permission and GPS must be active.
I immediately noticed that using the programmatic connection the native popup didn't show up anymore, but on many devices, after connection, mobile data connectivity was "disabled" by android.
I suppose this behavior is wanted by the system and is determined by the fact that android prefers wifi over metered connections.
I'm ok with that, but what happen in my case where wifi network has no internet access? Other applications that require connectivity stops working because these cannot reach the internet.
I need a solution that allows my application to communicate via both wifi and 4g without preventing other applications from working properly.
My min sdk api level is 23 (Marshmallow), targeting 29 (Android 10).
I managed to solve the problem by saving the networks that come from callbacks registered on the connectivity manager.
val connectivityManager by lazy {
MyApplication.context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
}
private val wifiNetworkCallback = object : ConnectivityManager.NetworkCallback() {
// Called when the framework connects and has declared a new network ready for use.
override fun onAvailable(network: Network) {
super.onAvailable(network)
listener?.onWifiConnected(network)
}
// Called when a network disconnects or otherwise no longer satisfies this request or callback.
override fun onLost(network: Network) {
super.onLost(network)
listener?.onWifiDisconnected()
}
}
private val mobileNetworkCallback = object : ConnectivityManager.NetworkCallback() {
// Called when the framework connects and has declared a new network ready for use.
override fun onAvailable(network: Network) {
super.onAvailable(network)
connectivityManager.bindProcessToNetwork(network)
listener?.onMobileConnected(network)
}
// Called when a network disconnects or otherwise no longer satisfies this request or callback.
override fun onLost(network: Network) {
super.onLost(network)
connectivityManager.bindProcessToNetwork(null)
listener?.onMobileDisconnected()
}
}
private fun setUpWifiNetworkCallback() {
val request = NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.build()
try {
connectivityManager.unregisterNetworkCallback(wifiNetworkCallback)
} catch (e: Exception) {
Log.d(TAG, "WiFi Network Callback was not registered or already unregistered")
}
connectivityManager.requestNetwork(request, wifiNetworkCallback)
}
private fun setUpMobileNetworkCallback() {
val request = NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.build()
try {
connectivityManager.unregisterNetworkCallback(mobileNetworkCallback)
} catch (e: Exception) {
Log.d(TAG, "Mobile Data Network Callback was not registered or already unregistered")
}
connectivityManager.requestNetwork(request, mobileNetworkCallback)
}
Mark this "connectivityManager.bindProcessToNetwork()" we'll talk about it later.
Subsequently I created two different retrofit services:
Private Retrofit: bound on the network object I receive from the wifi callback, expose api to communicate with my vehicle within the local network.
Public Retrofit: bound on the network object I received from the mobile data callback, expose api to communicate with my backend and everything else that needs the internet.
At this point in my app I am able to redirect the traffic that passes through retrofit,
but how do the libraries that I include in the project understand which network they should use?
Answer: they don't understand it, in fact they try to use the wifi network getting a timeout error.
I noticed this behavior when I added google maps to my application, the canvas showed only an empty grid.
Since it is not possible to redirect google maps traffic through the public retrofit service that I created earlier i had to look for another solution to fix this.
Here it is ConnectivityManager.bindProcessToNetwork() that you have seen before!
https://developer.android.com/reference/android/net/ConnectivityManager#bindProcessToNetwork(android.net.Network).
With this method I am able to tell the process of my application that all the sockets created in the future must use the network that came from the callback of mobile data.
Using this trick, google maps and all the other libraries of which I cannot control connectivity, they will use the data connection to communicate with the internet and therefore they will work properly.
In some phones, especially the older versions of android, there is still the problem of other applications that remain without connectivity because they try to use wifi instead of using mobile data.
As a user I would be quite frustrated if using one app all the others won't work anymore.
So I would like to understand if there is a way to meet all my requirements in such a way that both my app and the others work without problems regardless of the Android version and the phone vendor.
In summary, my questions are:
Why "Network without internet access" popup does not appear if connection is made programmatically?
If Android known that my WiFi has no internet access, why others applications don't use mobile data network as fallback automatically?
Is possible to tell android that other applications must use a certain network to open future sockets?
Each vendor has custom WiFi settings to provide enhanced internet experience (Huawei WiFi+, Samsung Adaptive WiFi, Oppo WiFi Assistant, ...). I have noticed that in some phones activating it solve other applications problem, it seems that these features have permissions to rebind the entire application ecosystem on a specific network interface. How can these features help / hinder me? Is it possible to write some code that does the same thing these features do?
First, regarding your questions:
This behavior is reserved to the system app.
Android knows there is a healthy connection to the WiFi network. It does not check further to verify that there is no connection to the outside world. It is actually not always the desired behavior btw.
Yes, see below
In some aspects yes, see below
It seems to me that what you're looking for is to alter the default routing mechanism of Android.
That is, you would like all the traffic to the server(s) inside the WiFi network be routed to the WiFi network, while all other traffic be routed via the mobile data interface. There are a couple of ways to achieve this:
If your app is part of the infotainment system of the vehicle, and can possess system privileges, or alternatively, on a rooted Android phones, you can directly alter the routing table, using ip route commands.
What you described is actually part of the functionality of a Virtual Private Network (VPN). You can implement a VPN service yourself server side and client side, based on open source solutions such as OpenVPN, in which the VPN server would be inside the wifi network. Android has prebuilt infrastructure for implementing the VPN client: https://developer.android.com/guide/topics/connectivity/vpn
You can use commercial VPN solutions. Some of them allow the configuration you're looking for, and I believe will meet the needs you described.

Android 10 / API 29 : how to connect the phone to a configured network?

I am working on an application in which one the user has to follow these steps :
connect the phone to wifi ;
connect the phone to a dedicated hotspot from a connected object.
When the user is connected to the dedicated hotspot of the connected object, the application does some HTTP requests in order to configure it. Then, I would like to reconnect automatically the application to the global wifi of step 1.
From API 21 to API 28 this feature is very easy to implement because I know the SSID I want to reconnect the phone too. It can be done with a few line of code:
private fun changeCurrentWifiNetworkLegacy(ssidToConnect: String) {
val wifiManager = applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
var ssidIdentifier: Int? = null
wifiManager.configuredNetworks?.forEach { config ->
Log.d("SSID", config.SSID)
if (config.SSID == "\"${ssidToConnect}\"") {
ssidIdentifier = config.networkId
}
}
ssidIdentifier?.let { id ->
wifiManager.enableNetwork(id, true)
}
}
On API 29 this simple code does not work anymore according to this article: https://developer.android.com/about/versions/10/privacy/changes#configure-wifi
According to the article, now, I should use 2 classes: WifiNetworkSpecifier and/or WifiNetworkSuggestion.
Unfortunately, I cannot find a working example using these classes in order to connect the user to a previous configured SSID.
Does someone already achieve that?
Thank you in advance for your help.
Just in case any poor soul encounters this, it's completely possible the API is just broken on your device. On my OnePlus 5, Android 10, Build 200513, this happens:
Call requestNetwork. Doesn't matter whether I use the Listener or PendingIntent version
The OS finds the network, connects, onAvailable and friends are called
OS immediately disconnects. I can see "App released request, cancelling NetworkRequest" in logcat
This is however, not true - the request was not cancelled, which Android relizes, and starts the process of connecting to the network again. Go to 2. and repeats forever.
Created an Android bug for this: https://issuetracker.google.com/issues/158344328
Additionally, you can get the OS into state when it will no longer show the "Device to use with " dialog if you don't terminate your app and the dialogs in the correct order, and only reboot helps.
Just save yourself the trouble, target Android 9 and use the WifiManager APIs (that are helpfully broken if you target 10). It even has better user experience than the new requestNetwork APIs.
You can set which network to connect to with the following code:
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
val cm = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val networkRequest = NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.setNetworkSpecifier(
WifiNetworkSpecifier.Builder()
.setSsid("My ssid")
.build()
)
.build()
cm.requestNetwork(networkRequest, object: ConnectivityManager.NetworkCallback() {
override fun onUnavailable() {
Log.d("TEST", "Network unavailable")
}
override fun onAvailable(network: Network) {
Log.d("TEST", "Network available")
}
})
}
This uses the ConnectivityManager's networkRequest method to request a network with a specific SSID.
This method requires the caller to hold either the Manifest.permission.CHANGE_NETWORK_STATE permission or the ability to modify system settings as determined by Settings.System.canWrite(Context).
See the NetworkCallback class for more documentation about what info you can get.
(edit: Missed adding Transport type.)
Further edit: I needed to use .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) to get it to work properly. Because in WifiNetworkSpecifier
can only be used to request a local wifi network (i.e no internet
capability)
According to the docs
This gives me a request for devices popup, but then eventually shows me the Wifi network I asked for.
I try lots of codes, including WifiManager.addNetworkSuggestions and ConnectivityManager.requestNetwork. Finally i found that, just call ConnectivityManager.unregisterNetworkCallback to restore user wifi connection. The only place that this code makes me dissatisfied, if the user saved WiFi A and WiFi B, then the system will automatically select a connection, not the one I specified.

android - Connecting to a WiFi P2P device without discovering peers

I want to establish a WiFi-Direct Connection with another device peered through NFC. My steps are as follows:
First, Device A gets its own WiFiP2P address and transmits it to Device B via NFC.
Then, Device B tries to establish a connection with Device A using the provided address.
As you can see I didn't involve discovering peers in the process. But when Device B is trying to connect, the result is always failed (reason 0, this should be ERROR).
I think this might be related to device visibility, but I don't know and can't find any code to make a device visible.
Here's my code:
//NOTE: These code should be executed on Device B
//Starting WiFi Direct Transmission
//First we should establish a connection
WifiP2pConfig config = new WifiP2pConfig();
config.deviceAddress = remoteWifiP2pDevice;
//remoteWifiP2pDevice is the address of device A obtained from NFC
config.wps.setup = WpsInfo.PBC;
mManager.connect(mChannel, config, new WifiP2pManager.ActionListener() {
#Override
public void onSuccess() {
//success logic
Toast.makeText(MainActivity.this, "success", Toast.LENGTH_SHORT).show();
if (!FILE_RECV)
{
new SendFilesTask().execute("");
}
}
#Override
public void onFailure(int reason) {
//failure logic
Toast.makeText(MainActivity.this, "failed" + reason, Toast.LENGTH_SHORT).show();
}
});
In OnCreate() I have
intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
mManager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
mChannel = mManager.initialize(this, getMainLooper(), null);
mReceiver = new WiFiDirectBroadcastReceiver(mManager, mChannel, this);
The WifiDirectBroadcastReceiver has code only related to getting Device A's address and can be considered empty.
So what's wrong with these and how can I fix it?
Thanks in advance.
P.S. If I connect Device A and B manually, and run my code again, it returns success.
WIfi Direct assumptions:
All the devices should be in discoverable (scanning mode)
simultaneously.
Devices scan for a 30 sec and after that
it stops by default.So we need to initiate scan programatically
using discoverpeers method.
Most important is displaying
nearby devices is device specific.ie sometimes devices wont show
nearby ones eventhough they are available.Thats why wifi direct
is not reliable and because of these, there wont be much wifi
direct apps in play store.
I have discovered that delaying several seconds will make the connection succeed. I don't know the reason, but this can be used as a workaround.
So after all is there a better solution to this? And why does the delay work?

Android loses Network connection and it stays disconnected

Im am working on a App that has to work in the Background, and it has to send Geolocation. It is allready in production and we sometimes have the problem that an App just stoppes sending information.
It seems that only a restart of the Phone helps.
Now I have to fix this somehow, it seems to me that resetting the Networkstack would solve the problem. My question is this, how can I programticlly reset the network connection? Or does anybody have any other suggestions for this problem.
how can I programticlly reset the network connection? Or does anybody
have any other suggestions for this problem.
You should not go for start a network connection programatically. I would suggest you to check for network connection before posting the location to server, and if device is disconnected show the notification to user to enable network connection.
Why I am not in favor of set network connection by code
This functionality can cost the user. So raise a notification and let the user to start network connection.
And yes if user dosn't start the network connection your application will not post location any more.
Update for Listen device network state
Register a Receiver to listen for CONNECTIVITY_ACTION
For example:
BroadcastReceiver sampleNetworkStateReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
Log.w("sampleNetworkStateReceiver", "Network State has Changed");
//For a disconnect event, the boolean extra EXTRA_NO_CONNECTIVITY is set to true if there are no connected networks at all.
// So you need to get EXTRA_NO_CONNECTIVITY extra from intent.
// that will tell you if device has loses the conectivity.
boolean noConnectivity =
intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
if (noConnectivity) {
//TODO: Task for Device has no connection
} else {
//TODO: Task for Device has connection
}
}
};
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
registerReceiver(sampleNetworkStateReceiver, filter);
Ref : NetworkConnectivityListener and Determining and Monitoring the Connectivity Status

Categories

Resources