Connect to Wifi in Android Q programmatically
Asked Answered
D

8

26

I had this function to connect in Wifi network, below Android 10 it works fine, but when I tried on Android 10, I had a successful connection but WITHOUT internet, I knew it's a bug in Android 10 but I found this application which can connect to wifi from Android 10 with no problem. I'm blocked for days.

My function :

private void connectToWifi(String ssid, String password)
    {
        WifiManager wifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
            try {
                Log.e(TAG,"connection wifi pre Q");
                WifiConfiguration wifiConfig = new WifiConfiguration();
                wifiConfig.SSID = "\"" + ssid + "\"";
                wifiConfig.preSharedKey = "\"" + password + "\"";
                int netId = wifiManager.addNetwork(wifiConfig);
                wifiManager.disconnect();
                wifiManager.enableNetwork(netId, true);
                wifiManager.reconnect();

            } catch ( Exception e) {
                e.printStackTrace();
            }
        } else {
            Log.e(TAG,"connection wifi  Q");

            WifiNetworkSpecifier wifiNetworkSpecifier = new WifiNetworkSpecifier.Builder()
                .setSsid( ssid )
                .setWpa2Passphrase(password)
                    .build();

            NetworkRequest networkRequest = new NetworkRequest.Builder()
                    .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
                    .setNetworkSpecifier(wifiNetworkSpecifier)
                    .build();

             connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);


                 networkCallback = new ConnectivityManager.NetworkCallback() {
                @Override
                public void onAvailable(Network network) {
                    super.onAvailable(network);

                     connectivityManager.bindProcessToNetwork(network);
                    Log.e(TAG,"onAvailable");
                }

                  @Override
                  public void onLosing(@NonNull Network network, int maxMsToLive) {
                      super.onLosing(network, maxMsToLive);
                      Log.e(TAG,"onLosing");
                  }

                  @Override
                public void onLost(Network network) {
                    super.onLost(network);
                    Log.e(TAG, "losing active connection");
                }

                @Override
                public void onUnavailable() {
                    super.onUnavailable();
                    Log.e(TAG,"onUnavailable");
                }
            };
            connectivityManager.requestNetwork(networkRequest,networkCallback);


        }
    }
Dowitcher answered 27/7, 2020 at 22:49 Comment(8)
Are you testing this on a device or emulator?Handgrip
I test on a deviceDowitcher
@Euphor08 did you found any solution for this?Dipstick
@RahulSharma compile your app with targetSdkVersion 28, still the best workaround for the moment.Dowitcher
@euphor but that won't work after 2nd Nov deadline. We can't push update to play store after 2nd Nov. Any other workaround?Dipstick
@NizarETH, have you got any solution to connect to specific wifi network programatically, which has internet.Thule
@PriyanshuPaliwal Yes, I used wifiNetworkSuggestionDowitcher
@Dowitcher please can you share the code, because I am trying from many days, I am able to connect to the specific wifi but there is no internet in it.Thule
D
0

So, the solution for me is compile your app with targetSdkVersion 28. and for connection to wifi use this function

connectToWifi(String ssid, String key)

it's just a workaround for the moment, waiting Google to publish a fix for this bug, for more information the issue reported to Google : issuetracker.google.com/issues/138335744

public void connectToWifi(String ssid, String key) {

Log.e(TAG, "connection wifi pre Q");
WifiConfiguration wifiConfig = new WifiConfiguration();
wifiConfig.SSID = "\"" + ssid + "\"";
wifiConfig.preSharedKey = "\"" + key + "\"";
int netId = wifiManager.addNetwork(wifiConfig);
if (netId == -1) netId = getExistingNetworkId(wifiConfig.SSID);

    wifiManager.disconnect();
    wifiManager.enableNetwork(netId, true);
    wifiManager.reconnect();
}
Dowitcher answered 28/10, 2020 at 9:21 Comment(2)
Unfortunately not possible since November 2020 - "From 2 November 2020, app updates must target Android 10 (API level 29) or higher" from https://developer.android.com/distribute/best-practices/develop/target-sdkLonglongan
This is deprecated. WIFI suggestions API is the solution, and if you need consistent behaviour across versions, we can use Intents to let users know which network it is trying to join.Dubose
C
28

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
Casemaker answered 16/12, 2020 at 16:56 Comment(6)
You put a lot of work into this I see. When I try this I always get onUnavailable() as I noted in another comment above and the user (me) did nothing. Do I have to disconnect from the currently connected wifi network first before connecting to a new one?Maus
Thank you. onUnavailable can be called for a number of reasons such as user cancellation, network not being found and possibly even a bug within the android kernel. To help you narrow it down I would need to see some codeCasemaker
Also knowing which devices you test it on helps and make sure the network is 'forgotten' beforehandCasemaker
Forgot to take the CAPABILITY_INTERNET outMaus
Happens, glad its working for you nowCasemaker
Recently tried this method on a Pixel 6 and looks to be working fine for me. I am using Xamarin Forms which is a little different but it uses the same underlying Java classesReserve
T
4

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.

