⬆️ ⬇️

Features of ProxyChanging software in Android. Part 1: From Jelly Bean to Lollipop

Once, for my own convenience, I wanted to write an application that changes the proxy settings in the Wifi network configuration for Android. The task, as it seemed to me then, was to spit on time, however, in practice, as always, unforeseen difficulties arose.







If you think it is useful in the future to know the decision, you want to learn something for yourself or if curiosity just woke up in you - welcome under cat. There you will find the internal structure of the classes responsible for the configuration of Wifi in different versions of Android, a small cup of Java code and a pinch of Reflection.



A bit of communication with Google on the topic “change wifi proxy settings in android programmatically” led, of course, to StackOverflow, where the solution was present via Reflection. Without thinking, I copied the code and ran it on my device.

')

Result
image



The disappointment was quickly replaced by interest and excitement, approximately at this moment the author decided that he had already spent too much time in order to simply score on the above question, and also realized that this task requires a more global approach. So let's go.



Step 1. We study the internal structure of the android.net library and the differences in Jelly Bean - Kitkat and Lollipop



A list of things you want to know to better understand what's going on below.
  • Your English level must be, at least, pre-intermediate.
  • You should not have questions “What is Context ?” And similar in complexity, if it arose at this moment - you can read developer.android.com or Alexander Klimov
  • Also, the annotations of @Before , @Test , @After and other things related to testing should not cause embarrassment. Again, link: developer.android.com


I would love to take it all apart, but you know, then my article will grow into a book.



I would also like to give a few more general clarifications:



  • You almost will not find comments in my code. I thought about this issue for a long time and doubted it for a very long time, but in the end I decided to just give it to a girl who doesn’t know java at all, and, after a brief explanation of what class , void , throws , exception was, she could read several classes , it is very accurate to say what happens in them and their methods, so I almost abandoned them.
  • If you have comments, additions, questions, comments (for example, on the previous paragraph) - the author is waiting for them.
  • The article at the moment is very generalized, again, if you want to see some points in more detail, then depending on the scope of work, I will write a comment or, perhaps, a separate article on the topic that interests you.
  • There are no imports in the code, because the package name contains the nickname of your humble servant, and, judging by the rules, they should not be shined in the sandbox article.


Some more questions on Google led me to android.googlesource



Proxy settings (as well as some others) are enclosed in the WifiConfiguration instance (Reference to the class for Kitkat mr2.2 ) for this network. When studying this class, an answer was obtained to why the solution with StackOverflow did not work on my device. It turned out that since the fifth version of the Android device of the WifiConfiguration class, as well as the android.net package, the LinkPropeties object with which the above code has worked does not exist within this class has undergone significant changes. But there is an IpConfiguraion object with a ProxyInfo object.



Given that these Android versions covered 80% of various devices, the task was to simply write something like this:



public void changeProxySettings(String host, int port){ if(Build.VERSION.SDK_INT > 14 && Build.VERSION.SDK_INT < 20){ changeProxyWithLikProperties(String host, int port); }else if(Build.VERSION.SDK_INT > 20 && Build.VERSION.SDK_INT < 23){ changeProxyWithProxyInfo(String host, int port); }else{ throw new Exception("Sorry, android version not supported") } } 


where changeProxyXXX - monstrous methods, on a couple of pages. Not the most elegant solution.



Step 2. Developing a library for configuring Wifi proxy in Android



So, the author decided not to dwell on the cumbersome class with a bunch of methods. There is free time (untimely leave on the occasion of the reduction of funding for the project in which I participated), so why not work more globally on the task.



Module architecture


We have different implementations for different versions of Android, which should have a single interface for changing proxy settings, and working with the WifiConfiguration object. Trying to satisfy these requirements as much as possible, at the initial stage my inflamed consciousness came up with something like this:







An explanatory comment on the picture above.
  • The BaseWifiConfiguration class, in fact, stores the WifiConfiguration object and contains the implementation of taking the configuration of the network that is current when created via Context .
  • The ProxyChanger interface, accordingly, guarantees the availability of methods for working with the network proxy configuration.
  • We have to work with Reflection, and it is desirable to move the main methods for this into a separate class, as they will be used frequently. Therefore, we create the ReflectionHelper class.


Classes for different versions of Android are inherited from BaseWifiConfiguration in order to have easy access to the WifiConfiguration instance of the network of interest to us and facilitate working with it, and should have implementations of the methods declared in ProxyChanger .



