2012年7月27日星期五

Android 一鍵建立wifi連線

最近有位同事覺得Android的wifi設定界面好難用,確實也是有點難用
於是想寫一個小程式能夠一鍵建立連線
這裏有教學 (How to programatically create and read WEP/EAP WiFi configurations in Android?)
最大的難點是Android的EAP使用的是non-public API,只能靠逆向工程的手法
幸好有教學有代碼:)

要看的主要是第四點:
有一些常量在這裏:
private static final String INT_PRIVATE_KEY = "private_key";
private static final String INT_PHASE2 = "phase2";
private static final String INT_PASSWORD = "password";
private static final String INT_IDENTITY = "identity";
private static final String INT_EAP = "eap";
private static final String INT_CLIENT_CERT = "client_cert";
private static final String INT_CA_CERT = "ca_cert";
private static final String INT_ANONYMOUS_IDENTITY = "anonymous_identity";
final String INT_ENTERPRISEFIELD_NAME = "android.net.wifi.WifiConfiguration$EnterpriseField";
然後是主要的saveEapConfig方法:
void saveEapConfig(String passString, String userName)
{

/********************************Configuration Strings****************************************************/
final String ENTERPRISE_EAP = "TLS";
final String ENTERPRISE_CLIENT_CERT = "keystore://USRCERT_CertificateName";
final String ENTERPRISE_PRIV_KEY = "keystore://USRPKEY_CertificateName";
//CertificateName = Name given to the certificate while installing it

/*Optional Params- My wireless Doesn't use these*/
final String ENTERPRISE_PHASE2 = "";
final String ENTERPRISE_ANON_IDENT = "ABC";
final String ENTERPRISE_CA_CERT = "";
/********************************Configuration Strings****************************************************/

/*Create a WifiConfig*/
WifiConfiguration selectedConfig = new WifiConfiguration();

/*AP Name*/
selectedConfig.SSID = "\"SSID_Name\"";

/*Priority*/
selectedConfig.priority = 40;

/*Enable Hidden SSID*/
selectedConfig.hiddenSSID = true;

/*Key Mgmnt*/
selectedConfig.allowedKeyManagement.clear();
selectedConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
selectedConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);

/*Group Ciphers*/
selectedConfig.allowedGroupCiphers.clear();
selectedConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
selectedConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
selectedConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP104);
selectedConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40);

/*Pairwise ciphers*/
selectedConfig.allowedPairwiseCiphers.clear();
selectedConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
selectedConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP);

/*Protocols*/
selectedConfig.allowedProtocols.clear();
selectedConfig.allowedProtocols.set(WifiConfiguration.Protocol.RSN);
selectedConfig.allowedProtocols.set(WifiConfiguration.Protocol.WPA);

