package be.shouldit.proxy.lib; import android.annotation.TargetApi; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.net.ConnectivityManager; import android.net.Uri; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiManager; import android.os.Build; import android.provider.Settings; import android.text.TextUtils; import android.util.Log; import java.lang.reflect.Field; import java.net.InetSocketAddress; import java.net.Proxy; import java.net.ProxySelector; import java.net.URI; import java.security.InvalidParameterException; import java.util.HashMap; import java.util.List; import java.util.Map; import be.shouldit.proxy.lib.constants.APLIntents; import be.shouldit.proxy.lib.enums.SaveStatus; import be.shouldit.proxy.lib.enums.SecurityType; import be.shouldit.proxy.lib.logging.TraceUtils; import be.shouldit.proxy.lib.reflection.ReflectionUtils; import be.shouldit.proxy.lib.reflection.android.ProxySetting; import be.shouldit.proxy.lib.utils.ProxyUtils; import be.shouldit.proxy.lib.utils.SaveResult; import timber.log.Timber; /** * Main class that contains utilities for getting the proxy configuration of the * current or the all configured networks */ public class APL { public static final String TAG = APL.class.getSimpleName(); private static ConnectivityManager mConnManager; private static WifiManager mWifiManager; private static Context gContext; private static boolean sSetupCalled; private static int deviceVersion; private static TraceUtils traceUtils; private static String webIsReachableUrl = "http://www.telize.com/ip"; public static String getWebIsReachableUrl() { return webIsReachableUrl; } public static void setWebIsReachableUrl(String webIsReachableUrl) { APL.webIsReachableUrl = webIsReachableUrl; } public static TraceUtils getTraceUtils() { return traceUtils; } public static boolean setup(Context context) { gContext = context; deviceVersion = Build.VERSION.SDK_INT; // Make sure this is only called once. if (sSetupCalled) { return false; } sSetupCalled = true; traceUtils = new TraceUtils(); Timber.d("APL setup executed"); return sSetupCalled; } public static Context getContext() { if (!sSetupCalled && gContext == null) throw new RuntimeException("you need to call setup() first"); return gContext; } public static int getDeviceVersion() { if (!sSetupCalled && gContext == null) throw new RuntimeException("you need to call setup() first"); return deviceVersion; } public static WifiManager getWifiManager() { if (!sSetupCalled && gContext == null) throw new RuntimeException("you need to call setup() first"); // if (mWifiManager == null) // { // Always get updated WifiManager mWifiManager = (WifiManager) gContext.getSystemService(Context.WIFI_SERVICE); // } return mWifiManager; } public static void enableWifi() throws Exception { if (!sSetupCalled && gContext == null) throw new RuntimeException("you need to call setup() first"); WifiManager wm = getWifiManager(); wm.setWifiEnabled(true); } public static void disableWifi() throws Exception { if (!sSetupCalled && gContext == null) throw new RuntimeException("you need to call setup() first"); WifiManager wm = getWifiManager(); wm.setWifiEnabled(false); } public static ConnectivityManager getConnectivityManager() { if (!sSetupCalled && gContext == null) throw new RuntimeException("you need to call setup() first"); if (mConnManager == null) { mConnManager = (ConnectivityManager) gContext.getSystemService(Context.CONNECTIVITY_SERVICE); } return mConnManager; } /** * Main entry point to access the proxy settings */ public static Proxy getCurrentProxyConfiguration(URI uri) throws Exception { if (!sSetupCalled && gContext == null) throw new RuntimeException("you need to call setup() first"); Proxy proxyConfig; if (deviceVersion >= 12) // Honeycomb 3.1 { proxyConfig = getProxySelectorConfiguration(uri); } else { proxyConfig = getGlobalProxy(); } /** * Set direct connection if no proxyConfig received * */ if (proxyConfig == null) { proxyConfig = Proxy.NO_PROXY; } /** * Add connection details * */ // ConnectivityManager connManager = (ConnectivityManager) gContext.getSystemService(Context.CONNECTIVITY_SERVICE); // NetworkInfo activeNetInfo = connManager.getActiveNetworkInfo(); //// proxyConfig.currentNetworkInfo = activeNetInfo; // // if (activeNetInfo != null) // { // switch (activeNetInfo.getType()) // { // case ConnectivityManager.TYPE_WIFI: // WifiManager wifiManager = (WifiManager) gContext.getSystemService(Context.WIFI_SERVICE); // WifiInfo wifiInfo = wifiManager.getConnectionInfo(); // List<WifiConfiguration> wifiConfigurations = wifiManager.getConfiguredNetworks(); // for (WifiConfiguration wc : wifiConfigurations) // { // if (wc.networkId == wifiInfo.getNetworkId()) // { // proxyConfig.ap = new AccessPoint(wc); // break; // } // } // break; // case ConnectivityManager.TYPE_MOBILE: // break; // default: // throw new UnsupportedOperationException("Not yet implemented support for" + activeNetInfo.getTypeName() + " network type"); // } // } return proxyConfig; } /** * For API >= 12: Returns the current proxy configuration based on the URI, * this implementation is a wrapper of the Android's ProxySelector class. * Just add some other details that can be useful to the developer. */ public static Proxy getProxySelectorConfiguration(URI uri) throws Exception { if (!sSetupCalled && gContext == null) throw new RuntimeException("you need to call setup() first"); ProxySelector defaultProxySelector = ProxySelector.getDefault(); Proxy proxy = null; List<Proxy> proxyList = defaultProxySelector.select(uri); if (proxyList.size() > 0) { proxy = proxyList.get(0); Timber.d("Current Proxy Configuration: %s", proxy.toString()); } else throw new Exception("Not found valid proxy configuration!"); // ConnectivityManager connManager = (ConnectivityManager) gContext.getSystemService(Context.CONNECTIVITY_SERVICE); // // WiFiApConfig proxyConfig = null; // if (proxy != Proxy.NO_PROXY) // { // proxyConfig = new WiFiApConfig(ProxySetting.STATIC, null, null, null, null); // } // else // { // InetSocketAddress proxyAddress = (InetSocketAddress) proxy.address(); // proxyConfig = new WiFiApConfig(ProxySetting.NONE, proxyAddress.getHostName(), proxyAddress.getPort(), null, null); // } return proxy; } /** * Return the current proxy configuration for HTTP protocol */ public static Proxy getCurrentHttpProxyConfiguration() throws Exception { if (!sSetupCalled && gContext == null) throw new RuntimeException("you need to call setup() first"); URI uri = new URI("http", "wwww.google.it", null, null); return getCurrentProxyConfiguration(uri); } /** * Return the current proxy configuration for HTTPS protocol */ public static Proxy getCurrentHttpsProxyConfiguration() throws Exception { if (!sSetupCalled && gContext == null) throw new RuntimeException("you need to call setup() first"); URI uri = new URI("https", "wwww.google.it", null, null); return getCurrentProxyConfiguration(uri); } /** * Return the current proxy configuration for FTP protocol */ public static Proxy getCurrentFtpProxyConfiguration() throws Exception { if (!sSetupCalled && gContext == null) throw new RuntimeException("you need to call setup() first"); URI uri = new URI("ftp", "google.it", null, null); return getCurrentProxyConfiguration(uri); } /** * For API < 12: Get global proxy configuration. */ @Deprecated public static Proxy getGlobalProxy() { if (!sSetupCalled && gContext == null) throw new RuntimeException("you need to call setup() first"); Proxy proxyConfig = null; ContentResolver contentResolver = gContext.getContentResolver(); String proxyString = Settings.Secure.getString(contentResolver, Settings.Secure.HTTP_PROXY); if (!TextUtils.isEmpty(proxyString) && proxyString.contains(":")) { String[] proxyParts = proxyString.split(":"); if (proxyParts.length == 2) { String proxyAddress = proxyParts[0]; try { int proxyPort = Integer.parseInt(proxyParts[1]); proxyConfig = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyAddress, proxyPort)); } catch (NumberFormatException e) { Timber.e(e, "Port is not a number: " + proxyParts[1]); } } } return proxyConfig; } /** * Get proxy configuration for Wi-Fi access point. Valid for API >= 12 */ @TargetApi(12) public static WiFiApConfig getWiFiAPConfiguration(WifiConfiguration wifiConf) { if (!sSetupCalled && gContext == null) throw new RuntimeException("you need to call setup() first"); WiFiApConfig wiFiApConfig = null; try { APL.getTraceUtils().startTrace(TAG, "getWiFiAPConfiguration", Log.DEBUG); Object proxySetting = ReflectionUtils.getProxySetting(wifiConf); if (proxySetting != null) { int ordinal = ((Enum) proxySetting).ordinal(); if (ordinal == ProxySetting.NONE.ordinal() || ordinal == ProxySetting.UNASSIGNED.ordinal()) { wiFiApConfig = new WiFiApConfig(wifiConf, ProxySetting.NONE, null, null, "", Uri.EMPTY); } else if (ordinal == ProxySetting.STATIC.ordinal()) { Object mHttpProxy = ReflectionUtils.getHttpProxy(wifiConf); if (mHttpProxy != null) { Field mHostField = ReflectionUtils.getField(mHttpProxy.getClass().getDeclaredFields(), "mHost"); mHostField.setAccessible(true); String mHost = (String) mHostField.get(mHttpProxy); Field mPortField = ReflectionUtils.getField(mHttpProxy.getClass().getDeclaredFields(), "mPort"); mPortField.setAccessible(true); Integer mPort = (Integer) mPortField.get(mHttpProxy); Field mExclusionListField = ReflectionUtils.getField(mHttpProxy.getClass().getDeclaredFields(), "mExclusionList"); mExclusionListField.setAccessible(true); String mExclusionList = (String) mExclusionListField.get(mHttpProxy); Timber.d("Read HTTP proxy configuration: '%s:%d' (el: '%s')",mHost,mPort,mExclusionList); wiFiApConfig = new WiFiApConfig(wifiConf, ProxySetting.STATIC, mHost, mPort, mExclusionList, Uri.EMPTY); } } else if (ordinal == ProxySetting.PAC.ordinal()) { Object mHttpProxy = ReflectionUtils.getHttpProxy(wifiConf); if (mHttpProxy != null) { Field mPacFileUrlField = ReflectionUtils.getField(mHttpProxy.getClass().getDeclaredFields(), "mPacFileUrl"); mPacFileUrlField.setAccessible(true); Uri mPacFileUrl = (Uri) mPacFileUrlField.get(mHttpProxy); Timber.d("Read PAC proxy configuration: '%s'", mPacFileUrl.toString()); wiFiApConfig = new WiFiApConfig(wifiConf, ProxySetting.PAC, null, null, null, mPacFileUrl); } } else { Timber.e(new InvalidParameterException(),"Not valid ProxySetting value: " + ordinal); } } if (wiFiApConfig == null) { Timber.e("Cannot find proxySettings object"); wiFiApConfig = new WiFiApConfig(wifiConf, ProxySetting.NONE, null, null, "", Uri.EMPTY); } APL.getTraceUtils().stopTrace(TAG, "getWiFiAPConfiguration", String.format("Got configuration for %s", wiFiApConfig.getAPLNetworkId().toString()), Log.DEBUG); } catch (Exception e) { Timber.e(e, "Problem getting WiFiApConfig from WifiConfiguration"); } return wiFiApConfig; } public static Map<APLNetworkId,WifiConfiguration> getConfiguredNetworks() { if (!sSetupCalled && gContext == null) throw new RuntimeException("you need to call setup() first"); Map<APLNetworkId,WifiConfiguration> networksMap = new HashMap<APLNetworkId, WifiConfiguration>(); APL.getTraceUtils().startTrace(TAG,"getConfiguredNetworks", Log.DEBUG); List<WifiConfiguration> configuredNetworks = getWifiManager().getConfiguredNetworks(); // APL.getTraceUtils().partialTrace(TAG,"getConfiguredNetworks", "Got configured networks from WifiManager", Log.DEBUG); if (configuredNetworks != null) { Timber.d("Found %d configured Wi-Fi networks", configuredNetworks.size()); for (WifiConfiguration wifiConf : configuredNetworks) { APLNetworkId networkId = new APLNetworkId(ProxyUtils.cleanUpSSID(wifiConf.SSID), ProxyUtils.getSecurity(wifiConf)); networksMap.put(networkId, wifiConf); // APL.getTraceUtils().partialTrace(TAG,"getConfiguredNetworks",String.format("Added %s to configured networks map", networkId.toString()),Log.DEBUG); } } else { Timber.d("NULL configured Wi-Fi networks"); } APL.getTraceUtils().stopTrace(TAG, "getConfiguredNetworks", String.format("Built configured newtworks map (#%d)",networksMap.size()), Log.DEBUG); return networksMap; } public static WifiConfiguration getConfiguredNetwork(int androidNetworkId) { if (!sSetupCalled && gContext == null) throw new RuntimeException("you need to call setup() first"); WifiConfiguration result=null; Map<APLNetworkId,WifiConfiguration> networksMap = getConfiguredNetworks(); for(WifiConfiguration configuration: networksMap.values()) { if (configuration.networkId == androidNetworkId) { result = configuration; } } return result; } public static WifiConfiguration getConfiguredNetwork(APLNetworkId networkId) { if (!sSetupCalled && gContext == null) throw new RuntimeException("you need to call setup() first"); WifiConfiguration result=null; Map<APLNetworkId,WifiConfiguration> networksMap = getConfiguredNetworks(); if (networksMap.containsKey(networkId)) { result = networksMap.get(networkId); } return result; } @TargetApi(12) public static Map<APLNetworkId,WiFiApConfig> getWifiAPConfigurations() { if (!sSetupCalled && gContext == null) throw new RuntimeException("you need to call setup() first"); Map<APLNetworkId,WiFiApConfig> WiFiAPConfigs = new HashMap<APLNetworkId, WiFiApConfig>(); APL.getTraceUtils().startTrace(TAG,"getWifiAPConfigurations", Log.DEBUG); Map<APLNetworkId,WifiConfiguration> configuredNetworks = getConfiguredNetworks(); APL.getTraceUtils().partialTrace(TAG, "getWifiAPConfigurations", "Got configured networks", Log.DEBUG); if (configuredNetworks != null) { for (WifiConfiguration wifiConf : configuredNetworks.values()) { WiFiApConfig conf = getWiFiAPConfiguration(wifiConf); WiFiAPConfigs.put(conf.getAPLNetworkId(), conf); } } APL.getTraceUtils().stopTrace(TAG, "getWifiAPConfigurations", "Got WiFiApConfig for configured networks", Log.DEBUG); return WiFiAPConfigs; } static final String WRITE_WIFI_KEY = "saveWifiConfiguration"; /** * Get proxy configuration for Wi-Fi access point. Valid for API >= 12 */ @TargetApi(12) public static SaveResult writeWifiAPConfig(WiFiApConfig confToSave, int maxAttempt, int timeout) throws Exception { if (!sSetupCalled && gContext == null) throw new RuntimeException("you need to call setup() first"); APL.getTraceUtils().startTrace(TAG, WRITE_WIFI_KEY, Log.INFO, true); SaveResult result = new SaveResult(); result.status = SaveStatus.FAILED; result.attempts = 0; result.elapsedTime = 0L; boolean succesfullySaved = false; int attempt = 1; int sleep = 100; int slept = 0; Timber.i("Writing WiFiAPConfig to device: %s", confToSave.toShortString()); if (confToSave.getSecurityType() == SecurityType.SECURITY_EAP) { Exception e = new Exception("writeConfiguration does not support Wi-Fi security 802.1x"); throw e; } WifiManager wifiManager = (WifiManager) APL.getContext().getSystemService(Context.WIFI_SERVICE); if (!wifiManager.isWifiEnabled()) { Exception e = new Exception("Wi-Fi should be enabled in order to save a Wi-Fi configuration"); throw e; } List<WifiConfiguration> configuredNetworks = null; while ((attempt <= maxAttempt) && (slept < timeout)) { configuredNetworks = wifiManager.getConfiguredNetworks(); if (configuredNetworks != null) { break; } attempt++; try { Thread.sleep(sleep); slept += sleep; } catch (InterruptedException e) { e.printStackTrace(); } } if (configuredNetworks == null) throw new Exception(String.format("Got a NULL result from WifiManager getConfiguredNetworks method after %d attempts", attempt)); if (configuredNetworks.size() == 0) throw new Exception("Cannot find any configured network to edit into the device"); WifiConfiguration selectedConfiguration = null; for (WifiConfiguration conf : configuredNetworks) { if (conf.networkId == confToSave.getNetworkId()) { selectedConfiguration = conf; break; } } if (selectedConfiguration != null) { WifiConfiguration newConf = ReflectionUtils.setProxyFieldsOnWifiConfiguration(confToSave, selectedConfiguration); APL.getTraceUtils().partialTrace(TAG, WRITE_WIFI_KEY, "Set proxy fields on WifiConfiguration", Log.INFO); ReflectionUtils.saveWifiConfiguration(wifiManager, newConf); APL.getTraceUtils().partialTrace(TAG, WRITE_WIFI_KEY, "Save configuration to device", Log.INFO); /*************************************************************************************** * TODO: improve method adding callback in order to return the result of the operation */ while ((attempt <= maxAttempt) && (slept < timeout)) { try { Thread.sleep(sleep); slept += sleep; } catch (InterruptedException e) { e.printStackTrace(); } WifiConfiguration savedWifiConfig = APL.getConfiguredNetwork(newConf.networkId); WiFiApConfig savedConf = APL.getWiFiAPConfiguration(savedWifiConfig); succesfullySaved = confToSave.isSameConfiguration(savedConf); if (succesfullySaved) { confToSave.updateProxyConfiguration(savedConf); break; } else { attempt++; Timber.w("Problem saving configuration to device after #%d attempt. Try again ...", attempt); ReflectionUtils.saveWifiConfiguration(wifiManager, newConf); } } result.attempts = attempt; result.elapsedTime = APL.getTraceUtils().totalElapsedTime(WRITE_WIFI_KEY); result.status = succesfullySaved? SaveStatus.SAVED : SaveStatus.FAILED; if (!succesfullySaved) { throw new Exception(String.format("Cannot save proxy configuration after %s attempt", attempt)); } /**************************************************************************************/ APL.getTraceUtils().stopTrace(TAG, WRITE_WIFI_KEY, Log.INFO); confToSave.getStatus().clear(); Timber.d("Succesfully updated configuration %s, after %d attempt", confToSave.toShortString(), attempt); Timber.i("Sending broadcast intent: " + APLIntents.APL_UPDATED_PROXY_CONFIGURATION); Intent intent = new Intent(APLIntents.APL_UPDATED_PROXY_CONFIGURATION); APL.getContext().sendBroadcast(intent); } else { throw new Exception("Cannot find selected configuration among configured networks during writing to the device: " + confToSave.toShortString()); } return result; } /** * Force a crash into APL in order to test CrashReporting for library * */ public static void crash() { Map<String,String> map = new HashMap<String, String>(); Timber.v("Test bug reporting log 0"); Timber.i("Test bug reporting log 1"); Timber.w("Test bug reporting log 2"); Timber.d("Test bug reporting log 3"); Timber.e(new Exception(),"EXCEPTION ONLY FOR TEST"); // Force a CRASH throw new RuntimeException("APL forced to crash!"); } }