PS
I do not argue that this is perhaps not the best architectural solution, and if you have any suggestions for improvement or any comments, I’m glad to see them in the comments.



For example, I am very interested in ReflectionHelper, as you can see, it is declared abstract, it is made for reasons that it should not have concrete implementations and is used only for structuring and easy access to the methods of interest to us. I do not know how correct this approach is, so if you have a comment on this issue (or some other), I will be very grateful to hear it.


Of course, this is only a common framework and, after several iterations of refactoring, it will acquire new details.



Tests


So, we thought about architecture, it is time to write a couple of lines of code. Of course, not working, and testing our project.



We create a small class that will be responsible for choosing ProxyChanger for a specific api, a class for working with the above-mentioned object in terms of changing the proxy configuration and one more, for taking information about the current network settings, we get a couple of phones and start.



WifiProxyChangerTest.java
 @RunWith(AndroidJUnit4.class) public class WifiProxyChangerTest { @Rule public ExpectedException expectedException = ExpectedException.none(); @Rule public ActivityTestRule mActivityRule = new ActivityTestRule<>( MainActivity.class); Context context; @Before public void prepare() throws Exception { context = mActivityRule.getActivity(); ExceptionsPreparer.prepareExceptions(expectedException, context); } @Test public void testChangeWifiStaticProxySettings() throws Exception { String testIp = RandomValuesGenerator.randomIp(); int testPort = RandomValuesGenerator.randomPort(); WifiProxyChanger.changeWifiStaticProxySettings(testIp, testPort, context); assertEquals(testIp, WifiProxyInfo.getHost(context)); assertEquals(testPort, WifiProxyInfo.getPort(context)); } @Test public void testProxySettingsClear() throws Exception { String testIp = RandomValuesGenerator.randomIp(); int testPort = RandomValuesGenerator.randomPort(); WifiProxyChanger.changeWifiStaticProxySettings(testIp, testPort, context); WifiProxyChanger.clearProxySettings(context); assertEquals(ProxySettings.NONE, CurrentProxyChangerGetter .chooseProxyChangerForCurrentApi(context) .getProxySettings()); } @After public void learSettings() throws Exception { if (NetworkHelper.isWifiConnected(context) && ApiChecker.isSupportedApi()) WifiProxyChanger.clearProxySettings(context); } } 




WifiProxyInfoTest.java
 @RunWith(AndroidJUnit4.class) public class WifiProxyInfoTest { @Rule public ExpectedException expectedException = ExpectedException.none(); @Rule public ActivityTestRule mActivityRule = new ActivityTestRule<>( MainActivity.class); Context context; @Before public void prepareAndPresetProxy() throws Exception { context = mActivityRule.getActivity(); ExceptionsPreparer.prepareExceptions(expectedException, context); if (ApiChecker.isSupportedApi()) { WifiProxyChanger.clearProxySettings(context); WifiProxyChanger.changeWifiStaticProxySettings("localhost", 3030, context); } } @Test public void testGetHost() throws Exception { assertEquals("localhost", WifiProxyInfo.getHost(context)); } @Test public void testGetPort() throws Exception { assertEquals(3030, WifiProxyInfo.getPort(context)); } @Test public void testGetProxySettings() throws Exception { assertEquals(ProxySettings.STATIC, WifiProxyInfo.getProxySettings(context)); } @After public void learSettings() throws Exception { if (NetworkHelper.isWifiConnected(context) && ApiChecker.isSupportedApi()) WifiProxyChanger.clearProxySettings(context); } } 




CurrentProxyChangerGetterTest .java
 @RunWith(AndroidJUnit4.class) public class CurrentProxyChangerGetterTest { @Rule public ExpectedException expectedException = ExpectedException.none(); @Rule public ActivityTestRule mActivityRule = new ActivityTestRule<>( MainActivity.class); Context context; @Before public void prepare() throws Exception { context = mActivityRule.getActivity(); ExceptionsPreparer.prepareExceptions(expectedException, context); } @Test public void testChooseProxyChangerForCurrentApi() throws Exception { ProxyChanger proxyChanger = CurrentProxyChangerGetter.chooseProxyChangerForCurrentApi(context); WifiManager manager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); assertEquals(manager.getConnectionInfo().getNetworkId(), proxyChanger.getWifiConfiguration().networkId); if (ApiChecker.isJellyBeanOrKitkat()) { assertTrue(proxyChanger instanceof WifiConfigurationForApiFrom15To19); } else if (ApiChecker.isLolipop()) { assertTrue(proxyChanger instanceof WifiConfigurationForApiFrom21To22); } } } 