Thorson answered 5/8, 2020 at 0:27 Comment(9)
no, it's not working, I used this lib before, and I had the same issue, I contacted the owner so, he use it only for IoT, and with internet access limited only inside the app, so for now it's Android bug, hope they will share the fix soonDowitcher
@Euphor08 i'am using code that i posted above and its working perfectly on Android Q devices (Pixel 4XL, Moto G7, SG S20).Thorson
@Euphor08 i misunderstood your question. Do you want to connect to WiFi App on Android Q for internet access?Thorson
there's a scope of internet access in Android Q you will have internet only inside your app if you open a browser you loss internet. the question is how to connect to a hotspot with an app to have internet access with no limitation, hope it's clear now, sorry if my question not directDowitcher
For security reasons, I don't believe this would be possible. If your app could just connect to any hotspot, this would be a huge security issue. If android < 10 let you do this... fair enough... but i wouldn't expect to be able to do this again going forward without a rooted device.Columnist
@Euphor08 maybe you need try to compile your app with targetSdkVersion 28?Thorson
yes, this is what I did but I'm looking for a solution in Android 10. Thank'sDowitcher
This answear should have more +1s. This is the way to go targetting Android 10Middleoftheroad
Just a minor nit, but you don't need .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) since that capability is included by default.Chopper
B
4

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);
    }
Bromberg answered 8/9, 2020 at 8:24 Comment(1)
Hey please can you elaborate how you are connecting to the wifi using wifi suggestion.Thule
D
3

May be late to the party, but here's what I did to connect to the wifi with the internet. WifiSpecifier only allows the peer-to-peer connection. WifiSuggestion adds suggestions, but connecting to that suggested network is best left to the system, provided the system doesn't find any other network than the suggested one.

So, I used WIFI suggestion API to suggest the network and a good old intent to connect to that suggested network.

final List<WifiNetworkSuggestion> wifiNetworkSuggestionList =createSuggestionList(capabilities, currentNetwork,password, ssid);
  wifiManager.removeNetworkSuggestions(wifiNetworkSuggestionList);

    /** This block explicitly asks the user to save the suggestion and connect it via the intent.
     */
    mContext.startActivity(new Intent(Settings.ACTION_WIFI_ADD_NETWORKS).putParcelableArrayListExtra(
            Settings.EXTRA_WIFI_NETWORK_LIST,
            (ArrayList<? extends Parcelable>) wifiNetworkSuggestionList).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));

  return addNetworkSuggestion(wifiNetworkSuggestionList);

Now this will ask you two things, first to add the network suggestion and the second one to connect. Allow both and you will be connected to the network.

The createSuggestionList will look something like this.

 private List<WifiNetworkSuggestion> createSuggestionList(String security, @Nullable ManagedNetwork managedNetwork, String password, String ssid) {
        final List<WifiNetworkSuggestion> suggestionsList = new ArrayList<>();

        if (managedNetwork == null) {
            return suggestionsList;
        }

        final WifiNetworkSuggestion.Builder builder = new WifiNetworkSuggestion.Builder()
                .setSsid(managedNetwork.getSsid())
                .setWpa2Passphrase(password)
                .setIsAppInteractionRequired(true);

        switch (getSecurity(security)) {
            case NONE:
            case WEP:
            case WPA:
            case WPA2:
                break;
            case EAP:
                try {
                    WifiEnterpriseConfig enterpriseConfig = createWifiEnterprise(managedNetwork.getSsid());
                    enterpriseConfig.setDomainSuffixMatch(managedNetwork.getNetworkConfig().getEnterpriseConfig().getServerDomainName());
                    builder.setWpa2EnterpriseConfig(enterpriseConfig);
                } catch (InvalidManagedNetworkException e) {
                    Timber.tag(CMLogTags.CONNECTION_FLOW).w("EnterpriseConfig error: %s", e.getMessage());
                }
                break;
        }

        suggestionsList.add(builder.build());
        return suggestionsList;
    }

Here managedNetwork is nothing but just an object of already known networks. You can simply ignore it and add your own parameters.

