Force Android to Use Wifi network with no internet
Asked Answered
R

6

42

I am building an android app that needs to communicate over a WiFi network that will not have any internet access. The problem is that even when the WiFi is connected android chooses to use cellular/mobile data when no connection internet is present on the wifi network.

I have read many posts on the issue many of which involve rooting the device but that is not possible with a production app (rooting devices is not an option). other solution (like my code bellow) suggest using bindProcessToNetwork() which works perfectly on my Sony Z2 but not on other devices I have tested on (all running 6.0.1)

private void bindToNetwork() {
    final ConnectivityManager connectivityManager = (ConnectivityManager) mActivity.getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkRequest.Builder builder;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        builder = new NetworkRequest.Builder();
        //set the transport type do WIFI
        builder.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
        connectivityManager.requestNetwork(builder.build(), new ConnectivityManager.NetworkCallback() {
            @Override
            public void onAvailable(Network network) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {


                    connectivityManager.bindProcessToNetwork(null);
                    if (barCodeData.getSsid().contains("ap-name")) {
                        connectivityManager.bindProcessToNetwork(network);
                    }

                } else {
                    //This method was deprecated in API level 23
                    ConnectivityManager.setProcessDefaultNetwork(null);
                    if (barCodeData.getSsid().contains("ap-name")) {

                        ConnectivityManager.setProcessDefaultNetwork(network);
                    }
                }

                connectivityManager.unregisterNetworkCallback(this);
            }
        });
    }
}
Republic answered 27/2, 2017 at 17:31 Comment(3)
You can try the solution I gave in this post: #42330275Azedarach
What is barCodeData is this context?Potentiate
hi @Lonergan6275, turnout could u fix this? I'm facing the same issue, cryFoxed
W
4

You can solve this by setting captive_portal_detection_enabled to 0 (false).

What's actually happening is that by default, everytime you connect to a wifi, the FW will test against a server (typically google) to see if it's a captive wifi (needs login). So if your wifi is not connected to google, this check will fail. After that, the device knows that wifi has no internet connection and simply will not autoconnect to it.

Setting this setting to 0 will avoid this check.

Programatically: Settings.Global.putInt(getContentResolver(), Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED, 0);

Edit: You may need to use the string "captive_portal_detection_enabled" directly, instead of the constant that's not visible depending on Android version.

Whiggism answered 3/3, 2017 at 4:7 Comment(4)
cannot resolve Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLEDRepublic
@Republic try using the string "captive_portal_detection_enabled" directly, instead of the constant that's not visible depending on version of Android you are using.Whiggism
this requires android.permission.WRITE_SECURE_SETTINGS which is not available. i specified in the question that roting is also not an option. https://mcmap.net/q/392186/-write_secure_settings-permission-error-even-when-added-in-manifestRepublic
This AFAIK will only determine whether or not to mark the network as VALIDATED to be used as the default network. Besides the other issues mentioned in the comments, this could also potentially cause the device to lose all connectivity if a non-internal capable network is set as the default.Potentiate
T
2

you'd need to disable mobile data in the Settings (not certain, if this can be done programmatically, which might be a possible option) - or take out the USIM;

else the common behavior is, that it will always fall back to the best available connection (while a connection with internet gateway might be preferred, because it is used by most application).

also see this answer.

Thug answered 9/3, 2017 at 11:13 Comment(0)
G
1

You're in the right path, the solution is indeed with ConnectivityManager.bindProcessToNetwork(network). This information was posted on the Android Developers Blog in this article: Connecting your App to a Wi-Fi Device.

Looking into your code this barCodeData.getSsid() doesn't look that is getting the SSID of the currently connected wifi network. If that's the case your code will never successfully bind to the network.

Try replace your if statement

if (barCodeData.getSsid().contains("ap-name"))

With

if (getNetworkSsid(context).equals("ap-name"))

Helper method in kotlin to retrieve the SSID of the connected wifi network

private fun getNetworkSsid(context: Context?): String {
    // WiFiManager must use application context (not activity context) otherwise a memory leak can occur
    val mWifiManager = context?.applicationContext?.getSystemService(Context.WIFI_SERVICE) as WifiManager
    val wifiInfo: WifiInfo? = mWifiManager.connectionInfo
    if (wifiInfo?.supplicantState == SupplicantState.COMPLETED) {
        return wifiInfo.ssid.removeSurrounding("\"")
    }
    return ""
}

If still doesn't work please follow my complete solution where I used the same method but with some extra checks. I tested it in the Android versions 5.1.1, 6.0, 6.0.1, 7.1.1 and 8.1.0.

Genuine answered 22/7, 2018 at 23:24 Comment(0)
S
1