ExceptionsPreparer.java
 public abstract class ExceptionsPreparer { public static void prepareExceptions(ExpectedException expectedException, Context context) throws Exception { if (!ApiChecker.isSupportedApi()) { expectedException.expect(ApiNotSupportedException.class); } else if (!NetworkHelper.isWifiConnected(context)) { expectedException.expect(NullWifiConfigurationException.class); } else if (!CurrentProxyChangerGetter.chooseProxyChangerForCurrentApi(context).isProxySetted()) { expectedException.expect(WifiProxyNotSettedException.class); } } } 




A comment to the code explaining where it came from there are a lot of incomprehensible things about which I did not mention.
I think, after reading, there were reasonable questions: What is this “ProxySettings.STATIC” , and for which he answers, where did the Exceptions come from, which were also not mentioned before, and so on.

The fact is that, unfortunately, I didn’t have the original version, and only the test classes that have passed several iterations of refactoring are present in bitbucket.



Running tests ends in failure, now we need to fix it somehow.



Step 3. Implementation



Finally, having thought over what to write and having made all the necessary preparations, we can proceed to the implementation of our project.



Part one: Preparatory work and auxiliary classes


For a start, let's proceed to our promised pinch of Reflection.


A little bit about Reflection api
It is commonly used in the Java virtual machine.

Oracle Java Turtorial


Class, but what exactly can you do about it?



Small example
Suppose we have a small library, there are a couple of classes in it for taking a web page and saving it. I want to connect it on the fly and use it. We write for this a small class:



 public class LibLoader { //      ,   2   . URLClassLoader urlClassLoader; String page; LibLoader(File myJar) throws MalformedURLException { urlClassLoader = new URLClassLoader(new URL[]{myJar.toURL()}, this.getClass().getClassLoader()); } public void loadPage(URL url) throws Exception { Class classToLoad = Class.forName("com.company.HtmlPageGetter", true, urlClassLoader); Method method = classToLoad.getDeclaredMethod("getPageFromURL", URL.class); Object instance = classToLoad.newInstance(); Object result = method.invoke(instance, url); page = (String) result; } public String getCurrentPage() { return page; } public void saveCurrentPage(String name) throws Exception { List<String> content = new ArrayList<>(); content.add(page); Class classToLoad = Class.forName("com.company.HtmlPageSaver", true, urlClassLoader); Method method = classToLoad.getDeclaredMethod("savePageToFile", String.class, List.class); Object instance = classToLoad.newInstance(); method.invoke(instance, name, content); } } 


Now use it:



  public static void main(String[] args) throws Exception { File lib = new File("htmlgetandsave.jar"); LibLoader libLoader = new LibLoader(lib); libLoader.loadPage(new URL("https://habrahabr.ru/post/69552/")); System.out.println(libLoader.getCurrentPage()); libLoader.saveCurrentPage("   -  reflection     "); } 


Run and enjoy the result:







Moreover, we could only know the location of the library file and not know anything about its structure, Reflection api would allow to study this question right in runtime, and use it after that.



However, what is important for us now is that, among other things, thanks to Reflection, we can get access to private fields and methods, as well as with the hide annotation.



So, we write already mentioned above ReflectionHelper .



ReflectionHelper.java
 public abstract class ReflectionHelper { /** * Used for getting public fields with @hide annotation */ public static Object getField(Object object, String name) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException { Field field = object.getClass().getField(name); return field.get(object); } /** * Used for getting private fields */ public static Object getDeclaredField(Object object, String name) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException { Field declaredField = object.getClass().getDeclaredField(name); declaredField.setAccessible(true); return declaredField.get(object); } /** * Used for setting private fields */ public static void setDeclaredField(Object object, String name, Object value) throws NoSuchFieldException, IllegalAccessException { Field declaredField = object.getClass().getDeclaredField(name); declaredField.setAccessible(true); declaredField.set(object, value); } /** * Used for setting Enum fields */ public static void setEnumField(Object object, String value, String name) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException { Field field = object.getClass().getField(name); field.set(object, Enum.valueOf((Class<Enum>) field.getType(), value)); } /** * Used for simplifying process of invoking private method * Automatically detects args types and founds method to get and invoke */ public static Object getMethodAndInvokeIt(Object object, String methodName, Object... args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Method method = object.getClass().getDeclaredMethod(methodName, parameterTypes(args)); method.setAccessible(true); return method.invoke(object, args); } private static Class[] parameterTypes(Object... args) { ArrayList<Class> classes = new ArrayList<>(); for (Object arg : args) { classes.add(arg.getClass()); } return classes.toArray(new Class[args.length]); } } 


Here, I still set myself a memo, because, for example, the difference between getField and getDeclaredField and in what cases which one to use can be easily forgotten.



Most of the work with Reflection has been moved to a separate class, let's deal with the implementation of the remaining parts.



Create Exceptions for 3 cases:




Implement the class that serves as the base class for subclasses working with WifiConfiguration under different api:


BaseWifiConfiguration.java
 public class BaseWifiConfiguration { protected WifiConfiguration wifiConfiguration; protected BaseWifiConfiguration(WifiConfiguration wifiConfiguration) throws NullWifiConfigurationException { if (wifiConfiguration == null) throw new NullWifiConfigurationException(); this.wifiConfiguration = wifiConfiguration; } protected BaseWifiConfiguration(Context context) throws NullWifiConfigurationException { this(getCurrentWifiConfigurationFromContext(context)); } public WifiConfiguration getWifiConfiguration() { return wifiConfiguration; } private static WifiConfiguration getCurrentWifiConfigurationFromContext(Context context) { final WifiManager manager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); List<WifiConfiguration> wifiConfigurationList = manager.getConfiguredNetworks(); if (!manager.isWifiEnabled() || wifiConfigurationList == null || wifiConfigurationList.isEmpty()) return null; return findWifiConfigurationByNetworkId(wifiConfigurationList, manager.getConnectionInfo().getNetworkId()); } private static WifiConfiguration findWifiConfigurationByNetworkId(List<WifiConfiguration> wifiConfigurationList, int networkId) { for (WifiConfiguration wifiConf : wifiConfigurationList) { if (wifiConf.networkId == networkId) return wifiConf; } return null; } } 




