package be.shouldit.proxy.lib.reflection; import android.annotation.TargetApi; import android.net.ProxyInfo; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiManager; import android.os.Build; import android.os.Looper; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import be.shouldit.proxy.lib.APL; import be.shouldit.proxy.lib.WiFiApConfig; import be.shouldit.proxy.lib.reflection.android.ProxySetting; import be.shouldit.proxy.lib.reflection.android.WifiServiceHandler; import timber.log.Timber; public class ReflectionUtils { public static final String TAG = ReflectionUtils.class.getSimpleName(); /* Events from WifiService */ /** @hide */ public static final int CMD_WPS_COMPLETED = 11; public static final int BASE_SYSTEM_ASYNC_CHANNEL = 0x00011000; private static final int BASE = BASE_SYSTEM_ASYNC_CHANNEL; /** * Command sent when the channel is half connected. Half connected * means that the channel can be used to sendEvent commends to the destination * but the destination is unaware that the channel exists. The first * command sent to the destination is typically CMD_CHANNEL_FULL_CONNECTION if * it is desired to establish a long term connection, but any command maybe * sent. * * msg.arg1 == 0 : STATUS_SUCCESSFUL * 1 : STATUS_BINDING_UNSUCCESSFUL * msg.obj == the AsyncChannel * msg.replyTo == dstMessenger if successful */ public static final int CMD_CHANNEL_HALF_CONNECTED = BASE + 0; /** Successful status always 0, !0 is an unsuccessful status */ public static final int STATUS_SUCCESSFUL = 0; /** Error attempting to bind on a connect */ public static final int STATUS_BINDING_UNSUCCESSFUL = 1; /** Error attempting to sendEvent a message */ public static final int STATUS_SEND_UNSUCCESSFUL = 2; public static void connectToWifi(WifiManager wifiManager, Integer networkId) throws Exception { boolean internalConnectDone = false; try { Class[] knownParam = new Class[1]; knownParam[0] = int.class; Method internalConnect = getMethod(WifiManager.class.getMethods(), "connect", knownParam); if (internalConnect != null) { Class<?>[] paramsTypes = internalConnect.getParameterTypes(); if (paramsTypes.length == 2) internalConnect.invoke(wifiManager, networkId, null); else if (paramsTypes.length == 1) internalConnect.invoke(wifiManager, networkId); internalConnectDone = true; } } catch (Exception e) { Timber.e(e, "Exception trying to connetToWifi"); } if (!internalConnectDone) { // Use the STANDARD API as a fallback solution wifiManager.enableNetwork(networkId, true); } } public static void saveWifiConfiguration(WifiManager wifiManager, WifiConfiguration configuration) throws Exception { boolean internalSaveDone = false; /** * 4.4.2_r1 * 4.4.1_r1 * 4.4_r1 * 4.3.1_r1 * 4.3_r1 * 4.2.2_r1 * 4.2.1_r1.2 * 4.2_r1 public void More ...save(WifiConfiguration config, ActionListener listener) * * 4.1.2_r1 * 4.1.1_r1 public void More ...save(Channel c, WifiConfiguration config, ActionListener listener) * * 4.0.4_r2.1 * 4.0.4_r1.2 * 4.0.3_r1 * 4.0.2_r1 * 4.0.1_r1 public void More ...saveNetwork(WifiConfiguration config) * * */ try { switch (Build.VERSION.SDK_INT) { case 14: case 15: internalSaveDone = save_4_0(wifiManager, configuration); break; case 16: internalSaveDone = save_4_1(wifiManager, configuration); break; case 17: case 18: case 19: case 20: default: internalSaveDone = save_4_2(wifiManager, configuration); break; } } catch (Exception e) { Timber.e(e, "Exception on saveWifiConfiguration"); } if (!internalSaveDone) { // Use the STANDARD API as a fallback solution wifiManager.updateNetwork(configuration); } } private static boolean save_4_0(WifiManager wifiManager, WifiConfiguration configuration) throws Exception { boolean internalSaveDone = false; Method internalAsyncConnect = getMethod(WifiManager.class.getMethods(), "asyncConnect"); Method internalSaveNetwork = getMethod(WifiManager.class.getMethods(), "saveNetwork"); if (internalAsyncConnect != null && internalSaveNetwork != null) { try { Looper looper = Looper.myLooper(); if (looper == null) Looper.prepare(); // Needed to invoke the asyncConnect method WifiServiceHandler wifiServiceHandler = new WifiServiceHandler(); internalAsyncConnect.invoke(wifiManager, APL.getContext(), wifiServiceHandler); if (internalSaveNetwork != null) { internalSaveNetwork.invoke(wifiManager, configuration); internalSaveDone = true; } } catch (Exception e) { throw new Exception("Exception during call of WifiManager.saveNetwork method (4.0) : " + e, e); } } return internalSaveDone; } private static boolean save_4_1(WifiManager wifiManager, WifiConfiguration configuration) throws Exception { boolean internalSaveDone = false; Method internalSave = getMethod(WifiManager.class.getMethods(), "save"); Method internalInitialize = getMethod(WifiManager.class.getMethods(), "initialize"); if (internalInitialize != null && internalSave != null) { try { Looper looper = Looper.myLooper(); int attempt = 0; while (attempt < 5 && looper == null) { Looper.prepare(); // Needed to invoke the asyncConnect method looper = Looper.myLooper(); attempt++; } Object channel = internalInitialize.invoke(wifiManager, APL.getContext(), looper, null); if (channel != null) { internalSave.invoke(wifiManager, channel, configuration, null); internalSaveDone = true; } } catch (Exception e) { throw new Exception("Exception during call of WifiManager.save method (4.1) : " + e, e); } } return internalSaveDone; } private static boolean save_4_2(WifiManager wifiManager, WifiConfiguration configuration) throws Exception { boolean internalSaveDone = false; Method internalSave = getMethod(WifiManager.class.getMethods(), "save"); if (internalSave != null) { try { /** * TODO: needs pass an instance of the interface WifiManager.ActionListener, in order to receive the status of the call */ internalSave.invoke(wifiManager, configuration, null); internalSaveDone = true; } catch (Exception e) { throw new Exception("Exception during call of WifiManager.save method (4.4) : " + e, e); } } return internalSaveDone; } private static boolean saveNoVersion(WifiManager wifiManager, WifiConfiguration configuration) throws Exception { boolean internalSaveDone = false; Method internalSaveNetwork = null; Method internalSave = null; try { internalSaveNetwork = getMethod(WifiManager.class.getMethods(), "saveNetwork"); } catch (Exception e) { } try { internalSave = getMethod(WifiManager.class.getMethods(), "save"); } catch (Exception e) { } if (internalSave != null) { Class<?>[] paramsTypes = internalSave.getParameterTypes(); if (paramsTypes.length == 2) { internalSave.invoke(wifiManager, configuration, null); internalSaveDone = true; } else if (paramsTypes.length == 1) { internalSave.invoke(wifiManager, configuration); internalSaveDone = true; } else { Timber.e("Not handled WifiManager.save method. Found params: " + paramsTypes.length); } } return internalSaveDone; } public static Constructor getConstructor(Constructor[] constructors, Class[] knownParameters) throws Exception { Constructor c = null; for (Constructor lc : constructors) { Boolean found = false; for (Class knowParam : knownParameters) { found = false; for (Class param : lc.getParameterTypes()) { if (param.getName().equals(knowParam.getName())) { found = true; break; } } if (found == false) break; } if (found) { c = lc; break; } } if (c == null) throw new Exception("Constructor not found!"); return c; } public static Method getMethod(Method[] methods, String methodName) throws Exception { Method m = null; for (Method lm : methods) { String currentMethodName = lm.getName(); if (currentMethodName.equals(methodName)) { m = lm; break; } } if (m == null) throw new Exception(methodName + " method not found!"); return m; } public static Method getMethod(Method[] methods, String methodName, Class[] knownParameters) throws Exception { Method m = null; for (Method lm : methods) { String currentMethodName = lm.getName(); if (currentMethodName.equals(methodName)) { Boolean found = false; for (Class knowParam : knownParameters) { found = false; for (Class param : lm.getParameterTypes()) { if (param.getName().equals(knowParam.getName())) { found = true; break; } } if (found == false) break; } if (found) { m = lm; break; } } } if (m == null) throw new Exception(methodName + " method not found!"); return m; } public static List<Method> getMethods(Method[] methods, String methodName) throws Exception { ArrayList<Method> ml = new ArrayList<Method>(); for (Method lm : methods) { String currentMethodName = lm.getName(); if (currentMethodName.equals(methodName)) { ml.add(lm); } } if (ml.size() == 0) throw new Exception(methodName + " method not found!"); return ml; } public static Field getField(Field[] fields, String fieldName) throws Exception { Field f = null; for (Field lf : fields) { String currentFieldName = lf.getName(); if (currentFieldName.equals(fieldName)) { f = lf; break; } } if (f == null) throw new Exception(fieldName + " field not found!"); return f; } public static Class getDeclaredClass(Class[] classes, String className) throws Exception { Class c = null; for (Class lc : classes) { String currentFieldName = lc.getName(); if (currentFieldName.equals(className)) { c = lc; break; } } if (c == null) throw new Exception(className + " class not found!"); return c; } static void describeClassOrInterface(Class className, String name) { displayModifiers(className.getModifiers()); displayFields(className.getDeclaredFields()); displayMethods(className.getDeclaredMethods()); if (className.isInterface()) { Timber.d("Interface: " + name); } else { Timber.d("Class: " + name); displayInterfaces(className.getInterfaces()); displayConstructors(className.getDeclaredConstructors()); } } static void displayModifiers(int m) { Timber.d("Modifiers: " + Modifier.toString(m)); } static void displayInterfaces(Class[] interfaces) { if (interfaces.length > 0) { Timber.d("Interfaces: "); for (int i = 0; i < interfaces.length; ++i) Timber.d(interfaces[i].getName()); } } static void displayFields(Field[] fields) { if (fields.length > 0) { Timber.d("Fields: "); for (int i = 0; i < fields.length; ++i) Timber.d(fields[i].toString()); } } static void displayConstructors(Constructor[] constructors) { if (constructors.length > 0) { Timber.d("Constructors: "); for (int i = 0; i < constructors.length; ++i) Timber.d(constructors[i].toString()); } } static void displayMethods(Method[] methods) { if (methods.length > 0) { Timber.d("Methods: "); for (int i = 0; i < methods.length; ++i) Timber.d(methods[i].toString()); } } public static Field[] getAllFields(Class klass) { List<Field> fields = new ArrayList<Field>(); fields.addAll(Arrays.asList(klass.getDeclaredFields())); if (klass.getSuperclass() != null) { fields.addAll(Arrays.asList(getAllFields(klass.getSuperclass()))); } return new Field[]{}; } public static WifiConfiguration setProxyFieldsOnWifiConfiguration(WiFiApConfig wiFiApConfig, WifiConfiguration selectedConfiguration) throws Exception { if (Build.VERSION.SDK_INT >= 20) { return setFieldsOnWifiConfigurationSDK21(wiFiApConfig, selectedConfiguration); } else { return setFieldsOnWifiConfiguration(wiFiApConfig, selectedConfiguration); } } @TargetApi(21) private static WifiConfiguration setFieldsOnWifiConfigurationSDK21(WiFiApConfig wiFiApConfig, WifiConfiguration selectedConfiguration) throws Exception { Constructor wfconfconstr = WifiConfiguration.class.getConstructors()[1]; WifiConfiguration newConf = (WifiConfiguration) wfconfconstr.newInstance(selectedConfiguration); Field mIpConfigurationField = getField(newConf.getClass().getDeclaredFields(), "mIpConfiguration"); if (mIpConfigurationField != null) { mIpConfigurationField.setAccessible(true); Object mIpConfiguration = mIpConfigurationField.get(newConf); if (mIpConfiguration != null) { Field proxySettingsField = getField(mIpConfiguration.getClass().getFields(), "proxySettings"); proxySettingsField.set(mIpConfiguration, (Object) proxySettingsField.getType().getEnumConstants()[wiFiApConfig.getProxySetting().ordinal()]); } } Object proxySettings = ReflectionUtils.getProxySetting(newConf); int ordinal = ((Enum) proxySettings).ordinal(); if (ordinal != wiFiApConfig.getProxySetting().ordinal()) throw new Exception("Cannot set proxySettings variable"); Object mIpConfiguration = null; Field mHttpProxyField = null; if (mIpConfigurationField != null) { mIpConfigurationField.setAccessible(true); mIpConfiguration = mIpConfigurationField.get(newConf); if (mIpConfiguration != null) { mHttpProxyField = getField(mIpConfiguration.getClass().getFields(), "httpProxy"); } } Constructor constr; Object proxyInfo = null; if (wiFiApConfig.getProxySetting() == ProxySetting.NONE || wiFiApConfig.getProxySetting() == ProxySetting.UNASSIGNED || !wiFiApConfig.isValidProxyConfiguration()) { constr = ProxyInfo.class.getConstructors()[4]; proxyInfo = constr.newInstance(null, 0, null); } else if (wiFiApConfig.getProxySetting() == ProxySetting.STATIC) { constr = ProxyInfo.class.getConstructors()[4]; Integer port = wiFiApConfig.getProxyPort(); proxyInfo = constr.newInstance(wiFiApConfig.getProxyHostString(), port, wiFiApConfig.getProxyExclusionList()); } else if (wiFiApConfig.getProxySetting() == ProxySetting.PAC) { constr = ProxyInfo.class.getConstructors()[1]; proxyInfo = constr.newInstance(wiFiApConfig.getPacFileUri()); } mHttpProxyField.set(mIpConfiguration, proxyInfo); return newConf; } private static WifiConfiguration setFieldsOnWifiConfiguration(WiFiApConfig wiFiApConfig, WifiConfiguration selectedConfiguration) throws Exception { Constructor wfconfconstr = WifiConfiguration.class.getConstructors()[1]; WifiConfiguration newConf = (WifiConfiguration) wfconfconstr.newInstance(selectedConfiguration); Field proxySettingsField = newConf.getClass().getField("proxySettings"); proxySettingsField.set(newConf, (Object) proxySettingsField.getType().getEnumConstants()[wiFiApConfig.getProxySetting().ordinal()]); Object proxySettings = ReflectionUtils.getProxySetting(newConf); int ordinal = ((Enum) proxySettings).ordinal(); if (ordinal != wiFiApConfig.getProxySetting().ordinal()) throw new Exception("Cannot set proxySettings variable"); Object linkProperties = null; Field mHttpProxyField = null; Field linkPropertiesField = newConf.getClass().getField("linkProperties"); linkProperties = linkPropertiesField.get(newConf); mHttpProxyField = getField(linkProperties.getClass().getDeclaredFields(), "mHttpProxy"); mHttpProxyField.setAccessible(true); if (wiFiApConfig.getProxySetting() == ProxySetting.NONE || wiFiApConfig.getProxySetting() == ProxySetting.UNASSIGNED) { mHttpProxyField.set(linkProperties, null); } else if (wiFiApConfig.getProxySetting() == ProxySetting.STATIC) { Class proxyPropertiesClass = mHttpProxyField.getType(); Integer port = wiFiApConfig.getProxyPort(); if (port == null) { Constructor constr = proxyPropertiesClass.getConstructors()[0]; Object ProxyProperties = constr.newInstance((Object) null); mHttpProxyField.set(linkProperties, ProxyProperties); } else { Constructor constr; Object proxyProperties; // NOTE: Hardcoded sdk version number. // Instead of comparing against Build.VERSION_CODES.KITKAT, we directly compare against the version // number to allow devs to compile with an older version of the sdk. if (Build.VERSION.SDK_INT < 19) { constr = proxyPropertiesClass.getConstructors()[1]; } else { // SDK 19, 20 = KITKAT, KITKAT_WATCH constr = proxyPropertiesClass.getConstructors()[3]; } proxyProperties = constr.newInstance(wiFiApConfig.getProxyHostString(), port, wiFiApConfig.getProxyExclusionList()); mHttpProxyField.set(linkProperties, proxyProperties); } } return newConf; } public static Object getHttpProxy(WifiConfiguration wifiConf) throws Exception { Field mHttpProxyField = null; Object httpProxy = null; if (Build.VERSION.SDK_INT >= 20) { httpProxy = ReflectionUtils.getProxyInfo(wifiConf); } else { Object linkProperties = ReflectionUtils.getProxyInfo(wifiConf); mHttpProxyField = getField(linkProperties.getClass().getDeclaredFields(), "mHttpProxy"); mHttpProxyField.setAccessible(true); httpProxy = mHttpProxyField.get(linkProperties); } return httpProxy; } public static Object getProxySetting(WifiConfiguration wifiConf) throws Exception { Field proxySettingsField = null; Object proxySettings = null; if (Build.VERSION.SDK_INT >= 20) { Field mIpConfigurationField = getField(wifiConf.getClass().getDeclaredFields(), "mIpConfiguration"); if (mIpConfigurationField != null) { mIpConfigurationField.setAccessible(true); Object mIpConfiguration = mIpConfigurationField.get(wifiConf); if (mIpConfiguration != null) { proxySettingsField = getField(mIpConfiguration.getClass().getFields(), "proxySettings"); proxySettings = proxySettingsField.get(mIpConfiguration); } } } else { proxySettingsField = getField(wifiConf.getClass().getFields(), "proxySettings"); proxySettings = proxySettingsField.get(wifiConf); } return proxySettings; } private static Object getProxyInfo(WifiConfiguration wifiConf) throws Exception { Field proxySettingsField = null; Object proxySettings = null; if (Build.VERSION.SDK_INT >= 20) { Field mIpConfigurationField = getField(wifiConf.getClass().getDeclaredFields(), "mIpConfiguration"); if (mIpConfigurationField != null) { mIpConfigurationField.setAccessible(true); Object mIpConfiguration = mIpConfigurationField.get(wifiConf); if (mIpConfiguration != null) { proxySettingsField = getField(mIpConfiguration.getClass().getFields(), "httpProxy"); proxySettings = proxySettingsField.get(mIpConfiguration); } } } else { proxySettingsField = getField(wifiConf.getClass().getFields(), "linkProperties"); proxySettings = proxySettingsField.get(wifiConf); } return proxySettings; } }