Solution on Kotlin

class ConnectWithoutInternetTest constructor(
private val mContext: Context,
private val connectivityManager: ConnectivityManager,
private val wifiManager: WifiManager
) {

private val mWifiBroadcastReceiver = object : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        when (intent.action) {
            WifiManager.NETWORK_STATE_CHANGED_ACTION -> {
                val info = intent.getParcelableExtra<NetworkInfo>(WifiManager.EXTRA_NETWORK_INFO)
                val isConnected = info.isConnected

                val ssid: String? = normalizeAndroidWifiSsid(wifiManager.connectionInfo?.ssid)

                if (isConnected) {
                    val builder = NetworkRequest.Builder()
                    builder.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
                    connectivityManager.registerNetworkCallback(
                        builder.build(),
                        object : ConnectivityManager.NetworkCallback() {
                            override fun onAvailable(network: Network) {
                                super.onAvailable(network)
                                val networkInfo = connectivityManager.getNetworkInfo(network)
                                val networkSsid = networkInfo.extraInfo
                                if (networkSsid == ssid) {
                                    connectivityManager.unregisterNetworkCallback(this)
                                }
                            }
                        })
                }
            }
        }
    }
}

private fun init() {
    val intentFilter = IntentFilter()
    intentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION)
    mContext.registerReceiver(mWifiBroadcastReceiver, intentFilter)
}

private fun destroy() {
    mContext.unregisterReceiver(mWifiBroadcastReceiver)
}

private fun normalizeAndroidWifiSsid(ssid: String?): String? {
    return ssid?.replace("\"", "") ?: ssid
}

fun connectToWifi(ssidParam: String, password: String?) {
    init()
    val ssid = "\"$ssidParam\""
    val config = wifiManager.configuredNetworks.find { it.SSID == ssid }
    val netId = if (config != null) {
        config.networkId
    } else {
        val wifiConfig = WifiConfiguration()
        wifiConfig.SSID = ssid
        password?.let { wifiConfig.preSharedKey = "\"$password\"" }
        wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE)
        wifiManager.addNetwork(wifiConfig)
    }

    wifiManager.disconnect()
    val successful = wifiManager.enableNetwork(netId, true)
}
Smog answered 11/12, 2018 at 16:49 Comment(1)
In case someone is trying this for an old API WifiManager.NETWORK_STATE_CHANGED_ACTION in the broadcast receiver should be WifiManager.WIFI_STATE_CHANGED_ACTIONCuevas
P
1

You're on the right path, you only need to tweak what you have a bit. One of the main issues I see is that you unregister the network callback on onAvailable() which is not a good idea as networks tend to switch between available/unavailable from time-to-time which would cause issues on your device.

The other thing to consider is requesting a network that's Wi-Fi and clearing the other network capabilities as they may cause you issues depending on your network's setup.

Here is another version of how to do that which is hopefully a bit simpler.

final NetworkRequest requestForWifi =
  new NetworkRequest.Builder()
  .clearCapabilities() // We only care about Wi-Fi
  .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
  .build();

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

final NetworkCallback networkCallbackWifi = new NetworkCallback() {
  @Override
  void onAvailable(Network network) {
      // Once this callback triggers, a Wi-Fi network is available
      WifiInfo wifiInfo = connectivity.getNetworkCapabilities(network).TransportInfo;
      string ssid = wifiInfo.SSID.Trim(new char[] {'"', '\"' });
      if (!ssid.contains("ap-name")) {
          return;
      }
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
          connectivityManager.bindProcessToNetwork(network);
      } else {
          ConnectivityManager.setProcessDefaultNetwork(network);
      }   
  }
};

// Last thing is to actually request a network.
connectivityManager.requestNetwork(requestForWifi, networkCallbackWifi);
Potentiate answered 3/2, 2022 at 19:20 Comment(0)
H
0

You can check if wifi is connected then proceed else show a dialog to user asking him to connect to a wifi network

Since the method NetworkInfo.isConnected() is now deprecated in API-23, here is a method which detects if the Wi-Fi adapter is on and also connected to an access point using WifiManager instead:

private boolean checkWifiOnAndConnected() {
    WifiManager wifiMgr = (WifiManager) getSystemService(Context.WIFI_SERVICE);

    if (wifiMgr.isWifiEnabled()) { // Wi-Fi adapter is ON

        WifiInfo wifiInfo = wifiMgr.getConnectionInfo();

        if( wifiInfo.getNetworkId() == -1 ){
            return false; // Not connected to an access point
        }
        return true; // Connected to an access point
    }
    else {
        return false; // Wi-Fi adapter is OFF
    }
}
Hugo answered 20/6, 2017 at 7:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.