We declare ProxyChanger interface


ProxyChanger.java
 public interface ProxyChanger { void setProxySettings(ProxySettings proxySettings) throws NoSuchFieldException, IllegalAccessException; ProxySettings getProxySettings() throws NoSuchFieldException, IllegalAccessException; void setProxyHostAndPort(String host, int port) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException; String getProxyHost() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, ApiNotSupportedException, NoSuchFieldException; int getProxyPort() throws ApiNotSupportedException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, NoSuchFieldException; boolean isProxySetted() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, ApiNotSupportedException, NoSuchFieldException; WifiConfiguration getWifiConfiguration(); } 


Yes, the Exception list when using Reflection is something.



Look like that's it? And, no, there is another small sub-item:



ProxySettings.java - what is it all about and why is it needed?


This is the equivalent of an enumeration that is in the Android library's WifiConfiguration class. We create it in order to facilitate the work with it and not to prescribe STATIC , NONE and others manually each time.



ProxySettings.java
 public enum ProxySettings { /* No proxy is to be used. Any existing proxy settings * should be cleared. */ NONE("NONE"), /* Use statically configured proxy. Configuration can be accessed * with httpProxy. */ STATIC("STATIC"), /* no proxy details are assigned, this is used to indicate * that any existing proxy settings should be retained */ UNASSIGNED("UNASSIGNED"), /* Use a Pac based proxy. */ PAC("PAC"); String value = ""; ProxySettings(String value) { this.value = value; } public String getValue() { return value; } } 




Part Two: We write classes that implement ProxyChanger for specific Api


So, it's time to finally write the same code that will change our proxy settings. At once I will make a reservation that there are various ways to get to them through Reflection: you can call the methods of the WifiConfiguration class, you can get to, in fact, the fields where they are and change them directly through setDeclaredField .



I wrote only a part for working with the current network (for this is what the author needed), i.e. with instantiating classes through Context , however, our architecture allows adding just a few lines to adapt these classes to work with an arbitrary WifiConfiguration object.



Kitkat and Jelly Bean


