Android 4.xで、静的IPへの変更をプログラムで実行する方法

Android端末でWi-Fi接続を行う際、アプリケーション上で静的IPに変更できるようにする実装方法を試してみましたので、参考にしていただければ幸いです。

Android のスマートフォン/タブレットを、外出先でWi-Fiスポットに接続する際は、DHCPによって自動的にIPアドレスが割り当てられるようになっています
一方、社内の無線LANなど、セキュリティを重視したネットワークでは、端末のIPアドレスを静的に設定するよう求められる場合もあります。この場合、アンドロイド標準のWi-Fi設定機能の画面で設定すればよいのですが、これをアプリケーション上で行いたい場合には、どのような実装になるのでしょうか?  Android 2.xと4.xで、設定を行ってみました。

Android 2.xの場合

筆者は以前、「特定のアクセスポイントに、静的IPアドレスで、自動的に帰属する」Androidアプリを作成した経験があります。この時のWi-Fiの設定は、以下のようなコードで行っていました。

▼ソースコード 1

// 静的IPを有効にする
Settings.System.putString(getContentResolver(),
android.provider.Settings.System.WIFI_USE_STATIC_IP, "1");
// IP Address設定
Settings.System.putString(getContentResolver(),
android.provider.Settings.System.WIFI_STATIC_IP, "<IP Address>");
// Gateway設定
Settings.System.putString(getContentResolver(),
android.provider.Settings.System.WIFI_STATIC_GATEWAY,
"<Gateway>");
// Netmask設定
Settings.System.putString(getContentResolver(),
android.provider.Settings.System.WIFI_STATIC_NETMASK,
"<Netmask>");
// DNS1設定
Settings.System.putString(getContentResolver(),
android.provider.Settings.System.WIFI_STATIC_DNS1, "<DNS1>");
// DNS2設定
Settings.System.putString(getContentResolver(),
android.provider.Settings.System.WIFI_STATIC_DNS2, "<DNS2>");

また、Wi-Fi設定の参照は、以下のようなコードで行うことができました。

▼ソースコード 2

// 静的IPを有効/無効を調べる
int nEnabled = Settings.System.getInt(getContentResolver(),
Settings.System.WIFI_USE_STATIC_IP, 0);
// IP Address取得
String strStaticIP = Settings.System.getString(getContentResolver(),
Settings.System.WIFI_STATIC_IP);
// Gateway取得
String strGateway = Settings.System.getString(getContentResolver(),
Settings.System.WIFI_STATIC_GATEWAY);
// Netmask取得
String strNetmask = Settings.System.getString(getContentResolver(),
Settings.System.WIFI_STATIC_NETMASK);
// DNS1取得
String strDns1 = Settings.System.getString(getContentResolver(),
Settings.System.WIFI_STATIC_DNS1);
// DNS2取得
String strDns2 = Settings.System.getString(getContentResolver(),
Settings.System.WIFI_STATIC_DNS2);

2.x と4.xでは、Wi-Fi接続設定が異なることがわかりました

最近になって、このアプリをAndroid 4.x の端末で動かそうとしたところ、どういうわけか静的IPへの変更ができませんでした。ビルドの実行もできますが、筆者が試したところ、実際にはプログラムによる設定の通りには動作していないことが確認できました。
さらにリサーチを進めたところ、Android 2.xと4.xでは、Wi-Fiの設定方法が異なることがわかりました。Android 2.xでは、システム全体でひとつの設定しか保持していないようなのですが、4.xではSSIDごとに設定を保持しており、その設定に従って帰属します。

Android 4.xの場合

以下がAndroid 4.xにおけるWi-Fi設定のソースコードになります。例外処理は省略しています。

▼ソースコード 3

// DHCP / STATIC / NONE 設定
private void setIpAssignment (String strIpAssign, WifiConfiguration wifiConf) {
setEnumField(wifiConf, strIpAssign, "ipAssignment");
}

// IP Address設定
private void setIpAddress (InetAddress inetAddr, int nPrefixLength, WifiConfiguration wifiConf) {
    Object objLinkProperties = getField(wifiConf, "linkProperties");
    if (objLinkProperties == null) return;
    Class clsLinkAddress = Class.forName("android.net.LinkAddress");
    Constructor cnstLinkAddress
     = clsLinkAddress.getConstructor(new Class[]{InetAddress.class, int.class});
    Object objLinkAddress = cnstLinkAddress.newInstance(inetAddr, nPrefixLength);
    ArrayList artkstLinkAddresses
 = (ArrayList)getDeclaredField(objLinkProperties, "mLinkAddresses");
    artkstLinkAddresses.clear();
    artkstLinkAddresses.add(objLinkAddress);
}