I hope this solves the issue until google creates some surface API to fix this behaviour.

enter image description here

enter image description here

Dubose answered 21/3, 2023 at 7:56 Comment(4)
Is it system wide or just for the own app? So the launch of new Intent(Settings.ACTION_WIFI_ADD_NETWORKS)is to save new wifiap in the system like on Settings app? Will it reconnect automatically when disconnected?Forgetmenot
As far as I've tested, and used, Intent will let you connect with the wifi instantly, without waiting for the system to take a call. Because there are multiple parameters associated with it, like NQE for finding best candidate. It will connect automatically if you've not cleared the suggestions. Also, using intent for this case is just use case specific, if you really can't wait for the system to take care of the connection, in terms of time taken, then use it, or else you are good with the suggestions api.Dubose
Just tested. If the wifiAP is "forgotten" in Setting app, call the intent can make it connected. But if it is already "saved", there is no effect. Also add wifiManager.addNetworkSuggestion(suggestionLisrt), it still not work, the wifiAP is not switched to suggest one.Maybe the wifiAP which the user selected on Setting app has the highest priority.Forgetmenot
The connection to wifiAP via suggestion API purely depends on the OS. And if you are already connected to a different wifiAP, suggestion API won't overturn the decision of the OS. The system will keep the current connection. Also, as per the documentation, if we are connected to wifiAP via suggestion API, and we decide to disconnect it from the settings, the network will not be connected again, even if we try to connect it again using the API. I have tested it, but a year ago. So make sure to cover that case as well.Dubose
V
1

If you have root access (adb root):

  1. Manually connect to the Wifi Network of your choosing.

  2. Pull these ADB files:

adb pull /data/misc/wifi/WifiConfigStore.xml
adb pull /data/misc/wifi/WifiConfigStore.xml.encrypted-checksum
  1. Save in a folder that would designate the Wifi Network:
Ex:  GarageWifi
Ex:  BusinessWifi

Copy to location of your choosing. Don't change the names of the files you pulled.

  1. Whenever you want to connect to a desired wifi network:
adb push <location>\WifiConfigStore.xml /data/misc/wifi/
adb push <location>\WifiConfigStore.xml.encrypted-checksum /data/misc/wifi/

adb reboot
Vinegarroon answered 5/8, 2020 at 22:6 Comment(1)
Is it possible to use this solution if we can’t connect manually ? Maybe getting the suggestion and after using the root mode to connect to a suggestion automaticallyGroenendael
H
1

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

Hardhack answered 24/3, 2021 at 8:40 Comment(2)
I always get onUnavailable() doing the above even though I know the network is available. Any idea why?Maus
@BrianReinhold I think you are using [networkRequestBuilder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);] which shouldn't added.Jarred
T
1

I was also facing the same issue, even after 3 months I was unable to solve this. But I have found one awesome and cool solution.

startActivity(new Intent("android.settings.panel.action.INTERNET_CONNECTIVITY"))

Just add these lines instead of connecting to the wifi from the app, this will prompt user to select a wifi and connect and as soon as user do it, it will connect to the wifi and also it will have internet also.

Just connecting to the wifi from the app will not access the internet. Do this, this is the best solution.

Tzar answered 20/5, 2021 at 10:33 Comment(0)
D
0

So, the solution for me is compile your app with targetSdkVersion 28. and for connection to wifi use this function

connectToWifi(String ssid, String key)

it's just a workaround for the moment, waiting Google to publish a fix for this bug, for more information the issue reported to Google : issuetracker.google.com/issues/138335744

public void connectToWifi(String ssid, String key) {

Log.e(TAG, "connection wifi pre Q");
WifiConfiguration wifiConfig = new WifiConfiguration();
wifiConfig.SSID = "\"" + ssid + "\"";
wifiConfig.preSharedKey = "\"" + key + "\"";
int netId = wifiManager.addNetwork(wifiConfig);
if (netId == -1) netId = getExistingNetworkId(wifiConfig.SSID);

    wifiManager.disconnect();
    wifiManager.enableNetwork(netId, true);
    wifiManager.reconnect();
}
Dowitcher answered 28/10, 2020 at 9:21 Comment(2)
Unfortunately not possible since November 2020 - "From 2 November 2020, app updates must target Android 10 (API level 29) or higher" from https://developer.android.com/distribute/best-practices/develop/target-sdkLonglongan
This is deprecated. WIFI suggestions API is the solution, and if you need consistent behaviour across versions, we can use Intents to let users know which network it is trying to join.Dubose

© 2022 - 2024 — McMap. All rights reserved.