// Enterprise Settings
// Reflection magic here too, need access to non-public APIs
try {
// Let the magic start
Class[] wcClasses = WifiConfiguration.class.getClasses();
// null for overzealous java compiler
Class wcEnterpriseField = null;

for (Class wcClass : wcClasses)
if (wcClass.getName().equals(INT_ENTERPRISEFIELD_NAME))
{
wcEnterpriseField = wcClass;
break;
}
boolean noEnterpriseFieldType = false;
if(wcEnterpriseField == null)
noEnterpriseFieldType = true; // Cupcake/Donut access enterprise settings directly

Field wcefAnonymousId = null, wcefCaCert = null, wcefClientCert = null, wcefEap = null, wcefIdentity = null, wcefPassword = null, wcefPhase2 = null, wcefPrivateKey = null;
Field[] wcefFields = WifiConfiguration.class.getFields();
// Dispatching Field vars
for (Field wcefField : wcefFields)
{
if (wcefField.getName().equals(INT_ANONYMOUS_IDENTITY))
wcefAnonymousId = wcefField;
else if (wcefField.getName().equals(INT_CA_CERT))
wcefCaCert = wcefField;
else if (wcefField.getName().equals(INT_CLIENT_CERT))
wcefClientCert = wcefField;
else if (wcefField.getName().equals(INT_EAP))
wcefEap = wcefField;
else if (wcefField.getName().equals(INT_IDENTITY))
wcefIdentity = wcefField;
else if (wcefField.getName().equals(INT_PASSWORD))
wcefPassword = wcefField;
else if (wcefField.getName().equals(INT_PHASE2))
wcefPhase2 = wcefField;
else if (wcefField.getName().equals(INT_PRIVATE_KEY))
wcefPrivateKey = wcefField;
}


Method wcefSetValue = null;
if(!noEnterpriseFieldType){
for(Method m: wcEnterpriseField.getMethods())
//System.out.println(m.getName());
if(m.getName().trim().equals("setValue"))
wcefSetValue = m;
}


/*EAP Method*/
if(!noEnterpriseFieldType)
wcefSetValue.invoke(wcefEap.get(selectedConfig), ENTERPRISE_EAP);

/*EAP Phase 2 Authentication*/
if(!noEnterpriseFieldType)
wcefSetValue.invoke(wcefPhase2.get(selectedConfig), ENTERPRISE_PHASE2);

/*EAP Anonymous Identity*/
if(!noEnterpriseFieldType)
wcefSetValue.invoke(wcefAnonymousId.get(selectedConfig), ENTERPRISE_ANON_IDENT);

/*EAP CA Certificate*/
if(!noEnterpriseFieldType)
wcefSetValue.invoke(wcefCaCert.get(selectedConfig), ENTERPRISE_CA_CERT);

/*EAP Private key*/
if(!noEnterpriseFieldType)
wcefSetValue.invoke(wcefPrivateKey.get(selectedConfig), ENTERPRISE_PRIV_KEY);

/*EAP Identity*/
if(!noEnterpriseFieldType)
wcefSetValue.invoke(wcefIdentity.get(selectedConfig), userName);

/*EAP Password*/
if(!noEnterpriseFieldType)
wcefSetValue.invoke(wcefPassword.get(selectedConfig), passString);

/*EAp Client certificate*/
if(!noEnterpriseFieldType)
wcefSetValue.invoke(wcefClientCert.get(selectedConfig), ENTERPRISE_CLIENT_CERT);

// Adhoc for CM6
// if non-CM6 fails gracefully thanks to nested try-catch

try{
Field wcAdhoc = WifiConfiguration.class.getField("adhocSSID");
Field wcAdhocFreq = WifiConfiguration.class.getField("frequency");
//wcAdhoc.setBoolean(selectedConfig, prefs.getBoolean(PREF_ADHOC,
// false));
wcAdhoc.setBoolean(selectedConfig, false);
int freq = 2462; // default to channel 11
//int freq = Integer.parseInt(prefs.getString(PREF_ADHOC_FREQUENCY,
//"2462")); // default to channel 11
//System.err.println(freq);
wcAdhocFreq.setInt(selectedConfig, freq);
} catch (Exception e)
{
e.printStackTrace();
}

} catch (Exception e)
{
// TODO Auto-generated catch block
// FIXME As above, what should I do here?
e.printStackTrace();
}

WifiManager wifiManag = (WifiManager) getSystemService(Context.WIFI_SERVICE);
boolean res1 = wifiManag.setWifiEnabled(true);
int res = wifiManag.addNetwork(selectedConfig);
Log.d("WifiPreference", "add Network returned " + res );
boolean b = wifiManag.enableNetwork(selectedConfig.networkId, false);
Log.d("WifiPreference", "enableNetwork returned " + b );
boolean c = wifiManag.saveConfiguration();
Log.d("WifiPreference", "Save configuration returned " + c );
boolean d = wifiManag.enableNetwork(res, true);
Log.d("WifiPreference", "enableNetwork returned " + d );
}

用了Java Reflection API,Android常常有non-public API所以reflection很有用的。因為在compile time時找不到一些non-public的symbol,用reflection就可以使用它們了。原理大概是先建立一些類型為Field的Object,然後比較classname找出目標object。最後找出所有預先知道了名字的Method,call Method的做法和平時的不一樣。看不明的話可以參考這裏
發佈留言

熱門文章