// Gateway設定
private void setGateway (InetAddress inetAddr, WifiConfiguration wifiConf) {
    	Object objLinkProperties = getField(wifiConf, "linkProperties");
    if (objLinkProperties == null) return;
    Class clsRouteInfo = Class.forName("android.net.RouteInfo");
    Constructor cnstRouteInfo = clsRouteInfo.getConstructor(new Class[]{InetAddress.class});
    Object objRouteInfo = cnstRouteInfo.newInstance(inetAddr);
    ArrayList arylstRoutes = (ArrayList)getDeclaredField(objLinkProperties, "mRoutes");
    arylstRoutes.clear();
    arylstRoutes.add(objRouteInfo);
}

private void setDNS (InetAddress inetAddr, WifiConfiguration wifiConf) {
    Object objLinkProperties = getField(wifiConf, "linkProperties");
    if(objLinkProperties == null)return;

    ArrayList<InetAddress> arylstDnses
 = (ArrayList<InetAddress>)getDeclaredField(objLinkProperties, "mDnses");
    arylstDnses.clear();
    arylstDnses.add(inetAddr);
}

private Object getField (Object object, String strName) {
    Field field = object.getClass().getField(strName);
    Object objField = field.get(object);
    return objField;
}

private Object getDeclaredField (Object object, String strName) {
    Field field = object.getClass().getDeclaredField(strName);
    field.setAccessible(true);
    Object objField = field.get(object);
    return objField;
}

private void setEnumField (Object object, String strValue, String strName) {
    Field field = object.getClass().getField(strName);
    field.set(object, Enum.valueOf((Class<Enum>) field.getType(), strValue));
}

// Wi-Fi設定
private void setWifiConfig (WifiConfiguration wifiConf) {
	// IP Assignment設定
	setIpAssignment (“<DHCP / STATIC / NONE>”, wifiConf);
	// IP Address設定
	setIpAddress (InetAddress.getByName(“<IP Address>”), 24, wifiConf);
	// Gateway設定
	setGateway (InetAddress.getByName(“<Gateway>”), wifiConf);
	// DNS設定
	setDNS (InetAddress.getByName(“<DNS>”), wifiConf);
}

そして、Android 4.xにおけるWi-Fi設定参照のコードが、以下になります。

▼ソースコード 4

// IP Assignment設定取得
private String getIpAssignment (WifiConfiguration wifiConf) {
    	return getEnumField(wifiConf, "ipAssignment");
}

// IP Address取得
private String getIpAddress (WifiManager wifiMng) {
    	int nIPAddress = wifiMng.getConnectionInfo().getIpAddress();
    	return ((nIPAddress >> 0) & 0xFF) + "." + ((nIPAddress >> 8) & 0xFF) + "."
+ ((nIPAddress >> 16) & 0xFF) + "." + ((nIPAddress >> 24) & 0xFF);
}

// Gateway取得
private String getGateway (WifiConfiguration wifiConf) {
    	Object objLinkProperties = getField(wifiConf, "linkProperties");
    	if (objLinkProperties == null) return "";
    ArrayList arylstRoutes = (ArrayList)getDeclaredField(objLinkProperties, "mRoutes");
    Object objRouteInfo = arylstRoutes.get(0);
    InetAddress inetAddressGateway = (InetAddress)getDeclaredField(objRouteInfo, "mGateway");
    if (inetAddressGateway == null) return "";
    byte aryGateway[] =  inetAddressGateway.getAddress();
    String strGateway = "";
    for (int i = 0; i < 4; i++) {
 	    if (!strGateway.isEmpty()) strGateway += ".";
        if (aryGateway[i] >= 0) {
        	strGateway += aryGateway[i];
        }
        else {
            strGateway += aryGateway[i] + 256;
        }
    }
    return strGateway;
}

// DNS取得
private String getDNS (WifiConfiguration wifiConf) {
    	Object objLinkProperties = getField(wifiConf, "linkProperties");
    	if (objLinkProperties == null) return "";
    	ArrayList<InetAddress> arylstDnses
= (ArrayList<InetAddress>)getDeclaredField(objLinkProperties, "mDnses");
    	byte aryDNS [] = arylstDnses.get(0).getAddress();
    	String strDNS = "";
    	for (int i = 0; i < 4; i++) {
    	    if (!strDNS.isEmpty()) strDNS += ".";
    	    if (aryDNS[i] >= 0) {
strDNS += aryDNS[i];
    	    }
    	    else {
    	        strDNS += aryDNS[i] + 256;
    	    }
    	}
    	return strDNS;
}

private String getEnumField (Object object, String strName) {
    	Field field = object.getClass().getField(strName);
    	return field.get(object).toString();
}

// Wi-Fi設定取得
private void getWifiConfig (WifiConfiguration wifiConf) {
	// IP Assignment設定取得
    String strIpAssignment = getIpAssignment(wifiConfiguration);
	// IP Address取得
String strIpAddress = getIpAddress();
// Gateway取得
String strGateway = getGateway(wifiConfiguration);
// DNS取得
String strDNS = getDNS(wifiConfiguration);
}

なお、適当なAPIが見当たらなかったため、Object#getClass()などを使用しています。
もっと良い方法がある可能性もありますので、引き続き調査と検証を進め、わかったことをこのコーナーで公開していきます。