As mentioned in step 1, in these versions of Android, the ProxyProperties object stored in LinkProperties , which in turn is in WifiConfiguration, is responsible for storing the Proxy settings. Yes, yes, a needle in an egg, an egg in a duck, a duck in a hare, and so on.



In order to change the proxy settings, create a new instance of ProxyProperties with the parameters we need, then replace this object with an existing one and then configure ProxySettings .



A separate class will be responsible for creating instances of ProxyProperties :



ProxyPropertiesConstructor.java
 public abstract class ProxyPropertiesConstructor { public static Object proxyProperties(String host, int port) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { return proxyProperties(host, port, null); } public static Object proxyProperties(String host, int port, String exclList) throws NoSuchMethodException, ClassNotFoundException, IllegalAccessException, InvocationTargetException, InstantiationException { return proxyPropertiesConstructor().newInstance(host, port, exclList); } private static Constructor proxyPropertiesConstructor() throws ClassNotFoundException, NoSuchMethodException { return Class.forName("android.net.ProxyProperties").getConstructor(String.class, int.class, String.class); } } 




For convenient work with this object, we will also create a container class that contains the ProxyProperties object and provides access to the main fields (and allows you to conveniently create it immediately through the host and port):



ProxyPropertiesContainer.java
 public class ProxyPropertiesContainer { Object proxyProperties; ProxyPropertiesContainer(Object proxyProperties) { this.proxyProperties = proxyProperties; } ProxyPropertiesContainer(String host, int port) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { this(host, port, null); } ProxyPropertiesContainer(String host, int port, String exclList) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { this(ProxyPropertiesConstructor.proxyProperties(host, port, exclList)); } public String getHost() throws NoSuchFieldException, IllegalAccessException { return (String) ReflectionHelper.getDeclaredField(proxyProperties, "mHost"); } public int getPort() throws NoSuchFieldException, IllegalAccessException { return (int) ReflectionHelper.getDeclaredField(proxyProperties, "mPort"); } public String getExclusionList() throws NoSuchFieldException, IllegalAccessException { return (String) ReflectionHelper.getDeclaredField(proxyProperties, "mExclusionList"); } public Object getProxyProperties() { return proxyProperties; } } 




Now we write the implementation of the actual class:



WifiConfigurationForApiFrom15To19.java
 public class WifiConfigurationForApiFrom15To19 extends BaseWifiConfiguration implements ProxyChanger { private ProxyPropertiesContainer proxyPropertiesContainer; public WifiConfigurationForApiFrom15To19(Context context) throws NoSuchFieldException, IllegalAccessException, NullWifiConfigurationException { super(context); this.proxyPropertiesContainer = new ProxyPropertiesContainer(getCurrentProxyProperties()); } public static WifiConfigurationForApiFrom15To19 createFromCurrentContext(Context context) throws NoSuchFieldException, IllegalAccessException, NullWifiConfigurationException { return new WifiConfigurationForApiFrom15To19(context); } @Override public void setProxySettings(ProxySettings proxySettings) throws NoSuchFieldException, IllegalAccessException { ReflectionHelper.setEnumField(wifiConfiguration, proxySettings.getValue(), "proxySettings"); } @Override public ProxySettings getProxySettings() throws NoSuchFieldException, IllegalAccessException { return ProxySettings.valueOf(String.valueOf(ReflectionHelper.getDeclaredField(wifiConfiguration, "proxySettings"))); } @Override public void setProxyHostAndPort(String host, int port) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException { proxyPropertiesContainer = new ProxyPropertiesContainer(host, port); ReflectionHelper.getMethodAndInvokeIt( getLinkProperties(), "setHttpProxy", proxyPropertiesContainer.getProxyProperties()); } @Override public String getProxyHost() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, ApiNotSupportedException, NoSuchFieldException { if (proxyPropertiesContainer == null) throw new WifiProxyNotSettedException(); return proxyPropertiesContainer.getHost(); } @Override public int getProxyPort() throws ApiNotSupportedException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, NoSuchFieldException { if (proxyPropertiesContainer == null) throw new WifiProxyNotSettedException(); return proxyPropertiesContainer.getPort(); } @Override public boolean isProxySetted() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, ApiNotSupportedException, NoSuchFieldException { return !(proxyPropertiesContainer == null); } private LinkProperties getLinkProperties() throws NoSuchFieldException, IllegalAccessException { return (LinkProperties) ReflectionHelper.getField(wifiConfiguration, "linkProperties"); } private Object getCurrentProxyProperties() throws NoSuchFieldException, IllegalAccessException { return ReflectionHelper.getDeclaredField(getLinkProperties(), "mHttpProxy"); } } 




