简体   繁体   中英

Programatically connected android device to Hotspot (no internet) switches back to wifi with internet

Sorry for my english. I am writing a code to connect to another android device hotspot. It gets connected. But, the hotspot will have no internet in my case. Now the connected device, switches back to another wifi network with internet. Is there any better way to connect to hotspot other than my way? My code is below:

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.layout);
    AppCompatButton btnConnect=findViewById(R.id.btnConnect);
    btnConnect.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            registerReceiver(mWifiBroadcastReceiver , new IntentFilter("android.net.wifi.STATE_CHANGE"));
            EditText eSSID=findViewById(R.id.ssid);
            EditText ePassword=findViewById(R.id.password);
            String ssid = eSSID.getText().toString();
            String key = ePassword.getText().toString();
            WifiConfiguration wifiConfig = new WifiConfiguration();
            wifiConfig.SSID = String.format("\"%s\"" , ssid);
            wifiConfig.preSharedKey = String.format("\"%s\"" , key);
            connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
            wifiManager = (WifiManager) getApplicationContext().getSystemService(WIFI_SERVICE);

            int netId = wifiManager.addNetwork(wifiConfig);
            wifiManager.disconnect();
            wifiManager.enableNetwork(netId , true);
            wifiManager.reconnect();
        }
    });


}

And the receiver is:

  private BroadcastReceiver mWifiBroadcastReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(final Context context , Intent intent) {
        switch (intent.getAction()) {
            case WifiManager.NETWORK_STATE_CHANGED_ACTION:
                NetworkInfo info = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
                boolean isConnected = info.isConnected();
                boolean isConnecting = info.isConnectedOrConnecting();

                //TODO: probably better to use the EXTRA_ info here
                String ssid = wifiManager.getConnectionInfo() != null ?
                        wifiManager.getConnectionInfo().getSSID() : null;


                ssid = normalizeAndroidWifiSsid(ssid);

                String stateName = "";
                switch (info.getState()) {
                    case CONNECTED:
                        stateName = "connected";
                        break;

                    case CONNECTING:
                        stateName = "connecting";
                        break;

                    case DISCONNECTED:
                        stateName = "disconnected";
                        break;

                    case DISCONNECTING:
                        stateName = "disconnecting";
                        break;

                    case SUSPENDED:
                        stateName = "suspended";
                        break;

                    case UNKNOWN:
                        stateName = "unknown";
                        break;
                }


                if (Build.VERSION.SDK_INT >= 21) {
                    if (isConnected) {
                        NetworkRequest.Builder builder = new NetworkRequest.Builder();
                        builder.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
                        builder.addCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL);
                        final String connectedSsid = ssid;
                        connectivityManager.registerNetworkCallback(builder.build() , new ConnectivityManager.NetworkCallback() {
                            @Override
                            public void onAvailable(Network network) {
                                super.onAvailable(network);
                                NetworkInfo networkInfo = connectivityManager.getNetworkInfo(network);

                                //This is always the SSID if it's wifi, even though this is *not* documented
                                String networkSsid = networkInfo.getExtraInfo();

                                if (networkSsid.equals(connectedSsid)) {
                                    runOnUiThread(new Runnable() {
                                        @Override
                                        public void run() {
                                            Toast.makeText(context,"Connected Successfully",Toast
                                                    .LENGTH_LONG).show();
                                        }
                                    });

                                    /*
                                     * We can now use network.openURLConnection and network.getSocketFactory()
                                     * to communicate using the wifi network that has no Internet
                                     */
                                    connectivityManager.unregisterNetworkCallback(this);
                                }
                            }
                        });

                    }
                }

                break;
        }
    }
};

Due to the asynchronous nature of WiFi handling in Android, I would focus on those 4 lines looking for a solution

        int netId = wifiManager.addNetwork(wifiConfig);
        wifiManager.disconnect();
        wifiManager.enableNetwork(netId , true);
        wifiManager.reconnect();

disconnect and reconnect calls are wrong here, they are only confusing internal wifi state machine. Invoking enableNetwork with parameter boolean attemptConnect set to true is enough to programmatically select wifi.

So just use:

    int netId = manager.addNetwork(wifiConfig);
    manager.enableNetwork(netId, true);

To be honest I've tried scan through the internal sources, but internal state machine is quite complex. If you are interested, you may look here for state machine and here to see how wifi config database is handled.

Also, to avoid having multiple configuration entries, before calling addNetwork, check already created configs to look for SSID you want to connect and only call enableNetwork .

    List<WifiConfiguration> networks = manager.getConfiguredNetworks();
    for(WifiConfiguration c : networks) {
        if (isThisWifiAppSpecific(c.SSID)) {
            manager.enableNetwork(c.networkId, true);
            return;
        }
    }

I'm using it in production and works fine. Never tested in API level lower than 24.

Since you are able to connect to the hotspot, temporarily, the end problem is the switching on the phone to a "better" alternative network. While the answer https://stackoverflow.com/a/53249295/949224 will help tidy up your code when connecting to your specific hotspot, you should also look into disabling "smart network switching" and Wi-Fi auto-reconnect. The former is also referred to as poor network watchdog, and is monitoring the connectivity on your current network and will switch to LTE data if it is likely to improve, and the latter will change to another known Wi-Fi AP in the case that can offer better connectivity.

Smart network switch can be disabled in the advanced WiFi settings activity. It's not exposed as a Java API in Android and 3rd party apps are not designed to be given a relevant permission, plus different OEM suppliers have different custom implementations, therefore the best approach is to launch the right settings panel from your application. Here is an example:

public static void requestSmartNetworkSettings(Context ctx){
    final Intent i = new Intent(Settings.ACTION_WIFI_IP_SETTINGS);
    final PackageManager mgr = ctx.getPackageManager();
    if( mgr.resolveActivity(i, PackageManager.MATCH_DEFAULT_ONLY) != null)
    {
        ctx.startActivity(i);
    }
}

On some devices, Smart network switching can be disabled without the external settings dialog via ConnectivityManager.setProcessDefaultNetwork(Network net) but I have seen this is unreliable. I normally set this anyway and then also use the settings.

Wi-Fi auto-reconnect can be disabled manually in the WiFi settings activity by selecting known APs and unchecking the check-box.

A quick way to do this programmatically is to iterate through your known AP IDs in WifiManager and call WifiManager.disableNetwork(int netID) . You can always enable these again programmatically after you want to end the Hotspot connection. That way the user should get their phone back as normal afterwards.

By the way, there are ways to check whether poor network avoidance (auto switching) is enabled via Java reflection, but it is cumbersome and so I don't think its worth it to post examples here.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM