Android Q, programmatically connect to different WiFi AP for internet
Asked Answered
E

5

25

As in Android Q, several WiFi APIs are restricted. I am trying to use alternate APIs to connect to different Wifi AP for internet.

Below is my code :

    WifiNetworkSpecifier.Builder builder = new WifiNetworkSpecifier.Builder();
    builder.setSsid("wifi-ap-ssid");
    builder.setWpa2Passphrase("wifi-ap-password");

    WifiNetworkSpecifier wifiNetworkSpecifier = builder.build();

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

    NetworkRequest nr = networkRequestBuilder1.build();
    ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
    cm.requestNetwork(nr, callback);

This allows me to connect but Internet is disabled. This is working as defined in Android docs.

Alternate way i tried is below :

    WifiNetworkSuggestion.Builder wifiNetworkSuggestionBuilder1 = new WifiNetworkSuggestion.Builder();
    wifiNetworkSuggestionBuilder1.setSsid("wifi-ap-ssid");
    wifiNetworkSuggestionBuilder1.setWpa2Passphrase("wifi-ap-password");
    WifiNetworkSuggestion wifiNetworkSuggestion = wifiNetworkSuggestionBuilder1.build();
    List<WifiNetworkSuggestion> list = new ArrayList<>();
    list.add(wifiNetworkSuggestion);
    wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
    wifiManager.removeNetworkSuggestions(new ArrayList<WifiNetworkSuggestion>());
    wifiManager.addNetworkSuggestions(list);

declared permission in Manifest :

<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>

Using this didn't change anything in behavior.

Please let know sequence of APIs to connect successfully to different Wifi AP with internet capability.

Epigrammatist answered 1/7, 2019 at 6:41 Comment(7)
There is an open ticket with google regarding this. I would recommend you guys to comment and voice over this ticket as it would help get google's attention. issuetracker.google.com/issues/138335744Frantz
@AnandKhinvasara : As, These APIs are not giving internet capability. I am displaying a popup for user to go to settings and connect to AP manually. Hope, this alternate way can work for your usecase.Epigrammatist
I know but its not a good solution. Google should fix it.Frantz
@AnandKhinvasara : Agreed.Epigrammatist
I got it to work. Please check my answer.Frantz
Did that work for you?Frantz
@AnandKhinvasara Did that work for you? Where is your answer?Vera
A
15

Try calling bindProcessToNetwork() in onAvailable() callback to regain network connectivity, it works fine for me.

Connect to network:

    WifiNetworkSpecifier.Builder builder = new WifiNetworkSpecifier.Builder();
    builder.setSsid("wifi-ap-ssid");
    builder.setWpa2Passphrase("wifi-ap-password");

    WifiNetworkSpecifier wifiNetworkSpecifier = builder.build();

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

    NetworkRequest nr = networkRequestBuilder1.build();
    ConnectivityManager cm = (ConnectivityManager)
            context.getSystemService(Context.CONNECTIVITY_SERVICE);
    ConnectivityManager.NetworkCallback networkCallback = new 
        ConnectivityManager.NetworkCallback() {
        @Override
        public void onAvailable(Network network) {
            super.onAvailable(network);
            Log.d(TAG, "onAvailable:" + network);
            cm.bindProcessToNetwork(network);
        }
    });
    cm.requestNetwork(nr, networkCallback);

Disconnect from the bound network:

cm.unregisterNetworkCallback(networkCallback);
Ancy answered 20/11, 2019 at 6:1 Comment(3)
This answer does not solve the problem This connection method does not have access to the InternetLafontaine
You saved my day, thanks of lot! Regards!Marlonmarlow
On Disconnection, I suggest to call cm.bindProcessToNetwork(null), too.Allude
F
6

WifiNetworkSuggestion API is used to suggest the user about joining an AP(System will post a notification for user to join)

Use WifiNetworkSpecifier to send your requests. Use the network object provided in onAvailable().

WifiNetworkSpecifier.Builder builder = new WifiNetworkSpecifier.Builder();
builder.setSsid("wifi-ap-ssid");
builder.setWpa2Passphrase("wifi-ap-password");