C finished with this version, remained:



Lollipop


Again, appealing to step 1, we can conclude that the proxy settings in this version of Api are in the ProxyInfo class contained in IpConfiguration , which in turn has our WifiConfiguration as its location. ProxySettings - also moved, now it is in the above IpConfiguration .



Let's write a class that makes new ProxyInfo instances by the specified parameters.



ProxyInfoConstructor.java
 public abstract class ProxyInfoConstructor { public static ProxyInfo proxyInfo(String host, int port) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { return proxyInfo(host, port, null); } public static ProxyInfo proxyInfo(String host, int port, String exclude) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Object newProxyInfo = proxyInfoConstructor().newInstance(host, port, exclude); return (ProxyInfo) newProxyInfo; } private static Constructor proxyInfoConstructor() throws ClassNotFoundException, NoSuchMethodException { return Class.forName("android.net.ProxyInfo").getConstructor(String.class, int.class, String.class); } } 




As you can see, here we are not returning Objects, but ProxyInfo instances, moreover, it will be seen later that this class also has getHost and getPort methods . In the previous case, we could not do this, the ProxyProperties class was hidden, which is why we wrote a “shell” for it.



And, actually, the code for another implementation:



WifiConfigurationForApiFrom21To22.java
 public class WifiConfigurationForApiFrom21To22 extends BaseWifiConfiguration implements ProxyChanger { public WifiConfigurationForApiFrom21To22(Context context) throws NullWifiConfigurationException { super(context); } public static WifiConfigurationForApiFrom21To22 createFromCurrentContext(Context context) throws NullWifiConfigurationException { return new WifiConfigurationForApiFrom21To22(context); } @Override public ProxySettings getProxySettings() throws NoSuchFieldException, IllegalAccessException { return ProxySettings.valueOf(String.valueOf(ReflectionHelper.getDeclaredField(getIpConfigurationObject(), "proxySettings"))); } @Override public void setProxySettings(ProxySettings proxySettings) throws NoSuchFieldException, IllegalAccessException { ReflectionHelper.setEnumField(getIpConfigurationObject(), proxySettings.getValue(), "proxySettings"); } @Override public void setProxyHostAndPort(String host, int port) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException { setProxyInfo(ProxyInfoConstructor.proxyInfo(host, port)); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override public String getProxyHost() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, ApiNotSupportedException { ProxyInfo info = getProxyInfo(); if (info == null) throw new WifiProxyNotSettedException(); return info.getHost(); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override public int getProxyPort() throws ApiNotSupportedException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { ProxyInfo info = getProxyInfo(); if (info == null) throw new WifiProxyNotSettedException(); return info.getPort(); } @Override public boolean isProxySetted() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, ApiNotSupportedException, NoSuchFieldException { return !(getProxyInfo() == null); } private Object getIpConfigurationObject() throws NoSuchFieldException, IllegalAccessException { return ReflectionHelper.getDeclaredField(wifiConfiguration, "mIpConfiguration"); } private ProxyInfo getProxyInfo() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { return (ProxyInfo) ReflectionHelper.getMethodAndInvokeIt(wifiConfiguration, "getHttpProxy"); } private void setProxyInfo(ProxyInfo proxyInfo) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, NoSuchFieldException { ReflectionHelper.getMethodAndInvokeIt(wifiConfiguration, "setHttpProxy", proxyInfo); } } 




With the main implementation that's all. To the finish line left quite a bit.



Step 4. Pre-launch preparation



We implement the classes mentioned earlier in the tests (note: we implement the configuration of the proxy by IP and port, respectively, the type ProxySettings - STATIC .)



CurrentProxyChangerGetter.java
 public abstract class CurrentProxyChangerGetter { public static ProxyChanger chooseProxyChangerForCurrentApi(Context context) throws ApiNotSupportedException, NoSuchFieldException, IllegalAccessException, NullWifiConfigurationException { if (ApiChecker.isJellyBeanOrKitkat()) { return WifiConfigurationForApiFrom15To19.createFromCurrentContext(context); } else if (ApiChecker.isLolipop()) { return WifiConfigurationForApiFrom21To22.createFromCurrentContext(context); } else { throw new ApiNotSupportedException(); } } } 




WifiProxyChanger.java
 public abstract class WifiProxyChanger { public static void changeWifiStaticProxySettings(String host, int port, Context context) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException, ApiNotSupportedException, NullWifiConfigurationException { updateWifiWithNewConfiguration( getCurrentWifiConfiguretionWithUpdatedSettings(host, port, ProxySettings.STATIC, context), context); } public static void clearProxySettings(Context context) throws IllegalAccessException, ApiNotSupportedException, NoSuchFieldException, NullWifiConfigurationException, ClassNotFoundException, NoSuchMethodException, InstantiationException, InvocationTargetException { updateWifiWithNewConfiguration( getCurrentWifiConfiguretionWithUpdatedSettings("", 0, ProxySettings.NONE, context), context); } private static WifiConfiguration getCurrentWifiConfiguretionWithUpdatedSettings(String host, int port, ProxySettings proxySettings, Context context) throws ApiNotSupportedException, IllegalAccessException, NullWifiConfigurationException, NoSuchFieldException, ClassNotFoundException, NoSuchMethodException, InstantiationException, InvocationTargetException { ProxyChanger proxyChanger = CurrentProxyChangerGetter.chooseProxyChangerForCurrentApi(context); proxyChanger.setProxyHostAndPort(host, port); proxyChanger.setProxySettings(proxySettings); return proxyChanger.getWifiConfiguration(); } private static void updateWifiWithNewConfiguration(WifiConfiguration wifiConfiguration, Context context) { WifiManager currentWifiManager = NetworkHelper.getWifiManager(context); currentWifiManager.updateNetwork(wifiConfiguration); currentWifiManager.saveConfiguration(); currentWifiManager.reconnect(); } } 




WifiProxyInfo.java
 public abstract class WifiProxyInfo { public static String getHost(Context context) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, ApiNotSupportedException, NoSuchFieldException, NullWifiConfigurationException { return CurrentProxyChangerGetter.chooseProxyChangerForCurrentApi(context).getProxyHost(); } public static int getPort(Context context) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, ApiNotSupportedException, NoSuchFieldException, NullWifiConfigurationException { return CurrentProxyChangerGetter.chooseProxyChangerForCurrentApi(context).getProxyPort(); } public static ProxySettings getProxySettings(Context context) throws ApiNotSupportedException, IllegalAccessException, NoSuchFieldException, NullWifiConfigurationException { return CurrentProxyChangerGetter.chooseProxyChangerForCurrentApi(context).getProxySettings(); } } 




Implement an auxiliary class to check the API version:



ApiChecker.java
 public abstract class ApiChecker { public static boolean isJellyBeanOrKitkat() { return Build.VERSION.SDK_INT > 14 && Build.VERSION.SDK_INT < 20; } public static boolean isLolipop() { return Build.VERSION.SDK_INT > 20 && Build.VERSION.SDK_INT < 23; } public static boolean isSupportedApi() { return isJellyBeanOrKitkat() || isLolipop(); } } 




LAUNCHING TESTS



(I apologize, but this is such a moment that I decided to single out its title)





...



Champagne! Wine! Folk festivals! Applause! Queen - We are the champions as music!



Small comment
, . , - , "!" . , 10 , , .


Step 5. Enjoy the fruits of our activities



We connect the library to the application created by default and check the result:



MainActivity.java
 public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); changeProxySettings("myhost.com", 12345); } void changeProxySettings(String host, int port) { try { WifiProxyChanger.changeWifiStaticProxySettings(host, port, this); } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | InvocationTargetException | NoSuchFieldException | IllegalAccessException | NullWifiConfigurationException | ApiNotSupportedException e) { e.printStackTrace(); } } } 




We start. We will not see anything interesting on the application screen, because we immediately go to the wifi network settings to which we are connected.



Result. The picture is too big.




The result is achieved.



Summarizing



At the moment we have a working, easily expandable library, which can already be used to change the settings of wifi proxy networks in the above versions of Android.



Future plans? Yes, the author has a list of them!





And, of course, this is not all.



Post scriptum and some more author comments
» , , , Bitbucket ( ).



» — , .



» , , — .


Thank you for your interest in the article, sincerely yours, “Nickname, which can not be left in the sandbox”

Source: https://habr.com/ru/post/311388/



All Articles