KennyPowers KennyPowers - 2 days ago 7
Java Question

Change WiFi's configuration on Android 5 (L) programmatically

There're some problems with the code worked on android 4.0.2..4.4.4, but doesn't really work on Android 5 and I don't know why. Basically, the code below allows to set new WiFi's IP assignment type: STATIC or DHCP. The code I used is fully covered in this answer: http://stackoverflow.com/a/10309323/876360

I'll try to put here the most important pats of the code with the output info.

...
WifiConfigurator.setIpAssignment("STATIC", wifiConf);
...


where wifiConf is

public static WifiConfiguration getCurrentWiFiConfiguration(Context context) {
WifiConfiguration wifiConf = null;
ConnectivityManager connManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
if (networkInfo.isConnected()) {
final WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
final WifiInfo connectionInfo = wifiManager.getConnectionInfo();
if (connectionInfo != null && !TextUtils.isEmpty(connectionInfo.getSSID())) {
List<WifiConfiguration> configuredNetworks = wifiManager.getConfiguredNetworks();
if(configuredNetworks != null){
for (WifiConfiguration conf : configuredNetworks) {
if (conf.networkId == connectionInfo.getNetworkId()) {
wifiConf = conf;
break;
}
}
}
}
}
return wifiConf;
}


So
WifiConfigurator.setIpAssigment()
calls the next code:

public static void setIpAssignment(String assign, WifiConfiguration wifiConf)
throws SecurityException, IllegalArgumentException, NoSuchFieldException, IllegalAccessException {

setEnumField(wifiConf, assign, "ipAssignment");
}

public static void setEnumField(Object obj, String value, String name)
throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
Log.d("myApp", obj.getClass().toString());
Field f = obj.getClass().getField(name);
f.set(obj, Enum.valueOf((Class<Enum>) f.getType(), value));
}


But for some reason the field "ipAssignment" cannot be found:

11-30 12:40:54.343 5941-5941/com.myApp D/myApp﹕ class android.net.wifi.WifiConfiguration
11-30 12:40:54.344 5941-5941/com.myApp D/myApp﹕ Can't update network configuration. java.lang.NoSuchFieldException: ipAssignment
at java.lang.Class.getField(Class.java:1048)
at com.myApp.WifiConfigurator.setEnumField(WifiConfigurator.java:141)
at com.myApp.WifiConfigurator.setIpAssignment(WifiConfigurator.java:25)
at com.myApp.WifiConfigurator.updateWifiNetwork(WifiConfigurator.java:220)
at com.myApp.ui.MainScreen.onAsyncTaskCompleted(MainScreen.java:251)
at com.myApp.myAPI$UpdateIPTask.onPostExecute(myAPI.java:257)
at com.myApp.myAPI$UpdateIPTask.onPostExecute(myAPI.java:194)
at android.os.AsyncTask.finish(AsyncTask.java:632)
at android.os.AsyncTask.access$600(AsyncTask.java:177)
at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:645)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5221)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)


At first, I thought google changed the field's name. Then I checked the Android L preview source code, the field is there. I'm confused why this is happening.

UPDATE

Thanks to Matiash for some input, I am able to update ipAssignment type, but I'm not able to update DNSes. To update DNSes the ipAssigment should be static. According to this Google stopped using LinkProperties for static configuration, they use StaticIpConfiguration class now.

So what I do:

public static void setDNS(InetAddress dns1, InetAddress dns2, WifiConfiguration wifiConf)
throws SecurityException, IllegalArgumentException, NoSuchMethodException, InvocationTargetException,
NoSuchFieldException, IllegalAccessException {

Object linkProperties = null;
ArrayList<InetAddress> mDnses;

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
staticIpConf = wifiConf.getClass().getMethod("getStaticIpConfiguration").invoke(wifiConf);
mDnses = (ArrayList<InetAddress>) getDeclaredField(staticIpConf, "dnsServers");
}
else{
linkProperties = getField(wifiConf, "linkProperties");
mDnses = (ArrayList<InetAddress>) getDeclaredField(linkProperties, "mDnses");
}

mDnses.clear();
mDnses.add(dns1);
mDnses.add(dns2);
}
public static Object getDeclaredField(Object obj, String name)
throws SecurityException, NoSuchFieldException,
IllegalArgumentException, IllegalAccessException {

Field f = obj.getClass().getDeclaredField(name);
f.setAccessible(true);
Object out = f.get(obj);
return out;
}


The next I see in error log:

12-22 09:00:49.854 25815-25815/com.myapp D/myapp﹕ Can't update network configuration. java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String java.lang.Object.toString()' on a null object reference
at com.myapp.WifiConfigurator.getDeclaredField(WifiConfigurator.java:245)
at com.myapp.WifiConfigurator.setDNS(WifiConfigurator.java:78)
at com.myapp.WifiConfigurator.updateWifiNetwork(WifiConfigurator.java:356)


My guess is
staticIpConfiguration
doesn't exists when I call it. I have to initialize it somehow.
Any ideas how to update DNSes?

Answer

Although the ipAssignment field was still present in the L preview (at least the version in grepcode), it's not in the released version, as you can see in the "master" branch of the source code or in the Github mirror.

Both the Enum definition and the field are now inside an inner object, of type IpConfiguration (also new). These are the dangers of using reflection to access undocumented waters... :)

However, it's simple to tweak the code to access it by reflection and set it there:

public static void setIpAssignment(String assign, WifiConfiguration wifiConf)
        throws SecurityException, IllegalArgumentException, NoSuchFieldException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        Object ipConfiguration = wifiConf.getClass().getMethod("getIpConfiguration").invoke(wifiConf);
        setEnumField(ipConfiguration, assign, "ipAssignment");
    } else {
        setEnumField(wifiConf, assign, "ipAssignment");
    }
}

This code "works" (in the sense that it does not throw an exception) but I haven't tested it any further.


See How to configure a static IP address, netmask, gateway, DNS programmatically on Android 5.x (Lollipop) for Wi-Fi connection for a more detailed solution.

Comments