WifiNetworkSpecifier wifiNetworkSpecifier = builder.build();

NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder();
networkRequestBuilder1.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
networkRequestBuilder1.setNetworkSpecifier(wifiNetworkSpecifier);

NetworkRequest networkRequest = networkRequestBuilder.build();
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
cm.requestNetwork(networkRequest, networkCallback);
networkCallback = new ConnectivityManager.NetworkCallback() {
            @Override
            public void onAvailable(@NonNull Network network) {
                //Use this network object to Send request. 
                //eg - Using OkHttp library to create a service request
                 //Service is an OkHttp interface where we define docs. Please read OkHttp docs
                 Service service = null;

                 OkHttpClient.Builder okHttpBuilder = new OkHttpClient.Builder();
                okHttpBuilder.socketFactory(network.getSocketFactory());

                service = new Retrofit.Builder()                                    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                        .addConverterFactory(GsonConverterFactory.create(gson))
                         .client(okHttpBuilder.build())
                         .build()
                         .create(Service.class);


               Observable<Object> observable = null;
               try {
                  if (service != null) {
                     observable = service.yourRestCall();
                  }
                  Subscriber<Object> sub = new Subscriber< Object >() {
                     @Override
                     public void onError(Throwable e) {
                        //Do on error
                     }

                     @Override
                     public void onNext(Object logs) {
                        //Do on next
                     }
                  };
                 if(observable != null) {
                     observable.subscribeOn(Schedulers.io())
                                          .observeOn(AndroidSchedulers.mainThread()).subscribe(sub);
                 }

                super.onAvailable(network);
            }
        };

After you are done using the Wifi access point do

connectivityManager.unregisterNetworkCallback(networkCallback);

From Google's Issue tracker by Google's Engineer:

The network suggestions API flow requires the user to approve the app (platform posts a notification to ask user for approval). Once the app is approved, the platform will consider all networks from the app in future auto-connection attempts. But, this API does not give you guarantees on when the device will connect to your AP for provisioning. So, WifiNetworkSuggestion is not the right API surface for the provided use-case (peer to peer instant connectivity).

Using WifiNetworkSpecifier establishes a local connection to the wifi access point as mentioned above. The default network will still be cellular in this case (we don't disrupt other app's internet connectivity). The app making the request should use the multi-network API's to route their traffic over the established connection. The |Network| object provided in the onAvailable() callback for the request is the handle that app needs to use for opening sockets over that local network (Look at https://developer.android.com/reference/android/net/Network.html#bindSocket(java.net.DatagramSocket) and other such API's available in the |Network| object surface.

Hope this helps.

Frantz answered 9/8, 2019 at 15:3 Comment(3)
With this code, I get the authorization window with the right AP ... but when I press connect it does not work. Obtaining ip address then reconnection to the normal network. An idea ? Thank youHolusbolus
Hi, @Anand Khinvasara, would you be able to post the full code for this? I'm having a lot of problems with no internet when connecting programmatically. Thanks!Aeromechanics
Ok So I have tried the WifiNetworkSpecifier ... I have this issue that when a network is selected the device wont connect to the network ,I have tried the documentation code too , is there a solution for local networks without internet connectionWavy
A
3

As stated here, Android 10 made it intentionally so that the WifiNetworkSpecifier prevents actual internet connectivity. It is meant for peer to peer connections.

The WifiNetworkSuggestion API, however, provides internet connectivity and behaves similarly to the WifiNetworkSpecifier API. As long as the device is not currently connected to any Wifi network, the WifiNetworkSuggestion API will automatically connect to the specified network. The first time a device uses it, a notification will appear asking if the app can suggest networks. The user must accept this notification for the WifiNetworkSuggestion API to work.

I found that Android's provided code in the WifiNetworkSuggestion documentation had a few compile errors. Here is the code that I found to work:

final WifiNetworkSuggestion suggestion1 = new WifiNetworkSuggestion.Builder()
.setSsid("SSID here")
.setWpa2Passphrase("password here")
.setIsAppInteractionRequired(true) // Optional (Needs location permission)
.build();

// Optional extra suggesstion, you can delete this or add more
final WifiNetworkSuggestion suggestion2 = new WifiNetworkSuggestion.Builder()
.setSsid("SSID here 2")
.setWpa2Passphrase("password here 2")
.setIsAppInteractionRequired(true) // Optional (Needs location permission)
.build();

final List<WifiNetworkSuggestion> suggestionsList = new ArrayList<WifiNetworkSuggestion>();
suggestionsList.add(suggestion1);
suggestionsList.add(suggestion2); // Optional extra suggestion
final WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
final int status = wifiManager.addNetworkSuggestions(suggestionsList);

if (status != WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS) {
     // Error handling
}

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;
        }
        // Post connection
    }
};
getApplicationContext().registerReceiver(broadcastReceiver, intentFilter);
Aeromechanics answered 18/4, 2020 at 0:41 Comment(2)
Does that mean I am forced to use WiFiNetworkSuggestion to connect to WiFi and actually use the internet? I am able to connect to WiFi using WiFiNetworkSpecifier but no actual internet access.Embow
Yes, you will need to use WifiNetworkSuggestion. WifiNetworkSpecifier is meant for if you want to connect to a network for some purpose other than internet access.Aeromechanics
D
1

You should use Wi-Fi network suggestion API in Q

final WifiNetworkSuggestion suggestion1 =
  new WifiNetworkSuggestion.Builder()
  .setSsid("test111111")
  .setIsAppInteractionRequired() // Optional (Needs location permission)
  .build()

final WifiNetworkSuggestion suggestion2 =
  new WifiNetworkSuggestion.Builder()
  .setSsid("test222222")
  .setWpa2Passphrase("test123456")
  .setIsAppInteractionRequired() // Optional (Needs location permission)
  .build()

final WifiNetworkSuggestion suggestion3 =
  new WifiNetworkSuggestion.Builder()
  .setSsid("test333333")
  .setWpa3Passphrase("test6789")
  .setIsAppInteractionRequired() // Optional (Needs location permission)
  .build()

final List<WifiNetworkSuggestion> suggestionsList =
  new ArrayList<WifiNetworkSuggestion> {{
    add(suggestion1);
    add(suggestion2);
    add(suggestion3);
  }};

final WifiManager wifiManager =
  (WifiManager) context.getSystemService(Context.WIFI_SERVICE);

final int status = wifiManager.addNetworkSuggestions(suggestionsList);
if (status != WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS) {
// do error handling here…
}

// Optional (Wait for post connection broadcast to one of your suggestions)
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;
    }
    // do post connect processing here..
  }
};
context.registerReceiver(broadcastReceiver, intentFilter);
Denis answered 16/7, 2019 at 2:26 Comment(3)
Does it work on the connections established using WifiNetworkSpecifier ?Epigrammatist
Thanks for your reply. But, I see network suggestions is not providing internet capability. Do you have any alternate work around for this ?Epigrammatist
For me status != WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS is always true. Thus I cant make it workFrantz
M
1

Inside the onAvailable(Network) callback on ConnectivityManager.NetworkCallback() (after you setup your WifiNetworkSpecifier.Builder() and NetworkRequest.Builder()), call ConnectivityManager.bindProcessNetwork to direct your traffic on the connected network, as documented in the Network API: https://developer.android.com/reference/android/net/Network

Identifies a Network. This is supplied to applications via ConnectivityManager.NetworkCallback in response to the active ConnectivityManager#requestNetwork or passive ConnectivityManager#registerNetworkCallback calls. It is used to direct traffic to the given Network, either on a Socket basis through a targeted SocketFactory or process-wide via ConnectivityManager#bindProcessToNetwork.

....
private inner class MyCallback : ConnectivityManager.NetworkCallback() {
    override fun onAvailable(network: Network) {
        // Call this method once this callback is triggered after
        // your call to mConnectivityManager.requestNetwork()
        mConnectivityManager.bindProcessToNetwork(network)
    }
    ....
}
....
Menke answered 10/8, 2020 at 1:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.