Connect to Wifi in Android Q programmatically
Since Android 10, I've have to use the following code to connect to a specific wifi network.
private ConnectivityManager mConnectivityManager;
@Override
public void onCreate(@Nullable Bundle savedInstanceState){
// instantiate the connectivity manager
mConnectivityManager = (ConnectivityManager) this.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
}
public void connect(String ssid, String password) {
NetworkSpecifier networkSpecifier = new WifiNetworkSpecifier.Builder()
.setSsid(ssid)
.setWpa2Passphrase(password)
.setIsHiddenSsid(true) //specify if the network does not broadcast itself and OS must perform a forced scan in order to connect
.build();
NetworkRequest networkRequest = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.setNetworkSpecifier(networkSpecifier)
.build();
mConnectivityManager.requestNetwork(networkRequest, mNetworkCallback);
}
public void disconnectFromNetwork(){
//Unregistering network callback instance supplied to requestNetwork call disconnects phone from the connected network
mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
}
private ConnectivityManager.NetworkCallback mNetworkCallback = new ConnectivityManager.NetworkCallback(){
@Override
public void onAvailable(@NonNull Network network) {
super.onAvailable(network);
//phone is connected to wifi network
}
@Override
public void onLosing(@NonNull Network network, int maxMsToLive) {
super.onLosing(network, maxMsToLive);
//phone is about to lose connection to network
}
@Override
public void onLost(@NonNull Network network) {
super.onLost(network);
//phone lost connection to network
}
@Override
public void onUnavailable() {
super.onUnavailable();
//user cancelled wifi connection
}
};
References: https://anutoshdatta.medium.com/new-wifi-apis-on-android-10-481c525108b7 https://developer.android.com/guide/topics/connectivity/wifi-suggest
So far what is working for me on the majority of devices I have tested with, with a fallback option to at least stop the dreaded 'looping request' and to allow a successful manual connection
The below code is written in Kotlin, please google how to covert to Java if needed.
Create a NetworkCallback which is required for API >= 29 (prior it was not required but could be used)
val networkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
super.onAvailable(network)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// To make sure that requests don't go over mobile data
connectivityManager.bindProcessToNetwork(network)
} else {
connectivityManager.setProcessDefaultNetwork(network)
}
}
override fun onLost(network: Network) {
super.onLost(network)
// This is to stop the looping request for OnePlus & Xiaomi models
connectivityManager.bindProcessToNetwork(null)
connectivityManager.unregisterNetworkCallback(networkCallback)
// Here you can have a fallback option to show a 'Please connect manually' page with an Intent to the Wifi settings
}
}
Connect to a network as follows:
val wifiNetworkSpecifier = WifiNetworkSpecifier.Builder()
.setSsid(ssid)
.setWpa2Passphrase(pass)
.build()
val networkRequest = NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
// Add the below 2 lines if the network should have internet capabilities.
// Adding/removing other capabilities has made no known difference so far
// .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
// .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
.setNetworkSpecifier(wifiNetworkSpecifier)
.build()
connectivityManager.requestNetwork(networkRequest, networkCallback)
As stated here by Google, some OEM Roms are not 'holding on to the request' and therefore the connection is dropping instantly. OnePlus have fixed this problem in some of their later models but not all. This bug will continuously exist for certain phone models on certain Android builds, therefore a successful fallback (i.e. a manual connection with no network disruption) is required. No known workaround is available, but if found I will update it here as an option.
To remove the network, do the following:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
//This is required for Xiaomi models for disconnecting
connectivityManager.bindProcessToNetwork(null)
} else {
connectivityManager.setProcessDefaultNetwork(null)
}
connectivityManager.unregisterNetworkCallback(it)
Please keep in mind, an automatic connection allows for an automatic & manual disconnection. A manual connection (such as the suggested fallback for OnePlus devices) does not allow an automatic disconnection. This will also need to be handled within the app for a better UX design when it comes to IoT devices.
Some extra small tips & info:
now that a system dialog opens, the app calls onPause and onResume respectively. This affected my logic regarding automatic connection to IoT devices. In some case, onResume is called before the network callback is finished.
In regards to tests, I have yet to be able to get around the dialog by just using espresso and it may block some tests that were working before API 29. It may be possible using other frameworks such as uiautomator. In my case I adjusted the tests to work up until the dialog shows, and run further tests thereafter. Using Intents.init() does not work.
onUnavailable is called when the the network has been found, but the user cancels. It is not called when the network was not found or if the user cancels the dialog before the network has been found, in this case no other methods are called, use onResume to catch it.
when it fails on the OnePlus it called onAvailable() -> onCapabilitiesChanged() -> onBlockedStatusChanged (blocked: false) -> onCapabilitiesChanged() -> onLost() respectively
removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) wont help keep the connection on a OnePlus as stated here
setting the Bssid wont help keep the connection on a OnePlus as stated here
google cannot help, they have stated it is out of their hands here
OnePlus forum posts confirming it working for some models (but not all) after an update, see here, here & here
when GPS is switched off, the SSID names of networks are not available
if the dialog comes several times, check your own activity lifecycle, in my case some models were calling onResume before the network callback was received.
manually connecting to a network without internet capabilities needs user confirmation to keep the connection (sometimes in the form of a dialog or as a notification), if ignored, the system will disconnect from the network shortly afterwards
List of devices tested:
- Google Pixel 2 - No issues found
- Samsung S10 SM-G970F - No issues found
- Samsung S9 SM-G960F - No issues found
- One Plus A5000 (OxegenOS 10.0.1) - Major Issue with automatic connection
- HTC One M8 (LineageOS 17.1) - No issues found
- Xiaomi Mi Note 10 - Issue with disconnecting (Fixed, see code example)
- Samsung A50 - Dialog repetitively appears after successful connection (sometimes)
- Huawei Mate Pro 20 - Dialog repetitively appears after successful connection (sometimes)
- Huawei P40 Lite - Doesn't call onLost()
- CAT S62 Pro - No issues found
- Sony Xperia SZ2 - No issues found
- Samsung Note10 - No issues found
You can try wifisuggestion api, I'm able to connect using them.
final WifiNetworkSuggestion suggestion1 =
new WifiNetworkSuggestion.Builder()
.setSsid("YOUR_SSID")
.setWpa2Passphrase("YOUR_PRE_SHARED_KEY")
.build();
final List<WifiNetworkSuggestion> suggestionsList =
new ArrayList<WifiNetworkSuggestion>();
suggestionsList.add(suggestion1);
WifiManager wifiManager =
(WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
int status = wifiManager.addNetworkSuggestions(suggestionsList);
if (status == 0 ){
Toast.makeText(this,"PSK network added",Toast.LENGTH_LONG).show();
Log.i(TAG, "PSK network added: "+status);
}else {
Toast.makeText(this,"PSK network not added",Toast.LENGTH_LONG).show();
Log.i(TAG, "PSK network not added: "+status);
}
In case if you want to connect to WiFi with INTERNET, you should use this kind of NetworkRequest:
NetworkRequest request = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.setNetworkSpecifier(wifiNetworkSpecifier)
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
.build();
Also, you need specify default route for your process to make requests to connected WiFi AP permanently. Just add call of next method to your NetworkCallback under onAvaliable like this:
networkCallback = new ConnectivityManager.NetworkCallback() {
@Override
public void onAvailable(Network network) {
createNetworkRoute(network, connectivityManager);
}
};
if (connectivityManager!= null) connectivityManager.requestNetwork(request, networkCallback);
.
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private static void createNetworkRoute(Network network, ConnectivityManager connectivityManager) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
connectivityManager.bindProcessToNetwork(network);
} else {
ConnectivityManager.setProcessDefaultNetwork(network);
}
}
Don't forget disconnect from the bound network:
connectivityManager.unregisterNetworkCallback(networkCallback);
Finally, you can find best practice in different libraries like WifiUtils.